diff --git a/go.mod b/go.mod index b6ce0425b4ed..0a4669f8d5d9 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/golang/protobuf v1.5.3 github.com/gorilla/websocket v1.5.0 github.com/grafana/xk6-browser v1.2.1 - github.com/grafana/xk6-grpc v0.1.4-0.20230919144024-6ed5daf33509 github.com/grafana/xk6-output-prometheus-remote v0.3.1 github.com/grafana/xk6-redis v0.2.0 github.com/grafana/xk6-timers v0.1.2 @@ -30,6 +29,7 @@ require ( github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa github.com/mstoykov/atlas v0.0.0-20220811071828-388f114305dd github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1 + github.com/mstoykov/k6-taskqueue-lib v0.1.0 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e github.com/sirupsen/logrus v1.9.3 @@ -77,7 +77,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mstoykov/k6-taskqueue-lib v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect diff --git a/go.sum b/go.sum index aaf8c8246015..e1a605418c0e 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,6 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/xk6-browser v1.2.1 h1:O2fuHHvmmhXvWTPXzD+jsnt1XkVgVjx0+Lj1hsGIWMM= github.com/grafana/xk6-browser v1.2.1/go.mod h1:D3k9/MQHnNKfyzU3fh32pHlrh3GY2LAlkY4wYt/Vn4Y= -github.com/grafana/xk6-grpc v0.1.4-0.20230919144024-6ed5daf33509 h1:9ujE4S5cA3WDhRJnwNuUDtfk3w9FeWx6PaZ+lb3o46M= -github.com/grafana/xk6-grpc v0.1.4-0.20230919144024-6ed5daf33509/go.mod h1:sFTwAsHAtp2f1PNiq0wPjJ7HrAIKploI7Y5mOYo+zIQ= github.com/grafana/xk6-output-prometheus-remote v0.3.1 h1:X23rQzlJD8dXWB31DkxR4uPnuRFo8L0Y0H22fSG9xl0= github.com/grafana/xk6-output-prometheus-remote v0.3.1/go.mod h1:0JLAm4ONsNUlNoxJXAwOCfA6GtDwTPs557OplAvE+3o= github.com/grafana/xk6-redis v0.2.0 h1:iXmAKVlAxafZ/h8ptuXTFhGu63IFsyDI8QjUgWm66BU= diff --git a/js/jsmodules.go b/js/jsmodules.go index b19a1e74aeb5..b5328b05ea5f 100644 --- a/js/jsmodules.go +++ b/js/jsmodules.go @@ -17,7 +17,6 @@ import ( "go.k6.io/k6/js/modules/k6/ws" "github.com/grafana/xk6-browser/browser" - expGrpc "github.com/grafana/xk6-grpc/grpc" "github.com/grafana/xk6-redis/redis" "github.com/grafana/xk6-timers/timers" "github.com/grafana/xk6-webcrypto/webcrypto" @@ -35,7 +34,7 @@ func getInternalJSModules() map[string]interface{} { "k6/experimental/redis": redis.New(), "k6/experimental/webcrypto": webcrypto.New(), "k6/experimental/websockets": &expws.RootModule{}, - "k6/experimental/grpc": expGrpc.New(), + "k6/experimental/grpc": grpc.New(), // TODO: make warning "k6/experimental/timers": timers.New(), "k6/experimental/tracing": tracing.New(), "k6/experimental/browser": browser.New(), diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 5c350e6de090..8b2b37e5e7e3 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -14,8 +14,6 @@ import ( "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" "go.k6.io/k6/lib/netext/grpcext" - "go.k6.io/k6/lib/types" - "go.k6.io/k6/metrics" "github.com/dop251/goja" "github.com/jhump/protoreflect/desc" @@ -213,7 +211,7 @@ func (c *Client) Connect(addr string, params goja.Value) (bool, error) { return false, common.NewInitContextError("connecting to a gRPC server in the init context is not supported") } - p, err := newConnectParams(c.vu.Runtime(), params) + p, err := newConnectParams(c.vu, params) if err != nil { return false, fmt.Errorf("invalid grpc.connect() parameters: %w", err) } @@ -299,9 +297,14 @@ func (c *Client) Invoke( return nil, fmt.Errorf("method %q not found in file descriptors", method) } - p, err := c.parseInvokeParams(params) + p, err := newCallParams(c.vu, params) if err != nil { - return nil, fmt.Errorf("invalid grpc.invoke() parameters: %w", err) + return nil, fmt.Errorf("invalid GRPC's client.invoke() parameters: %w", err) + } + + // k6 GRPC Invoke's default timeout is 2 minutes + if p.Timeout == time.Duration(0) { + p.Timeout = 2 * time.Minute } if req == nil { @@ -315,17 +318,7 @@ func (c *Client) Invoke( ctx, cancel := context.WithTimeout(c.vu.Context(), p.Timeout) defer cancel() - if state.Options.SystemTags.Has(metrics.TagURL) { - p.TagsAndMeta.SetSystemTagOrMeta(metrics.TagURL, fmt.Sprintf("%s%s", c.addr, method)) - } - parts := strings.Split(method[1:], "/") - p.TagsAndMeta.SetSystemTagOrMetaIfEnabled(state.Options.SystemTags, metrics.TagService, parts[0]) - p.TagsAndMeta.SetSystemTagOrMetaIfEnabled(state.Options.SystemTags, metrics.TagMethod, parts[1]) - - // Only set the name system tag if the user didn't explicitly set it beforehand - if _, ok := p.TagsAndMeta.Tags.Get("name"); !ok { - p.TagsAndMeta.SetSystemTagOrMetaIfEnabled(state.Options.SystemTags, metrics.TagName, method) - } + p.SetSystemTags(state, c.addr, method) reqmsg := grpcext.Request{ MethodDescriptor: methodDesc, @@ -394,6 +387,7 @@ func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]M appendMethodInfo(fd, sd, md) } } + messages := fd.Messages() stack := make([]protoreflect.MessageDescriptor, 0, messages.Len()) @@ -427,218 +421,49 @@ func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]M return rtn, nil } -type invokeParams struct { - Metadata metadata.MD - TagsAndMeta metrics.TagsAndMeta - Timeout time.Duration -} - -func (c *Client) parseInvokeParams(paramsVal goja.Value) (*invokeParams, error) { - result := &invokeParams{ - Timeout: 1 * time.Minute, - TagsAndMeta: c.vu.State().Tags.GetCurrentValues(), - Metadata: metadata.New(nil), - } - if paramsVal == nil || goja.IsUndefined(paramsVal) || goja.IsNull(paramsVal) { - return result, nil - } - rt := c.vu.Runtime() - params := paramsVal.ToObject(rt) - for _, k := range params.Keys() { - switch k { - case "metadata": - md, err := newMetadata(params.Get(k)) - if err != nil { - return result, fmt.Errorf("invalid metadata param: %w", err) - } - - result.Metadata = md - case "tags": - if err := common.ApplyCustomUserTags(rt, &result.TagsAndMeta, params.Get(k)); err != nil { - return result, fmt.Errorf("metric tags: %w", err) - } - case "timeout": - var err error - v := params.Get(k).Export() - result.Timeout, err = types.GetDurationValue(v) - if err != nil { - return result, fmt.Errorf("invalid timeout value: %w", err) - } - case "headers": - return result, errors.New("headers param is not supported anymore. Please, use metadata param instead") - default: - return result, fmt.Errorf("unknown param: %q", k) - } - } - return result, nil -} - -// newMetadata constructs a metadata.MD from the input value. -func newMetadata(input goja.Value) (metadata.MD, error) { - md := metadata.New(nil) - - if common.IsNullish(input) { - return md, nil - } - - v := input.Export() +func walkFileDescriptors(seen map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto { + fds := []*descriptorpb.FileDescriptorProto{} - raw, ok := v.(map[string]interface{}) - if !ok { - return md, errors.New("must be an object with key-value pairs") + if _, ok := seen[fd.GetName()]; ok { + return fds } + seen[fd.GetName()] = struct{}{} + fds = append(fds, fd.AsFileDescriptorProto()) - for hk, kv := range raw { - var val string - // The gRPC spec defines that Binary-valued keys end in -bin - // https://grpc.io/docs/what-is-grpc/core-concepts/#metadata - if strings.HasSuffix(hk, "-bin") { - var binVal []byte - if binVal, ok = kv.([]byte); !ok { - return md, fmt.Errorf("%q value must be binary", hk) - } - - // https://github.com/grpc/grpc-go/blob/v1.57.0/Documentation/grpc-metadata.md#storing-binary-data-in-metadata - val = string(binVal) - } else if val, ok = kv.(string); !ok { - return md, fmt.Errorf("%q value must be a string", hk) - } - - md.Append(hk, val) + for _, dep := range fd.GetDependencies() { + deps := walkFileDescriptors(seen, dep) + fds = append(fds, deps...) } - return md, nil -} - -type connectParams struct { - IsPlaintext bool - ReflectionMetadata metadata.MD - UseReflectionProtocol bool - Timeout time.Duration - MaxReceiveSize int64 - MaxSendSize int64 - TLS map[string]interface{} + return fds } -func newConnectParams(rt *goja.Runtime, input goja.Value) (connectParams, error) { //nolint:funlen,gocognit,cyclop - params := connectParams{ - IsPlaintext: false, - UseReflectionProtocol: false, - ReflectionMetadata: metadata.New(nil), - Timeout: time.Minute, - MaxReceiveSize: 0, - MaxSendSize: 0, +// sanitizeMethodName +func sanitizeMethodName(name string) string { + if name == "" { + return name } - if common.IsNullish(input) { - return params, nil + if !strings.HasPrefix(name, "/") { + name = "/" + name } - raw := input.ToObject(rt) - - for _, k := range raw.Keys() { - v := raw.Get(k).Export() - - switch k { - case "plaintext": - var ok bool - params.IsPlaintext, ok = v.(bool) - if !ok { - return params, fmt.Errorf("invalid plaintext value: '%#v', it needs to be boolean", v) - } - case "timeout": - var err error - params.Timeout, err = types.GetDurationValue(v) - if err != nil { - return params, fmt.Errorf("invalid timeout value: %w", err) - } - case "reflect": - var ok bool - params.UseReflectionProtocol, ok = v.(bool) - if !ok { - return params, fmt.Errorf("invalid reflect value: '%#v', it needs to be boolean", v) - } - case "reflectMetadata": - md, err := newMetadata(raw.Get(k)) - if err != nil { - return params, fmt.Errorf("invalid reflectMetadata param: %w", err) - } - params.ReflectionMetadata = md - case "maxReceiveSize": - var ok bool - params.MaxReceiveSize, ok = v.(int64) - if !ok { - return params, fmt.Errorf("invalid maxReceiveSize value: '%#v', it needs to be an integer", v) - } - if params.MaxReceiveSize < 0 { - return params, fmt.Errorf("invalid maxReceiveSize value: '%#v, it needs to be a positive integer", v) - } - case "maxSendSize": - var ok bool - params.MaxSendSize, ok = v.(int64) - if !ok { - return params, fmt.Errorf("invalid maxSendSize value: '%#v', it needs to be an integer", v) - } - if params.MaxSendSize < 0 { - return params, fmt.Errorf("invalid maxSendSize value: '%#v, it needs to be a positive integer", v) - } - case "tls": - var ok bool - params.TLS, ok = v.(map[string]interface{}) - - if !ok { - return params, fmt.Errorf("invalid tls value: '%#v', expected (optional) keys: cert, key, password, and cacerts", v) - } - // optional map keys below - if cert, certok := params.TLS["cert"]; certok { - if _, ok = cert.(string); !ok { - return params, fmt.Errorf("invalid tls cert value: '%#v', it needs to be a PEM formatted string", v) - } - } - if key, keyok := params.TLS["key"]; keyok { - if _, ok = key.(string); !ok { - return params, fmt.Errorf("invalid tls key value: '%#v', it needs to be a PEM formatted string", v) - } - } - if pass, passok := params.TLS["password"]; passok { - if _, ok = pass.(string); !ok { - return params, fmt.Errorf("invalid tls password value: '%#v', it needs to be a string", v) - } - } - if cacerts, cacertsok := params.TLS["cacerts"]; cacertsok { - var cacertsArray []interface{} - if cacertsArray, ok = cacerts.([]interface{}); ok { - for _, cacertsArrayEntry := range cacertsArray { - if _, ok = cacertsArrayEntry.(string); !ok { - return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or an array of PEM formatted strings", v) - } - } - } else if _, ok = cacerts.(string); !ok { - return params, fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or an array of PEM formatted strings", v) - } - } - default: - return params, fmt.Errorf("unknown connect param: %q", k) - } - } - return params, nil + return name } -func walkFileDescriptors(seen map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto { - fds := []*descriptorpb.FileDescriptorProto{} +// getMethodDescriptor sanitize it, and gets GRPC method descriptor or an error if not found +func (c *Client) getMethodDescriptor(method string) (protoreflect.MethodDescriptor, error) { + method = sanitizeMethodName(method) - if _, ok := seen[fd.GetName()]; ok { - return fds + if method == "" { + return nil, errors.New("method to invoke cannot be empty") } - seen[fd.GetName()] = struct{}{} - fds = append(fds, fd.AsFileDescriptorProto()) - for _, dep := range fd.GetDependencies() { - deps := walkFileDescriptors(seen, dep) - fds = append(fds, deps...) + methodDesc := c.mds[method] + + if methodDesc == nil { + return nil, fmt.Errorf("method %q not found in file descriptors", method) } - return fds + return methodDesc, nil } diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index f1212a3fbece..25eb60e3e7a2 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -9,29 +9,31 @@ import ( "strings" "testing" + k6grpc "go.k6.io/k6/js/modules/k6/grpc" + "go.k6.io/k6/lib/netext/grpcext" + "go.k6.io/k6/lib/testutils/httpmultibin" + grpcanytesting "go.k6.io/k6/lib/testutils/httpmultibin/grpc_any_testing" + "go.k6.io/k6/lib/testutils/httpmultibin/grpc_testing" + "go.k6.io/k6/lib/testutils/httpmultibin/grpc_wrappers_testing" + "go.k6.io/k6/metrics" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/wrapperspb" - "gopkg.in/guregu/null.v3" "github.com/golang/protobuf/ptypes/any" + _struct "github.com/golang/protobuf/ptypes/struct" "github.com/golang/protobuf/ptypes/wrappers" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + v1alphagrpc "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" grpcstats "google.golang.org/grpc/stats" - "google.golang.org/grpc/status" - - k6grpc "go.k6.io/k6/js/modules/k6/grpc" - "go.k6.io/k6/lib/netext/grpcext" - "go.k6.io/k6/lib/testutils/httpmultibin" - grpcanytesting "go.k6.io/k6/lib/testutils/httpmultibin/grpc_any_testing" - "go.k6.io/k6/lib/testutils/httpmultibin/grpc_testing" - "go.k6.io/k6/lib/testutils/httpmultibin/grpc_wrappers_testing" - "go.k6.io/k6/metrics" + "gopkg.in/guregu/null.v3" ) func TestClient(t *testing.T) { @@ -638,7 +640,51 @@ func TestClient(t *testing.T) { { name: "ReflectInvoke", setup: func(tb *httpmultibin.HTTPMultiBin) { + // this register both reflection APIs v1 and v1alpha reflection.Register(tb.ServerGRPC) + + tb.GRPCStub.EmptyCallFunc = func(ctx context.Context, _ *grpc_testing.Empty) (*grpc_testing.Empty, error) { + return &grpc_testing.Empty{}, nil + } + }, + initString: codeBlock{ + code: `var client = new grpc.Client();`, + }, + vuString: codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR", {reflect: true}) + client.invoke("grpc.testing.TestService/EmptyCall", {}) + `, + }, + }, + { + name: "ReflectV1Alpha_Invoke", + setup: func(tb *httpmultibin.HTTPMultiBin) { + // this register only v1alpha (this could be removed with removal v1alpha from grpc-go) + s := tb.ServerGRPC + svr := reflection.NewServer(reflection.ServerOptions{Services: s}) + v1alphagrpc.RegisterServerReflectionServer(s, svr) + + tb.GRPCStub.EmptyCallFunc = func(ctx context.Context, _ *grpc_testing.Empty) (*grpc_testing.Empty, error) { + return &grpc_testing.Empty{}, nil + } + }, + initString: codeBlock{ + code: `var client = new grpc.Client();`, + }, + vuString: codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR", {reflect: true}) + client.invoke("grpc.testing.TestService/EmptyCall", {}) + `, + }, + }, + { + name: "ReflectV1Invoke", + setup: func(tb *httpmultibin.HTTPMultiBin) { + // this register only reflection APIs v1 + reflection.RegisterV1(tb.ServerGRPC) + tb.GRPCStub.EmptyCallFunc = func(ctx context.Context, _ *grpc_testing.Empty) (*grpc_testing.Empty, error) { return &grpc_testing.Empty{}, nil } @@ -852,6 +898,63 @@ func TestClient(t *testing.T) { `, }, }, + { + name: "ValueReflection", + setup: func(hb *httpmultibin.HTTPMultiBin) { + reflection.Register(hb.ServerGRPC) + + srv := grpc_wrappers_testing.Register(hb.ServerGRPC) + + srv.TestValueImplementation = func(_ context.Context, in *_struct.Value) (*_struct.Value, error) { + if in.GetNumberValue() == 12 { + return &_struct.Value{ + Kind: &_struct.Value_NumberValue{ + NumberValue: 42, + }, + }, nil + } + + if in.GetStringValue() != "" { + return &_struct.Value{ + Kind: &_struct.Value_StringValue{ + StringValue: "hey " + in.GetStringValue(), + }, + }, nil + } + + return &_struct.Value{ + Kind: &_struct.Value_StringValue{ + StringValue: "I don't know what to answer", + }, + }, nil + } + }, + initString: codeBlock{ + code: ` + const client = new grpc.Client(); + `, + }, + vuString: codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR", {reflect: true}); + + let respString = client.invoke("grpc.wrappers.testing.Service/TestValue", "John") + if (respString.message !== "hey John") { + throw new Error("expected to get 'hey John', but got a " + respString.message) + } + + let respNumber = client.invoke("grpc.wrappers.testing.Service/TestValue", 12) + if (respNumber.message !== 42) { + throw new Error("expected to get '42', but got a " + respNumber.message) + } + + let respBool = client.invoke("grpc.wrappers.testing.Service/TestValue", false) + if (respBool.message !== "I don't know what to answer") { + throw new Error("expected to get 'I don't know what to answer', but got a " + respBool.message) + } + `, + }, + }, } for _, tt := range tests { @@ -870,7 +973,6 @@ func TestClient(t *testing.T) { assertResponse(t, tt.initString, err, val, ts) ts.ToVUContext() - val, err = ts.Run(tt.vuString.code) assertResponse(t, tt.vuString, err, val, ts) }) @@ -893,6 +995,7 @@ func TestClient_TlsParameters(t *testing.T) { clientAuthBad := "-----BEGIN CERTIFICATE-----\\nMIIB2TCCAX6gAwIBAgIUJIZKiR78AH2ioZ+Jae/sElgH85kwCgYIKoZIzj0EAwIw\\nEDEOMAwGA1UEAwwFTXkgQ0EwHhcNMjMwNzA3MTAyNjQ2WhcNMjQwNzA2MTAyNjQ2\\nWjARMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASj\\nOziUDGBuCi7QwIGfMzoNj4phzJkJ4w9h7SOHEsCFCSZ7x1i6MGxXvX5Ol3j/W93S\\ntSJlCPvvxGXVawAQHJ4Ho4G0MIGxMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQD\\nAgWgMCwGCWCGSAGG+EIBDQQfFh1Mb2NhbCBUZXN0IENsaWVudCBDZXJ0aWZpY2F0\\nZTAdBgNVHQ4EFgQUrbSXtZnDxJwTmesLyjxuMy9JtQswHwYDVR0jBBgwFoAUpLpA\\nQPJYBb7wSQGCrKElEfj1+9YwDgYDVR0PAQH/BAQDAgXgMBMGA1UdJQQMMAoGCCsG\\nAQUFBwMEMAoGCCqGSM49BAMCA0kAMEYCIQDcHrzug3V3WvUU+tEKhG1C4cPG5rPJ\\n/y3oOoM0roOnsgIhAP23UmiC6Qdgj+MOhXWSaNt3exWvlxdKmLm2edkxaTs+\\n-----END CERTIFICATE-----" trivialKeyPassword := "abc123" + trivialWrongKeyPassword := "abc321" tests := []testcase{ { @@ -943,7 +1046,7 @@ func TestClient_TlsParameters(t *testing.T) { tb.ServerHTTP2.TLS.ClientCAs = clientCAPool }, initString: codeBlock{code: "var client = new grpc.Client();"}, - vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: "%s", cert: "%s", key: "%s" }});`, localHostCert, clientAuth, clientAuthKey)}, + vuString: codeBlock{code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, localHostCert, clientAuth, clientAuthKey)}, }, { name: "ConnectTlsEncryptedKey", @@ -960,9 +1063,10 @@ func TestClient_TlsParameters(t *testing.T) { name: "ConnectTlsEncryptedKeyDecryptionFailed", initString: codeBlock{code: "var client = new grpc.Client();"}, vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cert: "%s", key: "%s", password: "abc321" }});`, + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '5s', tls: { cert: "%s", key: "%s", password: "%s" }});`, clientAuth, clientAuthKeyEncrypted, + trivialWrongKeyPassword, ), err: "x509: decryption password incorrect", }, @@ -977,7 +1081,7 @@ func TestClient_TlsParameters(t *testing.T) { }, initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ - code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, + code: fmt.Sprintf(`client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }});`, localHostCert, clientAuthBad, clientAuthKey), @@ -995,7 +1099,7 @@ func TestClient_TlsParameters(t *testing.T) { initString: codeBlock{code: `var client = new grpc.Client();`}, vuString: codeBlock{ code: fmt.Sprintf(` - client.connect("GRPCBIN_ADDR", { tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }}); + client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: ["%s"], cert: "%s", key: "%s", password: "%s" }}); `, localHostCert, clientAuthBad, @@ -1030,6 +1134,33 @@ func TestClient_TlsParameters(t *testing.T) { clientAuthKey), }, }, + { + name: "ConnectTlsBadAuthInvokeFails", + setup: func(tb *httpmultibin.HTTPMultiBin) { + clientCAPool := x509.NewCertPool() + clientCAPool.AppendCertsFromPEM(clientAuthCA) + tb.ServerHTTP2.TLS.ClientAuth = tls.RequireAndVerifyClientCert + tb.ServerHTTP2.TLS.ClientCAs = clientCAPool + tb.GRPCStub.EmptyCallFunc = func(context.Context, *grpc_testing.Empty) (*grpc_testing.Empty, error) { + return &grpc_testing.Empty{}, nil + } + }, + initString: codeBlock{code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`}, + vuString: codeBlock{ + code: fmt.Sprintf(` + client.connect("GRPCBIN_ADDR", { timeout: '2s', tls: { cacerts: ["%s"], cert: "%s", key: "%s" }}); + var resp = client.invoke("grpc.testing.TestService/EmptyCall", {}) + if (resp.status !== grpc.StatusOK) { + throw new Error("unexpected error: " + JSON.stringify(resp.error) + "or status: " + resp.status) + }`, + localHostCert, + clientAuthBad, + clientAuthKey), + err: "remote error: tls: bad certificate", + }, + }, } for _, tt := range tests { @@ -1048,7 +1179,6 @@ func TestClient_TlsParameters(t *testing.T) { assertResponse(t, tt.initString, err, val, ts) ts.ToVUContext() - val, err = ts.Run(tt.vuString.code) assertResponse(t, tt.vuString, err, val, ts) }) @@ -1128,35 +1258,6 @@ func TestDebugStat(t *testing.T) { } } -func TestClientLoadProto(t *testing.T) { - t.Parallel() - - ts := newTestState(t) - - code := ` - var client = new grpc.Client(); - client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/nested_types.proto");` - - _, err := ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) - assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) - - expectedTypes := []string{ - "grpc.testing.Outer", - "grpc.testing.Outer.MiddleAA", - "grpc.testing.Outer.MiddleAA.Inner", - "grpc.testing.Outer.MiddleBB", - "grpc.testing.Outer.MiddleBB.Inner", - "grpc.testing.MeldOuter", - } - - for _, expected := range expectedTypes { - found, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(expected)) - - assert.NotNil(t, found, "Expected to find the message type %s, but an error occurred", expected) - assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) - } -} - func TestClientConnectionReflectMetadata(t *testing.T) { t.Parallel() @@ -1199,3 +1300,37 @@ func TestClientConnectionReflectMetadata(t *testing.T) { assert.True(t, foundReflectionCall, "expected to find a reflection call in the logs, but didn't") } + +func TestClientLoadProto(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + tt := testcase{ + name: "LoadNestedTypesProto", + initString: codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/nested_types/nested_types.proto");`, + }, + } + + val, err := ts.Run(tt.initString.code) + assertResponse(t, tt.initString, err, val, ts) + + expectedTypes := []string{ + "grpc.testdata.nested.types.Outer", + "grpc.testdata.nested.types.Outer.MiddleAA", + "grpc.testdata.nested.types.Outer.MiddleAA.Inner", + "grpc.testdata.nested.types.Outer.MiddleBB", + "grpc.testdata.nested.types.Outer.MiddleBB.Inner", + "grpc.testdata.nested.types.MeldOuter", + } + + for _, expected := range expectedTypes { + found, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(expected)) + + assert.NotNil(t, found, "Expected to find the message type %s, but an error occurred", expected) + assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) + } +} diff --git a/js/modules/k6/grpc/grpc.go b/js/modules/k6/grpc/grpc.go index 46ab0c923b6e..fdf2a8f42ec4 100644 --- a/js/modules/k6/grpc/grpc.go +++ b/js/modules/k6/grpc/grpc.go @@ -1,7 +1,13 @@ +// Package grpc is the root module of the k6-grpc extension. package grpc import ( + "errors" + "fmt" + "github.com/dop251/goja" + "github.com/mstoykov/k6-taskqueue-lib/taskqueue" + "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" "google.golang.org/grpc/codes" ) @@ -15,6 +21,7 @@ type ( ModuleInstance struct { vu modules.VU exports map[string]interface{} + metrics *instanceMetrics } ) @@ -31,18 +38,26 @@ func New() *RootModule { // NewModuleInstance implements the modules.Module interface to return // a new instance for each VU. func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance { + metrics, err := registerMetrics(vu.InitEnv().Registry) + if err != nil { + common.Throw(vu.Runtime(), fmt.Errorf("failed to register GRPC module metrics: %w", err)) + } + mi := &ModuleInstance{ vu: vu, exports: make(map[string]interface{}), + metrics: metrics, } mi.exports["Client"] = mi.NewClient mi.defineConstants() + mi.exports["Stream"] = mi.stream + return mi } // NewClient is the JS constructor for the grpc Client. -func (mi *ModuleInstance) NewClient(call goja.ConstructorCall) *goja.Object { +func (mi *ModuleInstance) NewClient(_ goja.ConstructorCall) *goja.Object { rt := mi.vu.Runtime() return rt.ToValue(&Client{vu: mi.vu}).ToObject(rt) } @@ -79,3 +94,78 @@ func (mi *ModuleInstance) Exports() modules.Exports { Named: mi.exports, } } + +// stream returns a new stream object +func (mi *ModuleInstance) stream(c goja.ConstructorCall) *goja.Object { + rt := mi.vu.Runtime() + + client, err := extractClient(c.Argument(0), rt) + if err != nil { + common.Throw(rt, fmt.Errorf("invalid GRPC Stream's client: %w", err)) + } + + methodName := sanitizeMethodName(c.Argument(1).String()) + methodDescriptor, err := client.getMethodDescriptor(methodName) + if err != nil { + common.Throw(rt, fmt.Errorf("invalid GRPC Stream's method: %w", err)) + } + + p, err := newCallParams(mi.vu, c.Argument(2)) + if err != nil { + common.Throw(rt, fmt.Errorf("invalid GRPC Stream's parameters: %w", err)) + } + + p.SetSystemTags(mi.vu.State(), client.addr, methodName) + + logger := mi.vu.State().Logger.WithField("streamMethod", methodName) + + s := &stream{ + vu: mi.vu, + client: client, + methodDescriptor: methodDescriptor, + method: methodName, + logger: logger, + + tq: taskqueue.New(mi.vu.RegisterCallback), + + instanceMetrics: mi.metrics, + builtinMetrics: mi.vu.State().BuiltinMetrics, + done: make(chan struct{}), + writingState: opened, + + writeQueueCh: make(chan message), + + eventListeners: newEventListeners(), + obj: rt.NewObject(), + tagsAndMeta: &p.TagsAndMeta, + } + + defineStream(rt, s) + + err = s.beginStream(p) + if err != nil { + s.tq.Close() + + common.Throw(rt, err) + } + + return s.obj +} + +// extractClient extracts & validates a grpc.Client from a goja.Value. +func extractClient(v goja.Value, rt *goja.Runtime) (*Client, error) { + if common.IsNullish(v) { + return nil, errors.New("empty gRPC client") + } + + client, ok := v.ToObject(rt).Export().(*Client) + if !ok { + return nil, errors.New("not a gRPC client") + } + + if client.conn == nil { + return nil, errors.New("no gRPC connection, you must call connect first") + } + + return client, nil +} diff --git a/js/modules/k6/grpc/helpers_test.go b/js/modules/k6/grpc/helpers_test.go new file mode 100644 index 000000000000..b9c39023347e --- /dev/null +++ b/js/modules/k6/grpc/helpers_test.go @@ -0,0 +1,53 @@ +package grpc_test + +import ( + "errors" + "strings" + "testing" + + "github.com/dop251/goja" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.k6.io/k6/metrics" +) + +func assertResponse(t *testing.T, cb codeBlock, err error, val goja.Value, ts testState) { + if isWindows && cb.windowsErr != "" && err != nil { + err = errors.New(strings.ReplaceAll(err.Error(), cb.windowsErr, cb.err)) + } + if cb.err == "" { + assert.NoError(t, err) + } else { + require.Error(t, err) + assert.Contains(t, err.Error(), cb.err) + } + if cb.val != nil { + require.NotNil(t, val) + assert.Equal(t, cb.val, val.Export()) + } + if cb.asserts != nil { + cb.asserts(t, ts.httpBin, ts.samples, err) + } +} + +func assertMetricEmitted( + t *testing.T, + metricName string, //nolint:unparam + sampleContainers []metrics.SampleContainer, + url string, +) { + seenMetric := false + + for _, sampleContainer := range sampleContainers { + for _, sample := range sampleContainer.GetSamples() { + surl, ok := sample.Tags.Get("url") + assert.True(t, ok) + if surl == url { + if sample.Metric.Name == metricName { + seenMetric = true + } + } + } + } + assert.True(t, seenMetric, "url %s didn't emit %s", url, metricName) +} diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/listeners.go b/js/modules/k6/grpc/listeners.go similarity index 100% rename from vendor/github.com/grafana/xk6-grpc/grpc/listeners.go rename to js/modules/k6/grpc/listeners.go diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/metrics.go b/js/modules/k6/grpc/metrics.go similarity index 100% rename from vendor/github.com/grafana/xk6-grpc/grpc/metrics.go rename to js/modules/k6/grpc/metrics.go diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/params.go b/js/modules/k6/grpc/params.go similarity index 100% rename from vendor/github.com/grafana/xk6-grpc/grpc/params.go rename to js/modules/k6/grpc/params.go diff --git a/js/modules/k6/grpc/params_test.go b/js/modules/k6/grpc/params_test.go new file mode 100644 index 000000000000..c43c6d91fa2a --- /dev/null +++ b/js/modules/k6/grpc/params_test.go @@ -0,0 +1,181 @@ +package grpc + +import ( + "io" + "testing" + "time" + + "github.com/dop251/goja" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.k6.io/k6/js/common" + "go.k6.io/k6/js/modulestest" + "go.k6.io/k6/lib" + "go.k6.io/k6/metrics" + "google.golang.org/grpc/metadata" + "gopkg.in/guregu/null.v3" +) + +func TestCallParamsInvalidInput(t *testing.T) { + t.Parallel() + + testCases := []struct { + Name string + JSON string + ErrContains string + }{ + { + Name: "InvalidParam", + JSON: `{ void: true }`, + ErrContains: `unknown param: "void"`, + }, + { + Name: "InvalidTimeoutType", + JSON: `{ timeout: true }`, + ErrContains: `invalid timeout value: unable to use type bool as a duration value`, + }, + { + Name: "InvalidTimeout", + JSON: `{ timeout: "please" }`, + ErrContains: `invalid duration`, + }, + { + Name: "InvalidMetadata", + JSON: `{ metadata: "lorem" }`, + ErrContains: `invalid metadata param: must be an object with key-value pairs`, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + testRuntime, params := newParamsTestRuntime(t, tc.JSON) + + _, err := newCallParams(testRuntime.VU, params) + + assert.ErrorContains(t, err, tc.ErrContains) + }) + } +} + +func TestCallParamsMetadata(t *testing.T) { + t.Parallel() + + testCases := []struct { + Name string + JSON string + ExpectedMetadata metadata.MD + }{ + { + Name: "EmptyMetadata", + JSON: `{}`, + ExpectedMetadata: metadata.New(nil), + }, + { + Name: "Metadata", + JSON: `{metadata: {foo: "bar", baz: "qux"}}`, + ExpectedMetadata: metadata.New(map[string]string{"foo": "bar", "baz": "qux"}), + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + testRuntime, params := newParamsTestRuntime(t, tc.JSON) + + p, err := newCallParams(testRuntime.VU, params) + + require.NoError(t, err) + assert.Equal(t, tc.ExpectedMetadata, p.Metadata) + }) + } +} + +func TestCallParamsTimeOutParse(t *testing.T) { + t.Parallel() + + testCases := []struct { + Name string + JSON string + Timeout time.Duration + }{ + { + Name: "StringTimeout", + JSON: `{ timeout: "1h42m" }`, + Timeout: time.Hour + 42*time.Minute, + }, + { + Name: "FloatTimeout", + JSON: `{ timeout: 400.50 }`, + Timeout: 400*time.Millisecond + 500*time.Microsecond, + }, + { + Name: "IntegerTimeout", + JSON: `{ timeout: 2000 }`, + Timeout: 2000 * time.Millisecond, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + testRuntime, params := newParamsTestRuntime(t, tc.JSON) + + p, err := newCallParams(testRuntime.VU, params) + require.NoError(t, err) + + assert.Equal(t, tc.Timeout, p.Timeout) + }) + } +} + +// newParamsTestRuntime creates a new test runtime +// that could be used to test the params +// it also moves to the VU context and creates the params +// goja value that could be used in the tests +func newParamsTestRuntime(t *testing.T, paramsJSON string) (*modulestest.Runtime, goja.Value) { + t.Helper() + + testRuntime := modulestest.NewRuntime(t) + registry := metrics.NewRegistry() + root, err := lib.NewGroup("", nil) + require.NoError(t, err) + + logger := logrus.New() + logger.SetLevel(logrus.InfoLevel) + logger.Out = io.Discard + + state := &lib.State{ + Group: root, + Options: lib.Options{ + SystemTags: metrics.NewSystemTagSet( + metrics.TagName, + metrics.TagURL, + ), + UserAgent: null.StringFrom("k6-test"), + }, + BuiltinMetrics: metrics.RegisterBuiltinMetrics(registry), + Tags: lib.NewVUStateTags(registry.RootTagSet()), + Logger: logger, + } + + testRuntime.MoveToVUContext(state) + + _, err = testRuntime.VU.Runtime().RunString(`let params = ` + paramsJSON + `;`) + require.NoError(t, err) + + params := testRuntime.VU.Runtime().Get("params") + require.False(t, common.IsNullish(params)) + + return testRuntime, params +} diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/stream.go b/js/modules/k6/grpc/stream.go similarity index 99% rename from vendor/github.com/grafana/xk6-grpc/grpc/stream.go rename to js/modules/k6/grpc/stream.go index e4ce159bbfe0..d4df1f480c65 100644 --- a/vendor/github.com/grafana/xk6-grpc/grpc/stream.go +++ b/js/modules/k6/grpc/stream.go @@ -9,13 +9,14 @@ import ( "sync" "time" - "github.com/dop251/goja" - "github.com/grafana/xk6-grpc/lib/netext/grpcext" - "github.com/mstoykov/k6-taskqueue-lib/taskqueue" - "github.com/sirupsen/logrus" "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" + "go.k6.io/k6/lib/netext/grpcext" "go.k6.io/k6/metrics" + + "github.com/dop251/goja" + "github.com/mstoykov/k6-taskqueue-lib/taskqueue" + "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/reflect/protoreflect" diff --git a/js/modules/k6/grpc/stream_test.go b/js/modules/k6/grpc/stream_test.go new file mode 100644 index 000000000000..dbc660b11cc2 --- /dev/null +++ b/js/modules/k6/grpc/stream_test.go @@ -0,0 +1,365 @@ +package grpc_test + +import ( + "context" + "errors" + "io" + "strings" + "testing" + "time" + + "go.k6.io/k6/lib/testutils/grpcservice" + "go.k6.io/k6/lib/testutils/httpmultibin/grpc_wrappers_testing" + + "github.com/dop251/goja" + "github.com/golang/protobuf/ptypes/wrappers" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +func TestStream_InvalidHeader(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + initString := codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/test.proto");`, + } + + val, err := ts.Run(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + _, err = ts.Run(` + client.connect("GRPCBIN_ADDR"); + new grpc.Stream(client, "foo/bar")`) + + assert.Error(t, err) + assert.ErrorContains(t, err, `method "/foo/bar" not found in file descriptors`) +} + +func TestStream_RequestHeaders(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + var registeredMetadata metadata.MD + stub := &featureExplorerStub{} + stub.listFeatures = func(rect *grpcservice.Rectangle, stream grpcservice.FeatureExplorer_ListFeaturesServer) error { + // collect metadata from the stream context + md, ok := metadata.FromIncomingContext(stream.Context()) + if ok { + registeredMetadata = md + } + + return nil + } + + grpcservice.RegisterFeatureExplorerServer(ts.httpBin.ServerGRPC, stub) + + initString := codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/grpcservice/route_guide.proto");`, + } + vuString := codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR"); + let stream = new grpc.Stream(client, "main.FeatureExplorer/ListFeatures", { metadata: { "X-Load-Tester": "k6" } }) + stream.write({ + lo: { + latitude: 400000000, + longitude: -750000000, + }, + hi: { + latitude: 420000000, + longitude: -730000000, + }, + }); + `, + } + + val, err := ts.Run(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + val, err = ts.RunOnEventLoop(vuString.code) + + assertResponse(t, vuString, err, val, ts) + + // Check that the metadata was registered + assert.Len(t, registeredMetadata["x-load-tester"], 1) + assert.Equal(t, registeredMetadata["x-load-tester"][0], "k6") +} + +func TestStream_ErrorHandling(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + stub := &featureExplorerStub{} + + savedFeatures := []*grpcservice.Feature{ + { + Name: "foo", + Location: &grpcservice.Point{ + Latitude: 1, + Longitude: 2, + }, + }, + { + Name: "bar", + Location: &grpcservice.Point{ + Latitude: 3, + Longitude: 4, + }, + }, + } + + stub.listFeatures = func(rect *grpcservice.Rectangle, stream grpcservice.FeatureExplorer_ListFeaturesServer) error { + for _, feature := range savedFeatures { + if err := stream.Send(feature); err != nil { + return err + } + } + + return status.Error(codes.Internal, "lorem ipsum") + } + + grpcservice.RegisterFeatureExplorerServer(ts.httpBin.ServerGRPC, stub) + + initString := codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/grpcservice/route_guide.proto");`, + } + vuString := codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR"); + let stream = new grpc.Stream(client, "main.FeatureExplorer/ListFeatures") + stream.write({ + lo: { + latitude: 1, + longitude: 2, + }, + hi: { + latitude: 1, + longitude: 2, + }, + }); + stream.on('data', function (data) { + call('Feature:' + data.name); + }) + stream.on('error', function (e) { + call('Code: ' + e.code + ' Message: ' + e.message); + }); + `, + } + + val, err := ts.Run(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + val, err = ts.RunOnEventLoop(vuString.code) + + assertResponse(t, vuString, err, val, ts) + + assert.Equal(t, + []string{ + "Feature:foo", + "Feature:bar", + "Code: 13 Message: lorem ipsum", + }, + ts.callRecorder.Recorded(), + ) +} + +// this test case is checking that everything that server sends +// after the client finished (client.end called) is delivered to the client +// and the end event is called +func TestStream_ReceiveAllServerResponsesAfterEnd(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + stub := &featureExplorerStub{} + + savedFeatures := []*grpcservice.Feature{ + { + Name: "foo", + Location: &grpcservice.Point{ + Latitude: 1, + Longitude: 2, + }, + }, + { + Name: "bar", + Location: &grpcservice.Point{ + Latitude: 3, + Longitude: 4, + }, + }, + } + + stub.listFeatures = func(rect *grpcservice.Rectangle, stream grpcservice.FeatureExplorer_ListFeaturesServer) error { + for _, feature := range savedFeatures { + // adding a delay to make server response "slower" + time.Sleep(200 * time.Millisecond) + + if err := stream.Send(feature); err != nil { + return err + } + } + + return nil + } + + grpcservice.RegisterFeatureExplorerServer(ts.httpBin.ServerGRPC, stub) + + initString := codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/grpcservice/route_guide.proto");`, + } + vuString := codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR"); + let stream = new grpc.Stream(client, "main.FeatureExplorer/ListFeatures") + stream.on('data', function (data) { + call('Feature:' + data.name); + }); + stream.on('end', function () { + call('End called'); + }); + + stream.write({ + lo: { + latitude: 1, + longitude: 2, + }, + hi: { + latitude: 1, + longitude: 2, + }, + }); + stream.end(); + `, + } + + val, err := ts.Run(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + val, err = ts.RunOnEventLoop(vuString.code) + + assertResponse(t, vuString, err, val, ts) + + assert.Equal(t, ts.callRecorder.Recorded(), []string{ + "Feature:foo", + "Feature:bar", + "End called", + }, + ) +} + +// featureExplorerStub is a stub for FeatureExplorerServer +// it has ability to override methods +type featureExplorerStub struct { + grpcservice.UnimplementedFeatureExplorerServer + + getFeature func(ctx context.Context, point *grpcservice.Point) (*grpcservice.Feature, error) + listFeatures func(rect *grpcservice.Rectangle, stream grpcservice.FeatureExplorer_ListFeaturesServer) error +} + +func (s *featureExplorerStub) GetFeature(ctx context.Context, point *grpcservice.Point) (*grpcservice.Feature, error) { + if s.getFeature != nil { + return s.getFeature(ctx, point) + } + + return nil, status.Errorf(codes.Unimplemented, "method GetFeature not implemented") +} + +func (s *featureExplorerStub) ListFeatures(rect *grpcservice.Rectangle, stream grpcservice.FeatureExplorer_ListFeaturesServer) error { + if s.listFeatures != nil { + return s.listFeatures(rect, stream) + } + + return status.Errorf(codes.Unimplemented, "method ListFeatures not implemented") +} + +func TestStream_Wrappers(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + stub := grpc_wrappers_testing.Register(ts.httpBin.ServerGRPC) + stub.TestStreamImplementation = func(stream grpc_wrappers_testing.Service_TestStreamServer) error { + result := "" + + for { + msg, err := stream.Recv() + if errors.Is(err, io.EOF) { + return stream.SendAndClose(&wrappers.StringValue{ + Value: strings.TrimRight(result, " "), + }) + } + + if err != nil { + return err + } + + result += msg.Value + " " + } + } + + replace := func(code string) (goja.Value, error) { + return ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) + } + + initString := codeBlock{ + code: ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_wrappers_testing/test.proto");`, + } + vuString := codeBlock{ + code: ` + client.connect("GRPCBIN_ADDR"); + let stream = new grpc.Stream(client, "grpc.wrappers.testing.Service/TestStream"); + stream.on('data', function (data) { + call('Result: ' + data); + }) + + stream.write('Hey'); + stream.write('John'); + stream.end(); + + stream.on('error', function (e) { + call('Code: ' + e.code + ' Message: ' + e.message); + }); + `, + } + + val, err := replace(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + val, err = replace(vuString.code) + + ts.EventLoop.WaitOnRegistered() + + assertResponse(t, vuString, err, val, ts) + + assert.Equal(t, ts.callRecorder.Recorded(), []string{ + "Result: Hey John", + }, + ) +} diff --git a/js/modules/k6/grpc/teststate_test.go b/js/modules/k6/grpc/teststate_test.go index b4af7a98821d..0305e723b022 100644 --- a/js/modules/k6/grpc/teststate_test.go +++ b/js/modules/k6/grpc/teststate_test.go @@ -1,28 +1,25 @@ package grpc_test import ( - "errors" "io" "net/url" "os" "runtime" - "strings" + "sync" "testing" "github.com/dop251/goja" "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "gopkg.in/guregu/null.v3" - - k6grpc "go.k6.io/k6/js/modules/k6/grpc" "go.k6.io/k6/js/modulestest" "go.k6.io/k6/lib" "go.k6.io/k6/lib/fsext" "go.k6.io/k6/lib/testutils" "go.k6.io/k6/lib/testutils/httpmultibin" "go.k6.io/k6/metrics" + "gopkg.in/guregu/null.v3" + + xk6grpc "go.k6.io/k6/js/modules/k6/grpc" ) const isWindows = runtime.GOOS == "windows" @@ -43,24 +40,71 @@ type testcase struct { vuString codeBlock // runs in the vu context } +// callRecorder a helper type that records all calls +type callRecorder struct { + sync.Mutex + calls []string +} + +// Call records a call +func (r *callRecorder) Call(text string) { + r.Lock() + defer r.Unlock() + + r.calls = append(r.calls, text) +} + +// Len just returns the length of the calls +func (r *callRecorder) Len() int { + r.Lock() + defer r.Unlock() + + return len(r.calls) +} + +// Recorded returns the recorded calls +func (r *callRecorder) Recorded() []string { + r.Lock() + defer r.Unlock() + + result := []string{} + result = append(result, r.calls...) + + return result +} + type testState struct { *modulestest.Runtime - httpBin *httpmultibin.HTTPMultiBin - samples chan metrics.SampleContainer - logger logrus.FieldLogger - loggerHook *testutils.SimpleLogrusHook + httpBin *httpmultibin.HTTPMultiBin + samples chan metrics.SampleContainer + logger logrus.FieldLogger + loggerHook *testutils.SimpleLogrusHook + callRecorder *callRecorder +} + +// Run replaces the httpbin address and runs the code. +func (ts *testState) Run(code string) (goja.Value, error) { + return ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) +} + +// RunOnEventLoop replaces the httpbin address and run the code on event loop +func (ts *testState) RunOnEventLoop(code string) (goja.Value, error) { + return ts.Runtime.RunOnEventLoop(ts.httpBin.Replacer.Replace(code)) } +// newTestState creates a new test state. func newTestState(t *testing.T) testState { t.Helper() tb := httpmultibin.NewHTTPMultiBin(t) + samples := make(chan metrics.SampleContainer, 1000) testRuntime := modulestest.NewRuntime(t) - cwd, err := os.Getwd() //nolint:golint,forbidigo + cwd, err := os.Getwd() //nolint:forbidigo require.NoError(t, err) fs := fsext.NewOsFs() + if isWindows { fs = fsext.NewTrimFilePathSeparatorFs(fs) } @@ -74,17 +118,23 @@ func newTestState(t *testing.T) testState { hook := testutils.NewLogHook() logger.AddHook(hook) + recorder := &callRecorder{ + calls: make([]string, 0), + } + ts := testState{ - Runtime: testRuntime, - httpBin: tb, - samples: samples, - logger: logger, - loggerHook: hook, + Runtime: testRuntime, + httpBin: tb, + samples: samples, + logger: logger, + loggerHook: hook, + callRecorder: recorder, } - m, ok := k6grpc.New().NewModuleInstance(ts.VU).(*k6grpc.ModuleInstance) + m, ok := xk6grpc.New().NewModuleInstance(ts.VU).(*xk6grpc.ModuleInstance) require.True(t, ok) require.NoError(t, ts.VU.Runtime().Set("grpc", m.Exports().Named)) + require.NoError(t, ts.VU.Runtime().Set("call", recorder.Call)) return ts } @@ -116,49 +166,3 @@ func (ts *testState) ToVUContext() { ts.MoveToVUContext(state) } - -// Run replaces the httpbin address and runs the code. -func (ts *testState) Run(code string) (goja.Value, error) { - return ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) -} - -func assertMetricEmitted( - t *testing.T, - metricName string, //nolint:unparam - sampleContainers []metrics.SampleContainer, - url string, -) { - seenMetric := false - - for _, sampleContainer := range sampleContainers { - for _, sample := range sampleContainer.GetSamples() { - surl, ok := sample.Tags.Get("url") - assert.True(t, ok) - if surl == url { - if sample.Metric.Name == metricName { - seenMetric = true - } - } - } - } - assert.True(t, seenMetric, "url %s didn't emit %s", url, metricName) -} - -func assertResponse(t *testing.T, cb codeBlock, err error, val goja.Value, ts testState) { - if isWindows && cb.windowsErr != "" && err != nil { - err = errors.New(strings.ReplaceAll(err.Error(), cb.windowsErr, cb.err)) - } - if cb.err == "" { - assert.NoError(t, err) - } else { - require.Error(t, err) - assert.Contains(t, err.Error(), cb.err) - } - if cb.val != nil { - require.NotNil(t, val) - assert.Equal(t, cb.val, val.Export()) - } - if cb.asserts != nil { - cb.asserts(t, ts.httpBin, ts.samples, err) - } -} diff --git a/lib/netext/grpcext/conn.go b/lib/netext/grpcext/conn.go index 2709f90e2411..5e9f9e0fc0b9 100644 --- a/lib/netext/grpcext/conn.go +++ b/lib/netext/grpcext/conn.go @@ -9,11 +9,11 @@ import ( "strconv" "strings" + "github.com/sirupsen/logrus" "go.k6.io/k6/lib" "go.k6.io/k6/metrics" protov1 "github.com/golang/protobuf/proto" //nolint:staticcheck,nolintlint // this is the old v1 version - "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -34,6 +34,14 @@ type Request struct { Message []byte } +// StreamRequest represents a gRPC stream request. +type StreamRequest struct { + Method string + MethodDescriptor protoreflect.MethodDescriptor + TagsAndMeta *metrics.TagsAndMeta + Metadata metadata.MD +} + // Response represents a gRPC response. type Response struct { Message interface{} @@ -146,28 +154,42 @@ func (c *Conn) Invoke( } if resp != nil { - // (rogchap) there is a lot of marshaling/unmarshaling here, but if we just pass the dynamic message - // the default Marshaller would be used, which would strip any zero/default values from the JSON. - // eg. given this message: - // message Point { - // double x = 1; - // double y = 2; - // double z = 3; - // } - // and a value like this: - // msg := Point{X: 6, Y: 4, Z: 0} - // would result in JSON output: - // {"x":6,"y":4} - // rather than the desired: - // {"x":6,"y":4,"z":0} - raw, _ := marshaler.Marshal(resp) - var msg interface{} - _ = json.Unmarshal(raw, &msg) + msg, err := convert(marshaler, resp) + if err != nil { + return nil, fmt.Errorf("unable to convert response object to JSON: %w", err) + } + response.Message = msg } return &response, nil } +// NewStream creates a new gRPC stream. +func (c *Conn) NewStream( + ctx context.Context, + req StreamRequest, + opts ...grpc.CallOption, +) (*Stream, error) { + ctx = metadata.NewOutgoingContext(ctx, req.Metadata) + + ctx = withRPCState(ctx, &rpcState{tagsAndMeta: req.TagsAndMeta}) + + stream, err := c.raw.NewStream(ctx, &grpc.StreamDesc{ + StreamName: string(req.MethodDescriptor.Name()), + ServerStreams: req.MethodDescriptor.IsStreamingServer(), + ClientStreams: req.MethodDescriptor.IsStreamingClient(), + }, req.Method, opts...) + if err != nil { + return nil, err + } + + return &Stream{ + raw: stream, + method: req.Method, + methodDescriptor: req.MethodDescriptor, + }, nil +} + // Close closes the underhood connection. func (c *Conn) Close() error { return c.raw.Close() diff --git a/lib/netext/grpcext/conn_test.go b/lib/netext/grpcext/conn_test.go index 8a2a66243f32..fe418d79a34a 100644 --- a/lib/netext/grpcext/conn_test.go +++ b/lib/netext/grpcext/conn_test.go @@ -213,7 +213,7 @@ message Empty { // invokemock is a mock for the grpc connection supporting only unary requests. type invokemock func(in, out *dynamicpb.Message, opts ...grpc.CallOption) error -func (im invokemock) Invoke(ctx context.Context, url string, payload interface{}, reply interface{}, opts ...grpc.CallOption) error { +func (im invokemock) Invoke(_ context.Context, _ string, payload interface{}, reply interface{}, opts ...grpc.CallOption) error { in, ok := payload.(*dynamicpb.Message) if !ok { return fmt.Errorf("unexpected type for payload") @@ -225,12 +225,10 @@ func (im invokemock) Invoke(ctx context.Context, url string, payload interface{} return im(in, out, opts...) } -func (invokemock) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { +func (invokemock) NewStream(_ context.Context, _ *grpc.StreamDesc, _ string, _ ...grpc.CallOption) (grpc.ClientStream, error) { panic("not implemented") } func (invokemock) Close() error { return nil } - -// TODO: add a test for the ip metric tag diff --git a/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/stream.go b/lib/netext/grpcext/stream.go similarity index 100% rename from vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/stream.go rename to lib/netext/grpcext/stream.go diff --git a/lib/testutils/grpcservice/route_guide.pb.go b/lib/testutils/grpcservice/route_guide.pb.go new file mode 100644 index 000000000000..bb5837f6de4a --- /dev/null +++ b/lib/testutils/grpcservice/route_guide.pb.go @@ -0,0 +1,529 @@ +// Copyright 2015 gRPC authors, with some modification by the k6 team (2021). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.12.4 +// source: route_guide.proto + +package grpcservice + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +type Point struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Latitude int32 `protobuf:"varint,1,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude int32 `protobuf:"varint,2,opt,name=longitude,proto3" json:"longitude,omitempty"` +} + +func (x *Point) Reset() { + *x = Point{} + if protoimpl.UnsafeEnabled { + mi := &file_route_guide_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Point) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Point) ProtoMessage() {} + +func (x *Point) ProtoReflect() protoreflect.Message { + mi := &file_route_guide_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Point.ProtoReflect.Descriptor instead. +func (*Point) Descriptor() ([]byte, []int) { + return file_route_guide_proto_rawDescGZIP(), []int{0} +} + +func (x *Point) GetLatitude() int32 { + if x != nil { + return x.Latitude + } + return 0 +} + +func (x *Point) GetLongitude() int32 { + if x != nil { + return x.Longitude + } + return 0 +} + +// A latitude-longitude rectangle, represented as two diagonally opposite +// points "lo" and "hi". +type Rectangle struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // One corner of the rectangle. + Lo *Point `protobuf:"bytes,1,opt,name=lo,proto3" json:"lo,omitempty"` + // The other corner of the rectangle. + Hi *Point `protobuf:"bytes,2,opt,name=hi,proto3" json:"hi,omitempty"` +} + +func (x *Rectangle) Reset() { + *x = Rectangle{} + if protoimpl.UnsafeEnabled { + mi := &file_route_guide_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Rectangle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Rectangle) ProtoMessage() {} + +func (x *Rectangle) ProtoReflect() protoreflect.Message { + mi := &file_route_guide_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Rectangle.ProtoReflect.Descriptor instead. +func (*Rectangle) Descriptor() ([]byte, []int) { + return file_route_guide_proto_rawDescGZIP(), []int{1} +} + +func (x *Rectangle) GetLo() *Point { + if x != nil { + return x.Lo + } + return nil +} + +func (x *Rectangle) GetHi() *Point { + if x != nil { + return x.Hi + } + return nil +} + +// A feature names something at a given point. +// +// If a feature could not be named, the name is empty. +type Feature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the feature. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // The point where the feature is detected. + Location *Point `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` +} + +func (x *Feature) Reset() { + *x = Feature{} + if protoimpl.UnsafeEnabled { + mi := &file_route_guide_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feature) ProtoMessage() {} + +func (x *Feature) ProtoReflect() protoreflect.Message { + mi := &file_route_guide_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feature.ProtoReflect.Descriptor instead. +func (*Feature) Descriptor() ([]byte, []int) { + return file_route_guide_proto_rawDescGZIP(), []int{2} +} + +func (x *Feature) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Feature) GetLocation() *Point { + if x != nil { + return x.Location + } + return nil +} + +// A RouteNote is a message sent while at a given point. +type RouteNote struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The location from which the message is sent. + Location *Point `protobuf:"bytes,1,opt,name=location,proto3" json:"location,omitempty"` + // The message to be sent. + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *RouteNote) Reset() { + *x = RouteNote{} + if protoimpl.UnsafeEnabled { + mi := &file_route_guide_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RouteNote) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RouteNote) ProtoMessage() {} + +func (x *RouteNote) ProtoReflect() protoreflect.Message { + mi := &file_route_guide_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RouteNote.ProtoReflect.Descriptor instead. +func (*RouteNote) Descriptor() ([]byte, []int) { + return file_route_guide_proto_rawDescGZIP(), []int{3} +} + +func (x *RouteNote) GetLocation() *Point { + if x != nil { + return x.Location + } + return nil +} + +func (x *RouteNote) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +// A RouteSummary is received in response to a RecordRoute rpc. +// +// It contains the number of individual points received, the number of +// detected features, and the total distance covered as the cumulative sum of +// the distance between each point. +type RouteSummary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The number of points received. + PointCount int32 `protobuf:"varint,1,opt,name=point_count,json=pointCount,proto3" json:"point_count,omitempty"` + // The number of known features passed while traversing the route. + FeatureCount int32 `protobuf:"varint,2,opt,name=feature_count,json=featureCount,proto3" json:"feature_count,omitempty"` + // The distance covered in metres. + Distance int32 `protobuf:"varint,3,opt,name=distance,proto3" json:"distance,omitempty"` + // The duration of the traversal in seconds. + ElapsedTime int32 `protobuf:"varint,4,opt,name=elapsed_time,json=elapsedTime,proto3" json:"elapsed_time,omitempty"` +} + +func (x *RouteSummary) Reset() { + *x = RouteSummary{} + if protoimpl.UnsafeEnabled { + mi := &file_route_guide_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RouteSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RouteSummary) ProtoMessage() {} + +func (x *RouteSummary) ProtoReflect() protoreflect.Message { + mi := &file_route_guide_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RouteSummary.ProtoReflect.Descriptor instead. +func (*RouteSummary) Descriptor() ([]byte, []int) { + return file_route_guide_proto_rawDescGZIP(), []int{4} +} + +func (x *RouteSummary) GetPointCount() int32 { + if x != nil { + return x.PointCount + } + return 0 +} + +func (x *RouteSummary) GetFeatureCount() int32 { + if x != nil { + return x.FeatureCount + } + return 0 +} + +func (x *RouteSummary) GetDistance() int32 { + if x != nil { + return x.Distance + } + return 0 +} + +func (x *RouteSummary) GetElapsedTime() int32 { + if x != nil { + return x.ElapsedTime + } + return 0 +} + +var File_route_guide_proto protoreflect.FileDescriptor + +var file_route_guide_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x67, 0x75, 0x69, 0x64, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x41, 0x0a, 0x05, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0x45, 0x0a, 0x09, + 0x52, 0x65, 0x63, 0x74, 0x61, 0x6e, 0x67, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x02, 0x6c, 0x6f, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x50, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x02, 0x6c, 0x6f, 0x12, 0x1b, 0x0a, 0x02, 0x68, 0x69, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x02, 0x68, 0x69, 0x22, 0x46, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4e, 0x0a, 0x09, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6d, 0x61, 0x69, + 0x6e, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0c, + 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x0a, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, + 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6d, + 0x65, 0x32, 0x71, 0x0a, 0x0f, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x45, 0x78, 0x70, 0x6c, + 0x6f, 0x72, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x1a, + 0x0d, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x00, + 0x12, 0x32, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x12, 0x0f, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x63, 0x74, 0x61, 0x6e, 0x67, 0x6c, + 0x65, 0x1a, 0x0d, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x22, 0x00, 0x30, 0x01, 0x32, 0x75, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x47, 0x75, 0x69, + 0x64, 0x65, 0x12, 0x32, 0x0a, 0x0b, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x12, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x1a, 0x12, + 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x09, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x74, 0x12, 0x0f, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x4e, 0x6f, 0x74, 0x65, 0x1a, 0x0f, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, + 0x2f, 0x67, 0x72, 0x70, 0x63, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_route_guide_proto_rawDescOnce sync.Once + file_route_guide_proto_rawDescData = file_route_guide_proto_rawDesc +) + +func file_route_guide_proto_rawDescGZIP() []byte { + file_route_guide_proto_rawDescOnce.Do(func() { + file_route_guide_proto_rawDescData = protoimpl.X.CompressGZIP(file_route_guide_proto_rawDescData) + }) + return file_route_guide_proto_rawDescData +} + +var file_route_guide_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_route_guide_proto_goTypes = []interface{}{ + (*Point)(nil), // 0: main.Point + (*Rectangle)(nil), // 1: main.Rectangle + (*Feature)(nil), // 2: main.Feature + (*RouteNote)(nil), // 3: main.RouteNote + (*RouteSummary)(nil), // 4: main.RouteSummary +} +var file_route_guide_proto_depIdxs = []int32{ + 0, // 0: main.Rectangle.lo:type_name -> main.Point + 0, // 1: main.Rectangle.hi:type_name -> main.Point + 0, // 2: main.Feature.location:type_name -> main.Point + 0, // 3: main.RouteNote.location:type_name -> main.Point + 0, // 4: main.FeatureExplorer.GetFeature:input_type -> main.Point + 1, // 5: main.FeatureExplorer.ListFeatures:input_type -> main.Rectangle + 0, // 6: main.RouteGuide.RecordRoute:input_type -> main.Point + 3, // 7: main.RouteGuide.RouteChat:input_type -> main.RouteNote + 2, // 8: main.FeatureExplorer.GetFeature:output_type -> main.Feature + 2, // 9: main.FeatureExplorer.ListFeatures:output_type -> main.Feature + 4, // 10: main.RouteGuide.RecordRoute:output_type -> main.RouteSummary + 3, // 11: main.RouteGuide.RouteChat:output_type -> main.RouteNote + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_route_guide_proto_init() } +func file_route_guide_proto_init() { + if File_route_guide_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_route_guide_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Point); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_route_guide_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Rectangle); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_route_guide_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_route_guide_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteNote); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_route_guide_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RouteSummary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_route_guide_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_route_guide_proto_goTypes, + DependencyIndexes: file_route_guide_proto_depIdxs, + MessageInfos: file_route_guide_proto_msgTypes, + }.Build() + File_route_guide_proto = out.File + file_route_guide_proto_rawDesc = nil + file_route_guide_proto_goTypes = nil + file_route_guide_proto_depIdxs = nil +} diff --git a/lib/testutils/grpcservice/route_guide.proto b/lib/testutils/grpcservice/route_guide.proto new file mode 100644 index 000000000000..1742d46759c4 --- /dev/null +++ b/lib/testutils/grpcservice/route_guide.proto @@ -0,0 +1,113 @@ +// Copyright 2015 gRPC authors, with some modification by the k6 team (2021). +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package main; + +option go_package = "./grpcservice"; + +// FeatureExplorer is the service for exploring Features. +// Interface exported by the server. +service FeatureExplorer { + // A simple RPC. + // + // Obtains the feature at a given position. + // + // A feature with an empty name is returned if there's no feature at the given + // position. + rpc GetFeature(Point) returns (Feature) {} + + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} +} + +// Interface exported by the server. +service RouteGuide { + + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} +} + +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + int32 latitude = 1; + int32 longitude = 2; +} + +// A latitude-longitude rectangle, represented as two diagonally opposite +// points "lo" and "hi". +message Rectangle { + // One corner of the rectangle. + Point lo = 1; + + // The other corner of the rectangle. + Point hi = 2; +} + +// A feature names something at a given point. +// +// If a feature could not be named, the name is empty. +message Feature { + // The name of the feature. + string name = 1; + + // The point where the feature is detected. + Point location = 2; +} + +// A RouteNote is a message sent while at a given point. +message RouteNote { + // The location from which the message is sent. + Point location = 1; + + // The message to be sent. + string message = 2; +} + +// A RouteSummary is received in response to a RecordRoute rpc. +// +// It contains the number of individual points received, the number of +// detected features, and the total distance covered as the cumulative sum of +// the distance between each point. +message RouteSummary { + // The number of points received. + int32 point_count = 1; + + // The number of known features passed while traversing the route. + int32 feature_count = 2; + + // The distance covered in metres. + int32 distance = 3; + + // The duration of the traversal in seconds. + int32 elapsed_time = 4; +} diff --git a/lib/testutils/grpcservice/route_guide_grpc.pb.go b/lib/testutils/grpcservice/route_guide_grpc.pb.go new file mode 100644 index 000000000000..2552b6adbb45 --- /dev/null +++ b/lib/testutils/grpcservice/route_guide_grpc.pb.go @@ -0,0 +1,393 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package grpcservice + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// FeatureExplorerClient is the client API for FeatureExplorer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FeatureExplorerClient interface { + // A simple RPC. + // + // Obtains the feature at a given position. + // + // A feature with an empty name is returned if there's no feature at the given + // position. + GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error) + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + ListFeatures(ctx context.Context, in *Rectangle, opts ...grpc.CallOption) (FeatureExplorer_ListFeaturesClient, error) +} + +type featureExplorerClient struct { + cc grpc.ClientConnInterface +} + +func NewFeatureExplorerClient(cc grpc.ClientConnInterface) FeatureExplorerClient { + return &featureExplorerClient{cc} +} + +func (c *featureExplorerClient) GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error) { + out := new(Feature) + err := c.cc.Invoke(ctx, "/main.FeatureExplorer/GetFeature", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *featureExplorerClient) ListFeatures(ctx context.Context, in *Rectangle, opts ...grpc.CallOption) (FeatureExplorer_ListFeaturesClient, error) { + stream, err := c.cc.NewStream(ctx, &FeatureExplorer_ServiceDesc.Streams[0], "/main.FeatureExplorer/ListFeatures", opts...) + if err != nil { + return nil, err + } + x := &featureExplorerListFeaturesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type FeatureExplorer_ListFeaturesClient interface { + Recv() (*Feature, error) + grpc.ClientStream +} + +type featureExplorerListFeaturesClient struct { + grpc.ClientStream +} + +func (x *featureExplorerListFeaturesClient) Recv() (*Feature, error) { + m := new(Feature) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// FeatureExplorerServer is the server API for FeatureExplorer service. +// All implementations must embed UnimplementedFeatureExplorerServer +// for forward compatibility +type FeatureExplorerServer interface { + // A simple RPC. + // + // Obtains the feature at a given position. + // + // A feature with an empty name is returned if there's no feature at the given + // position. + GetFeature(context.Context, *Point) (*Feature, error) + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + ListFeatures(*Rectangle, FeatureExplorer_ListFeaturesServer) error + mustEmbedUnimplementedFeatureExplorerServer() +} + +// UnimplementedFeatureExplorerServer must be embedded to have forward compatible implementations. +type UnimplementedFeatureExplorerServer struct { +} + +func (UnimplementedFeatureExplorerServer) GetFeature(context.Context, *Point) (*Feature, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetFeature not implemented") +} +func (UnimplementedFeatureExplorerServer) ListFeatures(*Rectangle, FeatureExplorer_ListFeaturesServer) error { + return status.Errorf(codes.Unimplemented, "method ListFeatures not implemented") +} +func (UnimplementedFeatureExplorerServer) mustEmbedUnimplementedFeatureExplorerServer() {} + +// UnsafeFeatureExplorerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FeatureExplorerServer will +// result in compilation errors. +type UnsafeFeatureExplorerServer interface { + mustEmbedUnimplementedFeatureExplorerServer() +} + +func RegisterFeatureExplorerServer(s grpc.ServiceRegistrar, srv FeatureExplorerServer) { + s.RegisterService(&FeatureExplorer_ServiceDesc, srv) +} + +func _FeatureExplorer_GetFeature_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Point) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FeatureExplorerServer).GetFeature(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/main.FeatureExplorer/GetFeature", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FeatureExplorerServer).GetFeature(ctx, req.(*Point)) + } + return interceptor(ctx, in, info, handler) +} + +func _FeatureExplorer_ListFeatures_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Rectangle) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(FeatureExplorerServer).ListFeatures(m, &featureExplorerListFeaturesServer{stream}) +} + +type FeatureExplorer_ListFeaturesServer interface { + Send(*Feature) error + grpc.ServerStream +} + +type featureExplorerListFeaturesServer struct { + grpc.ServerStream +} + +func (x *featureExplorerListFeaturesServer) Send(m *Feature) error { + return x.ServerStream.SendMsg(m) +} + +// FeatureExplorer_ServiceDesc is the grpc.ServiceDesc for FeatureExplorer service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var FeatureExplorer_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "main.FeatureExplorer", + HandlerType: (*FeatureExplorerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetFeature", + Handler: _FeatureExplorer_GetFeature_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ListFeatures", + Handler: _FeatureExplorer_ListFeatures_Handler, + ServerStreams: true, + }, + }, + Metadata: "route_guide.proto", +} + +// RouteGuideClient is the client API for RouteGuide service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RouteGuideClient interface { + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + RecordRoute(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RecordRouteClient, error) + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + RouteChat(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RouteChatClient, error) +} + +type routeGuideClient struct { + cc grpc.ClientConnInterface +} + +func NewRouteGuideClient(cc grpc.ClientConnInterface) RouteGuideClient { + return &routeGuideClient{cc} +} + +func (c *routeGuideClient) RecordRoute(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RecordRouteClient, error) { + stream, err := c.cc.NewStream(ctx, &RouteGuide_ServiceDesc.Streams[0], "/main.RouteGuide/RecordRoute", opts...) + if err != nil { + return nil, err + } + x := &routeGuideRecordRouteClient{stream} + return x, nil +} + +type RouteGuide_RecordRouteClient interface { + Send(*Point) error + CloseAndRecv() (*RouteSummary, error) + grpc.ClientStream +} + +type routeGuideRecordRouteClient struct { + grpc.ClientStream +} + +func (x *routeGuideRecordRouteClient) Send(m *Point) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routeGuideRecordRouteClient) CloseAndRecv() (*RouteSummary, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(RouteSummary) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routeGuideClient) RouteChat(ctx context.Context, opts ...grpc.CallOption) (RouteGuide_RouteChatClient, error) { + stream, err := c.cc.NewStream(ctx, &RouteGuide_ServiceDesc.Streams[1], "/main.RouteGuide/RouteChat", opts...) + if err != nil { + return nil, err + } + x := &routeGuideRouteChatClient{stream} + return x, nil +} + +type RouteGuide_RouteChatClient interface { + Send(*RouteNote) error + Recv() (*RouteNote, error) + grpc.ClientStream +} + +type routeGuideRouteChatClient struct { + grpc.ClientStream +} + +func (x *routeGuideRouteChatClient) Send(m *RouteNote) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routeGuideRouteChatClient) Recv() (*RouteNote, error) { + m := new(RouteNote) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// RouteGuideServer is the server API for RouteGuide service. +// All implementations must embed UnimplementedRouteGuideServer +// for forward compatibility +type RouteGuideServer interface { + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + RecordRoute(RouteGuide_RecordRouteServer) error + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + RouteChat(RouteGuide_RouteChatServer) error + mustEmbedUnimplementedRouteGuideServer() +} + +// UnimplementedRouteGuideServer must be embedded to have forward compatible implementations. +type UnimplementedRouteGuideServer struct { +} + +func (UnimplementedRouteGuideServer) RecordRoute(RouteGuide_RecordRouteServer) error { + return status.Errorf(codes.Unimplemented, "method RecordRoute not implemented") +} +func (UnimplementedRouteGuideServer) RouteChat(RouteGuide_RouteChatServer) error { + return status.Errorf(codes.Unimplemented, "method RouteChat not implemented") +} +func (UnimplementedRouteGuideServer) mustEmbedUnimplementedRouteGuideServer() {} + +// UnsafeRouteGuideServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RouteGuideServer will +// result in compilation errors. +type UnsafeRouteGuideServer interface { + mustEmbedUnimplementedRouteGuideServer() +} + +func RegisterRouteGuideServer(s grpc.ServiceRegistrar, srv RouteGuideServer) { + s.RegisterService(&RouteGuide_ServiceDesc, srv) +} + +func _RouteGuide_RecordRoute_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouteGuideServer).RecordRoute(&routeGuideRecordRouteServer{stream}) +} + +type RouteGuide_RecordRouteServer interface { + SendAndClose(*RouteSummary) error + Recv() (*Point, error) + grpc.ServerStream +} + +type routeGuideRecordRouteServer struct { + grpc.ServerStream +} + +func (x *routeGuideRecordRouteServer) SendAndClose(m *RouteSummary) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routeGuideRecordRouteServer) Recv() (*Point, error) { + m := new(Point) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _RouteGuide_RouteChat_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouteGuideServer).RouteChat(&routeGuideRouteChatServer{stream}) +} + +type RouteGuide_RouteChatServer interface { + Send(*RouteNote) error + Recv() (*RouteNote, error) + grpc.ServerStream +} + +type routeGuideRouteChatServer struct { + grpc.ServerStream +} + +func (x *routeGuideRouteChatServer) Send(m *RouteNote) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routeGuideRouteChatServer) Recv() (*RouteNote, error) { + m := new(RouteNote) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// RouteGuide_ServiceDesc is the grpc.ServiceDesc for RouteGuide service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RouteGuide_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "main.RouteGuide", + HandlerType: (*RouteGuideServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "RecordRoute", + Handler: _RouteGuide_RecordRoute_Handler, + ClientStreams: true, + }, + { + StreamName: "RouteChat", + Handler: _RouteGuide_RouteChat_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "route_guide.proto", +} diff --git a/lib/testutils/grpcservice/service.go b/lib/testutils/grpcservice/service.go new file mode 100644 index 000000000000..267c02f2c275 --- /dev/null +++ b/lib/testutils/grpcservice/service.go @@ -0,0 +1,834 @@ +// Package grpcservice contains the implementation of the test GRPC service. +package grpcservice + +import ( + context "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "math" + "math/rand" + "os" + sync "sync" + "time" + + "google.golang.org/protobuf/proto" +) + +// It generates the Go code for protobuf and the gRPC server used by this implementation. +// Check the following links for getting more details about protoc generation: +// * https://grpc.io/docs/protoc-installation +// * https://grpc.io/docs/languages/go/quickstart/ +// +// +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative route_guide.proto + +// FeatureExplorerImplementation contains an implementation of the FeatureExplorer service. +type FeatureExplorerImplementation struct { + Logf func(format string, v ...any) + UnimplementedFeatureExplorerServer + savedFeatures []*Feature // read-only after initialized +} + +// NewFeatureExplorerServer creates a FeatureExplorer server. +func NewFeatureExplorerServer(features ...*Feature) *FeatureExplorerImplementation { + return &FeatureExplorerImplementation{savedFeatures: features, Logf: log.Printf} +} + +// GetFeature returns the feature at the given point. +func (s *FeatureExplorerImplementation) GetFeature(_ context.Context, point *Point) (*Feature, error) { + s.Logf("GetFeature called with: %+v\n", point) + + n := rand.Intn(1000) //nolint:gosec + time.Sleep(time.Duration(n) * time.Millisecond) + + for _, feature := range s.savedFeatures { + if proto.Equal(feature.Location, point) { + return feature, nil + } + } + // No feature was found, return an unnamed feature + return &Feature{Location: point}, nil +} + +// ListFeatures lists all features contained within the given bounding Rectangle. +func (s *FeatureExplorerImplementation) ListFeatures(rect *Rectangle, stream FeatureExplorer_ListFeaturesServer) error { + s.Logf("ListFeatures called with: %+v\n", rect) + + for _, feature := range s.savedFeatures { + if inRange(feature.Location, rect) { + time.Sleep(100 * time.Millisecond) + if err := stream.Send(feature); err != nil { + return err + } + } + } + return nil +} + +// RouteGuideImplementation contains an implementation of the RouteGuide service. +type RouteGuideImplementation struct { + Logf func(format string, v ...any) + UnimplementedRouteGuideServer + savedFeatures []*Feature // read-only after initialized + + mu sync.Mutex // protects routeNotes + routeNotes map[string][]*RouteNote +} + +// NewRouteGuideServer creates a RouteGuide server. +func NewRouteGuideServer(features ...*Feature) *RouteGuideImplementation { + s := &RouteGuideImplementation{ + savedFeatures: features, + routeNotes: make(map[string][]*RouteNote), + Logf: log.Printf, + } + return s +} + +// RecordRoute records a route composited of a sequence of points. +// +// It gets a stream of points, and responds with statistics about the "trip": +// number of points, number of known features visited, total distance traveled, and +// total time spent. +func (s *RouteGuideImplementation) RecordRoute(stream RouteGuide_RecordRouteServer) error { + s.Logf("RecordRoute called") + + var pointCount, featureCount, distance int32 + var lastPoint *Point + startTime := time.Now() + for { + point, err := stream.Recv() + if errors.Is(err, io.EOF) { + endTime := time.Now() + + s.Logf("RecordRoute finished, sending summary") + + errClose := stream.SendAndClose(&RouteSummary{ + PointCount: pointCount, + FeatureCount: featureCount, + Distance: distance, + ElapsedTime: int32(endTime.Sub(startTime).Seconds()), + }) + if errClose != nil { + s.Logf("Close error: %s\n", errClose) + } + + return errClose + } + if err != nil { + return err + } + pointCount++ + for _, feature := range s.savedFeatures { + if proto.Equal(feature.Location, point) { + featureCount++ + } + } + if lastPoint != nil { + distance += calcDistance(lastPoint, point) + } + lastPoint = point + } +} + +// RouteChat receives a stream of message/location pairs, and responds with a stream of all +// previous messages at each of those locations. +func (s *RouteGuideImplementation) RouteChat(stream RouteGuide_RouteChatServer) error { + s.Logf("RouteChat called") + + for { + in, err := stream.Recv() + if errors.Is(err, io.EOF) { + return nil + } + if err != nil { + return err + } + key := serialize(in.Location) + + s.mu.Lock() + s.routeNotes[key] = append(s.routeNotes[key], in) + // Note: this copy prevents blocking other clients while serving this one. + // We don't need to do a deep copy, because elements in the slice are + // insert-only and never modified. + rn := make([]*RouteNote, len(s.routeNotes[key])) + copy(rn, s.routeNotes[key]) + s.mu.Unlock() + + for _, note := range rn { + if err := stream.Send(note); err != nil { + return err + } + } + } +} + +// LoadFeatures loads features from a JSON file. +func LoadFeatures(filePath string) []*Feature { + var data []byte + if filePath != "" { + var err error + data, err = os.ReadFile(filePath) //nolint:forbidigo,gosec + if err != nil { + panic(fmt.Sprintf("Failed to load default features: %v", err)) + } + } else { + data = []byte(exampleData) + } + var features []*Feature + if err := json.Unmarshal(data, &features); err != nil { + panic(fmt.Sprintf("Failed to load default features: %v", err)) + } + return features +} + +func toRadians(num float64) float64 { + return num * math.Pi / float64(180) +} + +// calcDistance calculates the distance between two points using the "haversine" formula. +// The formula is based on http://mathforum.org/library/drmath/view/51879.html. +func calcDistance(p1 *Point, p2 *Point) int32 { + const CordFactor float64 = 1e7 + const R = float64(6371000) // earth radius in metres + lat1 := toRadians(float64(p1.Latitude) / CordFactor) + lat2 := toRadians(float64(p2.Latitude) / CordFactor) + lng1 := toRadians(float64(p1.Longitude) / CordFactor) + lng2 := toRadians(float64(p2.Longitude) / CordFactor) + dlat := lat2 - lat1 + dlng := lng2 - lng1 + + a := math.Sin(dlat/2)*math.Sin(dlat/2) + + math.Cos(lat1)*math.Cos(lat2)* + math.Sin(dlng/2)*math.Sin(dlng/2) + c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) + + distance := R * c + return int32(distance) +} + +func inRange(point *Point, rect *Rectangle) bool { + left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) + right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude)) + top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) + bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude)) + + if float64(point.Longitude) >= left && + float64(point.Longitude) <= right && + float64(point.Latitude) >= bottom && + float64(point.Latitude) <= top { + return true + } + return false +} + +func serialize(point *Point) string { + return fmt.Sprintf("%d %d", point.Latitude, point.Longitude) +} + +// exampleData is a copy of testdata/route_guide_db.json. It's to avoid +// specifying file path with `go run`. +const exampleData = `[{ + "location": { + "latitude": 407838351, + "longitude": -746143763 + }, + "name": "Patriots Path, Mendham, NJ 07945, USA" +}, { + "location": { + "latitude": 408122808, + "longitude": -743999179 + }, + "name": "101 New Jersey 10, Whippany, NJ 07981, USA" +}, { + "location": { + "latitude": 413628156, + "longitude": -749015468 + }, + "name": "U.S. 6, Shohola, PA 18458, USA" +}, { + "location": { + "latitude": 419999544, + "longitude": -740371136 + }, + "name": "5 Conners Road, Kingston, NY 12401, USA" +}, { + "location": { + "latitude": 414008389, + "longitude": -743951297 + }, + "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" +}, { + "location": { + "latitude": 419611318, + "longitude": -746524769 + }, + "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" +}, { + "location": { + "latitude": 406109563, + "longitude": -742186778 + }, + "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" +}, { + "location": { + "latitude": 416802456, + "longitude": -742370183 + }, + "name": "352 South Mountain Road, Wallkill, NY 12589, USA" +}, { + "location": { + "latitude": 412950425, + "longitude": -741077389 + }, + "name": "Bailey Turn Road, Harriman, NY 10926, USA" +}, { + "location": { + "latitude": 412144655, + "longitude": -743949739 + }, + "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" +}, { + "location": { + "latitude": 415736605, + "longitude": -742847522 + }, + "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" +}, { + "location": { + "latitude": 413843930, + "longitude": -740501726 + }, + "name": "162 Merrill Road, Highland Mills, NY 10930, USA" +}, { + "location": { + "latitude": 410873075, + "longitude": -744459023 + }, + "name": "Clinton Road, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 412346009, + "longitude": -744026814 + }, + "name": "16 Old Brook Lane, Warwick, NY 10990, USA" +}, { + "location": { + "latitude": 402948455, + "longitude": -747903913 + }, + "name": "3 Drake Lane, Pennington, NJ 08534, USA" +}, { + "location": { + "latitude": 406337092, + "longitude": -740122226 + }, + "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" +}, { + "location": { + "latitude": 406421967, + "longitude": -747727624 + }, + "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" +}, { + "location": { + "latitude": 416318082, + "longitude": -749677716 + }, + "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" +}, { + "location": { + "latitude": 415301720, + "longitude": -748416257 + }, + "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" +}, { + "location": { + "latitude": 402647019, + "longitude": -747071791 + }, + "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" +}, { + "location": { + "latitude": 412567807, + "longitude": -741058078 + }, + "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" +}, { + "location": { + "latitude": 416855156, + "longitude": -744420597 + }, + "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" +}, { + "location": { + "latitude": 404663628, + "longitude": -744820157 + }, + "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" +}, { + "location": { + "latitude": 407113723, + "longitude": -749746483 + }, + "name": "" +}, { + "location": { + "latitude": 402133926, + "longitude": -743613249 + }, + "name": "" +}, { + "location": { + "latitude": 400273442, + "longitude": -741220915 + }, + "name": "" +}, { + "location": { + "latitude": 411236786, + "longitude": -744070769 + }, + "name": "" +}, { + "location": { + "latitude": 411633782, + "longitude": -746784970 + }, + "name": "211-225 Plains Road, Augusta, NJ 07822, USA" +}, { + "location": { + "latitude": 415830701, + "longitude": -742952812 + }, + "name": "" +}, { + "location": { + "latitude": 413447164, + "longitude": -748712898 + }, + "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" +}, { + "location": { + "latitude": 405047245, + "longitude": -749800722 + }, + "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" +}, { + "location": { + "latitude": 418858923, + "longitude": -746156790 + }, + "name": "" +}, { + "location": { + "latitude": 417951888, + "longitude": -748484944 + }, + "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" +}, { + "location": { + "latitude": 407033786, + "longitude": -743977337 + }, + "name": "26 East 3rd Street, New Providence, NJ 07974, USA" +}, { + "location": { + "latitude": 417548014, + "longitude": -740075041 + }, + "name": "" +}, { + "location": { + "latitude": 410395868, + "longitude": -744972325 + }, + "name": "" +}, { + "location": { + "latitude": 404615353, + "longitude": -745129803 + }, + "name": "" +}, { + "location": { + "latitude": 406589790, + "longitude": -743560121 + }, + "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" +}, { + "location": { + "latitude": 414653148, + "longitude": -740477477 + }, + "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" +}, { + "location": { + "latitude": 405957808, + "longitude": -743255336 + }, + "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" +}, { + "location": { + "latitude": 411733589, + "longitude": -741648093 + }, + "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" +}, { + "location": { + "latitude": 412676291, + "longitude": -742606606 + }, + "name": "1270 Lakes Road, Monroe, NY 10950, USA" +}, { + "location": { + "latitude": 409224445, + "longitude": -748286738 + }, + "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" +}, { + "location": { + "latitude": 406523420, + "longitude": -742135517 + }, + "name": "652 Garden Street, Elizabeth, NJ 07202, USA" +}, { + "location": { + "latitude": 401827388, + "longitude": -740294537 + }, + "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" +}, { + "location": { + "latitude": 410564152, + "longitude": -743685054 + }, + "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 408472324, + "longitude": -740726046 + }, + "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" +}, { + "location": { + "latitude": 412452168, + "longitude": -740214052 + }, + "name": "5 White Oak Lane, Stony Point, NY 10980, USA" +}, { + "location": { + "latitude": 409146138, + "longitude": -746188906 + }, + "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" +}, { + "location": { + "latitude": 404701380, + "longitude": -744781745 + }, + "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 409642566, + "longitude": -746017679 + }, + "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" +}, { + "location": { + "latitude": 408031728, + "longitude": -748645385 + }, + "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" +}, { + "location": { + "latitude": 413700272, + "longitude": -742135189 + }, + "name": "367 Prospect Road, Chester, NY 10918, USA" +}, { + "location": { + "latitude": 404310607, + "longitude": -740282632 + }, + "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" +}, { + "location": { + "latitude": 409319800, + "longitude": -746201391 + }, + "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" +}, { + "location": { + "latitude": 406685311, + "longitude": -742108603 + }, + "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" +}, { + "location": { + "latitude": 419018117, + "longitude": -749142781 + }, + "name": "43 Dreher Road, Roscoe, NY 12776, USA" +}, { + "location": { + "latitude": 412856162, + "longitude": -745148837 + }, + "name": "Swan Street, Pine Island, NY 10969, USA" +}, { + "location": { + "latitude": 416560744, + "longitude": -746721964 + }, + "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" +}, { + "location": { + "latitude": 405314270, + "longitude": -749836354 + }, + "name": "" +}, { + "location": { + "latitude": 414219548, + "longitude": -743327440 + }, + "name": "" +}, { + "location": { + "latitude": 415534177, + "longitude": -742900616 + }, + "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" +}, { + "location": { + "latitude": 406898530, + "longitude": -749127080 + }, + "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" +}, { + "location": { + "latitude": 407586880, + "longitude": -741670168 + }, + "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" +}, { + "location": { + "latitude": 400106455, + "longitude": -742870190 + }, + "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" +}, { + "location": { + "latitude": 400066188, + "longitude": -746793294 + }, + "name": "" +}, { + "location": { + "latitude": 418803880, + "longitude": -744102673 + }, + "name": "40 Mountain Road, Napanoch, NY 12458, USA" +}, { + "location": { + "latitude": 414204288, + "longitude": -747895140 + }, + "name": "" +}, { + "location": { + "latitude": 414777405, + "longitude": -740615601 + }, + "name": "" +}, { + "location": { + "latitude": 415464475, + "longitude": -747175374 + }, + "name": "48 North Road, Forestburgh, NY 12777, USA" +}, { + "location": { + "latitude": 404062378, + "longitude": -746376177 + }, + "name": "" +}, { + "location": { + "latitude": 405688272, + "longitude": -749285130 + }, + "name": "" +}, { + "location": { + "latitude": 400342070, + "longitude": -748788996 + }, + "name": "" +}, { + "location": { + "latitude": 401809022, + "longitude": -744157964 + }, + "name": "" +}, { + "location": { + "latitude": 404226644, + "longitude": -740517141 + }, + "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" +}, { + "location": { + "latitude": 410322033, + "longitude": -747871659 + }, + "name": "" +}, { + "location": { + "latitude": 407100674, + "longitude": -747742727 + }, + "name": "" +}, { + "location": { + "latitude": 418811433, + "longitude": -741718005 + }, + "name": "213 Bush Road, Stone Ridge, NY 12484, USA" +}, { + "location": { + "latitude": 415034302, + "longitude": -743850945 + }, + "name": "" +}, { + "location": { + "latitude": 411349992, + "longitude": -743694161 + }, + "name": "" +}, { + "location": { + "latitude": 404839914, + "longitude": -744759616 + }, + "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 414638017, + "longitude": -745957854 + }, + "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" +}, { + "location": { + "latitude": 412127800, + "longitude": -740173578 + }, + "name": "" +}, { + "location": { + "latitude": 401263460, + "longitude": -747964303 + }, + "name": "" +}, { + "location": { + "latitude": 412843391, + "longitude": -749086026 + }, + "name": "" +}, { + "location": { + "latitude": 418512773, + "longitude": -743067823 + }, + "name": "" +}, { + "location": { + "latitude": 404318328, + "longitude": -740835638 + }, + "name": "42-102 Main Street, Belford, NJ 07718, USA" +}, { + "location": { + "latitude": 419020746, + "longitude": -741172328 + }, + "name": "" +}, { + "location": { + "latitude": 404080723, + "longitude": -746119569 + }, + "name": "" +}, { + "location": { + "latitude": 401012643, + "longitude": -744035134 + }, + "name": "" +}, { + "location": { + "latitude": 404306372, + "longitude": -741079661 + }, + "name": "" +}, { + "location": { + "latitude": 403966326, + "longitude": -748519297 + }, + "name": "" +}, { + "location": { + "latitude": 405002031, + "longitude": -748407866 + }, + "name": "" +}, { + "location": { + "latitude": 409532885, + "longitude": -742200683 + }, + "name": "" +}, { + "location": { + "latitude": 416851321, + "longitude": -742674555 + }, + "name": "" +}, { + "location": { + "latitude": 406411633, + "longitude": -741722051 + }, + "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" +}, { + "location": { + "latitude": 413069058, + "longitude": -744597778 + }, + "name": "261 Van Sickle Road, Goshen, NY 10924, USA" +}, { + "location": { + "latitude": 418465462, + "longitude": -746859398 + }, + "name": "" +}, { + "location": { + "latitude": 411733222, + "longitude": -744228360 + }, + "name": "" +}, { + "location": { + "latitude": 410248224, + "longitude": -747127767 + }, + "name": "3 Hasta Way, Newton, NJ 07860, USA" +}]` diff --git a/lib/testutils/httpmultibin/grpc_wrappers_testing/generate.go b/lib/testutils/httpmultibin/grpc_wrappers_testing/generate.go deleted file mode 100644 index 4d8e3d9df9dd..000000000000 --- a/lib/testutils/httpmultibin/grpc_wrappers_testing/generate.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package grpc_wrappers_testing contains the test service that helps to test gRPC wrappers. -package grpc_wrappers_testing //nolint:revive,stylecheck - -//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative test.proto diff --git a/lib/testutils/httpmultibin/grpc_wrappers_testing/service.go b/lib/testutils/httpmultibin/grpc_wrappers_testing/service.go index c6092a545313..92fe2acd29aa 100644 --- a/lib/testutils/httpmultibin/grpc_wrappers_testing/service.go +++ b/lib/testutils/httpmultibin/grpc_wrappers_testing/service.go @@ -1,14 +1,18 @@ -package grpc_wrappers_testing //nolint:revive,stylecheck +// Package grpc_wrappers_testing provides a test service that could be used for the testing gRPC wrappers +package grpc_wrappers_testing //nolint:revive,stylecheck // we want to be consistent with the other packages import ( - "context" + context "context" + _struct "github.com/golang/protobuf/ptypes/struct" wrappers "github.com/golang/protobuf/ptypes/wrappers" - "google.golang.org/grpc" + grpc "google.golang.org/grpc" ) +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative test.proto + // Register registers a test service that could be used for the testing gRPC wrappers -func Register(r grpc.ServiceRegistrar) *service { //nolint:revive +func Register(r grpc.ServiceRegistrar) *service { //nolint:revive // this is a test service s := &service{} RegisterServiceServer(r, s) @@ -23,6 +27,8 @@ type service struct { TestIntegerImplementation func(context.Context, *wrappers.Int64Value) (*wrappers.Int64Value, error) TestBooleanImplementation func(context.Context, *wrappers.BoolValue) (*wrappers.BoolValue, error) TestDoubleImplementation func(context.Context, *wrappers.DoubleValue) (*wrappers.DoubleValue, error) + TestValueImplementation func(context.Context, *_struct.Value) (*_struct.Value, error) + TestStreamImplementation func(Service_TestStreamServer) error } func (s *service) TestString(ctx context.Context, in *wrappers.StringValue) (*wrappers.StringValue, error) { @@ -56,3 +62,19 @@ func (s *service) TestDouble(ctx context.Context, in *wrappers.DoubleValue) (*wr return s.UnimplementedServiceServer.TestDouble(ctx, in) } + +func (s *service) TestValue(ctx context.Context, in *_struct.Value) (*_struct.Value, error) { + if s.TestValueImplementation != nil { + return s.TestValueImplementation(ctx, in) + } + + return s.UnimplementedServiceServer.TestValue(ctx, in) +} + +func (s *service) TestStream(stream Service_TestStreamServer) error { + if s.TestStreamImplementation != nil { + return s.TestStreamImplementation(stream) + } + + return s.UnimplementedServiceServer.TestStream(stream) +} diff --git a/lib/testutils/httpmultibin/grpc_wrappers_testing/test.pb.go b/lib/testutils/httpmultibin/grpc_wrappers_testing/test.pb.go index 3c69d5be8694..b11e8500b828 100644 --- a/lib/testutils/httpmultibin/grpc_wrappers_testing/test.pb.go +++ b/lib/testutils/httpmultibin/grpc_wrappers_testing/test.pb.go @@ -7,6 +7,7 @@ package grpc_wrappers_testing import ( + _struct "github.com/golang/protobuf/ptypes/struct" wrappers "github.com/golang/protobuf/ptypes/wrappers" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -27,28 +28,38 @@ var file_test_proto_rawDesc = []byte{ 0x70, 0x63, 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x32, 0xad, 0x02, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0x48, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, + 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0xb6, 0x03, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, + 0x0a, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x54, 0x65, 0x73, - 0x74, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, - 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x0a, 0x54, 0x65, 0x73, - 0x74, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x49, + 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x45, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x12, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, + 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x44, + 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x42, 0x19, 0x5a, 0x17, 0x2e, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x77, 0x72, - 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, + 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x28, 0x01, 0x42, 0x19, 0x5a, 0x17, 0x2e, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x5f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x5f, 0x74, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var file_test_proto_goTypes = []interface{}{ @@ -56,18 +67,23 @@ var file_test_proto_goTypes = []interface{}{ (*wrappers.Int64Value)(nil), // 1: google.protobuf.Int64Value (*wrappers.BoolValue)(nil), // 2: google.protobuf.BoolValue (*wrappers.DoubleValue)(nil), // 3: google.protobuf.DoubleValue + (*_struct.Value)(nil), // 4: google.protobuf.Value } var file_test_proto_depIdxs = []int32{ 0, // 0: grpc.wrappers.testing.Service.TestString:input_type -> google.protobuf.StringValue 1, // 1: grpc.wrappers.testing.Service.TestInteger:input_type -> google.protobuf.Int64Value 2, // 2: grpc.wrappers.testing.Service.TestBoolean:input_type -> google.protobuf.BoolValue 3, // 3: grpc.wrappers.testing.Service.TestDouble:input_type -> google.protobuf.DoubleValue - 0, // 4: grpc.wrappers.testing.Service.TestString:output_type -> google.protobuf.StringValue - 1, // 5: grpc.wrappers.testing.Service.TestInteger:output_type -> google.protobuf.Int64Value - 2, // 6: grpc.wrappers.testing.Service.TestBoolean:output_type -> google.protobuf.BoolValue - 3, // 7: grpc.wrappers.testing.Service.TestDouble:output_type -> google.protobuf.DoubleValue - 4, // [4:8] is the sub-list for method output_type - 0, // [0:4] is the sub-list for method input_type + 4, // 4: grpc.wrappers.testing.Service.TestValue:input_type -> google.protobuf.Value + 0, // 5: grpc.wrappers.testing.Service.TestStream:input_type -> google.protobuf.StringValue + 0, // 6: grpc.wrappers.testing.Service.TestString:output_type -> google.protobuf.StringValue + 1, // 7: grpc.wrappers.testing.Service.TestInteger:output_type -> google.protobuf.Int64Value + 2, // 8: grpc.wrappers.testing.Service.TestBoolean:output_type -> google.protobuf.BoolValue + 3, // 9: grpc.wrappers.testing.Service.TestDouble:output_type -> google.protobuf.DoubleValue + 4, // 10: grpc.wrappers.testing.Service.TestValue:output_type -> google.protobuf.Value + 0, // 11: grpc.wrappers.testing.Service.TestStream:output_type -> google.protobuf.StringValue + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/lib/testutils/httpmultibin/grpc_wrappers_testing/test.proto b/lib/testutils/httpmultibin/grpc_wrappers_testing/test.proto index 6ba9f925a1b0..a1e9eb5fec20 100644 --- a/lib/testutils/httpmultibin/grpc_wrappers_testing/test.proto +++ b/lib/testutils/httpmultibin/grpc_wrappers_testing/test.proto @@ -6,6 +6,7 @@ package grpc.wrappers.testing; // https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/wrappers.proto import "google/protobuf/wrappers.proto"; +import "google/protobuf/struct.proto"; option go_package ="./grpc_wrappers_testing"; @@ -14,4 +15,7 @@ service Service { rpc TestInteger(google.protobuf.Int64Value) returns (google.protobuf.Int64Value); rpc TestBoolean(google.protobuf.BoolValue) returns (google.protobuf.BoolValue); rpc TestDouble(google.protobuf.DoubleValue) returns (google.protobuf.DoubleValue); + rpc TestValue(google.protobuf.Value) returns (google.protobuf.Value); + + rpc TestStream(stream google.protobuf.StringValue) returns (google.protobuf.StringValue); } diff --git a/lib/testutils/httpmultibin/grpc_wrappers_testing/test_grpc.pb.go b/lib/testutils/httpmultibin/grpc_wrappers_testing/test_grpc.pb.go index 4f7cb02ec5f0..0af4769311e5 100644 --- a/lib/testutils/httpmultibin/grpc_wrappers_testing/test_grpc.pb.go +++ b/lib/testutils/httpmultibin/grpc_wrappers_testing/test_grpc.pb.go @@ -4,6 +4,7 @@ package grpc_wrappers_testing import ( context "context" + _struct "github.com/golang/protobuf/ptypes/struct" wrappers "github.com/golang/protobuf/ptypes/wrappers" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -23,6 +24,8 @@ type ServiceClient interface { TestInteger(ctx context.Context, in *wrappers.Int64Value, opts ...grpc.CallOption) (*wrappers.Int64Value, error) TestBoolean(ctx context.Context, in *wrappers.BoolValue, opts ...grpc.CallOption) (*wrappers.BoolValue, error) TestDouble(ctx context.Context, in *wrappers.DoubleValue, opts ...grpc.CallOption) (*wrappers.DoubleValue, error) + TestValue(ctx context.Context, in *_struct.Value, opts ...grpc.CallOption) (*_struct.Value, error) + TestStream(ctx context.Context, opts ...grpc.CallOption) (Service_TestStreamClient, error) } type serviceClient struct { @@ -69,6 +72,49 @@ func (c *serviceClient) TestDouble(ctx context.Context, in *wrappers.DoubleValue return out, nil } +func (c *serviceClient) TestValue(ctx context.Context, in *_struct.Value, opts ...grpc.CallOption) (*_struct.Value, error) { + out := new(_struct.Value) + err := c.cc.Invoke(ctx, "/grpc.wrappers.testing.Service/TestValue", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) TestStream(ctx context.Context, opts ...grpc.CallOption) (Service_TestStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &Service_ServiceDesc.Streams[0], "/grpc.wrappers.testing.Service/TestStream", opts...) + if err != nil { + return nil, err + } + x := &serviceTestStreamClient{stream} + return x, nil +} + +type Service_TestStreamClient interface { + Send(*wrappers.StringValue) error + CloseAndRecv() (*wrappers.StringValue, error) + grpc.ClientStream +} + +type serviceTestStreamClient struct { + grpc.ClientStream +} + +func (x *serviceTestStreamClient) Send(m *wrappers.StringValue) error { + return x.ClientStream.SendMsg(m) +} + +func (x *serviceTestStreamClient) CloseAndRecv() (*wrappers.StringValue, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(wrappers.StringValue) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // ServiceServer is the server API for Service service. // All implementations must embed UnimplementedServiceServer // for forward compatibility @@ -77,6 +123,8 @@ type ServiceServer interface { TestInteger(context.Context, *wrappers.Int64Value) (*wrappers.Int64Value, error) TestBoolean(context.Context, *wrappers.BoolValue) (*wrappers.BoolValue, error) TestDouble(context.Context, *wrappers.DoubleValue) (*wrappers.DoubleValue, error) + TestValue(context.Context, *_struct.Value) (*_struct.Value, error) + TestStream(Service_TestStreamServer) error mustEmbedUnimplementedServiceServer() } @@ -96,6 +144,12 @@ func (UnimplementedServiceServer) TestBoolean(context.Context, *wrappers.BoolVal func (UnimplementedServiceServer) TestDouble(context.Context, *wrappers.DoubleValue) (*wrappers.DoubleValue, error) { return nil, status.Errorf(codes.Unimplemented, "method TestDouble not implemented") } +func (UnimplementedServiceServer) TestValue(context.Context, *_struct.Value) (*_struct.Value, error) { + return nil, status.Errorf(codes.Unimplemented, "method TestValue not implemented") +} +func (UnimplementedServiceServer) TestStream(Service_TestStreamServer) error { + return status.Errorf(codes.Unimplemented, "method TestStream not implemented") +} func (UnimplementedServiceServer) mustEmbedUnimplementedServiceServer() {} // UnsafeServiceServer may be embedded to opt out of forward compatibility for this service. @@ -181,6 +235,50 @@ func _Service_TestDouble_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Service_TestValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(_struct.Value) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ServiceServer).TestValue(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.wrappers.testing.Service/TestValue", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ServiceServer).TestValue(ctx, req.(*_struct.Value)) + } + return interceptor(ctx, in, info, handler) +} + +func _Service_TestStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(ServiceServer).TestStream(&serviceTestStreamServer{stream}) +} + +type Service_TestStreamServer interface { + SendAndClose(*wrappers.StringValue) error + Recv() (*wrappers.StringValue, error) + grpc.ServerStream +} + +type serviceTestStreamServer struct { + grpc.ServerStream +} + +func (x *serviceTestStreamServer) SendAndClose(m *wrappers.StringValue) error { + return x.ServerStream.SendMsg(m) +} + +func (x *serviceTestStreamServer) Recv() (*wrappers.StringValue, error) { + m := new(wrappers.StringValue) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // Service_ServiceDesc is the grpc.ServiceDesc for Service service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -204,7 +302,17 @@ var Service_ServiceDesc = grpc.ServiceDesc{ MethodName: "TestDouble", Handler: _Service_TestDouble_Handler, }, + { + MethodName: "TestValue", + Handler: _Service_TestValue_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "TestStream", + Handler: _Service_TestStream_Handler, + ClientStreams: true, + }, }, - Streams: []grpc.StreamDesc{}, Metadata: "test.proto", } diff --git a/lib/testutils/httpmultibin/nested_types/nested_types.proto b/lib/testutils/httpmultibin/nested_types/nested_types.proto new file mode 100644 index 000000000000..66dd353be35d --- /dev/null +++ b/lib/testutils/httpmultibin/nested_types/nested_types.proto @@ -0,0 +1,36 @@ +// The purpose of this proto file is to demonstrate that we can have +// nested types and that we should be able to load them correctly. + +syntax = "proto3"; + +package grpc.testdata.nested.types; + +// Example to demonstrate that it is possible to define +// and use message types within other message types +message Outer { // Level 0 + message MiddleAA { // Level 1 + message Inner { // Level 2 + int64 ival = 1; + bool booly = 2; + } + Inner inner = 1; + } + + message MiddleBB { // Level 1 + message Inner { // Level 2 + int32 ival = 1; + bool booly = 2; + } + Inner inner = 1; + } + + MiddleAA middleAA = 1; + MiddleBB middleBB = 2; +} + +// Example to demonstrate that it is possible to reuse +// a message type outside its parent message type +message MeldOuter { + Outer.MiddleAA.Inner innerAA = 1; + Outer.MiddleBB.Inner innerBB = 2; +} diff --git a/vendor/github.com/golang/protobuf/ptypes/struct/struct.pb.go b/vendor/github.com/golang/protobuf/ptypes/struct/struct.pb.go new file mode 100644 index 000000000000..8d82abe21333 --- /dev/null +++ b/vendor/github.com/golang/protobuf/ptypes/struct/struct.pb.go @@ -0,0 +1,78 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: github.com/golang/protobuf/ptypes/struct/struct.proto + +package structpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" +) + +// Symbols defined in public import of google/protobuf/struct.proto. + +type NullValue = structpb.NullValue + +const NullValue_NULL_VALUE = structpb.NullValue_NULL_VALUE + +var NullValue_name = structpb.NullValue_name +var NullValue_value = structpb.NullValue_value + +type Struct = structpb.Struct +type Value = structpb.Value +type Value_NullValue = structpb.Value_NullValue +type Value_NumberValue = structpb.Value_NumberValue +type Value_StringValue = structpb.Value_StringValue +type Value_BoolValue = structpb.Value_BoolValue +type Value_StructValue = structpb.Value_StructValue +type Value_ListValue = structpb.Value_ListValue +type ListValue = structpb.ListValue + +var File_github_com_golang_protobuf_ptypes_struct_struct_proto protoreflect.FileDescriptor + +var file_github_com_golang_protobuf_ptypes_struct_struct_proto_rawDesc = []byte{ + 0x0a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, + 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x70, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x3b, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x70, 0x62, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var file_github_com_golang_protobuf_ptypes_struct_struct_proto_goTypes = []interface{}{} +var file_github_com_golang_protobuf_ptypes_struct_struct_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_github_com_golang_protobuf_ptypes_struct_struct_proto_init() } +func file_github_com_golang_protobuf_ptypes_struct_struct_proto_init() { + if File_github_com_golang_protobuf_ptypes_struct_struct_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_github_com_golang_protobuf_ptypes_struct_struct_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_github_com_golang_protobuf_ptypes_struct_struct_proto_goTypes, + DependencyIndexes: file_github_com_golang_protobuf_ptypes_struct_struct_proto_depIdxs, + }.Build() + File_github_com_golang_protobuf_ptypes_struct_struct_proto = out.File + file_github_com_golang_protobuf_ptypes_struct_struct_proto_rawDesc = nil + file_github_com_golang_protobuf_ptypes_struct_struct_proto_goTypes = nil + file_github_com_golang_protobuf_ptypes_struct_struct_proto_depIdxs = nil +} diff --git a/vendor/github.com/grafana/xk6-grpc/LICENSE b/vendor/github.com/grafana/xk6-grpc/LICENSE deleted file mode 100644 index 0ad25db4bd1d..000000000000 --- a/vendor/github.com/grafana/xk6-grpc/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/client.go b/vendor/github.com/grafana/xk6-grpc/grpc/client.go deleted file mode 100644 index ef46ffce0e25..000000000000 --- a/vendor/github.com/grafana/xk6-grpc/grpc/client.go +++ /dev/null @@ -1,469 +0,0 @@ -package grpc - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io" - "strings" - "time" - - "github.com/grafana/xk6-grpc/lib/netext/grpcext" - "go.k6.io/k6/js/common" - "go.k6.io/k6/js/modules" - - "github.com/dop251/goja" - "github.com/jhump/protoreflect/desc" - "github.com/jhump/protoreflect/desc/protoparse" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protodesc" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/types/descriptorpb" - "google.golang.org/protobuf/types/dynamicpb" -) - -// Client represents a gRPC client that can be used to make RPC requests -type Client struct { - mds map[string]protoreflect.MethodDescriptor - conn *grpcext.Conn - vu modules.VU - addr string -} - -// Load will parse the given proto files and make the file descriptors available to request. -func (c *Client) Load(importPaths []string, filenames ...string) ([]MethodInfo, error) { - if c.vu.State() != nil { - return nil, errors.New("load must be called in the init context") - } - - initEnv := c.vu.InitEnv() - if initEnv == nil { - return nil, errors.New("missing init environment") - } - - // If no import paths are specified, use the current working directory - if len(importPaths) == 0 { - importPaths = append(importPaths, initEnv.CWD.Path) - } - - parser := protoparse.Parser{ - ImportPaths: importPaths, - InferImportPaths: false, - Accessor: protoparse.FileAccessor(func(filename string) (io.ReadCloser, error) { - absFilePath := initEnv.GetAbsFilePath(filename) - return initEnv.FileSystems["file"].Open(absFilePath) - }), - } - - fds, err := parser.ParseFiles(filenames...) - if err != nil { - return nil, err - } - - fdset := &descriptorpb.FileDescriptorSet{} - - seen := make(map[string]struct{}) - for _, fd := range fds { - fdset.File = append(fdset.File, walkFileDescriptors(seen, fd)...) - } - return c.convertToMethodInfo(fdset) -} - -// LoadProtoset will parse the given protoset file (serialized FileDescriptorSet) and make the file -// descriptors available to request. -func (c *Client) LoadProtoset(protosetPath string) ([]MethodInfo, error) { - if c.vu.State() != nil { - return nil, errors.New("load must be called in the init context") - } - - initEnv := c.vu.InitEnv() - if initEnv == nil { - return nil, errors.New("missing init environment") - } - - absFilePath := initEnv.GetAbsFilePath(protosetPath) - fdsetFile, err := initEnv.FileSystems["file"].Open(absFilePath) - if err != nil { - return nil, fmt.Errorf("couldn't open protoset: %w", err) - } - - defer func() { _ = fdsetFile.Close() }() - fdsetBytes, err := io.ReadAll(fdsetFile) - if err != nil { - return nil, fmt.Errorf("couldn't read protoset: %w", err) - } - - fdset := &descriptorpb.FileDescriptorSet{} - if err = proto.Unmarshal(fdsetBytes, fdset); err != nil { - return nil, fmt.Errorf("couldn't unmarshal protoset file %s: %w", protosetPath, err) - } - - return c.convertToMethodInfo(fdset) -} - -// Note: this function was lifted from `lib/options.go` -func decryptPrivateKey(key, password []byte) ([]byte, error) { - block, _ := pem.Decode(key) - if block == nil { - return nil, errors.New("failed to decode PEM key") - } - - blockType := block.Type - if blockType == "ENCRYPTED PRIVATE KEY" { - return nil, errors.New("encrypted pkcs8 formatted key is not supported") - } - /* - Even though `DecryptPEMBlock` has been deprecated since 1.16.x it is still - being used here because it is deprecated due to it not supporting *good* cryptography - ultimately though we want to support something so we will be using it for now. - */ - decryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck - if err != nil { - return nil, err - } - key = pem.EncodeToMemory(&pem.Block{ - Type: blockType, - Bytes: decryptedKey, - }) - return key, nil -} - -func buildTLSConfig(parentConfig *tls.Config, certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) { - var cp *x509.CertPool - if len(caCertificates) > 0 { - cp, _ = x509.SystemCertPool() - for i, caCert := range caCertificates { - if ok := cp.AppendCertsFromPEM(caCert); !ok { - return nil, fmt.Errorf("failed to append ca certificate [%d] from PEM", i) - } - } - } - - // Ignoring 'TLS MinVersion is too low' because this tls.Config will inherit MinValue and MaxValue - // from the vu state tls.Config - - //nolint:golint,gosec - tlsCfg := &tls.Config{ - CipherSuites: parentConfig.CipherSuites, - InsecureSkipVerify: parentConfig.InsecureSkipVerify, - MinVersion: parentConfig.MinVersion, - MaxVersion: parentConfig.MaxVersion, - Renegotiation: parentConfig.Renegotiation, - RootCAs: cp, - } - if len(certificate) > 0 && len(key) > 0 { - cert, err := tls.X509KeyPair(certificate, key) - if err != nil { - return nil, fmt.Errorf("failed to append certificate from PEM: %w", err) - } - tlsCfg.Certificates = []tls.Certificate{cert} - } - return tlsCfg, nil -} - -func buildTLSConfigFromMap(parentConfig *tls.Config, tlsConfigMap map[string]interface{}) (*tls.Config, error) { - var cert, key, pass []byte - var ca [][]byte - var err error - if certstr, ok := tlsConfigMap["cert"].(string); ok { - cert = []byte(certstr) - } - if keystr, ok := tlsConfigMap["key"].(string); ok { - key = []byte(keystr) - } - if passwordStr, ok := tlsConfigMap["password"].(string); ok { - pass = []byte(passwordStr) - if len(pass) > 0 { - if key, err = decryptPrivateKey(key, pass); err != nil { - return nil, err - } - } - } - if cas, ok := tlsConfigMap["cacerts"]; ok { - var caCertsArray []interface{} - if caCertsArray, ok = cas.([]interface{}); ok { - ca = make([][]byte, len(caCertsArray)) - for i, entry := range caCertsArray { - var entryStr string - if entryStr, ok = entry.(string); ok { - ca[i] = []byte(entryStr) - } - } - } else if caCertStr, caCertStrOk := cas.(string); caCertStrOk { - ca = [][]byte{[]byte(caCertStr)} - } - } - return buildTLSConfig(parentConfig, cert, key, ca) -} - -// Connect is a block dial to the gRPC server at the given address (host:port) -func (c *Client) Connect(addr string, params goja.Value) (bool, error) { - state := c.vu.State() - if state == nil { - return false, common.NewInitContextError("connecting to a gRPC server in the init context is not supported") - } - - p, err := newConnectParams(c.vu, params) - if err != nil { - return false, fmt.Errorf("invalid grpc.connect() parameters: %w", err) - } - - opts := grpcext.DefaultOptions(c.vu.State) - - var tcred credentials.TransportCredentials - if !p.IsPlaintext { - tlsCfg := state.TLSConfig.Clone() - if len(p.TLS) > 0 { - if tlsCfg, err = buildTLSConfigFromMap(tlsCfg, p.TLS); err != nil { - return false, err - } - } - tlsCfg.NextProtos = []string{"h2"} - - tcred = credentials.NewTLS(tlsCfg) - } else { - tcred = insecure.NewCredentials() - } - opts = append(opts, grpc.WithTransportCredentials(tcred)) - - if ua := state.Options.UserAgent; ua.Valid { - opts = append(opts, grpc.WithUserAgent(ua.ValueOrZero())) - } - - ctx, cancel := context.WithTimeout(c.vu.Context(), p.Timeout) - defer cancel() - - if p.MaxReceiveSize > 0 { - opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(p.MaxReceiveSize)))) - } - - if p.MaxSendSize > 0 { - opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(int(p.MaxSendSize)))) - } - - c.addr = addr - c.conn, err = grpcext.Dial(ctx, addr, opts...) - if err != nil { - return false, err - } - - if !p.UseReflectionProtocol { - return true, nil - } - - ctx = metadata.NewOutgoingContext(ctx, p.ReflectionMetadata) - - fdset, err := c.conn.Reflect(ctx) - if err != nil { - return false, err - } - _, err = c.convertToMethodInfo(fdset) - if err != nil { - return false, fmt.Errorf("can't convert method info: %w", err) - } - - return true, err -} - -// Invoke creates and calls a unary RPC by fully qualified method name -func (c *Client) Invoke( - method string, - req goja.Value, - params goja.Value, -) (*grpcext.Response, error) { - state := c.vu.State() - if state == nil { - return nil, common.NewInitContextError("invoking RPC methods in the init context is not supported") - } - if c.conn == nil { - return nil, errors.New("no gRPC connection, you must call connect first") - } - if method == "" { - return nil, errors.New("method to invoke cannot be empty") - } - if method[0] != '/' { - method = "/" + method - } - methodDesc := c.mds[method] - if methodDesc == nil { - return nil, fmt.Errorf("method %q not found in file descriptors", method) - } - - p, err := newCallParams(c.vu, params) - if err != nil { - return nil, fmt.Errorf("invalid GRPC's client.invoke() parameters: %w", err) - } - - // k6 GRPC Invoke's default timeout is 2 minutes - if p.Timeout == time.Duration(0) { - p.Timeout = 2 * time.Minute - } - - if req == nil { - return nil, errors.New("request cannot be nil") - } - b, err := req.ToObject(c.vu.Runtime()).MarshalJSON() - if err != nil { - return nil, fmt.Errorf("unable to serialise request object: %w", err) - } - - ctx, cancel := context.WithTimeout(c.vu.Context(), p.Timeout) - defer cancel() - - p.SetSystemTags(state, c.addr, method) - - reqmsg := grpcext.Request{ - MethodDescriptor: methodDesc, - Message: b, - TagsAndMeta: &p.TagsAndMeta, - } - - return c.conn.Invoke(ctx, method, p.Metadata, reqmsg) -} - -// Close will close the client gRPC connection -func (c *Client) Close() error { - if c.conn == nil { - return nil - } - err := c.conn.Close() - c.conn = nil - - return err -} - -// MethodInfo holds information on any parsed method descriptors that can be used by the goja VM -type MethodInfo struct { - Package string - Service string - FullMethod string - grpc.MethodInfo `json:"-" js:"-"` -} - -func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]MethodInfo, error) { - files, err := protodesc.NewFiles(fdset) - if err != nil { - return nil, err - } - var rtn []MethodInfo - if c.mds == nil { - // This allows us to call load() multiple times, without overwriting the - // previously loaded definitions. - c.mds = make(map[string]protoreflect.MethodDescriptor) - } - appendMethodInfo := func( - fd protoreflect.FileDescriptor, - sd protoreflect.ServiceDescriptor, - md protoreflect.MethodDescriptor, - ) { - name := fmt.Sprintf("/%s/%s", sd.FullName(), md.Name()) - c.mds[name] = md - rtn = append(rtn, MethodInfo{ - MethodInfo: grpc.MethodInfo{ - Name: string(md.Name()), - IsClientStream: md.IsStreamingClient(), - IsServerStream: md.IsStreamingServer(), - }, - Package: string(fd.Package()), - Service: string(sd.Name()), - FullMethod: name, - }) - } - files.RangeFiles(func(fd protoreflect.FileDescriptor) bool { - sds := fd.Services() - for i := 0; i < sds.Len(); i++ { - sd := sds.Get(i) - mds := sd.Methods() - for j := 0; j < mds.Len(); j++ { - md := mds.Get(j) - appendMethodInfo(fd, sd, md) - } - } - - messages := fd.Messages() - - stack := make([]protoreflect.MessageDescriptor, 0, messages.Len()) - for i := 0; i < messages.Len(); i++ { - stack = append(stack, messages.Get(i)) - } - - for len(stack) > 0 { - message := stack[len(stack)-1] - stack = stack[:len(stack)-1] - - _, errFind := protoregistry.GlobalTypes.FindMessageByName(message.FullName()) - if errors.Is(errFind, protoregistry.NotFound) { - err = protoregistry.GlobalTypes.RegisterMessage(dynamicpb.NewMessageType(message)) - if err != nil { - return false - } - } - - nested := message.Messages() - for i := 0; i < nested.Len(); i++ { - stack = append(stack, nested.Get(i)) - } - } - - return true - }) - if err != nil { - return nil, err - } - return rtn, nil -} - -func walkFileDescriptors(seen map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto { - fds := []*descriptorpb.FileDescriptorProto{} - - if _, ok := seen[fd.GetName()]; ok { - return fds - } - seen[fd.GetName()] = struct{}{} - fds = append(fds, fd.AsFileDescriptorProto()) - - for _, dep := range fd.GetDependencies() { - deps := walkFileDescriptors(seen, dep) - fds = append(fds, deps...) - } - - return fds -} - -// sanitizeMethodName -func sanitizeMethodName(name string) string { - if name == "" { - return name - } - - if !strings.HasPrefix(name, "/") { - name = "/" + name - } - - return name -} - -// getMethodDescriptor sanitize it, and gets GRPC method descriptor or an error if not found -func (c *Client) getMethodDescriptor(method string) (protoreflect.MethodDescriptor, error) { - method = sanitizeMethodName(method) - - if method == "" { - return nil, errors.New("method to invoke cannot be empty") - } - - methodDesc := c.mds[method] - - if methodDesc == nil { - return nil, fmt.Errorf("method %q not found in file descriptors", method) - } - - return methodDesc, nil -} diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/grpc.go b/vendor/github.com/grafana/xk6-grpc/grpc/grpc.go deleted file mode 100644 index fdf2a8f42ec4..000000000000 --- a/vendor/github.com/grafana/xk6-grpc/grpc/grpc.go +++ /dev/null @@ -1,171 +0,0 @@ -// Package grpc is the root module of the k6-grpc extension. -package grpc - -import ( - "errors" - "fmt" - - "github.com/dop251/goja" - "github.com/mstoykov/k6-taskqueue-lib/taskqueue" - "go.k6.io/k6/js/common" - "go.k6.io/k6/js/modules" - "google.golang.org/grpc/codes" -) - -type ( - // RootModule is the global module instance that will create module - // instances for each VU. - RootModule struct{} - - // ModuleInstance represents an instance of the GRPC module for every VU. - ModuleInstance struct { - vu modules.VU - exports map[string]interface{} - metrics *instanceMetrics - } -) - -var ( - _ modules.Module = &RootModule{} - _ modules.Instance = &ModuleInstance{} -) - -// New returns a pointer to a new RootModule instance. -func New() *RootModule { - return &RootModule{} -} - -// NewModuleInstance implements the modules.Module interface to return -// a new instance for each VU. -func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance { - metrics, err := registerMetrics(vu.InitEnv().Registry) - if err != nil { - common.Throw(vu.Runtime(), fmt.Errorf("failed to register GRPC module metrics: %w", err)) - } - - mi := &ModuleInstance{ - vu: vu, - exports: make(map[string]interface{}), - metrics: metrics, - } - - mi.exports["Client"] = mi.NewClient - mi.defineConstants() - mi.exports["Stream"] = mi.stream - - return mi -} - -// NewClient is the JS constructor for the grpc Client. -func (mi *ModuleInstance) NewClient(_ goja.ConstructorCall) *goja.Object { - rt := mi.vu.Runtime() - return rt.ToValue(&Client{vu: mi.vu}).ToObject(rt) -} - -// defineConstants defines the constant variables of the module. -func (mi *ModuleInstance) defineConstants() { - rt := mi.vu.Runtime() - mustAddCode := func(name string, code codes.Code) { - mi.exports[name] = rt.ToValue(code) - } - - mustAddCode("StatusOK", codes.OK) - mustAddCode("StatusCanceled", codes.Canceled) - mustAddCode("StatusUnknown", codes.Unknown) - mustAddCode("StatusInvalidArgument", codes.InvalidArgument) - mustAddCode("StatusDeadlineExceeded", codes.DeadlineExceeded) - mustAddCode("StatusNotFound", codes.NotFound) - mustAddCode("StatusAlreadyExists", codes.AlreadyExists) - mustAddCode("StatusPermissionDenied", codes.PermissionDenied) - mustAddCode("StatusResourceExhausted", codes.ResourceExhausted) - mustAddCode("StatusFailedPrecondition", codes.FailedPrecondition) - mustAddCode("StatusAborted", codes.Aborted) - mustAddCode("StatusOutOfRange", codes.OutOfRange) - mustAddCode("StatusUnimplemented", codes.Unimplemented) - mustAddCode("StatusInternal", codes.Internal) - mustAddCode("StatusUnavailable", codes.Unavailable) - mustAddCode("StatusDataLoss", codes.DataLoss) - mustAddCode("StatusUnauthenticated", codes.Unauthenticated) -} - -// Exports returns the exports of the grpc module. -func (mi *ModuleInstance) Exports() modules.Exports { - return modules.Exports{ - Named: mi.exports, - } -} - -// stream returns a new stream object -func (mi *ModuleInstance) stream(c goja.ConstructorCall) *goja.Object { - rt := mi.vu.Runtime() - - client, err := extractClient(c.Argument(0), rt) - if err != nil { - common.Throw(rt, fmt.Errorf("invalid GRPC Stream's client: %w", err)) - } - - methodName := sanitizeMethodName(c.Argument(1).String()) - methodDescriptor, err := client.getMethodDescriptor(methodName) - if err != nil { - common.Throw(rt, fmt.Errorf("invalid GRPC Stream's method: %w", err)) - } - - p, err := newCallParams(mi.vu, c.Argument(2)) - if err != nil { - common.Throw(rt, fmt.Errorf("invalid GRPC Stream's parameters: %w", err)) - } - - p.SetSystemTags(mi.vu.State(), client.addr, methodName) - - logger := mi.vu.State().Logger.WithField("streamMethod", methodName) - - s := &stream{ - vu: mi.vu, - client: client, - methodDescriptor: methodDescriptor, - method: methodName, - logger: logger, - - tq: taskqueue.New(mi.vu.RegisterCallback), - - instanceMetrics: mi.metrics, - builtinMetrics: mi.vu.State().BuiltinMetrics, - done: make(chan struct{}), - writingState: opened, - - writeQueueCh: make(chan message), - - eventListeners: newEventListeners(), - obj: rt.NewObject(), - tagsAndMeta: &p.TagsAndMeta, - } - - defineStream(rt, s) - - err = s.beginStream(p) - if err != nil { - s.tq.Close() - - common.Throw(rt, err) - } - - return s.obj -} - -// extractClient extracts & validates a grpc.Client from a goja.Value. -func extractClient(v goja.Value, rt *goja.Runtime) (*Client, error) { - if common.IsNullish(v) { - return nil, errors.New("empty gRPC client") - } - - client, ok := v.ToObject(rt).Export().(*Client) - if !ok { - return nil, errors.New("not a gRPC client") - } - - if client.conn == nil { - return nil, errors.New("no gRPC connection, you must call connect first") - } - - return client, nil -} diff --git a/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/conn.go b/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/conn.go deleted file mode 100644 index 5e9f9e0fc0b9..000000000000 --- a/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/conn.go +++ /dev/null @@ -1,346 +0,0 @@ -// Package grpcext allows gRPC requests collecting stats info. -package grpcext - -import ( - "context" - "encoding/json" - "fmt" - "net" - "strconv" - "strings" - - "github.com/sirupsen/logrus" - "go.k6.io/k6/lib" - "go.k6.io/k6/metrics" - - protov1 "github.com/golang/protobuf/proto" //nolint:staticcheck,nolintlint // this is the old v1 version - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - grpcstats "google.golang.org/grpc/stats" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/types/descriptorpb" - "google.golang.org/protobuf/types/dynamicpb" -) - -// Request represents a gRPC request. -type Request struct { - MethodDescriptor protoreflect.MethodDescriptor - TagsAndMeta *metrics.TagsAndMeta - Message []byte -} - -// StreamRequest represents a gRPC stream request. -type StreamRequest struct { - Method string - MethodDescriptor protoreflect.MethodDescriptor - TagsAndMeta *metrics.TagsAndMeta - Metadata metadata.MD -} - -// Response represents a gRPC response. -type Response struct { - Message interface{} - Error interface{} - Headers map[string][]string - Trailers map[string][]string - Status codes.Code -} - -type clientConnCloser interface { - grpc.ClientConnInterface - Close() error -} - -// Conn is a gRPC client connection. -type Conn struct { - raw clientConnCloser -} - -// DefaultOptions generates an option set -// with common options for requests from a VU. -func DefaultOptions(getState func() *lib.State) []grpc.DialOption { - dialer := func(ctx context.Context, addr string) (net.Conn, error) { - return getState().Dialer.DialContext(ctx, "tcp", addr) - } - - return []grpc.DialOption{ - grpc.WithBlock(), - grpc.FailOnNonTempDialError(true), - grpc.WithReturnConnectionError(), - grpc.WithStatsHandler(statsHandler{getState: getState}), - grpc.WithContextDialer(dialer), - } -} - -// Dial establish a gRPC connection. -func Dial(ctx context.Context, addr string, options ...grpc.DialOption) (*Conn, error) { - conn, err := grpc.DialContext(ctx, addr, options...) - if err != nil { - return nil, err - } - return &Conn{ - raw: conn, - }, nil -} - -// Reflect returns using the reflection the FileDescriptorSet describing the service. -func (c *Conn) Reflect(ctx context.Context) (*descriptorpb.FileDescriptorSet, error) { - rc := reflectionClient{Conn: c.raw} - return rc.Reflect(ctx) -} - -// Invoke executes a unary gRPC request. -func (c *Conn) Invoke( - ctx context.Context, - url string, - md metadata.MD, - req Request, - opts ...grpc.CallOption, -) (*Response, error) { - if url == "" { - return nil, fmt.Errorf("url is required") - } - if req.MethodDescriptor == nil { - return nil, fmt.Errorf("request method descriptor is required") - } - if len(req.Message) == 0 { - return nil, fmt.Errorf("request message is required") - } - - ctx = metadata.NewOutgoingContext(ctx, md) - - reqdm := dynamicpb.NewMessage(req.MethodDescriptor.Input()) - if err := protojson.Unmarshal(req.Message, reqdm); err != nil { - return nil, fmt.Errorf("unable to serialise request object to protocol buffer: %w", err) - } - - ctx = withRPCState(ctx, &rpcState{tagsAndMeta: req.TagsAndMeta}) - - resp := dynamicpb.NewMessage(req.MethodDescriptor.Output()) - header, trailer := metadata.New(nil), metadata.New(nil) - - copts := make([]grpc.CallOption, 0, len(opts)+2) - copts = append(copts, opts...) - copts = append(copts, grpc.Header(&header), grpc.Trailer(&trailer)) - - err := c.raw.Invoke(ctx, url, reqdm, resp, copts...) - - response := Response{ - Headers: header, - Trailers: trailer, - } - - marshaler := protojson.MarshalOptions{EmitUnpopulated: true} - - if err != nil { - sterr := status.Convert(err) - response.Status = sterr.Code() - - // (rogchap) when you access a JSON property in goja, you are actually accessing the underling - // Go type (struct, map, slice etc); because these are dynamic messages the Unmarshaled JSON does - // not map back to a "real" field or value (as a normal Go type would). If we don't marshal and then - // unmarshal back to a map, you will get "undefined" when accessing JSON properties, even when - // JSON.Stringify() shows the object to be correctly present. - - raw, _ := marshaler.Marshal(sterr.Proto()) - errMsg := make(map[string]interface{}) - _ = json.Unmarshal(raw, &errMsg) - response.Error = errMsg - } - - if resp != nil { - msg, err := convert(marshaler, resp) - if err != nil { - return nil, fmt.Errorf("unable to convert response object to JSON: %w", err) - } - - response.Message = msg - } - return &response, nil -} - -// NewStream creates a new gRPC stream. -func (c *Conn) NewStream( - ctx context.Context, - req StreamRequest, - opts ...grpc.CallOption, -) (*Stream, error) { - ctx = metadata.NewOutgoingContext(ctx, req.Metadata) - - ctx = withRPCState(ctx, &rpcState{tagsAndMeta: req.TagsAndMeta}) - - stream, err := c.raw.NewStream(ctx, &grpc.StreamDesc{ - StreamName: string(req.MethodDescriptor.Name()), - ServerStreams: req.MethodDescriptor.IsStreamingServer(), - ClientStreams: req.MethodDescriptor.IsStreamingClient(), - }, req.Method, opts...) - if err != nil { - return nil, err - } - - return &Stream{ - raw: stream, - method: req.Method, - methodDescriptor: req.MethodDescriptor, - }, nil -} - -// Close closes the underhood connection. -func (c *Conn) Close() error { - return c.raw.Close() -} - -type statsHandler struct { - getState func() *lib.State -} - -// TagConn implements the grpcstats.Handler interface -func (statsHandler) TagConn(ctx context.Context, _ *grpcstats.ConnTagInfo) context.Context { // noop - return ctx -} - -// HandleConn implements the grpcstats.Handler interface -func (statsHandler) HandleConn(context.Context, grpcstats.ConnStats) { - // noop -} - -// TagRPC implements the grpcstats.Handler interface -func (statsHandler) TagRPC(ctx context.Context, _ *grpcstats.RPCTagInfo) context.Context { - // noop - return ctx -} - -// HandleRPC implements the grpcstats.Handler interface -func (h statsHandler) HandleRPC(ctx context.Context, stat grpcstats.RPCStats) { - state := h.getState() - stateRPC := getRPCState(ctx) //nolint:ifshort - - // If the request is done by the reflection handler then the tags will be - // nil. In this case, we can reuse the VU.State's Tags. - if stateRPC == nil { - // TODO: investigate this more, there has to be a way to fix it :/ - ctm := state.Tags.GetCurrentValues() - stateRPC = &rpcState{tagsAndMeta: &ctm} - } - - switch s := stat.(type) { - case *grpcstats.OutHeader: - // TODO: figure out something better, e.g. via TagConn() or TagRPC()? - if state.Options.SystemTags.Has(metrics.TagIP) && s.RemoteAddr != nil { - if ip, _, err := net.SplitHostPort(s.RemoteAddr.String()); err == nil { - stateRPC.tagsAndMeta.SetSystemTagOrMeta(metrics.TagIP, ip) - } - } - case *grpcstats.End: - if state.Options.SystemTags.Has(metrics.TagStatus) { - stateRPC.tagsAndMeta.SetSystemTagOrMeta(metrics.TagStatus, strconv.Itoa(int(status.Code(s.Error)))) - } - - metrics.PushIfNotDone(ctx, state.Samples, metrics.Sample{ - TimeSeries: metrics.TimeSeries{ - Metric: state.BuiltinMetrics.GRPCReqDuration, - Tags: stateRPC.tagsAndMeta.Tags, - }, - Time: s.EndTime, - Metadata: stateRPC.tagsAndMeta.Metadata, - Value: metrics.D(s.EndTime.Sub(s.BeginTime)), - }) - } - - // (rogchap) Re-using --http-debug flag as gRPC is technically still HTTP - if state.Options.HTTPDebug.String != "" { - logger := state.Logger.WithField("source", "http-debug") - httpDebugOption := state.Options.HTTPDebug.String - DebugStat(logger, stat, httpDebugOption) - } -} - -// DebugStat prints debugging information based on RPCStats. -func DebugStat(logger logrus.FieldLogger, stat grpcstats.RPCStats, httpDebugOption string) { - switch s := stat.(type) { - case *grpcstats.OutHeader: - logger.Infof("Out Header:\nFull Method: %s\nRemote Address: %s\n%s\n", - s.FullMethod, s.RemoteAddr, formatMetadata(s.Header)) - case *grpcstats.OutTrailer: - if len(s.Trailer) > 0 { - logger.Infof("Out Trailer:\n%s\n", formatMetadata(s.Trailer)) - } - case *grpcstats.OutPayload: - if httpDebugOption == "full" { - logger.Infof("Out Payload:\nWire Length: %d\nSent Time: %s\n%s\n\n", - s.WireLength, s.SentTime, formatPayload(s.Payload)) - } - case *grpcstats.InHeader: - if len(s.Header) > 0 { - logger.Infof("In Header:\nWire Length: %d\n%s\n", s.WireLength, formatMetadata(s.Header)) - } - case *grpcstats.InTrailer: - if len(s.Trailer) > 0 { - logger.Infof("In Trailer:\nWire Length: %d\n%s\n", s.WireLength, formatMetadata(s.Trailer)) - } - case *grpcstats.InPayload: - if httpDebugOption == "full" { - logger.Infof("In Payload:\nWire Length: %d\nReceived Time: %s\n%s\n\n", - s.WireLength, s.RecvTime, formatPayload(s.Payload)) - } - } -} - -func formatMetadata(md metadata.MD) string { - var sb strings.Builder - for k, v := range md { - sb.WriteString(k) - sb.WriteString(": ") - sb.WriteString(strings.Join(v, ", ")) - sb.WriteRune('\n') - } - - return sb.String() -} - -func formatPayload(payload interface{}) string { - msg, ok := payload.(proto.Message) - if !ok { - // check to see if we are dealing with a APIv1 message - msgV1, ok := payload.(protov1.Message) - if !ok { - return "" - } - msg = protov1.MessageV2(msgV1) - } - - marshaler := prototext.MarshalOptions{ - Multiline: true, - Indent: " ", - } - b, err := marshaler.Marshal(msg) - if err != nil { - return "" - } - return string(b) -} - -type contextKey string - -var ctxKeyRPCState = contextKey("rpcState") //nolint:gochecknoglobals - -type rpcState struct { - tagsAndMeta *metrics.TagsAndMeta -} - -func withRPCState(ctx context.Context, rpcState *rpcState) context.Context { - return context.WithValue(ctx, ctxKeyRPCState, rpcState) -} - -func getRPCState(ctx context.Context) *rpcState { - v := ctx.Value(ctxKeyRPCState) - if v == nil { - return nil - } - return v.(*rpcState) //nolint: forcetypeassert -} diff --git a/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/reflect.go b/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/reflect.go deleted file mode 100644 index 548e2aeab3bc..000000000000 --- a/vendor/github.com/grafana/xk6-grpc/lib/netext/grpcext/reflect.go +++ /dev/null @@ -1,69 +0,0 @@ -package grpcext - -import ( - "context" - "fmt" - - "github.com/jhump/protoreflect/desc" - "github.com/jhump/protoreflect/grpcreflect" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/descriptorpb" -) - -// ReflectionClient wraps a grpc.ServerReflectionClient. -type reflectionClient struct { - Conn grpc.ClientConnInterface -} - -// Reflect will use the grpc reflection api to make the file descriptors available to request. -// It is called in the connect function the first time the Client.Connect function is called. -func (rc *reflectionClient) Reflect(ctx context.Context) (*descriptorpb.FileDescriptorSet, error) { - client := grpcreflect.NewClientAuto(ctx, rc.Conn) - - services, err := client.ListServices() - if err != nil { - return nil, fmt.Errorf("can't list services: %w", err) - } - - seen := make(map[fileDescriptorLookupKey]bool, len(services)) - fdset := &descriptorpb.FileDescriptorSet{ - File: make([]*descriptorpb.FileDescriptorProto, 0, len(services)), - } - - for _, srv := range services { - srvDescriptor, err := client.ResolveService(srv) - if err != nil { - return nil, fmt.Errorf("can't get method on service %q: %w", srv, err) - } - - stack := []*desc.FileDescriptor{srvDescriptor.GetFile()} - - for len(stack) > 0 { - fdp := stack[len(stack)-1] - stack = stack[:len(stack)-1] - - fdkey := fileDescriptorLookupKey{ - Package: fdp.GetPackage(), - Name: fdp.GetName(), - } - - stack = append(stack, fdp.GetDependencies()...) - - if seen[fdkey] { - // When a proto file contains declarations for multiple services - // then the same proto file is returned multiple times, - // this prevents adding the returned proto file as a duplicate. - continue - } - seen[fdkey] = true - fdset.File = append(fdset.File, fdp.AsFileDescriptorProto()) - } - } - - return fdset, nil -} - -type fileDescriptorLookupKey struct { - Package string - Name string -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 14bb54479a91..008c04ceffd7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -137,6 +137,7 @@ github.com/golang/protobuf/proto github.com/golang/protobuf/ptypes github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/duration +github.com/golang/protobuf/ptypes/struct github.com/golang/protobuf/ptypes/timestamp github.com/golang/protobuf/ptypes/wrappers # github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 @@ -160,10 +161,6 @@ github.com/grafana/xk6-browser/k6ext github.com/grafana/xk6-browser/keyboardlayout github.com/grafana/xk6-browser/log github.com/grafana/xk6-browser/storage -# github.com/grafana/xk6-grpc v0.1.4-0.20230919144024-6ed5daf33509 -## explicit; go 1.19 -github.com/grafana/xk6-grpc/grpc -github.com/grafana/xk6-grpc/lib/netext/grpcext # github.com/grafana/xk6-output-prometheus-remote v0.3.1 ## explicit; go 1.18 github.com/grafana/xk6-output-prometheus-remote/pkg/remote