From f35380f928467c450bc29b8907047ec675ccb252 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Tue, 19 Sep 2023 18:50:00 +0200 Subject: [PATCH] Update xk6-grpc to the 6ed5daf33509c8d0546997cc265fe1846a018722 --- go.mod | 2 +- go.sum | 4 +- .../grafana/xk6-grpc/grpc/client.go | 115 +---------- .../grafana/xk6-grpc/grpc/params.go | 192 +++++++++++++++--- vendor/modules.txt | 2 +- 5 files changed, 179 insertions(+), 136 deletions(-) diff --git a/go.mod b/go.mod index 181cd11cf1a..a6cf80f8a07 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/golang/protobuf v1.5.3 github.com/gorilla/websocket v1.5.0 github.com/grafana/xk6-browser v1.0.2 - github.com/grafana/xk6-grpc v0.1.4-0.20230911131934-0250ce428019 + github.com/grafana/xk6-grpc v0.1.4-0.20230919144024-6ed5daf33509 github.com/grafana/xk6-output-prometheus-remote v0.2.3 github.com/grafana/xk6-redis v0.1.1 github.com/grafana/xk6-timers v0.1.2 diff --git a/go.sum b/go.sum index 2988552b9c5..361f5def652 100644 --- a/go.sum +++ b/go.sum @@ -185,8 +185,8 @@ 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.0.2 h1:B9ll8xLH68hfCBy3sTzhmksCxwgJBIcqgPeX3mht6jM= github.com/grafana/xk6-browser v1.0.2/go.mod h1:LV/ECGBCN3vRN/A4St+Ep9JUpbKJuRsj+6TBihQptGw= -github.com/grafana/xk6-grpc v0.1.4-0.20230911131934-0250ce428019 h1:+qBXTzoXx0RvQ+xUcSSOEFeh5Bke/jtzS3MiDW9D2Hs= -github.com/grafana/xk6-grpc v0.1.4-0.20230911131934-0250ce428019/go.mod h1:sFTwAsHAtp2f1PNiq0wPjJ7HrAIKploI7Y5mOYo+zIQ= +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.2.3 h1:ta4wFrO85+29H0papAbeMCavHrBuHDZ4bdKC1Zv8zlo= github.com/grafana/xk6-output-prometheus-remote v0.2.3/go.mod h1:Pmhhq0FFkwb+XdY99erTQnwleyxciUSBLzS4hh9g9N0= github.com/grafana/xk6-redis v0.1.1 h1:rvWnLanRB2qzDwuY6NMBe6PXei3wJ3kjYvfCwRJ+q+8= diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/client.go b/vendor/github.com/grafana/xk6-grpc/grpc/client.go index 7607d9cef82..ef46ffce0e2 100644 --- a/vendor/github.com/grafana/xk6-grpc/grpc/client.go +++ b/vendor/github.com/grafana/xk6-grpc/grpc/client.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/xk6-grpc/lib/netext/grpcext" "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" - "go.k6.io/k6/lib/types" "github.com/dop251/goja" "github.com/jhump/protoreflect/desc" @@ -22,6 +21,7 @@ import ( "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" @@ -205,13 +205,13 @@ func buildTLSConfigFromMap(parentConfig *tls.Config, tlsConfigMap map[string]int } // Connect is a block dial to the gRPC server at the given address (host:port) -func (c *Client) Connect(addr string, params map[string]interface{}) (bool, error) { +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 := c.parseConnectParams(params) + p, err := newConnectParams(c.vu, params) if err != nil { return false, fmt.Errorf("invalid grpc.connect() parameters: %w", err) } @@ -258,6 +258,9 @@ func (c *Client) Connect(addr string, params map[string]interface{}) (bool, erro if !p.UseReflectionProtocol { return true, nil } + + ctx = metadata.NewOutgoingContext(ctx, p.ReflectionMetadata) + fdset, err := c.conn.Reflect(ctx) if err != nil { return false, err @@ -418,112 +421,6 @@ func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]M return rtn, nil } -type connectParams struct { - IsPlaintext bool - UseReflectionProtocol bool - Timeout time.Duration - MaxReceiveSize int64 - MaxSendSize int64 - TLS map[string]interface{} -} - -func (c *Client) parseConnectParams(raw map[string]interface{}) (connectParams, error) { - params := connectParams{ - IsPlaintext: false, - UseReflectionProtocol: false, - Timeout: time.Minute, - MaxReceiveSize: 0, - MaxSendSize: 0, - } - for k, v := range raw { - 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 "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": - if err := parseConnectTLSParam(¶ms, v); err != nil { - return params, err - } - default: - return params, fmt.Errorf("unknown connect param: %q", k) - } - } - return params, nil -} - -func parseConnectTLSParam(params *connectParams, v interface{}) error { - var ok bool - params.TLS, ok = v.(map[string]interface{}) - - if !ok { - return 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 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 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 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 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 fmt.Errorf("invalid tls cacerts value: '%#v',"+ - " it needs to be a string or an array of PEM formatted strings", v) - } - } - return nil -} - func walkFileDescriptors(seen map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto { fds := []*descriptorpb.FileDescriptorProto{} diff --git a/vendor/github.com/grafana/xk6-grpc/grpc/params.go b/vendor/github.com/grafana/xk6-grpc/grpc/params.go index 14e3af9cfd6..d9fb9136b3c 100644 --- a/vendor/github.com/grafana/xk6-grpc/grpc/params.go +++ b/vendor/github.com/grafana/xk6-grpc/grpc/params.go @@ -31,7 +31,7 @@ func newCallParams(vu modules.VU, input goja.Value) (*callParams, error) { TagsAndMeta: vu.State().Tags.GetCurrentValues(), } - if input == nil || goja.IsUndefined(input) || goja.IsNull(input) { + if common.IsNullish(input) { return result, nil } @@ -41,30 +41,12 @@ func newCallParams(vu modules.VU, input goja.Value) (*callParams, error) { for _, k := range params.Keys() { switch k { case "metadata": - v := params.Get(k).Export() - rawHeaders, ok := v.(map[string]interface{}) - if !ok { - return result, errors.New("metadata must be an object with key-value pairs") + md, err := newMetadata(params.Get(k)) + if err != nil { + return result, fmt.Errorf("invalid metadata param: %w", err) } - for hk, kv := range rawHeaders { - 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 result, fmt.Errorf("metadata %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 result, fmt.Errorf("metadata %q value must be a string", hk) - } - result.Metadata.Append(hk, val) - } + 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) @@ -84,6 +66,43 @@ func newCallParams(vu modules.VU, input goja.Value) (*callParams, error) { 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() + + rawHeaders, ok := v.(map[string]interface{}) + if !ok { + return md, errors.New("must be an object with key-value pairs") + } + + for hk, kv := range rawHeaders { + 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) + } + + return md, nil +} + // SetSystemTags sets the system tags for the call. func (p *callParams) SetSystemTags(state *lib.State, addr string, methodName string) { if state.Options.SystemTags.Has(metrics.TagURL) { @@ -99,3 +118,130 @@ func (p *callParams) SetSystemTags(state *lib.State, addr string, methodName str p.TagsAndMeta.SetSystemTagOrMetaIfEnabled(state.Options.SystemTags, metrics.TagName, methodName) } } + +// connectParams is the parameters that can be passed to a gRPC connect call. +type connectParams struct { + IsPlaintext bool + UseReflectionProtocol bool + ReflectionMetadata metadata.MD + Timeout time.Duration + MaxReceiveSize int64 + MaxSendSize int64 + TLS map[string]interface{} +} + +func newConnectParams(vu modules.VU, input goja.Value) (*connectParams, error) { //nolint:gocognit + result := &connectParams{ + IsPlaintext: false, + UseReflectionProtocol: false, + Timeout: time.Minute, + MaxReceiveSize: 0, + MaxSendSize: 0, + ReflectionMetadata: metadata.New(nil), + } + + if common.IsNullish(input) { + return result, nil + } + + rt := vu.Runtime() + params := input.ToObject(rt) + + for _, k := range params.Keys() { + v := params.Get(k).Export() + + switch k { + case "plaintext": + var ok bool + result.IsPlaintext, ok = v.(bool) + if !ok { + return result, fmt.Errorf("invalid plaintext value: '%#v', it needs to be boolean", v) + } + case "timeout": + var err error + result.Timeout, err = types.GetDurationValue(v) + if err != nil { + return result, fmt.Errorf("invalid timeout value: %w", err) + } + case "reflect": + var ok bool + result.UseReflectionProtocol, ok = v.(bool) + if !ok { + return result, fmt.Errorf("invalid reflect value: '%#v', it needs to be boolean", v) + } + case "reflectMetadata": + md, err := newMetadata(params.Get(k)) + if err != nil { + return result, fmt.Errorf("invalid reflectMetadata param: %w", err) + } + + result.ReflectionMetadata = md + case "maxReceiveSize": + var ok bool + result.MaxReceiveSize, ok = v.(int64) + if !ok { + return result, fmt.Errorf("invalid maxReceiveSize value: '%#v', it needs to be an integer", v) + } + if result.MaxReceiveSize < 0 { + return result, fmt.Errorf("invalid maxReceiveSize value: '%#v, it needs to be a positive integer", v) + } + case "maxSendSize": + var ok bool + result.MaxSendSize, ok = v.(int64) + if !ok { + return result, fmt.Errorf("invalid maxSendSize value: '%#v', it needs to be an integer", v) + } + if result.MaxSendSize < 0 { + return result, fmt.Errorf("invalid maxSendSize value: '%#v, it needs to be a positive integer", v) + } + case "tls": + if err := parseConnectTLSParam(result, v); err != nil { + return result, err + } + default: + return result, fmt.Errorf("unknown connect param: %q", k) + } + } + + return result, nil +} + +func parseConnectTLSParam(params *connectParams, v interface{}) error { + var ok bool + params.TLS, ok = v.(map[string]interface{}) + + if !ok { + return 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 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 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 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 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 fmt.Errorf("invalid tls cacerts value: '%#v',"+ + " it needs to be a string or an array of PEM formatted strings", v) + } + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 441bf4e3c96..d78329cf563 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -163,7 +163,7 @@ 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.20230911131934-0250ce428019 +# 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