diff --git a/go.mod b/go.mod index cc8f915a6..4e9dc098c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.3.0 - istio.io/istio v0.0.0-20211211014447-a9f4988c313b + istio.io/istio v0.0.0-20211216225035-26e13f954263 k8s.io/api v0.22.4 k8s.io/apimachinery v0.22.4 k8s.io/client-go v0.22.4 diff --git a/go.sum b/go.sum index 79d2e1de6..f3d0a6b8a 100644 --- a/go.sum +++ b/go.sum @@ -1935,6 +1935,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2084,6 +2086,8 @@ istio.io/istio v0.0.0-20211207200644-b1737ef9ef85 h1:7S3GlQYMIBM5na7VdH2danLDAnh istio.io/istio v0.0.0-20211207200644-b1737ef9ef85/go.mod h1:Jjvk8+7ocDxPUMp7PZN5guRYPFBoHWtLHTxLV9Du+B0= istio.io/istio v0.0.0-20211211014447-a9f4988c313b h1:T5SSmcYZXiM0T7ZW9jrof6s9zpn2kR7oLpS3kD6f9us= istio.io/istio v0.0.0-20211211014447-a9f4988c313b/go.mod h1:1q2v1Z83C2l2TOnKXGkkQnlaHm4768L4NiHxPxlI4Pc= +istio.io/istio v0.0.0-20211216225035-26e13f954263 h1:rbe/IrUDlWVIB3QjTSK9YbWjkub1dY3fLjIwoR0WOI0= +istio.io/istio v0.0.0-20211216225035-26e13f954263/go.mod h1:UJTp4/6N4FyCNlOojXtz07vo5mVf/qp/oAEh/KA74TI= istio.io/pkg v0.0.0-20210914184925-da9399ebffe5 h1:a9BTDf4yGOeMo9KKYETOUrNhUF4vclYl4WtJFqx1xGc= istio.io/pkg v0.0.0-20210914184925-da9399ebffe5/go.mod h1:rJLxqU2GEnFR3cIiun1uoiG87ghuQSD8jFkcjyT3Qes= istio.io/pkg v0.0.0-20211112173506-bca79fa408e9 h1:+5roGviidNaGj5gXmWIZIlwXn+dJadBeTMWkYdMvSrA= diff --git a/vendor/google.golang.org/grpc/balancer/balancer.go b/vendor/google.golang.org/grpc/balancer/balancer.go index 178de0898..bcc6f5451 100644 --- a/vendor/google.golang.org/grpc/balancer/balancer.go +++ b/vendor/google.golang.org/grpc/balancer/balancer.go @@ -174,25 +174,32 @@ type ClientConn interface { // BuildOptions contains additional information for Build. type BuildOptions struct { - // DialCreds is the transport credential the Balancer implementation can - // use to dial to a remote load balancer server. The Balancer implementations - // can ignore this if it does not need to talk to another party securely. + // DialCreds is the transport credentials to use when communicating with a + // remote load balancer server. Balancer implementations which do not + // communicate with a remote load balancer server can ignore this field. DialCreds credentials.TransportCredentials - // CredsBundle is the credentials bundle that the Balancer can use. + // CredsBundle is the credentials bundle to use when communicating with a + // remote load balancer server. Balancer implementations which do not + // communicate with a remote load balancer server can ignore this field. CredsBundle credentials.Bundle - // Dialer is the custom dialer the Balancer implementation can use to dial - // to a remote load balancer server. The Balancer implementations - // can ignore this if it doesn't need to talk to remote balancer. + // Dialer is the custom dialer to use when communicating with a remote load + // balancer server. Balancer implementations which do not communicate with a + // remote load balancer server can ignore this field. Dialer func(context.Context, string) (net.Conn, error) - // ChannelzParentID is the entity parent's channelz unique identification number. + // Authority is the server name to use as part of the authentication + // handshake when communicating with a remote load balancer server. Balancer + // implementations which do not communicate with a remote load balancer + // server can ignore this field. + Authority string + // ChannelzParentID is the parent ClientConn's channelz ID. ChannelzParentID int64 // CustomUserAgent is the custom user agent set on the parent ClientConn. // The balancer should set the same custom user agent if it creates a // ClientConn. CustomUserAgent string - // Target contains the parsed address info of the dial target. It is the same resolver.Target as - // passed to the resolver. - // See the documentation for the resolver.Target type for details about what it contains. + // Target contains the parsed address info of the dial target. It is the + // same resolver.Target as passed to the resolver. See the documentation for + // the resolver.Target type for details about what it contains. Target resolver.Target } diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/logging.go b/vendor/google.golang.org/grpc/balancer/weightedtarget/logging.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/logging.go rename to vendor/google.golang.org/grpc/balancer/weightedtarget/logging.go diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedaggregator/aggregator.go b/vendor/google.golang.org/grpc/balancer/weightedtarget/weightedaggregator/aggregator.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedaggregator/aggregator.go rename to vendor/google.golang.org/grpc/balancer/weightedtarget/weightedaggregator/aggregator.go diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedtarget.go b/vendor/google.golang.org/grpc/balancer/weightedtarget/weightedtarget.go similarity index 90% rename from vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedtarget.go rename to vendor/google.golang.org/grpc/balancer/weightedtarget/weightedtarget.go index f05e0aca1..b6fa532b5 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedtarget.go +++ b/vendor/google.golang.org/grpc/balancer/weightedtarget/weightedtarget.go @@ -17,6 +17,8 @@ */ // Package weightedtarget implements the weighted_target balancer. +// +// All APIs in this package are experimental. package weightedtarget import ( @@ -24,14 +26,14 @@ import ( "fmt" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/weightedtarget/weightedaggregator" + "google.golang.org/grpc/internal/balancergroup" "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/hierarchy" "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/internal/wrr" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" - "google.golang.org/grpc/xds/internal/balancer/balancergroup" - "google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedaggregator" ) // Name is the name of the weighted_target balancer. @@ -52,7 +54,7 @@ func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Ba b.logger = prefixLogger(b) b.stateAggregator = weightedaggregator.New(cc, b.logger, NewRandomWRR) b.stateAggregator.Start() - b.bg = balancergroup.New(cc, bOpts, b.stateAggregator, nil, b.logger) + b.bg = balancergroup.New(cc, bOpts, b.stateAggregator, b.logger) b.bg.Start() b.logger.Infof("Created") return b @@ -69,11 +71,6 @@ func (bb) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, err type weightedTargetBalancer struct { logger *grpclog.PrefixLogger - // TODO: Make this package not dependent on any xds specific code. - // BalancerGroup uses xdsinternal.LocalityID as the key in the map of child - // policies that it maintains and reports load using LRS. Once these two - // dependencies are removed from the balancerGroup, this package will not - // have any dependencies on xds code. bg *balancergroup.BalancerGroup stateAggregator *weightedaggregator.Aggregator diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedtarget_config.go b/vendor/google.golang.org/grpc/balancer/weightedtarget/weightedtarget_config.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedtarget_config.go rename to vendor/google.golang.org/grpc/balancer/weightedtarget/weightedtarget_config.go diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 5a9e7d754..28f09dc87 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -83,13 +83,13 @@ var ( // errTransportCredsAndBundle indicates that creds bundle is used together // with other individual Transport Credentials. errTransportCredsAndBundle = errors.New("grpc: credentials.Bundle may not be used with individual TransportCredentials") - // errTransportCredentialsMissing indicates that users want to transmit security - // information (e.g., OAuth2 token) which requires secure connection on an insecure - // connection. + // errNoTransportCredsInBundle indicated that the configured creds bundle + // returned a transport credentials which was nil. + errNoTransportCredsInBundle = errors.New("grpc: credentials.Bundle must return non-nil transport credentials") + // errTransportCredentialsMissing indicates that users want to transmit + // security information (e.g., OAuth2 token) which requires secure + // connection on an insecure connection. errTransportCredentialsMissing = errors.New("grpc: the credentials require transport level security (use grpc.WithTransportCredentials() to set)") - // errCredentialsConflict indicates that grpc.WithTransportCredentials() - // and grpc.WithInsecure() are both called for a connection. - errCredentialsConflict = errors.New("grpc: transport credentials are set for an insecure connection (grpc.WithTransportCredentials() and grpc.WithInsecure() are both called)") ) const ( @@ -177,17 +177,20 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * cc.csMgr.channelzID = cc.channelzID } - if !cc.dopts.insecure { - if cc.dopts.copts.TransportCredentials == nil && cc.dopts.copts.CredsBundle == nil { - return nil, errNoTransportSecurity - } - if cc.dopts.copts.TransportCredentials != nil && cc.dopts.copts.CredsBundle != nil { - return nil, errTransportCredsAndBundle - } - } else { - if cc.dopts.copts.TransportCredentials != nil || cc.dopts.copts.CredsBundle != nil { - return nil, errCredentialsConflict - } + if cc.dopts.copts.TransportCredentials == nil && cc.dopts.copts.CredsBundle == nil { + return nil, errNoTransportSecurity + } + if cc.dopts.copts.TransportCredentials != nil && cc.dopts.copts.CredsBundle != nil { + return nil, errTransportCredsAndBundle + } + if cc.dopts.copts.CredsBundle != nil && cc.dopts.copts.CredsBundle.TransportCredentials() == nil { + return nil, errNoTransportCredsInBundle + } + transportCreds := cc.dopts.copts.TransportCredentials + if transportCreds == nil { + transportCreds = cc.dopts.copts.CredsBundle.TransportCredentials() + } + if transportCreds.Info().SecurityProtocol == "insecure" { for _, cd := range cc.dopts.copts.PerRPCCredentials { if cd.RequireTransportSecurity() { return nil, errTransportCredentialsMissing @@ -282,6 +285,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * DialCreds: credsClone, CredsBundle: cc.dopts.copts.CredsBundle, Dialer: cc.dopts.copts.Dialer, + Authority: cc.authority, CustomUserAgent: cc.dopts.copts.UserAgent, ChannelzParentID: cc.channelzID, Target: cc.parsedTarget, @@ -629,7 +633,10 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error { } var ret error - if cc.dopts.disableServiceConfig || s.ServiceConfig == nil { + if cc.dopts.disableServiceConfig { + channelz.Infof(logger, cc.channelzID, "ignoring service config from resolver (%v) and applying the default because service config is disabled", s.ServiceConfig) + cc.maybeApplyDefaultServiceConfig(s.Addresses) + } else if s.ServiceConfig == nil { cc.maybeApplyDefaultServiceConfig(s.Addresses) // TODO: do we need to apply a failing LB policy if there is no // default, per the error handling design? diff --git a/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/google.golang.org/grpc/credentials/credentials.go index a67110758..96ff1877e 100644 --- a/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/google.golang.org/grpc/credentials/credentials.go @@ -178,8 +178,18 @@ type TransportCredentials interface { // // This API is experimental. type Bundle interface { + // TransportCredentials returns the transport credentials from the Bundle. + // + // Implementations must return non-nil transport credentials. If transport + // security is not needed by the Bundle, implementations may choose to + // return insecure.NewCredentials(). TransportCredentials() TransportCredentials + + // PerRPCCredentials returns the per-RPC credentials from the Bundle. + // + // May be nil if per-RPC credentials are not needed. PerRPCCredentials() PerRPCCredentials + // NewWithMode should make a copy of Bundle, and switch mode. Modifying the // existing Bundle may cause races. // diff --git a/vendor/google.golang.org/grpc/credentials/insecure/insecure.go b/vendor/google.golang.org/grpc/credentials/insecure/insecure.go index c4fa27c92..22a8f996a 100644 --- a/vendor/google.golang.org/grpc/credentials/insecure/insecure.go +++ b/vendor/google.golang.org/grpc/credentials/insecure/insecure.go @@ -33,6 +33,9 @@ import ( ) // NewCredentials returns a credentials which disables transport security. +// +// Note that using this credentials with per-RPC credentials which require +// transport security is incompatible and will cause grpc.Dial() to fail. func NewCredentials() credentials.TransportCredentials { return insecureTC{} } diff --git a/vendor/google.golang.org/grpc/dialoptions.go b/vendor/google.golang.org/grpc/dialoptions.go index 40d8ba659..063f1e903 100644 --- a/vendor/google.golang.org/grpc/dialoptions.go +++ b/vendor/google.golang.org/grpc/dialoptions.go @@ -27,9 +27,9 @@ import ( "google.golang.org/grpc/backoff" "google.golang.org/grpc/balancer" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/internal" internalbackoff "google.golang.org/grpc/internal/backoff" - "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/resolver" @@ -50,7 +50,6 @@ type dialOptions struct { bs internalbackoff.Strategy block bool returnLastError bool - insecure bool timeout time.Duration scChan <-chan ServiceConfig authority string @@ -228,18 +227,14 @@ func WithServiceConfig(c <-chan ServiceConfig) DialOption { }) } -// WithConnectParams configures the dialer to use the provided ConnectParams. +// WithConnectParams configures the ClientConn to use the provided ConnectParams +// for creating and maintaining connections to servers. // // The backoff configuration specified as part of the ConnectParams overrides // all defaults specified in // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. Consider // using the backoff.DefaultConfig as a base, in cases where you want to // override only a subset of the backoff configuration. -// -// Experimental -// -// Notice: This API is EXPERIMENTAL and may be changed or removed in a -// later release. func WithConnectParams(p ConnectParams) DialOption { return newFuncDialOption(func(o *dialOptions) { o.bs = internalbackoff.Exponential{Config: p.Backoff} @@ -303,11 +298,17 @@ func WithReturnConnectionError() DialOption { } // WithInsecure returns a DialOption which disables transport security for this -// ClientConn. Note that transport security is required unless WithInsecure is -// set. +// ClientConn. Under the hood, it uses insecure.NewCredentials(). +// +// Note that using this DialOption with per-RPC credentials (through +// WithCredentialsBundle or WithPerRPCCredentials) which require transport +// security is incompatible and will cause grpc.Dial() to fail. +// +// Deprecated: use insecure.NewCredentials() instead. +// Will be supported throughout 1.x. func WithInsecure() DialOption { return newFuncDialOption(func(o *dialOptions) { - o.insecure = true + o.copts.TransportCredentials = insecure.NewCredentials() }) } @@ -580,7 +581,6 @@ func withHealthCheckFunc(f internal.HealthChecker) DialOption { func defaultDialOptions() dialOptions { return dialOptions{ - disableRetry: !envconfig.Retry, healthCheckFunc: internal.HealthCheckFunc, copts: transport.ConnectOptions{ WriteBufferSize: defaultWriteBufSize, diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/balancergroup/balancergroup.go b/vendor/google.golang.org/grpc/internal/balancergroup/balancergroup.go similarity index 90% rename from vendor/google.golang.org/grpc/xds/internal/balancer/balancergroup/balancergroup.go rename to vendor/google.golang.org/grpc/internal/balancergroup/balancergroup.go index 749c6b36e..9776158dd 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/balancergroup/balancergroup.go +++ b/vendor/google.golang.org/grpc/internal/balancergroup/balancergroup.go @@ -23,9 +23,6 @@ import ( "sync" "time" - orcapb "github.com/cncf/xds/go/xds/data/orca/v3" - "google.golang.org/grpc/xds/internal/xdsclient/load" - "google.golang.org/grpc/balancer" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/internal/cache" @@ -178,7 +175,6 @@ func (sbc *subBalancerWrapper) stopBalancer() { // // Updates from ClientConn are forwarded to sub-balancers // - service config update -// - Not implemented // - address update // - subConn state change // - find the corresponding balancer and forward @@ -199,7 +195,6 @@ type BalancerGroup struct { cc balancer.ClientConn buildOpts balancer.BuildOptions logger *grpclog.PrefixLogger - loadStore load.PerClusterReporter // TODO: delete this, no longer needed. It was used by EDS. // stateAggregator is where the state/picker updates will be sent to. It's // provided by the parent balancer, to build a picker with all the @@ -254,15 +249,11 @@ var DefaultSubBalancerCloseTimeout = 15 * time.Minute // New creates a new BalancerGroup. Note that the BalancerGroup // needs to be started to work. -// -// TODO(easwars): Pass an options struct instead of N args. -func New(cc balancer.ClientConn, bOpts balancer.BuildOptions, stateAggregator BalancerStateAggregator, loadStore load.PerClusterReporter, logger *grpclog.PrefixLogger) *BalancerGroup { +func New(cc balancer.ClientConn, bOpts balancer.BuildOptions, stateAggregator BalancerStateAggregator, logger *grpclog.PrefixLogger) *BalancerGroup { return &BalancerGroup{ - cc: cc, - buildOpts: bOpts, - logger: logger, - loadStore: loadStore, - + cc: cc, + buildOpts: bOpts, + logger: logger, stateAggregator: stateAggregator, idToBalancerConfig: make(map[string]*subBalancerWrapper), @@ -467,10 +458,6 @@ func (bg *BalancerGroup) newSubConn(config *subBalancerWrapper, addrs []resolver // state, then forward to ClientConn. func (bg *BalancerGroup) updateBalancerState(id string, state balancer.State) { bg.logger.Infof("Balancer state update from locality %v, new state: %+v", id, state) - if bg.loadStore != nil { - // Only wrap the picker to do load reporting if loadStore was set. - state.Picker = newLoadReportPicker(state.Picker, id, bg.loadStore) - } // Send new state to the aggregator, without holding the incomingMu. // incomingMu is to protect all calls to the parent ClientConn, this update @@ -520,52 +507,12 @@ func (bg *BalancerGroup) ExitIdle() { bg.outgoingMu.Unlock() } -const ( - serverLoadCPUName = "cpu_utilization" - serverLoadMemoryName = "mem_utilization" -) - -type loadReportPicker struct { - p balancer.Picker - - locality string - loadStore load.PerClusterReporter -} - -func newLoadReportPicker(p balancer.Picker, id string, loadStore load.PerClusterReporter) *loadReportPicker { - return &loadReportPicker{ - p: p, - locality: id, - loadStore: loadStore, - } -} - -func (lrp *loadReportPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { - res, err := lrp.p.Pick(info) - if err != nil { - return res, err - } - - lrp.loadStore.CallStarted(lrp.locality) - oldDone := res.Done - res.Done = func(info balancer.DoneInfo) { - if oldDone != nil { - oldDone(info) - } - lrp.loadStore.CallFinished(lrp.locality, info.Err) - - load, ok := info.ServerLoad.(*orcapb.OrcaLoadReport) - if !ok { - return - } - lrp.loadStore.CallServerLoad(lrp.locality, serverLoadCPUName, load.CpuUtilization) - lrp.loadStore.CallServerLoad(lrp.locality, serverLoadMemoryName, load.MemUtilization) - for n, d := range load.RequestCost { - lrp.loadStore.CallServerLoad(lrp.locality, n, d) - } - for n, d := range load.Utilization { - lrp.loadStore.CallServerLoad(lrp.locality, n, d) - } +// ExitIdleOne instructs the sub-balancer `id` to exit IDLE state, if +// appropriate and possible. +func (bg *BalancerGroup) ExitIdleOne(id string) { + bg.outgoingMu.Lock() + if config := bg.idToBalancerConfig[id]; config != nil { + config.exitIdle() } - return res, err + bg.outgoingMu.Unlock() } diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/balancergroup/balancerstateaggregator.go b/vendor/google.golang.org/grpc/internal/balancergroup/balancerstateaggregator.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/balancer/balancergroup/balancerstateaggregator.go rename to vendor/google.golang.org/grpc/internal/balancergroup/balancerstateaggregator.go diff --git a/vendor/google.golang.org/grpc/internal/channelz/funcs.go b/vendor/google.golang.org/grpc/internal/channelz/funcs.go index 6d5760d95..cd1807543 100644 --- a/vendor/google.golang.org/grpc/internal/channelz/funcs.go +++ b/vendor/google.golang.org/grpc/internal/channelz/funcs.go @@ -204,9 +204,9 @@ func RegisterChannel(c Channel, pid int64, ref string) int64 { trace: &channelTrace{createdTime: time.Now(), events: make([]*TraceEvent, 0, getMaxTraceEntry())}, } if pid == 0 { - db.get().addChannel(id, cn, true, pid, ref) + db.get().addChannel(id, cn, true, pid) } else { - db.get().addChannel(id, cn, false, pid, ref) + db.get().addChannel(id, cn, false, pid) } return id } @@ -228,7 +228,7 @@ func RegisterSubChannel(c Channel, pid int64, ref string) int64 { pid: pid, trace: &channelTrace{createdTime: time.Now(), events: make([]*TraceEvent, 0, getMaxTraceEntry())}, } - db.get().addSubChannel(id, sc, pid, ref) + db.get().addSubChannel(id, sc, pid) return id } @@ -258,7 +258,7 @@ func RegisterListenSocket(s Socket, pid int64, ref string) int64 { } id := idGen.genID() ls := &listenSocket{refName: ref, s: s, id: id, pid: pid} - db.get().addListenSocket(id, ls, pid, ref) + db.get().addListenSocket(id, ls, pid) return id } @@ -273,11 +273,11 @@ func RegisterNormalSocket(s Socket, pid int64, ref string) int64 { } id := idGen.genID() ns := &normalSocket{refName: ref, s: s, id: id, pid: pid} - db.get().addNormalSocket(id, ns, pid, ref) + db.get().addNormalSocket(id, ns, pid) return id } -// RemoveEntry removes an entry with unique channelz trakcing id to be id from +// RemoveEntry removes an entry with unique channelz tracking id to be id from // channelz database. func RemoveEntry(id int64) { db.get().removeEntry(id) @@ -333,7 +333,7 @@ func (c *channelMap) addServer(id int64, s *server) { c.mu.Unlock() } -func (c *channelMap) addChannel(id int64, cn *channel, isTopChannel bool, pid int64, ref string) { +func (c *channelMap) addChannel(id int64, cn *channel, isTopChannel bool, pid int64) { c.mu.Lock() cn.cm = c cn.trace.cm = c @@ -346,7 +346,7 @@ func (c *channelMap) addChannel(id int64, cn *channel, isTopChannel bool, pid in c.mu.Unlock() } -func (c *channelMap) addSubChannel(id int64, sc *subChannel, pid int64, ref string) { +func (c *channelMap) addSubChannel(id int64, sc *subChannel, pid int64) { c.mu.Lock() sc.cm = c sc.trace.cm = c @@ -355,7 +355,7 @@ func (c *channelMap) addSubChannel(id int64, sc *subChannel, pid int64, ref stri c.mu.Unlock() } -func (c *channelMap) addListenSocket(id int64, ls *listenSocket, pid int64, ref string) { +func (c *channelMap) addListenSocket(id int64, ls *listenSocket, pid int64) { c.mu.Lock() ls.cm = c c.listenSockets[id] = ls @@ -363,7 +363,7 @@ func (c *channelMap) addListenSocket(id int64, ls *listenSocket, pid int64, ref c.mu.Unlock() } -func (c *channelMap) addNormalSocket(id int64, ns *normalSocket, pid int64, ref string) { +func (c *channelMap) addNormalSocket(id int64, ns *normalSocket, pid int64) { c.mu.Lock() ns.cm = c c.normalSockets[id] = ns diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index 9f25a67fc..6f0272543 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -22,20 +22,14 @@ package envconfig import ( "os" "strings" - - xdsenv "google.golang.org/grpc/internal/xds/env" ) const ( prefix = "GRPC_GO_" - retryStr = prefix + "RETRY" txtErrIgnoreStr = prefix + "IGNORE_TXT_ERRORS" ) var ( - // Retry is enabled unless explicitly disabled via "GRPC_GO_RETRY=off" or - // if XDS retry support is explicitly disabled. - Retry = !strings.EqualFold(os.Getenv(retryStr), "off") && xdsenv.RetrySupport // TXTErrIgnore is set if TXT errors should be ignored ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false"). TXTErrIgnore = !strings.EqualFold(os.Getenv(txtErrIgnoreStr), "false") ) diff --git a/vendor/google.golang.org/grpc/internal/xds/env/env.go b/vendor/google.golang.org/grpc/internal/envconfig/xds.go similarity index 50% rename from vendor/google.golang.org/grpc/internal/xds/env/env.go rename to vendor/google.golang.org/grpc/internal/envconfig/xds.go index 87d3c2433..93522d716 100644 --- a/vendor/google.golang.org/grpc/internal/xds/env/env.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/xds.go @@ -16,9 +16,7 @@ * */ -// Package env acts a single source of definition for all environment variables -// related to the xDS implementation in gRPC. -package env +package envconfig import ( "os" @@ -26,72 +24,67 @@ import ( ) const ( - // BootstrapFileNameEnv is the env variable to set bootstrap file name. + // XDSBootstrapFileNameEnv is the env variable to set bootstrap file name. // Do not use this and read from env directly. Its value is read and kept in // variable BootstrapFileName. // // When both bootstrap FileName and FileContent are set, FileName is used. - BootstrapFileNameEnv = "GRPC_XDS_BOOTSTRAP" - // BootstrapFileContentEnv is the env variable to set bootstrapp file + XDSBootstrapFileNameEnv = "GRPC_XDS_BOOTSTRAP" + // XDSBootstrapFileContentEnv is the env variable to set bootstrapp file // content. Do not use this and read from env directly. Its value is read // and kept in variable BootstrapFileName. // // When both bootstrap FileName and FileContent are set, FileName is used. - BootstrapFileContentEnv = "GRPC_XDS_BOOTSTRAP_CONFIG" + XDSBootstrapFileContentEnv = "GRPC_XDS_BOOTSTRAP_CONFIG" ringHashSupportEnv = "GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH" clientSideSecuritySupportEnv = "GRPC_XDS_EXPERIMENTAL_SECURITY_SUPPORT" aggregateAndDNSSupportEnv = "GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER" - retrySupportEnv = "GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY" rbacSupportEnv = "GRPC_XDS_EXPERIMENTAL_RBAC" + federationEnv = "GRPC_EXPERIMENTAL_XDS_FEDERATION" - c2pResolverSupportEnv = "GRPC_EXPERIMENTAL_GOOGLE_C2P_RESOLVER" c2pResolverTestOnlyTrafficDirectorURIEnv = "GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI" ) var ( - // BootstrapFileName holds the name of the file which contains xDS bootstrap - // configuration. Users can specify the location of the bootstrap file by - // setting the environment variable "GRPC_XDS_BOOTSTRAP". + // XDSBootstrapFileName holds the name of the file which contains xDS + // bootstrap configuration. Users can specify the location of the bootstrap + // file by setting the environment variable "GRPC_XDS_BOOTSTRAP". // // When both bootstrap FileName and FileContent are set, FileName is used. - BootstrapFileName = os.Getenv(BootstrapFileNameEnv) - // BootstrapFileContent holds the content of the xDS bootstrap - // configuration. Users can specify the bootstrap config by - // setting the environment variable "GRPC_XDS_BOOTSTRAP_CONFIG". + XDSBootstrapFileName = os.Getenv(XDSBootstrapFileNameEnv) + // XDSBootstrapFileContent holds the content of the xDS bootstrap + // configuration. Users can specify the bootstrap config by setting the + // environment variable "GRPC_XDS_BOOTSTRAP_CONFIG". // // When both bootstrap FileName and FileContent are set, FileName is used. - BootstrapFileContent = os.Getenv(BootstrapFileContentEnv) - // RingHashSupport indicates whether ring hash support is enabled, which can - // be disabled by setting the environment variable + XDSBootstrapFileContent = os.Getenv(XDSBootstrapFileContentEnv) + // XDSRingHash indicates whether ring hash support is enabled, which can be + // disabled by setting the environment variable // "GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH" to "false". - RingHashSupport = !strings.EqualFold(os.Getenv(ringHashSupportEnv), "false") - // ClientSideSecuritySupport is used to control processing of security + XDSRingHash = !strings.EqualFold(os.Getenv(ringHashSupportEnv), "false") + // XDSClientSideSecurity is used to control processing of security // configuration on the client-side. // // Note that there is no env var protection for the server-side because we // have a brand new API on the server-side and users explicitly need to use // the new API to get security integration on the server. - ClientSideSecuritySupport = !strings.EqualFold(os.Getenv(clientSideSecuritySupportEnv), "false") - // AggregateAndDNSSupportEnv indicates whether processing of aggregated - // cluster and DNS cluster is enabled, which can be enabled by setting the + XDSClientSideSecurity = !strings.EqualFold(os.Getenv(clientSideSecuritySupportEnv), "false") + // XDSAggregateAndDNS indicates whether processing of aggregated cluster + // and DNS cluster is enabled, which can be enabled by setting the // environment variable // "GRPC_XDS_EXPERIMENTAL_ENABLE_AGGREGATE_AND_LOGICAL_DNS_CLUSTER" to // "true". - AggregateAndDNSSupportEnv = strings.EqualFold(os.Getenv(aggregateAndDNSSupportEnv), "true") + XDSAggregateAndDNS = strings.EqualFold(os.Getenv(aggregateAndDNSSupportEnv), "true") - // RetrySupport indicates whether xDS retry is enabled. - RetrySupport = !strings.EqualFold(os.Getenv(retrySupportEnv), "false") - - // RBACSupport indicates whether xDS configured RBAC HTTP Filter is enabled, + // XDSRBAC indicates whether xDS configured RBAC HTTP Filter is enabled, // which can be disabled by setting the environment variable // "GRPC_XDS_EXPERIMENTAL_RBAC" to "false". - RBACSupport = !strings.EqualFold(os.Getenv(rbacSupportEnv), "false") + XDSRBAC = !strings.EqualFold(os.Getenv(rbacSupportEnv), "false") + + // XDSFederation indicates whether federation support is enabled. + XDSFederation = strings.EqualFold(os.Getenv(federationEnv), "true") - // C2PResolverSupport indicates whether support for C2P resolver is enabled. - // This can be enabled by setting the environment variable - // "GRPC_EXPERIMENTAL_GOOGLE_C2P_RESOLVER" to "true". - C2PResolverSupport = strings.EqualFold(os.Getenv(c2pResolverSupportEnv), "true") // C2PResolverTestOnlyTrafficDirectorURI is the TD URI for testing. C2PResolverTestOnlyTrafficDirectorURI = os.Getenv(c2pResolverTestOnlyTrafficDirectorURIEnv) ) diff --git a/vendor/google.golang.org/grpc/internal/transport/flowcontrol.go b/vendor/google.golang.org/grpc/internal/transport/flowcontrol.go index f262edd8e..97198c515 100644 --- a/vendor/google.golang.org/grpc/internal/transport/flowcontrol.go +++ b/vendor/google.golang.org/grpc/internal/transport/flowcontrol.go @@ -136,12 +136,10 @@ type inFlow struct { // newLimit updates the inflow window to a new value n. // It assumes that n is always greater than the old limit. -func (f *inFlow) newLimit(n uint32) uint32 { +func (f *inFlow) newLimit(n uint32) { f.mu.Lock() - d := n - f.limit f.limit = n f.mu.Unlock() - return d } func (f *inFlow) maybeAdjust(n uint32) uint32 { diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 2521a7d7a..f0c72d337 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -201,6 +201,12 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } }() + // gRPC, resolver, balancer etc. can specify arbitrary data in the + // Attributes field of resolver.Address, which is shoved into connectCtx + // and passed to the dialer and credential handshaker. This makes it possible for + // address specific arbitrary data to reach custom dialers and credential handshakers. + connectCtx = icredentials.NewClientHandshakeInfoContext(connectCtx, credentials.ClientHandshakeInfo{Attributes: addr.Attributes}) + conn, err := dial(connectCtx, opts.Dialer, addr, opts.UseProxy, opts.UserAgent) if err != nil { if opts.FailOnNonTempDialError { @@ -245,11 +251,6 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } } if transportCreds != nil { - // gRPC, resolver, balancer etc. can specify arbitrary data in the - // Attributes field of resolver.Address, which is shoved into connectCtx - // and passed to the credential handshaker. This makes it possible for - // address specific arbitrary data to reach the credential handshaker. - connectCtx = icredentials.NewClientHandshakeInfoContext(connectCtx, credentials.ClientHandshakeInfo{Attributes: addr.Attributes}) rawConn := conn // Pull the deadline from the connectCtx, which will be used for // timeouts in the authentication protocol handshake. Can ignore the @@ -587,7 +588,7 @@ func (t *http2Client) getTrAuthData(ctx context.Context, audience string) (map[s return nil, err } - return nil, status.Errorf(codes.Unauthenticated, "transport: %v", err) + return nil, status.Errorf(codes.Unauthenticated, "transport: per-RPC creds failed due to error: %v", err) } for k, v := range data { // Capital header names are illegal in HTTP/2. @@ -1556,7 +1557,7 @@ func minTime(a, b time.Duration) time.Duration { return b } -// keepalive running in a separate goroutune makes sure the connection is alive by sending pings. +// keepalive running in a separate goroutine makes sure the connection is alive by sending pings. func (t *http2Client) keepalive() { p := &ping{data: [8]byte{}} // True iff a ping has been sent, and no data has been received since then. diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index f2cad9ebc..2c6eaf0e5 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -73,7 +73,6 @@ type http2Server struct { writerDone chan struct{} // sync point to enable testing. remoteAddr net.Addr localAddr net.Addr - maxStreamID uint32 // max stream ID ever seen authInfo credentials.AuthInfo // auth info about the connection inTapHandle tap.ServerInHandle framer *framer @@ -123,6 +122,11 @@ type http2Server struct { bufferPool *bufferPool connectionID uint64 + + // maxStreamMu guards the maximum stream ID + // This lock may not be taken if mu is already held. + maxStreamMu sync.Mutex + maxStreamID uint32 // max stream ID ever seen } // NewServerTransport creates a http2 transport with conn and configuration @@ -334,6 +338,10 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, // operateHeader takes action on the decoded headers. func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (fatal bool) { + // Acquire max stream ID lock for entire duration + t.maxStreamMu.Lock() + defer t.maxStreamMu.Unlock() + streamID := frame.Header().StreamID // frame.Truncated is set to true when framer detects that the current header @@ -348,6 +356,15 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( return false } + if streamID%2 != 1 || streamID <= t.maxStreamID { + // illegal gRPC stream id. + if logger.V(logLevel) { + logger.Errorf("transport: http2Server.HandleStreams received an illegal stream id: %v", streamID) + } + return true + } + t.maxStreamID = streamID + buf := newRecvBuffer() s := &Stream{ id: streamID, @@ -355,7 +372,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( buf: buf, fc: &inFlow{limit: uint32(t.initialWindowSize)}, } - var ( // If a gRPC Response-Headers has already been received, then it means // that the peer is speaking gRPC and we are in gRPC mode. @@ -498,16 +514,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( s.cancel() return false } - if streamID%2 != 1 || streamID <= t.maxStreamID { - t.mu.Unlock() - // illegal gRPC stream id. - if logger.V(logLevel) { - logger.Errorf("transport: http2Server.HandleStreams received an illegal stream id: %v", streamID) - } - s.cancel() - return true - } - t.maxStreamID = streamID if httpMethod != http.MethodPost { t.mu.Unlock() if logger.V(logLevel) { @@ -1293,20 +1299,23 @@ var goAwayPing = &ping{data: [8]byte{1, 6, 1, 8, 0, 3, 3, 9}} // Handles outgoing GoAway and returns true if loopy needs to put itself // in draining mode. func (t *http2Server) outgoingGoAwayHandler(g *goAway) (bool, error) { + t.maxStreamMu.Lock() t.mu.Lock() if t.state == closing { // TODO(mmukhi): This seems unnecessary. t.mu.Unlock() + t.maxStreamMu.Unlock() // The transport is closing. return false, ErrConnClosing } - sid := t.maxStreamID if !g.headsUp { // Stop accepting more streams now. t.state = draining + sid := t.maxStreamID if len(t.activeStreams) == 0 { g.closeConn = true } t.mu.Unlock() + t.maxStreamMu.Unlock() if err := t.framer.fr.WriteGoAway(sid, g.code, g.debugData); err != nil { return false, err } @@ -1319,6 +1328,7 @@ func (t *http2Server) outgoingGoAwayHandler(g *goAway) (bool, error) { return true, nil } t.mu.Unlock() + t.maxStreamMu.Unlock() // For a graceful close, send out a GoAway with stream ID of MaxUInt32, // Follow that with a ping and wait for the ack to come back or a timer // to expire. During this time accept new streams since they might have diff --git a/vendor/google.golang.org/grpc/internal/transport/proxy.go b/vendor/google.golang.org/grpc/internal/transport/proxy.go index a662bf39a..415961987 100644 --- a/vendor/google.golang.org/grpc/internal/transport/proxy.go +++ b/vendor/google.golang.org/grpc/internal/transport/proxy.go @@ -37,7 +37,7 @@ var ( httpProxyFromEnvironment = http.ProxyFromEnvironment ) -func mapAddress(ctx context.Context, address string) (*url.URL, error) { +func mapAddress(address string) (*url.URL, error) { req := &http.Request{ URL: &url.URL{ Scheme: "https", @@ -114,7 +114,7 @@ func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr stri // connection. func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn, err error) { newAddr := addr - proxyURL, err := mapAddress(ctx, addr) + proxyURL, err := mapAddress(addr) if err != nil { return nil, err } diff --git a/vendor/google.golang.org/grpc/internal/xds/rbac/rbac_engine.go b/vendor/google.golang.org/grpc/internal/xds/rbac/rbac_engine.go index ecb8512ac..66c7bf10b 100644 --- a/vendor/google.golang.org/grpc/internal/xds/rbac/rbac_engine.go +++ b/vendor/google.golang.org/grpc/internal/xds/rbac/rbac_engine.go @@ -108,13 +108,13 @@ type engine struct { // newEngine creates an RBAC Engine based on the contents of policy. Returns a // non-nil error if the policy is invalid. func newEngine(config *v3rbacpb.RBAC) (*engine, error) { - a := *config.Action.Enum() + a := config.GetAction() if a != v3rbacpb.RBAC_ALLOW && a != v3rbacpb.RBAC_DENY { return nil, fmt.Errorf("unsupported action %s", config.Action) } - policies := make(map[string]*policyMatcher, len(config.Policies)) - for name, policy := range config.Policies { + policies := make(map[string]*policyMatcher, len(config.GetPolicies())) + for name, policy := range config.GetPolicies() { matcher, err := newPolicyMatcher(policy) if err != nil { return nil, err diff --git a/vendor/google.golang.org/grpc/pickfirst.go b/vendor/google.golang.org/grpc/pickfirst.go index f194d14a0..5168b62b0 100644 --- a/vendor/google.golang.org/grpc/pickfirst.go +++ b/vendor/google.golang.org/grpc/pickfirst.go @@ -125,7 +125,7 @@ func (b *pickfirstBalancer) Close() { } func (b *pickfirstBalancer) ExitIdle() { - if b.state == connectivity.Idle { + if b.sc != nil && b.state == connectivity.Idle { b.sc.Connect() } } diff --git a/vendor/google.golang.org/grpc/regenerate.sh b/vendor/google.golang.org/grpc/regenerate.sh index dfd3226a1..a0a71aae9 100644 --- a/vendor/google.golang.org/grpc/regenerate.sh +++ b/vendor/google.golang.org/grpc/regenerate.sh @@ -102,8 +102,8 @@ done # The go_package option in grpc/lookup/v1/rls.proto doesn't match the # current location. Move it into the right place. -mkdir -p ${WORKDIR}/out/google.golang.org/grpc/balancer/rls/internal/proto/grpc_lookup_v1 -mv ${WORKDIR}/out/google.golang.org/grpc/lookup/grpc_lookup_v1/* ${WORKDIR}/out/google.golang.org/grpc/balancer/rls/internal/proto/grpc_lookup_v1 +mkdir -p ${WORKDIR}/out/google.golang.org/grpc/internal/proto/grpc_lookup_v1 +mv ${WORKDIR}/out/google.golang.org/grpc/lookup/grpc_lookup_v1/* ${WORKDIR}/out/google.golang.org/grpc/internal/proto/grpc_lookup_v1 # grpc_testingv3/testv3.pb.go is not re-generated because it was # intentionally generated by an older version of protoc-gen-go. diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 87987a2e6..5d407b004 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -712,13 +712,11 @@ func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxRecei if err != nil { return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) } - } else { - size = len(d) - } - if size > maxReceiveMessageSize { - // TODO: Revisit the error code. Currently keep it consistent with java - // implementation. - return nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", size, maxReceiveMessageSize) + if size > maxReceiveMessageSize { + // TODO: Revisit the error code. Currently keep it consistent with java + // implementation. + return nil, status.Errorf(codes.ResourceExhausted, "grpc: received message after decompression larger than max (%d vs. %d)", size, maxReceiveMessageSize) + } } return d, nil } diff --git a/vendor/google.golang.org/grpc/status/status.go b/vendor/google.golang.org/grpc/status/status.go index af2cffe98..6d163b6e3 100644 --- a/vendor/google.golang.org/grpc/status/status.go +++ b/vendor/google.golang.org/grpc/status/status.go @@ -29,6 +29,7 @@ package status import ( "context" + "errors" "fmt" spb "google.golang.org/genproto/googleapis/rpc/status" @@ -117,18 +118,18 @@ func Code(err error) codes.Code { return codes.Unknown } -// FromContextError converts a context error into a Status. It returns a -// Status with codes.OK if err is nil, or a Status with codes.Unknown if err is -// non-nil and not a context error. +// FromContextError converts a context error or wrapped context error into a +// Status. It returns a Status with codes.OK if err is nil, or a Status with +// codes.Unknown if err is non-nil and not a context error. func FromContextError(err error) *Status { - switch err { - case nil: + if err == nil { return nil - case context.DeadlineExceeded: + } + if errors.Is(err, context.DeadlineExceeded) { return New(codes.DeadlineExceeded, err.Error()) - case context.Canceled: + } + if errors.Is(err, context.Canceled) { return New(codes.Canceled, err.Error()) - default: - return New(codes.Unknown, err.Error()) } + return New(codes.Unknown, err.Error()) } diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 1a5fd584a..8ef095879 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.42.0" +const Version = "1.43.0" diff --git a/vendor/google.golang.org/grpc/xds/csds/csds.go b/vendor/google.golang.org/grpc/xds/csds/csds.go index 1d817fbcc..0d71f8f85 100644 --- a/vendor/google.golang.org/grpc/xds/csds/csds.go +++ b/vendor/google.golang.org/grpc/xds/csds/csds.go @@ -37,10 +37,11 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/status" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" "google.golang.org/protobuf/types/known/timestamppb" - _ "google.golang.org/grpc/xds/internal/xdsclient/v2" // Register v2 xds_client. - _ "google.golang.org/grpc/xds/internal/xdsclient/v3" // Register v3 xds_client. + _ "google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2" // Register v2 xds_client. + _ "google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3" // Register v3 xds_client. ) var ( @@ -127,7 +128,7 @@ func (s *ClientStatusDiscoveryServer) buildClientStatusRespForReq(req *v3statusp ret := &v3statuspb.ClientStatusResponse{ Config: []*v3statuspb.ClientConfig{ { - Node: nodeProtoToV3(s.xdsClient.BootstrapConfig().NodeProto), + Node: nodeProtoToV3(s.xdsClient.BootstrapConfig().XDSServer.NodeProto), GenericXdsConfigs: configs, }, }, @@ -173,8 +174,8 @@ func nodeProtoToV3(n proto.Message) *v3corepb.Node { return node } -func dumpToGenericXdsConfig(typeURL string, dumpF func() (string, map[string]xdsclient.UpdateWithMD)) []*v3statuspb.ClientConfig_GenericXdsConfig { - _, dump := dumpF() +func dumpToGenericXdsConfig(typeURL string, dumpF func() map[string]xdsresource.UpdateWithMD) []*v3statuspb.ClientConfig_GenericXdsConfig { + dump := dumpF() ret := make([]*v3statuspb.ClientConfig_GenericXdsConfig, 0, len(dump)) for name, d := range dump { config := &v3statuspb.ClientConfig_GenericXdsConfig{ @@ -197,17 +198,17 @@ func dumpToGenericXdsConfig(typeURL string, dumpF func() (string, map[string]xds return ret } -func serviceStatusToProto(serviceStatus xdsclient.ServiceStatus) v3adminpb.ClientResourceStatus { +func serviceStatusToProto(serviceStatus xdsresource.ServiceStatus) v3adminpb.ClientResourceStatus { switch serviceStatus { - case xdsclient.ServiceStatusUnknown: + case xdsresource.ServiceStatusUnknown: return v3adminpb.ClientResourceStatus_UNKNOWN - case xdsclient.ServiceStatusRequested: + case xdsresource.ServiceStatusRequested: return v3adminpb.ClientResourceStatus_REQUESTED - case xdsclient.ServiceStatusNotExist: + case xdsresource.ServiceStatusNotExist: return v3adminpb.ClientResourceStatus_DOES_NOT_EXIST - case xdsclient.ServiceStatusACKed: + case xdsresource.ServiceStatusACKed: return v3adminpb.ClientResourceStatus_ACKED - case xdsclient.ServiceStatusNACKed: + case xdsresource.ServiceStatusNACKed: return v3adminpb.ClientResourceStatus_NACKED default: return v3adminpb.ClientResourceStatus_UNKNOWN diff --git a/vendor/google.golang.org/grpc/xds/googledirectpath/googlec2p.go b/vendor/google.golang.org/grpc/xds/googledirectpath/googlec2p.go index b9f1c7120..d759d25c8 100644 --- a/vendor/google.golang.org/grpc/xds/googledirectpath/googlec2p.go +++ b/vendor/google.golang.org/grpc/xds/googledirectpath/googlec2p.go @@ -29,26 +29,27 @@ import ( "fmt" "time" - v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "google.golang.org/grpc" "google.golang.org/grpc/credentials/google" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/googlecloud" internalgrpclog "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcrand" - "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/resolver" _ "google.golang.org/grpc/xds" // To register xds resolvers and balancers. - "google.golang.org/grpc/xds/internal/version" "google.golang.org/grpc/xds/internal/xdsclient" "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" "google.golang.org/protobuf/types/known/structpb" + + v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ) const ( - c2pScheme = "google-c2p" + c2pScheme = "google-c2p-experimental" - tdURL = "directpath-pa.googleapis.com" + tdURL = "dns:///directpath-pa.googleapis.com" httpReqTimeout = 10 * time.Second zoneURL = "http://metadata.google.internal/computeMetadata/v1/instance/zone" ipv6URL = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/ipv6s" @@ -74,9 +75,7 @@ var ( ) func init() { - if env.C2PResolverSupport { - resolver.Register(c2pResolverBuilder{}) - } + resolver.Register(c2pResolverBuilder{}) } type c2pResolverBuilder struct{} @@ -98,15 +97,18 @@ func (c2pResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts go func() { zoneCh <- getZone(httpReqTimeout) }() go func() { ipv6CapableCh <- getIPv6Capable(httpReqTimeout) }() - balancerName := env.C2PResolverTestOnlyTrafficDirectorURI + balancerName := envconfig.C2PResolverTestOnlyTrafficDirectorURI if balancerName == "" { balancerName = tdURL } config := &bootstrap.Config{ - BalancerName: balancerName, - Creds: grpc.WithCredentialsBundle(google.NewDefaultCredentials()), - TransportAPI: version.TransportV3, - NodeProto: newNode(<-zoneCh, <-ipv6CapableCh), + XDSServer: &bootstrap.ServerConfig{ + ServerURI: balancerName, + Creds: grpc.WithCredentialsBundle(google.NewDefaultCredentials()), + TransportAPI: version.TransportV3, + NodeProto: newNode(<-zoneCh, <-ipv6CapableCh), + }, + ClientDefaultListenerResourceNameTemplate: "%s", } // Create singleton xds client with this config. The xds client will be @@ -174,5 +176,5 @@ func newNode(zone string, ipv6Capable bool) *v3corepb.Node { // direct path is enabled if this client is running on GCE, and the normal xDS // is not used (bootstrap env vars are not set). func runDirectPath() bool { - return env.BootstrapFileName == "" && env.BootstrapFileContent == "" && onGCE() + return envconfig.XDSBootstrapFileName == "" && envconfig.XDSBootstrapFileContent == "" && onGCE() } diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/balancer.go b/vendor/google.golang.org/grpc/xds/internal/balancer/balancer.go index 86656736a..8d81aced2 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/balancer.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/balancer.go @@ -20,10 +20,10 @@ package balancer import ( + _ "google.golang.org/grpc/balancer/weightedtarget" // Register the weighted_target balancer _ "google.golang.org/grpc/xds/internal/balancer/cdsbalancer" // Register the CDS balancer _ "google.golang.org/grpc/xds/internal/balancer/clusterimpl" // Register the xds_cluster_impl balancer _ "google.golang.org/grpc/xds/internal/balancer/clustermanager" // Register the xds_cluster_manager balancer _ "google.golang.org/grpc/xds/internal/balancer/clusterresolver" // Register the xds_cluster_resolver balancer _ "google.golang.org/grpc/xds/internal/balancer/priority" // Register the priority balancer - _ "google.golang.org/grpc/xds/internal/balancer/weightedtarget" // Register the weighted_target balancer ) diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cdsbalancer.go b/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cdsbalancer.go index 82d2a9695..5f898c879 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cdsbalancer.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cdsbalancer.go @@ -38,6 +38,7 @@ import ( "google.golang.org/grpc/xds/internal/balancer/clusterresolver" "google.golang.org/grpc/xds/internal/balancer/ringhash" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const ( @@ -185,7 +186,7 @@ func (b *cdsBalancer) handleClientConnUpdate(update *ccUpdate) { // management server, creates appropriate certificate provider plugins, and // updates the HandhakeInfo which is added as an address attribute in // NewSubConn() calls. -func (b *cdsBalancer) handleSecurityConfig(config *xdsclient.SecurityConfig) error { +func (b *cdsBalancer) handleSecurityConfig(config *xdsresource.SecurityConfig) error { // If xdsCredentials are not in use, i.e, the user did not want to get // security configuration from an xDS server, we should not be acting on the // received security config here. Doing so poses a security threat. @@ -310,7 +311,7 @@ func (b *cdsBalancer) handleWatchUpdate(update clusterHandlerUpdate) { dms := make([]clusterresolver.DiscoveryMechanism, len(update.updates)) for i, cu := range update.updates { switch cu.ClusterType { - case xdsclient.ClusterTypeEDS: + case xdsresource.ClusterTypeEDS: dms[i] = clusterresolver.DiscoveryMechanism{ Type: clusterresolver.DiscoveryMechanismTypeEDS, Cluster: cu.ClusterName, @@ -324,7 +325,7 @@ func (b *cdsBalancer) handleWatchUpdate(update clusterHandlerUpdate) { dms[i].LoadReportingServerName = new(string) } - case xdsclient.ClusterTypeLogicalDNS: + case xdsresource.ClusterTypeLogicalDNS: dms[i] = clusterresolver.DiscoveryMechanism{ Type: clusterresolver.DiscoveryMechanismTypeLogicalDNS, DNSHostname: cu.DNSHostName, @@ -430,11 +431,11 @@ func (b *cdsBalancer) run() { func (b *cdsBalancer) handleErrorFromUpdate(err error, fromParent bool) { // This is not necessary today, because xds client never sends connection // errors. - if fromParent && xdsclient.ErrType(err) == xdsclient.ErrorTypeResourceNotFound { + if fromParent && xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceNotFound { b.clusterHandler.close() } if b.childLB != nil { - if xdsclient.ErrType(err) != xdsclient.ErrorTypeConnection { + if xdsresource.ErrType(err) != xdsresource.ErrorTypeConnection { // Connection errors will be sent to the child balancers directly. // There's no need to forward them. b.childLB.ResolverError(err) diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cluster_handler.go b/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cluster_handler.go index 163a8c0a2..a10d8d772 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cluster_handler.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/cdsbalancer/cluster_handler.go @@ -21,6 +21,7 @@ import ( "sync" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) var errNotReceivedUpdate = errors.New("tried to construct a cluster update on a cluster that has not received an update") @@ -31,17 +32,17 @@ var errNotReceivedUpdate = errors.New("tried to construct a cluster update on a // (if one doesn't already exist) and pushing the update to it. type clusterHandlerUpdate struct { // securityCfg is the Security Config from the top (root) cluster. - securityCfg *xdsclient.SecurityConfig + securityCfg *xdsresource.SecurityConfig // lbPolicy is the lb policy from the top (root) cluster. // // Currently, we only support roundrobin or ringhash, and since roundrobin // does need configs, this is only set to the ringhash config, if the policy // is ringhash. In the future, if we support more policies, we can make this // an interface, and set it to config of the other policies. - lbPolicy *xdsclient.ClusterLBPolicyRingHash + lbPolicy *xdsresource.ClusterLBPolicyRingHash // updates is a list of ClusterUpdates from all the leaf clusters. - updates []xdsclient.ClusterUpdate + updates []xdsresource.ClusterUpdate err error } @@ -139,7 +140,7 @@ type clusterNode struct { // A ClusterUpdate in order to build a list of cluster updates for CDS to // send down to child XdsClusterResolverLoadBalancingPolicy. - clusterUpdate xdsclient.ClusterUpdate + clusterUpdate xdsresource.ClusterUpdate // This boolean determines whether this Node has received an update or not. // This isn't the best practice, but this will protect a list of Cluster @@ -176,7 +177,7 @@ func (c *clusterNode) delete() { } // Construct cluster update (potentially a list of ClusterUpdates) for a node. -func (c *clusterNode) constructClusterUpdate() ([]xdsclient.ClusterUpdate, error) { +func (c *clusterNode) constructClusterUpdate() ([]xdsresource.ClusterUpdate, error) { // If the cluster has not yet received an update, the cluster update is not // yet ready. if !c.receivedUpdate { @@ -185,13 +186,13 @@ func (c *clusterNode) constructClusterUpdate() ([]xdsclient.ClusterUpdate, error // Base case - LogicalDNS or EDS. Both of these cluster types will be tied // to a single ClusterUpdate. - if c.clusterUpdate.ClusterType != xdsclient.ClusterTypeAggregate { - return []xdsclient.ClusterUpdate{c.clusterUpdate}, nil + if c.clusterUpdate.ClusterType != xdsresource.ClusterTypeAggregate { + return []xdsresource.ClusterUpdate{c.clusterUpdate}, nil } // If an aggregate construct a list by recursively calling down to all of // it's children. - var childrenUpdates []xdsclient.ClusterUpdate + var childrenUpdates []xdsresource.ClusterUpdate for _, child := range c.children { childUpdateList, err := child.constructClusterUpdate() if err != nil { @@ -206,7 +207,7 @@ func (c *clusterNode) constructClusterUpdate() ([]xdsclient.ClusterUpdate, error // also handles any logic with regards to any child state that may have changed. // At the end of the handleResp(), the clusterUpdate will be pinged in certain // situations to try and construct an update to send back to CDS. -func (c *clusterNode) handleResp(clusterUpdate xdsclient.ClusterUpdate, err error) { +func (c *clusterNode) handleResp(clusterUpdate xdsresource.ClusterUpdate, err error) { c.clusterHandler.clusterMutex.Lock() defer c.clusterHandler.clusterMutex.Unlock() if err != nil { // Write this error for run() to pick up in CDS LB policy. @@ -230,7 +231,7 @@ func (c *clusterNode) handleResp(clusterUpdate xdsclient.ClusterUpdate, err erro // handler to return. Also, if there was any children from previously, // delete the children, as the cluster type is no longer an aggregate // cluster. - if clusterUpdate.ClusterType != xdsclient.ClusterTypeAggregate { + if clusterUpdate.ClusterType != xdsresource.ClusterTypeAggregate { for _, child := range c.children { child.delete() } diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/clustermanager/clustermanager.go b/vendor/google.golang.org/grpc/xds/internal/balancer/clustermanager/clustermanager.go index 318545d79..8d71200d8 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/clustermanager/clustermanager.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/clustermanager/clustermanager.go @@ -25,12 +25,12 @@ import ( "google.golang.org/grpc/balancer" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/internal/balancergroup" internalgrpclog "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/hierarchy" "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" - "google.golang.org/grpc/xds/internal/balancer/balancergroup" ) const balancerName = "xds_cluster_manager_experimental" @@ -46,7 +46,7 @@ func (bb) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Bal b.logger = prefixLogger(b) b.stateAggregator = newBalancerStateAggregator(cc, b.logger) b.stateAggregator.start() - b.bg = balancergroup.New(cc, opts, b.stateAggregator, nil, b.logger) + b.bg = balancergroup.New(cc, opts, b.stateAggregator, b.logger) b.bg.Start() b.logger.Infof("Created") return b diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/clusterresolver.go b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/clusterresolver.go index 66a5aab30..d49014cfa 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/clusterresolver.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/clusterresolver.go @@ -36,6 +36,7 @@ import ( "google.golang.org/grpc/serviceconfig" "google.golang.org/grpc/xds/internal/balancer/priority" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) // Name is the name of the cluster_resolver balancer. @@ -244,7 +245,7 @@ func (b *clusterResolverBalancer) updateChildConfig() error { // In both cases, the sub-balancers will be receive the error. func (b *clusterResolverBalancer) handleErrorFromUpdate(err error, fromParent bool) { b.logger.Warningf("Received error: %v", err) - if fromParent && xdsclient.ErrType(err) == xdsclient.ErrorTypeResourceNotFound { + if fromParent && xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceNotFound { // This is an error from the parent ClientConn (can be the parent CDS // balancer), and is a resource-not-found error. This means the resource // (can be either LDS or CDS) was removed. Stop the EDS watch. diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/configbuilder.go b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/configbuilder.go index 475497d48..1c0c8d0d7 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/configbuilder.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/configbuilder.go @@ -25,6 +25,7 @@ import ( "google.golang.org/grpc/balancer/roundrobin" "google.golang.org/grpc/balancer/weightedroundrobin" + "google.golang.org/grpc/balancer/weightedtarget" "google.golang.org/grpc/internal/hierarchy" internalserviceconfig "google.golang.org/grpc/internal/serviceconfig" "google.golang.org/grpc/resolver" @@ -32,8 +33,7 @@ import ( "google.golang.org/grpc/xds/internal/balancer/clusterimpl" "google.golang.org/grpc/xds/internal/balancer/priority" "google.golang.org/grpc/xds/internal/balancer/ringhash" - "google.golang.org/grpc/xds/internal/balancer/weightedtarget" - "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const million = 1000000 @@ -48,7 +48,7 @@ const million = 1000000 type priorityConfig struct { mechanism DiscoveryMechanism // edsResp is set only if type is EDS. - edsResp xdsclient.EndpointsUpdate + edsResp xdsresource.EndpointsUpdate // addresses is set only if type is DNS. addresses []string } @@ -169,7 +169,7 @@ func buildClusterImplConfigForDNS(parentPriority int, addrStrs []string) (string // - map{"p0":p0_config, "p1":p1_config} // - [p0_address_0, p0_address_1, p1_address_0, p1_address_1] // - p0 addresses' hierarchy attributes are set to p0 -func buildClusterImplConfigForEDS(parentPriority int, edsResp xdsclient.EndpointsUpdate, mechanism DiscoveryMechanism, xdsLBPolicy *internalserviceconfig.BalancerConfig) ([]string, map[string]*clusterimpl.LBConfig, []resolver.Address, error) { +func buildClusterImplConfigForEDS(parentPriority int, edsResp xdsresource.EndpointsUpdate, mechanism DiscoveryMechanism, xdsLBPolicy *internalserviceconfig.BalancerConfig) ([]string, map[string]*clusterimpl.LBConfig, []resolver.Address, error) { drops := make([]clusterimpl.DropConfig, 0, len(edsResp.Drops)) for _, d := range edsResp.Drops { drops = append(drops, clusterimpl.DropConfig{ @@ -205,9 +205,9 @@ func buildClusterImplConfigForEDS(parentPriority int, edsResp xdsclient.Endpoint // For example, for L0-p0, L1-p0, L2-p1, results will be // - ["p0", "p1"] // - map{"p0":[L0, L1], "p1":[L2]} -func groupLocalitiesByPriority(localities []xdsclient.Locality) ([]string, map[string][]xdsclient.Locality) { +func groupLocalitiesByPriority(localities []xdsresource.Locality) ([]string, map[string][]xdsresource.Locality) { var priorityIntSlice []int - priorities := make(map[string][]xdsclient.Locality) + priorities := make(map[string][]xdsresource.Locality) for _, locality := range localities { if locality.Weight == 0 { continue @@ -252,7 +252,7 @@ var rrBalancerConfig = &internalserviceconfig.BalancerConfig{Name: roundrobin.Na // priorityLocalitiesToClusterImpl takes a list of localities (with the same // priority), and generates a cluster impl policy config, and a list of // addresses. -func priorityLocalitiesToClusterImpl(localities []xdsclient.Locality, priorityName string, mechanism DiscoveryMechanism, drops []clusterimpl.DropConfig, xdsLBPolicy *internalserviceconfig.BalancerConfig) (*clusterimpl.LBConfig, []resolver.Address, error) { +func priorityLocalitiesToClusterImpl(localities []xdsresource.Locality, priorityName string, mechanism DiscoveryMechanism, drops []clusterimpl.DropConfig, xdsLBPolicy *internalserviceconfig.BalancerConfig) (*clusterimpl.LBConfig, []resolver.Address, error) { clusterImplCfg := &clusterimpl.LBConfig{ Cluster: mechanism.Cluster, EDSServiceName: mechanism.EDSServiceName, @@ -293,7 +293,7 @@ func priorityLocalitiesToClusterImpl(localities []xdsclient.Locality, priorityNa // // The addresses have path hierarchy set to [priority-name], so priority knows // which child policy they are for. -func localitiesToRingHash(localities []xdsclient.Locality, priorityName string) []resolver.Address { +func localitiesToRingHash(localities []xdsresource.Locality, priorityName string) []resolver.Address { var addrs []resolver.Address for _, locality := range localities { var lw uint32 = 1 @@ -308,7 +308,7 @@ func localitiesToRingHash(localities []xdsclient.Locality, priorityName string) // Filter out all "unhealthy" endpoints (unknown and healthy are // both considered to be healthy: // https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/health_check.proto#envoy-api-enum-core-healthstatus). - if endpoint.HealthStatus != xdsclient.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsclient.EndpointHealthStatusUnknown { + if endpoint.HealthStatus != xdsresource.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsresource.EndpointHealthStatusUnknown { continue } @@ -333,7 +333,7 @@ func localitiesToRingHash(localities []xdsclient.Locality, priorityName string) // // The addresses have path hierarchy set to [priority-name, locality-name], so // priority and weighted target know which child policy they are for. -func localitiesToWeightedTarget(localities []xdsclient.Locality, priorityName string, childPolicy *internalserviceconfig.BalancerConfig) (*weightedtarget.LBConfig, []resolver.Address) { +func localitiesToWeightedTarget(localities []xdsresource.Locality, priorityName string, childPolicy *internalserviceconfig.BalancerConfig) (*weightedtarget.LBConfig, []resolver.Address) { weightedTargets := make(map[string]weightedtarget.Target) var addrs []resolver.Address for _, locality := range localities { @@ -346,7 +346,7 @@ func localitiesToWeightedTarget(localities []xdsclient.Locality, priorityName st // Filter out all "unhealthy" endpoints (unknown and healthy are // both considered to be healthy: // https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/core/health_check.proto#envoy-api-enum-core-healthstatus). - if endpoint.HealthStatus != xdsclient.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsclient.EndpointHealthStatusUnknown { + if endpoint.HealthStatus != xdsresource.EndpointHealthStatusHealthy && endpoint.HealthStatus != xdsresource.EndpointHealthStatusUnknown { continue } diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/resource_resolver.go b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/resource_resolver.go index 2125bd232..9d7db26ad 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/resource_resolver.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/clusterresolver/resource_resolver.go @@ -22,6 +22,7 @@ import ( "sync" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) // resourceUpdate is a combined update from all the resources, in the order of @@ -186,7 +187,7 @@ func (rr *resourceResolver) generate() { return } switch uu := u.(type) { - case xdsclient.EndpointsUpdate: + case xdsresource.EndpointsUpdate: ret = append(ret, priorityConfig{mechanism: rDM.dm, edsResp: uu}) case []string: ret = append(ret, priorityConfig{mechanism: rDM.dm, addresses: uu}) @@ -202,7 +203,7 @@ func (rr *resourceResolver) generate() { type edsDiscoveryMechanism struct { cancel func() - update xdsclient.EndpointsUpdate + update xdsresource.EndpointsUpdate updateReceived bool } @@ -224,7 +225,7 @@ func (er *edsDiscoveryMechanism) stop() { func newEDSResolver(nameToWatch string, xdsc xdsclient.XDSClient, topLevelResolver *resourceResolver) *edsDiscoveryMechanism { ret := &edsDiscoveryMechanism{} topLevelResolver.parent.logger.Infof("EDS watch started on %v", nameToWatch) - cancel := xdsc.WatchEndpoints(nameToWatch, func(update xdsclient.EndpointsUpdate, err error) { + cancel := xdsc.WatchEndpoints(nameToWatch, func(update xdsresource.EndpointsUpdate, err error) { topLevelResolver.mu.Lock() defer topLevelResolver.mu.Unlock() if err != nil { diff --git a/vendor/google.golang.org/grpc/xds/internal/balancer/priority/balancer.go b/vendor/google.golang.org/grpc/xds/internal/balancer/priority/balancer.go index 23e8aa775..98fd0672a 100644 --- a/vendor/google.golang.org/grpc/xds/internal/balancer/priority/balancer.go +++ b/vendor/google.golang.org/grpc/xds/internal/balancer/priority/balancer.go @@ -30,6 +30,7 @@ import ( "time" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/internal/balancergroup" "google.golang.org/grpc/internal/buffer" "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcsync" @@ -37,7 +38,6 @@ import ( "google.golang.org/grpc/internal/pretty" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" - "google.golang.org/grpc/xds/internal/balancer/balancergroup" ) // Name is the name of the priority balancer. @@ -59,7 +59,7 @@ func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Ba } b.logger = prefixLogger(b) - b.bg = balancergroup.New(cc, bOpts, b, nil, b.logger) + b.bg = balancergroup.New(cc, bOpts, b, b.logger) b.bg.Start() go b.run() b.logger.Infof("Created") diff --git a/vendor/google.golang.org/grpc/xds/internal/clusterspecifier/cluster_specifier.go b/vendor/google.golang.org/grpc/xds/internal/clusterspecifier/cluster_specifier.go new file mode 100644 index 000000000..54776f20c --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/clusterspecifier/cluster_specifier.go @@ -0,0 +1,67 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + * + */ + +// Package clusterspecifier contains the ClusterSpecifier interface and a registry for +// storing and retrieving their implementations. +package clusterspecifier + +import ( + "github.com/golang/protobuf/proto" +) + +// BalancerConfig is the Go Native JSON representation of a balancer +// configuration. +type BalancerConfig []map[string]interface{} + +// ClusterSpecifier defines the parsing functionality of a Cluster Specifier. +type ClusterSpecifier interface { + // TypeURLs are the proto message types supported by this + // ClusterSpecifierPlugin. A ClusterSpecifierPlugin will be registered by + // each of its supported message types. + TypeURLs() []string + // ParseClusterSpecifierConfig parses the provided configuration + // proto.Message from the top level RDS configuration. The resulting + // BalancerConfig will be used as configuration for a child LB Policy of the + // Cluster Manager LB Policy. + ParseClusterSpecifierConfig(proto.Message) (BalancerConfig, error) +} + +var ( + // m is a map from scheme to filter. + m = make(map[string]ClusterSpecifier) +) + +// Register registers the ClusterSpecifierPlugin to the ClusterSpecifier map. +// cs.TypeURLs() will be used as the types for this ClusterSpecifierPlugin. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple cluster specifier +// plugins are registered with the same type URL, the one registered last will +// take effect. +func Register(cs ClusterSpecifier) { + for _, u := range cs.TypeURLs() { + m[u] = cs + } +} + +// Get returns the ClusterSpecifier registered with typeURL. +// +// If no cluster specifier is registered with typeURL, nil will be returned. +func Get(typeURL string) ClusterSpecifier { + return m[typeURL] +} diff --git a/vendor/google.golang.org/grpc/xds/internal/httpfilter/rbac/rbac.go b/vendor/google.golang.org/grpc/xds/internal/httpfilter/rbac/rbac.go index e92e2e644..bb85dc80d 100644 --- a/vendor/google.golang.org/grpc/xds/internal/httpfilter/rbac/rbac.go +++ b/vendor/google.golang.org/grpc/xds/internal/httpfilter/rbac/rbac.go @@ -27,8 +27,8 @@ import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/resolver" - "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/internal/xds/rbac" "google.golang.org/grpc/xds/internal/httpfilter" "google.golang.org/protobuf/types/known/anypb" @@ -38,7 +38,7 @@ import ( ) func init() { - if env.RBACSupport { + if envconfig.XDSRBAC { httpfilter.Register(builder{}) } } @@ -64,7 +64,7 @@ type builder struct { type config struct { httpfilter.FilterConfig - config *rpb.RBAC + chainEngine *rbac.ChainEngine } func (builder) TypeURLs() []string { @@ -90,23 +90,57 @@ func parseConfig(rbacCfg *rpb.RBAC) (httpfilter.FilterConfig, error) { // "It is also a validation failure if Permission or Principal has a // header matcher for a grpc- prefixed header name or :scheme." - A41 for _, principal := range policy.Principals { - if principal.GetHeader() != nil { - name := principal.GetHeader().GetName() - if name == ":scheme" || strings.HasPrefix(name, "grpc-") { - return nil, fmt.Errorf("rbac: principal header matcher for %v is :scheme or starts with grpc", name) - } + name := principal.GetHeader().GetName() + if name == ":scheme" || strings.HasPrefix(name, "grpc-") { + return nil, fmt.Errorf("rbac: principal header matcher for %v is :scheme or starts with grpc", name) + } + } + for _, permission := range policy.Permissions { + name := permission.GetHeader().GetName() + if name == ":scheme" || strings.HasPrefix(name, "grpc-") { + return nil, fmt.Errorf("rbac: permission header matcher for %v is :scheme or starts with grpc", name) + } + } + } + + // "Envoy aliases :authority and Host in its header map implementation, so + // they should be treated equivalent for the RBAC matchers; there must be no + // behavior change depending on which of the two header names is used in the + // RBAC policy." - A41. Loop through config's principals and policies, change + // any header matcher with value "host" to :authority", as that is what + // grpc-go shifts both headers to in transport layer. + for _, policy := range rbacCfg.GetRules().GetPolicies() { + for _, principal := range policy.Principals { + if principal.GetHeader().GetName() == "host" { + principal.GetHeader().Name = ":authority" } } for _, permission := range policy.Permissions { - if permission.GetHeader() != nil { - name := permission.GetHeader().GetName() - if name == ":scheme" || strings.HasPrefix(name, "grpc-") { - return nil, fmt.Errorf("rbac: permission header matcher for %v is :scheme or starts with grpc", name) - } + if permission.GetHeader().GetName() == "host" { + permission.GetHeader().Name = ":authority" } } } - return config{config: rbacCfg}, nil + + // Two cases where this HTTP Filter is a no op: + // "If absent, no enforcing RBAC policy will be applied" - RBAC + // Documentation for Rules field. + // "At this time, if the RBAC.action is Action.LOG then the policy will be + // completely ignored, as if RBAC was not configurated." - A41 + if rbacCfg.Rules == nil || rbacCfg.GetRules().GetAction() == v3rbacpb.RBAC_LOG { + return config{}, nil + } + + ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{rbacCfg.GetRules()}) + if err != nil { + // "At this time, if the RBAC.action is Action.LOG then the policy will be + // completely ignored, as if RBAC was not configurated." - A41 + if rbacCfg.GetRules().GetAction() != v3rbacpb.RBAC_LOG { + return nil, fmt.Errorf("rbac: error constructing matching engine: %v", err) + } + } + + return config{chainEngine: ce}, nil } func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) { @@ -166,49 +200,15 @@ func (builder) BuildServerInterceptor(cfg httpfilter.FilterConfig, override http } } - icfg := c.config + // RBAC HTTP Filter is a no op from one of these two cases: // "If absent, no enforcing RBAC policy will be applied" - RBAC // Documentation for Rules field. - if icfg.Rules == nil { - return nil, nil - } - // "At this time, if the RBAC.action is Action.LOG then the policy will be // completely ignored, as if RBAC was not configurated." - A41 - if icfg.Rules.Action == v3rbacpb.RBAC_LOG { + if c.chainEngine == nil { return nil, nil } - - // "Envoy aliases :authority and Host in its header map implementation, so - // they should be treated equivalent for the RBAC matchers; there must be no - // behavior change depending on which of the two header names is used in the - // RBAC policy." - A41. Loop through config's principals and policies, change - // any header matcher with value "host" to :authority", as that is what - // grpc-go shifts both headers to in transport layer. - for _, policy := range icfg.Rules.GetPolicies() { - for _, principal := range policy.Principals { - if principal.GetHeader() != nil { - name := principal.GetHeader().GetName() - if name == "host" { - principal.GetHeader().Name = ":authority" - } - } - } - for _, permission := range policy.Permissions { - if permission.GetHeader() != nil { - name := permission.GetHeader().GetName() - if name == "host" { - permission.GetHeader().Name = ":authority" - } - } - } - } - - ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{icfg.Rules}) - if err != nil { - return nil, fmt.Errorf("error constructing matching engine: %v", err) - } - return &interceptor{chainEngine: ce}, nil + return &interceptor{chainEngine: c.chainEngine}, nil } type interceptor struct { diff --git a/vendor/google.golang.org/grpc/xds/internal/resolver/serviceconfig.go b/vendor/google.golang.org/grpc/xds/internal/resolver/serviceconfig.go index ddf699f93..fd75af210 100644 --- a/vendor/google.golang.org/grpc/xds/internal/resolver/serviceconfig.go +++ b/vendor/google.golang.org/grpc/xds/internal/resolver/serviceconfig.go @@ -29,23 +29,25 @@ import ( xxhash "github.com/cespare/xxhash/v2" "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/grpcrand" iresolver "google.golang.org/grpc/internal/resolver" "google.golang.org/grpc/internal/serviceconfig" "google.golang.org/grpc/internal/wrr" - "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/grpc/xds/internal/balancer/clustermanager" "google.golang.org/grpc/xds/internal/balancer/ringhash" "google.golang.org/grpc/xds/internal/httpfilter" "google.golang.org/grpc/xds/internal/httpfilter/router" - "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const ( - cdsName = "cds_experimental" - xdsClusterManagerName = "xds_cluster_manager_experimental" + cdsName = "cds_experimental" + xdsClusterManagerName = "xds_cluster_manager_experimental" + clusterPrefix = "cluster:" + clusterSpecifierPluginPrefix = "cluster_specifier_plugin:" ) type serviceConfig struct { @@ -86,10 +88,8 @@ func (r *xdsResolver) pruneActiveClusters() { func serviceConfigJSON(activeClusters map[string]*clusterInfo) ([]byte, error) { // Generate children (all entries in activeClusters). children := make(map[string]xdsChildConfig) - for cluster := range activeClusters { - children[cluster] = xdsChildConfig{ - ChildPolicy: newBalancerConfig(cdsName, cdsBalancerConfig{Cluster: cluster}), - } + for cluster, ci := range activeClusters { + children[cluster] = ci.cfg } sc := serviceConfig{ @@ -109,7 +109,7 @@ type virtualHost struct { // map from filter name to its config httpFilterConfigOverride map[string]httpfilter.FilterConfig // retry policy present in virtual host - retryConfig *xdsclient.RetryConfig + retryConfig *xdsresource.RetryConfig } // routeCluster holds information about a cluster as referenced by a route. @@ -120,13 +120,13 @@ type routeCluster struct { } type route struct { - m *xdsclient.CompositeMatcher // converted from route matchers - clusters wrr.WRR // holds *routeCluster entries + m *xdsresource.CompositeMatcher // converted from route matchers + clusters wrr.WRR // holds *routeCluster entries maxStreamDuration time.Duration // map from filter name to its config httpFilterConfigOverride map[string]httpfilter.FilterConfig - retryConfig *xdsclient.RetryConfig - hashPolicies []*xdsclient.HashPolicy + retryConfig *xdsresource.RetryConfig + hashPolicies []*xdsresource.HashPolicy } func (r route) String() string { @@ -138,7 +138,7 @@ type configSelector struct { virtualHost virtualHost routes []route clusters map[string]*clusterInfo - httpFilterConfig []xdsclient.HTTPFilter + httpFilterConfig []xdsresource.HTTPFilter } var errNoMatchedRouteFound = status.Errorf(codes.Unavailable, "no matched route was found") @@ -158,10 +158,12 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP if rt == nil || rt.clusters == nil { return nil, errNoMatchedRouteFound } + cluster, ok := rt.clusters.Next().(*routeCluster) if !ok { return nil, status.Errorf(codes.Internal, "error retrieving cluster for match: %v (%T)", cluster, cluster) } + // Add a ref to the selected cluster, as this RPC needs this cluster until // it is committed. ref := &cs.clusters[cluster.name].refCount @@ -174,7 +176,7 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP lbCtx := clustermanager.SetPickedCluster(rpcInfo.Context, cluster.name) // Request Hashes are only applicable for a Ring Hash LB. - if env.RingHashSupport { + if envconfig.XDSRingHash { lbCtx = ringhash.SetRequestHash(lbCtx, cs.generateHash(rpcInfo, rt.hashPolicies)) } @@ -208,7 +210,7 @@ func (cs *configSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RP return config, nil } -func retryConfigToPolicy(config *xdsclient.RetryConfig) *serviceconfig.RetryPolicy { +func retryConfigToPolicy(config *xdsresource.RetryConfig) *serviceconfig.RetryPolicy { return &serviceconfig.RetryPolicy{ MaxAttempts: int(config.NumRetries) + 1, InitialBackoff: config.RetryBackoff.BaseInterval, @@ -218,14 +220,14 @@ func retryConfigToPolicy(config *xdsclient.RetryConfig) *serviceconfig.RetryPoli } } -func (cs *configSelector) generateHash(rpcInfo iresolver.RPCInfo, hashPolicies []*xdsclient.HashPolicy) uint64 { +func (cs *configSelector) generateHash(rpcInfo iresolver.RPCInfo, hashPolicies []*xdsresource.HashPolicy) uint64 { var hash uint64 var generatedHash bool for _, policy := range hashPolicies { var policyHash uint64 var generatedPolicyHash bool switch policy.HashPolicyType { - case xdsclient.HashPolicyTypeHeader: + case xdsresource.HashPolicyTypeHeader: md, ok := metadata.FromOutgoingContext(rpcInfo.Context) if !ok { continue @@ -242,7 +244,7 @@ func (cs *configSelector) generateHash(rpcInfo iresolver.RPCInfo, hashPolicies [ policyHash = xxhash.Sum64String(joinedValues) generatedHash = true generatedPolicyHash = true - case xdsclient.HashPolicyTypeChannelID: + case xdsresource.HashPolicyTypeChannelID: // Hash the ClientConn pointer which logically uniquely // identifies the client. policyHash = xxhash.Sum64String(fmt.Sprintf("%p", &cs.r.cc)) @@ -353,26 +355,30 @@ func (r *xdsResolver) newConfigSelector(su serviceUpdate) (*configSelector, erro for i, rt := range su.virtualHost.Routes { clusters := newWRR() - for cluster, wc := range rt.WeightedClusters { + if rt.ClusterSpecifierPlugin != "" { + clusterName := clusterSpecifierPluginPrefix + rt.ClusterSpecifierPlugin clusters.Add(&routeCluster{ - name: cluster, - httpFilterConfigOverride: wc.HTTPFilterConfigOverride, - }, int64(wc.Weight)) - - // Initialize entries in cs.clusters map, creating entries in - // r.activeClusters as necessary. Set to zero as they will be - // incremented by incRefs. - ci := r.activeClusters[cluster] - if ci == nil { - ci = &clusterInfo{refCount: 0} - r.activeClusters[cluster] = ci + name: clusterName, + }, 1) + cs.initializeCluster(clusterName, xdsChildConfig{ + ChildPolicy: balancerConfig(su.clusterSpecifierPlugins[rt.ClusterSpecifierPlugin]), + }) + } else { + for cluster, wc := range rt.WeightedClusters { + clusterName := clusterPrefix + cluster + clusters.Add(&routeCluster{ + name: clusterName, + httpFilterConfigOverride: wc.HTTPFilterConfigOverride, + }, int64(wc.Weight)) + cs.initializeCluster(clusterName, xdsChildConfig{ + ChildPolicy: newBalancerConfig(cdsName, cdsBalancerConfig{Cluster: cluster}), + }) } - cs.clusters[cluster] = ci } cs.routes[i].clusters = clusters var err error - cs.routes[i].m, err = xdsclient.RouteToMatcher(rt) + cs.routes[i].m, err = xdsresource.RouteToMatcher(rt) if err != nil { return nil, err } @@ -397,9 +403,25 @@ func (r *xdsResolver) newConfigSelector(su serviceUpdate) (*configSelector, erro return cs, nil } +// initializeCluster initializes entries in cs.clusters map, creating entries in +// r.activeClusters as necessary. Any created entries will have a ref count set +// to zero as their ref count will be incremented by incRefs. +func (cs *configSelector) initializeCluster(clusterName string, cfg xdsChildConfig) { + ci := cs.r.activeClusters[clusterName] + if ci == nil { + ci = &clusterInfo{refCount: 0} + cs.r.activeClusters[clusterName] = ci + } + cs.clusters[clusterName] = ci + cs.clusters[clusterName].cfg = cfg +} + type clusterInfo struct { // number of references to this cluster; accessed atomically refCount int32 + // cfg is the child configuration for this cluster, containing either the + // csp config or the cds cluster config. + cfg xdsChildConfig } type interceptorList struct { diff --git a/vendor/google.golang.org/grpc/xds/internal/resolver/watch_service.go b/vendor/google.golang.org/grpc/xds/internal/resolver/watch_service.go index da0bf95f3..3db9be1ca 100644 --- a/vendor/google.golang.org/grpc/xds/internal/resolver/watch_service.go +++ b/vendor/google.golang.org/grpc/xds/internal/resolver/watch_service.go @@ -25,7 +25,9 @@ import ( "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal/clusterspecifier" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) // serviceUpdate contains information received from the LDS/RDS responses which @@ -33,7 +35,10 @@ import ( // making a LDS to get the RouteConfig name. type serviceUpdate struct { // virtualHost contains routes and other configuration to route RPCs. - virtualHost *xdsclient.VirtualHost + virtualHost *xdsresource.VirtualHost + // clusterSpecifierPlugins contains the configurations for any cluster + // specifier plugins emitted by the xdsclient. + clusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig // ldsConfig contains configuration that applies to all routes. ldsConfig ldsConfig } @@ -44,7 +49,7 @@ type ldsConfig struct { // maxStreamDuration is from the HTTP connection manager's // common_http_protocol_options field. maxStreamDuration time.Duration - httpFilterConfig []xdsclient.HTTPFilter + httpFilterConfig []xdsresource.HTTPFilter } // watchService uses LDS and RDS to discover information about the provided @@ -81,7 +86,7 @@ type serviceUpdateWatcher struct { rdsCancel func() } -func (w *serviceUpdateWatcher) handleLDSResp(update xdsclient.ListenerUpdate, err error) { +func (w *serviceUpdateWatcher) handleLDSResp(update xdsresource.ListenerUpdate, err error) { w.logger.Infof("received LDS update: %+v, err: %v", pretty.ToJSON(update), err) w.mu.Lock() defer w.mu.Unlock() @@ -93,7 +98,7 @@ func (w *serviceUpdateWatcher) handleLDSResp(update xdsclient.ListenerUpdate, er // type we check is ResourceNotFound, which indicates the LDS resource // was removed, and besides sending the error to callback, we also // cancel the RDS watch. - if xdsclient.ErrType(err) == xdsclient.ErrorTypeResourceNotFound && w.rdsCancel != nil { + if xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceNotFound && w.rdsCancel != nil { w.rdsCancel() w.rdsName = "" w.rdsCancel = nil @@ -119,7 +124,7 @@ func (w *serviceUpdateWatcher) handleLDSResp(update xdsclient.ListenerUpdate, er } // Handle the inline RDS update as if it's from an RDS watch. - w.updateVirtualHostsFromRDS(*update.InlineRouteConfig) + w.applyRouteConfigUpdate(*update.InlineRouteConfig) return } @@ -150,8 +155,8 @@ func (w *serviceUpdateWatcher) handleLDSResp(update xdsclient.ListenerUpdate, er w.rdsCancel = w.c.WatchRouteConfig(update.RouteConfigName, w.handleRDSResp) } -func (w *serviceUpdateWatcher) updateVirtualHostsFromRDS(update xdsclient.RouteConfigUpdate) { - matchVh := xdsclient.FindBestMatchingVirtualHost(w.serviceName, update.VirtualHosts) +func (w *serviceUpdateWatcher) applyRouteConfigUpdate(update xdsresource.RouteConfigUpdate) { + matchVh := xdsresource.FindBestMatchingVirtualHost(w.serviceName, update.VirtualHosts) if matchVh == nil { // No matching virtual host found. w.serviceCb(serviceUpdate{}, fmt.Errorf("no matching virtual host found for %q", w.serviceName)) @@ -159,10 +164,11 @@ func (w *serviceUpdateWatcher) updateVirtualHostsFromRDS(update xdsclient.RouteC } w.lastUpdate.virtualHost = matchVh + w.lastUpdate.clusterSpecifierPlugins = update.ClusterSpecifierPlugins w.serviceCb(w.lastUpdate, nil) } -func (w *serviceUpdateWatcher) handleRDSResp(update xdsclient.RouteConfigUpdate, err error) { +func (w *serviceUpdateWatcher) handleRDSResp(update xdsresource.RouteConfigUpdate, err error) { w.logger.Infof("received RDS update: %+v, err: %v", pretty.ToJSON(update), err) w.mu.Lock() defer w.mu.Unlock() @@ -178,7 +184,7 @@ func (w *serviceUpdateWatcher) handleRDSResp(update xdsclient.RouteConfigUpdate, w.serviceCb(serviceUpdate{}, err) return } - w.updateVirtualHostsFromRDS(update) + w.applyRouteConfigUpdate(update) } func (w *serviceUpdateWatcher) close() { diff --git a/vendor/google.golang.org/grpc/xds/internal/resolver/xds_resolver.go b/vendor/google.golang.org/grpc/xds/internal/resolver/xds_resolver.go index 19ee01773..6788090e2 100644 --- a/vendor/google.golang.org/grpc/xds/internal/resolver/xds_resolver.go +++ b/vendor/google.golang.org/grpc/xds/internal/resolver/xds_resolver.go @@ -22,6 +22,7 @@ package resolver import ( "errors" "fmt" + "strings" "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/grpclog" @@ -30,6 +31,8 @@ import ( iresolver "google.golang.org/grpc/internal/resolver" "google.golang.org/grpc/resolver" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const xdsScheme = "xds" @@ -60,7 +63,7 @@ type xdsResolverBuilder struct { // // The xds bootstrap process is performed (and a new xds client is built) every // time an xds resolver is built. -func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { +func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (_ resolver.Resolver, retErr error) { r := &xdsResolver{ target: t, cc: cc, @@ -68,7 +71,12 @@ func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, op updateCh: make(chan suWithError, 1), activeClusters: make(map[string]*clusterInfo), } - r.logger = prefixLogger((r)) + defer func() { + if retErr != nil { + r.Close() + } + }() + r.logger = prefixLogger(r) r.logger.Infof("Creating resolver for target: %+v", t) newXDSClient := newXDSClient @@ -81,6 +89,10 @@ func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, op return nil, fmt.Errorf("xds: failed to create xds-client: %v", err) } r.client = client + bootstrapConfig := client.BootstrapConfig() + if bootstrapConfig == nil { + return nil, errors.New("bootstrap configuration is empty") + } // If xds credentials were specified by the user, but bootstrap configs do // not contain any certificate provider configuration, it is better to fail @@ -94,14 +106,36 @@ func (b *xdsResolverBuilder) Build(t resolver.Target, cc resolver.ClientConn, op creds = opts.CredsBundle.TransportCredentials() } if xc, ok := creds.(interface{ UsesXDS() bool }); ok && xc.UsesXDS() { - bc := client.BootstrapConfig() - if len(bc.CertProviderConfigs) == 0 { + if len(bootstrapConfig.CertProviderConfigs) == 0 { return nil, errors.New("xds: xdsCreds specified but certificate_providers config missing in bootstrap file") } } + // Find the client listener template to use from the bootstrap config: + // - If authority is not set in the target, use the top level template + // - If authority is set, use the template from the authority map. + template := bootstrapConfig.ClientDefaultListenerResourceNameTemplate + if authority := r.target.URL.Host; authority != "" { + a := bootstrapConfig.Authorities[authority] + if a == nil { + return nil, fmt.Errorf("xds: authority %q is not found in the bootstrap file", authority) + } + if a.ClientListenerResourceNameTemplate != "" { + // This check will never be false, because + // ClientListenerResourceNameTemplate is required to start with + // xdstp://, and has a default value (not an empty string) if unset. + template = a.ClientListenerResourceNameTemplate + } + } + endpoint := r.target.URL.Path + if endpoint == "" { + endpoint = r.target.URL.Opaque + } + endpoint = strings.TrimPrefix(endpoint, "/") + resourceName := bootstrap.PopulateResourceTemplate(template, endpoint) + // Register a watch on the xdsClient for the user's dial target. - cancelWatch := watchService(r.client, r.target.Endpoint, r.handleServiceUpdate, r.logger) + cancelWatch := watchService(r.client, resourceName, r.handleServiceUpdate, r.logger) r.logger.Infof("Watch started on resource name %v with xds-client %p", r.target.Endpoint, r.client) r.cancelWatch = func() { cancelWatch() @@ -171,7 +205,6 @@ func (r *xdsResolver) sendNewServiceConfig(cs *configSelector) bool { return true } - // Produce the service config. sc, err := serviceConfigJSON(r.activeClusters) if err != nil { // JSON marshal error; should never happen. @@ -199,7 +232,7 @@ func (r *xdsResolver) run() { case update := <-r.updateCh: if update.err != nil { r.logger.Warningf("Watch error on resource %v from xds-client %p, %v", r.target.Endpoint, r.client, update.err) - if xdsclient.ErrType(update.err) == xdsclient.ErrorTypeResourceNotFound { + if xdsresource.ErrType(update.err) == xdsresource.ErrorTypeResourceNotFound { // If error is resource-not-found, it means the LDS // resource was removed. Ultimately send an empty service // config, which picks pick-first, with no address, and @@ -268,8 +301,15 @@ func (*xdsResolver) ResolveNow(o resolver.ResolveNowOptions) {} // Close closes the resolver, and also closes the underlying xdsClient. func (r *xdsResolver) Close() { - r.cancelWatch() - r.client.Close() + // Note that Close needs to check for nils even if some of them are always + // set in the constructor. This is because the constructor defers Close() in + // error cases, and the fields might not be set when the error happens. + if r.cancelWatch != nil { + r.cancelWatch() + } + if r.client != nil { + r.client.Close() + } r.closed.Fire() r.logger.Infof("Shutdown") } diff --git a/vendor/google.golang.org/grpc/xds/internal/server/conn_wrapper.go b/vendor/google.golang.org/grpc/xds/internal/server/conn_wrapper.go index dd0374dc8..f1ee06e7b 100644 --- a/vendor/google.golang.org/grpc/xds/internal/server/conn_wrapper.go +++ b/vendor/google.golang.org/grpc/xds/internal/server/conn_wrapper.go @@ -27,7 +27,7 @@ import ( "google.golang.org/grpc/credentials/tls/certprovider" xdsinternal "google.golang.org/grpc/internal/credentials/xds" - "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) // connWrapper is a thin wrapper around a net.Conn returned by Accept(). It @@ -43,7 +43,7 @@ type connWrapper struct { net.Conn // The specific filter chain picked for handling this connection. - filterChain *xdsclient.FilterChain + filterChain *xdsresource.FilterChain // A reference fo the listenerWrapper on which this connection was accepted. parent *listenerWrapper @@ -61,11 +61,11 @@ type connWrapper struct { // The virtual hosts with matchable routes and instantiated HTTP Filters per // route. - virtualHosts []xdsclient.VirtualHostWithInterceptors + virtualHosts []xdsresource.VirtualHostWithInterceptors } // VirtualHosts returns the virtual hosts to be used for server side routing. -func (c *connWrapper) VirtualHosts() []xdsclient.VirtualHostWithInterceptors { +func (c *connWrapper) VirtualHosts() []xdsresource.VirtualHostWithInterceptors { return c.virtualHosts } diff --git a/vendor/google.golang.org/grpc/xds/internal/server/listener_wrapper.go b/vendor/google.golang.org/grpc/xds/internal/server/listener_wrapper.go index 99c9a7532..c90f9672e 100644 --- a/vendor/google.golang.org/grpc/xds/internal/server/listener_wrapper.go +++ b/vendor/google.golang.org/grpc/xds/internal/server/listener_wrapper.go @@ -33,11 +33,11 @@ import ( "google.golang.org/grpc/connectivity" "google.golang.org/grpc/grpclog" internalbackoff "google.golang.org/grpc/internal/backoff" + "google.golang.org/grpc/internal/envconfig" internalgrpclog "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcsync" - "google.golang.org/grpc/internal/xds/env" - "google.golang.org/grpc/xds/internal/xdsclient" "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) var ( @@ -73,8 +73,8 @@ func prefixLogger(p *listenerWrapper) *internalgrpclog.PrefixLogger { // XDSClient wraps the methods on the XDSClient which are required by // the listenerWrapper. type XDSClient interface { - WatchListener(string, func(xdsclient.ListenerUpdate, error)) func() - WatchRouteConfig(string, func(xdsclient.RouteConfigUpdate, error)) func() + WatchListener(string, func(xdsresource.ListenerUpdate, error)) func() + WatchRouteConfig(string, func(xdsresource.RouteConfigUpdate, error)) func() BootstrapConfig() *bootstrap.Config } @@ -136,7 +136,7 @@ func NewListenerWrapper(params ListenerWrapperParams) (net.Listener, <-chan stru } type ldsUpdateWithError struct { - update xdsclient.ListenerUpdate + update xdsresource.ListenerUpdate err error } @@ -182,7 +182,7 @@ type listenerWrapper struct { // Current serving mode. mode connectivity.ServingMode // Filter chains received as part of the last good update. - filterChains *xdsclient.FilterChainManager + filterChains *xdsresource.FilterChainManager // rdsHandler is used for any dynamic RDS resources specified in a LDS // update. @@ -250,7 +250,7 @@ func (l *listenerWrapper) Accept() (net.Conn, error) { conn.Close() continue } - fc, err := l.filterChains.Lookup(xdsclient.FilterChainLookupParams{ + fc, err := l.filterChains.Lookup(xdsresource.FilterChainLookupParams{ IsUnspecifiedListener: l.isUnspecifiedAddr, DestAddr: destAddr.IP, SourceAddr: srcAddr.IP, @@ -273,15 +273,15 @@ func (l *listenerWrapper) Accept() (net.Conn, error) { conn.Close() continue } - if !env.RBACSupport { + if !envconfig.XDSRBAC { return &connWrapper{Conn: conn, filterChain: fc, parent: l}, nil } - var rc xdsclient.RouteConfigUpdate + var rc xdsresource.RouteConfigUpdate if fc.InlineRouteConfig != nil { rc = *fc.InlineRouteConfig } else { rcPtr := atomic.LoadPointer(&l.rdsUpdates) - rcuPtr := (*map[string]xdsclient.RouteConfigUpdate)(rcPtr) + rcuPtr := (*map[string]xdsresource.RouteConfigUpdate)(rcPtr) // This shouldn't happen, but this error protects against a panic. if rcuPtr == nil { return nil, errors.New("route configuration pointer is nil") @@ -340,7 +340,7 @@ func (l *listenerWrapper) run() { // handleLDSUpdate is the callback which handles LDS Updates. It writes the // received update to the update channel, which is picked up by the run // goroutine. -func (l *listenerWrapper) handleListenerUpdate(update xdsclient.ListenerUpdate, err error) { +func (l *listenerWrapper) handleListenerUpdate(update xdsresource.ListenerUpdate, err error) { if l.closed.HasFired() { l.logger.Warningf("Resource %q received update: %v with error: %v, after listener was closed", l.name, update, err) return @@ -364,7 +364,7 @@ func (l *listenerWrapper) handleRDSUpdate(update rdsHandlerUpdate) { } if update.err != nil { l.logger.Warningf("Received error for rds names specified in resource %q: %+v", l.name, update.err) - if xdsclient.ErrType(update.err) == xdsclient.ErrorTypeResourceNotFound { + if xdsresource.ErrType(update.err) == xdsresource.ErrorTypeResourceNotFound { l.switchMode(nil, connectivity.ServingModeNotServing, update.err) } // For errors which are anything other than "resource-not-found", we @@ -380,7 +380,7 @@ func (l *listenerWrapper) handleRDSUpdate(update rdsHandlerUpdate) { func (l *listenerWrapper) handleLDSUpdate(update ldsUpdateWithError) { if update.err != nil { l.logger.Warningf("Received error for resource %q: %+v", l.name, update.err) - if xdsclient.ErrType(update.err) == xdsclient.ErrorTypeResourceNotFound { + if xdsresource.ErrType(update.err) == xdsresource.ErrorTypeResourceNotFound { l.switchMode(nil, connectivity.ServingModeNotServing, update.err) } // For errors which are anything other than "resource-not-found", we @@ -414,7 +414,7 @@ func (l *listenerWrapper) handleLDSUpdate(update ldsUpdateWithError) { // Server's state to ServingModeNotServing. That prevents new connections // from being accepted, whereas here we simply want the clients to reconnect // to get the updated configuration. - if env.RBACSupport { + if envconfig.XDSRBAC { if l.drainCallback != nil { l.drainCallback(l.Listener.Addr()) } @@ -429,7 +429,7 @@ func (l *listenerWrapper) handleLDSUpdate(update ldsUpdateWithError) { } } -func (l *listenerWrapper) switchMode(fcs *xdsclient.FilterChainManager, newMode connectivity.ServingMode, err error) { +func (l *listenerWrapper) switchMode(fcs *xdsresource.FilterChainManager, newMode connectivity.ServingMode, err error) { l.mu.Lock() defer l.mu.Unlock() diff --git a/vendor/google.golang.org/grpc/xds/internal/server/rds_handler.go b/vendor/google.golang.org/grpc/xds/internal/server/rds_handler.go index cc676c4ca..722748cbd 100644 --- a/vendor/google.golang.org/grpc/xds/internal/server/rds_handler.go +++ b/vendor/google.golang.org/grpc/xds/internal/server/rds_handler.go @@ -21,13 +21,13 @@ package server import ( "sync" - "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) // rdsHandlerUpdate wraps the full RouteConfigUpdate that are dynamically // queried for a given server side listener. type rdsHandlerUpdate struct { - updates map[string]xdsclient.RouteConfigUpdate + updates map[string]xdsresource.RouteConfigUpdate err error } @@ -37,7 +37,7 @@ type rdsHandler struct { xdsC XDSClient mu sync.Mutex - updates map[string]xdsclient.RouteConfigUpdate + updates map[string]xdsresource.RouteConfigUpdate cancels map[string]func() // For a rdsHandler update, the only update wrapped listener cares about is @@ -53,7 +53,7 @@ func newRDSHandler(xdsC XDSClient, ch chan rdsHandlerUpdate) *rdsHandler { return &rdsHandler{ xdsC: xdsC, updateChannel: ch, - updates: make(map[string]xdsclient.RouteConfigUpdate), + updates: make(map[string]xdsresource.RouteConfigUpdate), cancels: make(map[string]func()), } } @@ -70,7 +70,7 @@ func (rh *rdsHandler) updateRouteNamesToWatch(routeNamesToWatch map[string]bool) for routeName := range routeNamesToWatch { if _, ok := rh.cancels[routeName]; !ok { func(routeName string) { - rh.cancels[routeName] = rh.xdsC.WatchRouteConfig(routeName, func(update xdsclient.RouteConfigUpdate, err error) { + rh.cancels[routeName] = rh.xdsC.WatchRouteConfig(routeName, func(update xdsresource.RouteConfigUpdate, err error) { rh.handleRouteUpdate(routeName, update, err) }) }(routeName) @@ -97,7 +97,7 @@ func (rh *rdsHandler) updateRouteNamesToWatch(routeNamesToWatch map[string]bool) // handleRouteUpdate persists the route config for a given route name, and also // sends an update to the Listener Wrapper on an error received or if the rds // handler has a full collection of updates. -func (rh *rdsHandler) handleRouteUpdate(routeName string, update xdsclient.RouteConfigUpdate, err error) { +func (rh *rdsHandler) handleRouteUpdate(routeName string, update xdsresource.RouteConfigUpdate, err error) { if err != nil { drainAndPush(rh.updateChannel, rdsHandlerUpdate{err: err}) return diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/attributes.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/attributes.go index 467c205a2..64f87f296 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/attributes.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/attributes.go @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc/resolver" "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" "google.golang.org/grpc/xds/internal/xdsclient/load" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) type clientKeyType string @@ -31,16 +32,16 @@ const clientKey = clientKeyType("grpc.xds.internal.client.Client") // (collectively termed as xDS) on a remote management server, to discover // various dynamic resources. type XDSClient interface { - WatchListener(string, func(ListenerUpdate, error)) func() - WatchRouteConfig(string, func(RouteConfigUpdate, error)) func() - WatchCluster(string, func(ClusterUpdate, error)) func() - WatchEndpoints(clusterName string, edsCb func(EndpointsUpdate, error)) (cancel func()) + WatchListener(string, func(xdsresource.ListenerUpdate, error)) func() + WatchRouteConfig(string, func(xdsresource.RouteConfigUpdate, error)) func() + WatchCluster(string, func(xdsresource.ClusterUpdate, error)) func() + WatchEndpoints(clusterName string, edsCb func(xdsresource.EndpointsUpdate, error)) (cancel func()) ReportLoad(server string) (*load.Store, func()) - DumpLDS() (string, map[string]UpdateWithMD) - DumpRDS() (string, map[string]UpdateWithMD) - DumpCDS() (string, map[string]UpdateWithMD) - DumpEDS() (string, map[string]UpdateWithMD) + DumpLDS() map[string]xdsresource.UpdateWithMD + DumpRDS() map[string]xdsresource.UpdateWithMD + DumpCDS() map[string]xdsresource.UpdateWithMD + DumpEDS() map[string]xdsresource.UpdateWithMD BootstrapConfig() *bootstrap.Config Close() diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/bootstrap.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/bootstrap.go index fa229d995..ecec17077 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/bootstrap.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/bootstrap.go @@ -25,9 +25,8 @@ import ( "encoding/json" "fmt" "io/ioutil" + "strings" - v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" - v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" "google.golang.org/grpc" @@ -35,9 +34,12 @@ import ( "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/tls/certprovider" "google.golang.org/grpc/internal" + "google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/pretty" - "google.golang.org/grpc/internal/xds/env" - "google.golang.org/grpc/xds/internal/version" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + + v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ) const ( @@ -58,34 +60,184 @@ var gRPCVersion = fmt.Sprintf("%s %s", gRPCUserAgentName, grpc.Version) // For overriding in unit tests. var bootstrapFileReadFunc = ioutil.ReadFile -// Config provides the xDS client with several key bits of information that it -// requires in its interaction with the management server. The Config is -// initialized from the bootstrap file. -type Config struct { - // BalancerName is the name of the management server to connect to. +// ServerConfig contains the configuration to connect to a server, including +// URI, creds, and transport API version (e.g. v2 or v3). +type ServerConfig struct { + // ServerURI is the management server to connect to. // - // The bootstrap file contains a list of servers (with name+creds), but we - // pick the first one. - BalancerName string + // The bootstrap file contains an ordered list of xDS servers to contact for + // this authority. The first one is picked. + ServerURI string // Creds contains the credentials to be used while talking to the xDS // server, as a grpc.DialOption. Creds grpc.DialOption + // CredsType is the type of the creds. It will be used to dedup servers. + CredsType string // TransportAPI indicates the API version of xDS transport protocol to use. // This describes the xDS gRPC endpoint and version of // DiscoveryRequest/Response used on the wire. TransportAPI version.TransportAPI // NodeProto contains the Node proto to be used in xDS requests. The actual // type depends on the transport protocol version used. + // + // Note that it's specified in the bootstrap globally for all the servers, + // but we keep it in each server config so that its type (e.g. *v2pb.Node or + // *v3pb.Node) is consistent with the transport API version. NodeProto proto.Message +} + +// String returns the string representation of the ServerConfig. +// +// This string representation will be used as map keys in federation +// (`map[ServerConfig]authority`), so that the xDS ClientConn and stream will be +// shared by authorities with different names but the same server config. +// +// It covers (almost) all the fields so the string can represent the config +// content. It doesn't cover NodeProto because NodeProto isn't used by +// federation. +func (sc *ServerConfig) String() string { + var ver string + switch sc.TransportAPI { + case version.TransportV3: + ver = "xDSv3" + case version.TransportV2: + ver = "xDSv2" + } + return strings.Join([]string{sc.ServerURI, sc.CredsType, ver}, "-") +} + +// UnmarshalJSON takes the json data (a list of servers) and unmarshals the +// first one in the list. +func (sc *ServerConfig) UnmarshalJSON(data []byte) error { + var servers []*xdsServer + if err := json.Unmarshal(data, &servers); err != nil { + return fmt.Errorf("xds: json.Unmarshal(data) for field xds_servers failed during bootstrap: %v", err) + } + if len(servers) < 1 { + return fmt.Errorf("xds: bootstrap file parsing failed during bootstrap: file doesn't contain any management server to connect to") + } + xs := servers[0] + sc.ServerURI = xs.ServerURI + for _, cc := range xs.ChannelCreds { + // We stop at the first credential type that we support. + sc.CredsType = cc.Type + if cc.Type == credsGoogleDefault { + sc.Creds = grpc.WithCredentialsBundle(google.NewDefaultCredentials()) + break + } else if cc.Type == credsInsecure { + sc.Creds = grpc.WithTransportCredentials(insecure.NewCredentials()) + break + } + } + for _, f := range xs.ServerFeatures { + if f == serverFeaturesV3 { + sc.TransportAPI = version.TransportV3 + } + } + return nil +} + +// Authority contains configuration for an Authority for an xDS control plane +// server. See the Authorities field in the Config struct for how it's used. +type Authority struct { + // ClientListenerResourceNameTemplate is template for the name of the + // Listener resource to subscribe to for a gRPC client channel. Used only + // when the channel is created using an "xds:" URI with this authority name. + // + // The token "%s", if present in this string, will be replaced + // with %-encoded service authority (i.e., the path part of the target + // URI used to create the gRPC channel). + // + // Must start with "xdstp:///". If it does not, + // that is considered a bootstrap file parsing error. + // + // If not present in the bootstrap file, defaults to + // "xdstp:///envoy.config.listener.v3.Listener/%s". + ClientListenerResourceNameTemplate string + // XDSServer contains the management server and config to connect to for + // this authority. + XDSServer *ServerConfig +} + +// UnmarshalJSON implement json unmarshaller. +func (a *Authority) UnmarshalJSON(data []byte) error { + var jsonData map[string]json.RawMessage + if err := json.Unmarshal(data, &jsonData); err != nil { + return fmt.Errorf("xds: failed to parse authority: %v", err) + } + + for k, v := range jsonData { + switch k { + case "xds_servers": + if err := json.Unmarshal(v, &a.XDSServer); err != nil { + return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) + } + case "client_listener_resource_name_template": + if err := json.Unmarshal(v, &a.ClientListenerResourceNameTemplate); err != nil { + return fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) + } + } + } + return nil +} + +// Config provides the xDS client with several key bits of information that it +// requires in its interaction with the management server. The Config is +// initialized from the bootstrap file. +type Config struct { + // XDSServer is the management server to connect to. + // + // The bootstrap file contains a list of servers (with name+creds), but we + // pick the first one. + XDSServer *ServerConfig // CertProviderConfigs contains a mapping from certificate provider plugin // instance names to parsed buildable configs. CertProviderConfigs map[string]*certprovider.BuildableConfig // ServerListenerResourceNameTemplate is a template for the name of the - // Listener resource to subscribe to for a gRPC server. If the token `%s` is - // present in the string, it will be replaced with the server's listening - // "IP:port" (e.g., "0.0.0.0:8080", "[::]:8080"). For example, a value of - // "example/resource/%s" could become "example/resource/0.0.0.0:8080". + // Listener resource to subscribe to for a gRPC server. + // + // If starts with "xdstp:", will be interpreted as a new-style name, + // in which case the authority of the URI will be used to select the + // relevant configuration in the "authorities" map. + // + // The token "%s", if present in this string, will be replaced with the IP + // and port on which the server is listening. (e.g., "0.0.0.0:8080", + // "[::]:8080"). For example, a value of "example/resource/%s" could become + // "example/resource/0.0.0.0:8080". If the template starts with "xdstp:", + // the replaced string will be %-encoded. + // + // There is no default; if unset, xDS-based server creation fails. ServerListenerResourceNameTemplate string + // A template for the name of the Listener resource to subscribe to + // for a gRPC client channel. Used only when the channel is created + // with an "xds:" URI with no authority. + // + // If starts with "xdstp:", will be interpreted as a new-style name, + // in which case the authority of the URI will be used to select the + // relevant configuration in the "authorities" map. + // + // The token "%s", if present in this string, will be replaced with + // the service authority (i.e., the path part of the target URI + // used to create the gRPC channel). If the template starts with + // "xdstp:", the replaced string will be %-encoded. + // + // Defaults to "%s". + ClientDefaultListenerResourceNameTemplate string + + // Authorities is a map of authority name to corresponding configuration. + // + // This is used in the following cases: + // - A gRPC client channel is created using an "xds:" URI that includes + // an authority. + // - A gRPC client channel is created using an "xds:" URI with no + // authority, but the "client_default_listener_resource_name_template" + // field above turns it into an "xdstp:" URI. + // - A gRPC server is created and the + // "server_listener_resource_name_template" field is an "xdstp:" URI. + // + // In any of those cases, it is an error if the specified authority is + // not present in this map. + Authorities map[string]*Authority } type channelCreds struct { @@ -100,8 +252,8 @@ type xdsServer struct { } func bootstrapConfigFromEnvVariable() ([]byte, error) { - fName := env.BootstrapFileName - fContent := env.BootstrapFileContent + fName := envconfig.XDSBootstrapFileName + fContent := envconfig.XDSBootstrapFileContent // Bootstrap file name has higher priority than bootstrap content. if fName != "" { @@ -119,40 +271,13 @@ func bootstrapConfigFromEnvVariable() ([]byte, error) { return []byte(fContent), nil } - return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined", env.BootstrapFileNameEnv, env.BootstrapFileContentEnv) + return nil, fmt.Errorf("none of the bootstrap environment variables (%q or %q) defined", + envconfig.XDSBootstrapFileNameEnv, envconfig.XDSBootstrapFileContentEnv) } // NewConfig returns a new instance of Config initialized by reading the // bootstrap file found at ${GRPC_XDS_BOOTSTRAP}. // -// The format of the bootstrap file will be as follows: -// { -// "xds_servers": [ -// { -// "server_uri": , -// "channel_creds": [ -// { -// "type": , -// "config": -// } -// ], -// "server_features": [ ... ], -// } -// ], -// "node": , -// "certificate_providers" : { -// "default": { -// "plugin_name": "default-plugin-name", -// "config": { default plugin config in JSON } -// }, -// "foo": { -// "plugin_name": "foo", -// "config": { foo plugin config in JSON } -// } -// }, -// "server_listener_resource_name_template": "grpc/server?xds.resource.listening_address=%s" -// } -// // Currently, we support exactly one type of credential, which is // "google_default", where we use the host's default certs for transport // credentials and a Google oauth token for call credentials. @@ -162,6 +287,8 @@ func bootstrapConfigFromEnvVariable() ([]byte, error) { // fields left unspecified, in which case the caller should use some sane // defaults. func NewConfig() (*Config, error) { + // Examples of the bootstrap json can be found in the generator tests + // https://github.com/GoogleCloudPlatform/traffic-director-grpc-bootstrap/blob/master/main_test.go. data, err := bootstrapConfigFromEnvVariable() if err != nil { return nil, fmt.Errorf("xds: Failed to read bootstrap config: %v", err) @@ -181,7 +308,7 @@ func NewConfigFromContents(data []byte) (*Config, error) { return nil, fmt.Errorf("xds: Failed to parse bootstrap config: %v", err) } - serverSupportsV3 := false + var node *v3corepb.Node m := jsonpb.Unmarshaler{AllowUnknownFields: true} for k, v := range jsonData { switch k { @@ -192,37 +319,14 @@ func NewConfigFromContents(data []byte) (*Config, error) { // "build_version" field. In any case, the unmarshal will succeed // because we have set the `AllowUnknownFields` option on the // unmarshaler. - n := &v3corepb.Node{} - if err := m.Unmarshal(bytes.NewReader(v), n); err != nil { + node = &v3corepb.Node{} + if err := m.Unmarshal(bytes.NewReader(v), node); err != nil { return nil, fmt.Errorf("xds: jsonpb.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) } - config.NodeProto = n case "xds_servers": - var servers []*xdsServer - if err := json.Unmarshal(v, &servers); err != nil { + if err := json.Unmarshal(v, &config.XDSServer); err != nil { return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) } - if len(servers) < 1 { - return nil, fmt.Errorf("xds: bootstrap file parsing failed during bootstrap: file doesn't contain any management server to connect to") - } - xs := servers[0] - config.BalancerName = xs.ServerURI - for _, cc := range xs.ChannelCreds { - // We stop at the first credential type that we support. - if cc.Type == credsGoogleDefault { - config.Creds = grpc.WithCredentialsBundle(google.NewDefaultCredentials()) - break - } else if cc.Type == credsInsecure { - config.Creds = grpc.WithTransportCredentials(insecure.NewCredentials()) - break - } - } - for _, f := range xs.ServerFeatures { - switch f { - case serverFeaturesV3: - serverSupportsV3 = true - } - } case "certificate_providers": var providerInstances map[string]json.RawMessage if err := json.Unmarshal(v, &providerInstances); err != nil { @@ -256,27 +360,58 @@ func NewConfigFromContents(data []byte) (*Config, error) { if err := json.Unmarshal(v, &config.ServerListenerResourceNameTemplate); err != nil { return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) } + case "client_default_listener_resource_name_template": + if !envconfig.XDSFederation { + logger.Warningf("xds: bootstrap field %v is not support when Federation is disabled", k) + continue + } + if err := json.Unmarshal(v, &config.ClientDefaultListenerResourceNameTemplate); err != nil { + return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) + } + case "authorities": + if !envconfig.XDSFederation { + logger.Warningf("xds: bootstrap field %v is not support when Federation is disabled", k) + continue + } + if err := json.Unmarshal(v, &config.Authorities); err != nil { + return nil, fmt.Errorf("xds: json.Unmarshal(%v) for field %q failed during bootstrap: %v", string(v), k, err) + } + default: + logger.Warningf("Bootstrap content has unknown field: %s", k) } // Do not fail the xDS bootstrap when an unknown field is seen. This can // happen when an older version client reads a newer version bootstrap // file with new fields. } - if config.BalancerName == "" { + if config.ClientDefaultListenerResourceNameTemplate == "" { + // Default value of the default client listener name template is "%s". + config.ClientDefaultListenerResourceNameTemplate = "%s" + } + if config.XDSServer == nil { + return nil, fmt.Errorf("xds: Required field %q not found in bootstrap %s", "xds_servers", jsonData["xds_servers"]) + } + if config.XDSServer.ServerURI == "" { return nil, fmt.Errorf("xds: Required field %q not found in bootstrap %s", "xds_servers.server_uri", jsonData["xds_servers"]) } - if config.Creds == nil { + if config.XDSServer.Creds == nil { return nil, fmt.Errorf("xds: Required field %q doesn't contain valid value in bootstrap %s", "xds_servers.channel_creds", jsonData["xds_servers"]) } - - // We end up using v3 transport protocol version only if the server supports - // v3, indicated by the presence of "xds_v3" in server_features. The default - // value of the enum type "version.TransportAPI" is v2. - if serverSupportsV3 { - config.TransportAPI = version.TransportV3 + // Post-process the authorities' client listener resource template field: + // - if set, it must start with "xdstp:///" + // - if not set, it defaults to "xdstp:///envoy.config.listener.v3.Listener/%s" + for name, authority := range config.Authorities { + prefix := fmt.Sprintf("xdstp://%s", name) + if authority.ClientListenerResourceNameTemplate == "" { + authority.ClientListenerResourceNameTemplate = prefix + "/envoy.config.listener.v3.Listener/%s" + continue + } + if !strings.HasPrefix(authority.ClientListenerResourceNameTemplate, prefix) { + return nil, fmt.Errorf("xds: field ClientListenerResourceNameTemplate %q of authority %q doesn't start with prefix %q", authority.ClientListenerResourceNameTemplate, name, prefix) + } } - if err := config.updateNodeProto(); err != nil { + if err := config.updateNodeProto(node); err != nil { return nil, err } logger.Infof("Bootstrap config for creating xds-client: %v", pretty.ToJSON(config)) @@ -285,47 +420,57 @@ func NewConfigFromContents(data []byte) (*Config, error) { // updateNodeProto updates the node proto read from the bootstrap file. // -// Node proto in Config contains a v3.Node protobuf message corresponding to the -// JSON contents found in the bootstrap file. This method performs some post +// The input node is a v3.Node protobuf message corresponding to the JSON +// contents found in the bootstrap file. This method performs some post // processing on it: -// 1. If we don't find a nodeProto in the bootstrap file, we create an empty one -// here. That way, callers of this function can always expect that the NodeProto -// field is non-nil. -// 2. If the transport protocol version to be used is not v3, we convert the -// current v3.Node proto in a v2.Node proto. -// 3. Some additional fields which are not expected to be set in the bootstrap +// 1. If the node is nil, we create an empty one here. That way, callers of this +// function can always expect that the NodeProto field is non-nil. +// 2. Some additional fields which are not expected to be set in the bootstrap // file are populated here. -func (c *Config) updateNodeProto() error { - if c.TransportAPI == version.TransportV3 { - v3, _ := c.NodeProto.(*v3corepb.Node) - if v3 == nil { - v3 = &v3corepb.Node{} - } - v3.UserAgentName = gRPCUserAgentName - v3.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} - v3.ClientFeatures = append(v3.ClientFeatures, clientFeatureNoOverprovisioning) - c.NodeProto = v3 - return nil +// 3. For each server config (both top level and in each authority), we set its +// node field to the v3.Node, or a v2.Node with the same content, depending on +// the server's transprot API version. +func (c *Config) updateNodeProto(node *v3corepb.Node) error { + v3 := node + if v3 == nil { + v3 = &v3corepb.Node{} } + v3.UserAgentName = gRPCUserAgentName + v3.UserAgentVersionType = &v3corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} + v3.ClientFeatures = append(v3.ClientFeatures, clientFeatureNoOverprovisioning) v2 := &v2corepb.Node{} - if c.NodeProto != nil { - v3, err := proto.Marshal(c.NodeProto) - if err != nil { - return fmt.Errorf("xds: proto.Marshal(%v): %v", c.NodeProto, err) - } - if err := proto.Unmarshal(v3, v2); err != nil { - return fmt.Errorf("xds: proto.Unmarshal(%v): %v", v3, err) - } + v3bytes, err := proto.Marshal(v3) + if err != nil { + return fmt.Errorf("xds: proto.Marshal(%v): %v", v3, err) + } + if err := proto.Unmarshal(v3bytes, v2); err != nil { + return fmt.Errorf("xds: proto.Unmarshal(%v): %v", v3bytes, err) } - c.NodeProto = v2 - // BuildVersion is deprecated, and is replaced by user_agent_name and // user_agent_version. But the management servers are still using the old // field, so we will keep both set. v2.BuildVersion = gRPCVersion - v2.UserAgentName = gRPCUserAgentName v2.UserAgentVersionType = &v2corepb.Node_UserAgentVersion{UserAgentVersion: grpc.Version} - v2.ClientFeatures = append(v2.ClientFeatures, clientFeatureNoOverprovisioning) + + switch c.XDSServer.TransportAPI { + case version.TransportV2: + c.XDSServer.NodeProto = v2 + case version.TransportV3: + c.XDSServer.NodeProto = v3 + } + + for _, a := range c.Authorities { + if a.XDSServer == nil { + continue + } + switch a.XDSServer.TransportAPI { + case version.TransportV2: + a.XDSServer.NodeProto = v2 + case version.TransportV3: + a.XDSServer.NodeProto = v3 + } + } + return nil } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/template.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/template.go new file mode 100644 index 000000000..9b51fcc83 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/bootstrap/template.go @@ -0,0 +1,47 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package bootstrap + +import ( + "net/url" + "strings" +) + +// PopulateResourceTemplate populates the given template using the target +// string. "%s", if exists in the template, will be replaced with target. +// +// If the template starts with "xdstp:", the replaced string will be %-encoded. +// But note that "/" is not percent encoded. +func PopulateResourceTemplate(template, target string) string { + if !strings.Contains(template, "%s") { + return template + } + if strings.HasPrefix(template, "xdstp:") { + target = percentEncode(target) + } + return strings.Replace(template, "%s", target, -1) +} + +// percentEncode percent encode t, except for "/". See the tests for examples. +func percentEncode(t string) string { + segs := strings.Split(t, "/") + for i := range segs { + segs[i] = url.PathEscape(segs[i]) + } + return strings.Join(segs, "/") +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/callback.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/callback.go index 0c2665e84..1ad1659e1 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/callback.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/callback.go @@ -19,128 +19,16 @@ package xdsclient import ( - "google.golang.org/grpc/internal/pretty" - "google.golang.org/protobuf/proto" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) -type watcherInfoWithUpdate struct { - wi *watchInfo - update interface{} - err error -} - -// scheduleCallback should only be called by methods of watchInfo, which checks -// for watcher states and maintain consistency. -func (c *clientImpl) scheduleCallback(wi *watchInfo, update interface{}, err error) { - c.updateCh.Put(&watcherInfoWithUpdate{ - wi: wi, - update: update, - err: err, - }) -} - -func (c *clientImpl) callCallback(wiu *watcherInfoWithUpdate) { - c.mu.Lock() - // Use a closure to capture the callback and type assertion, to save one - // more switch case. - // - // The callback must be called without c.mu. Otherwise if the callback calls - // another watch() inline, it will cause a deadlock. This leaves a small - // window that a watcher's callback could be called after the watcher is - // canceled, and the user needs to take care of it. - var ccb func() - switch wiu.wi.rType { - case ListenerResource: - if s, ok := c.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] { - ccb = func() { wiu.wi.ldsCallback(wiu.update.(ListenerUpdate), wiu.err) } - } - case RouteConfigResource: - if s, ok := c.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { - ccb = func() { wiu.wi.rdsCallback(wiu.update.(RouteConfigUpdate), wiu.err) } - } - case ClusterResource: - if s, ok := c.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { - ccb = func() { wiu.wi.cdsCallback(wiu.update.(ClusterUpdate), wiu.err) } - } - case EndpointsResource: - if s, ok := c.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] { - ccb = func() { wiu.wi.edsCallback(wiu.update.(EndpointsUpdate), wiu.err) } - } - } - c.mu.Unlock() - - if ccb != nil { - ccb() - } -} - // NewListeners is called by the underlying xdsAPIClient when it receives an // xDS response. // // A response can contain multiple resources. They will be parsed and put in a // map from resource name to the resource content. -func (c *clientImpl) NewListeners(updates map[string]ListenerUpdateErrTuple, metadata UpdateMetadata) { - c.mu.Lock() - defer c.mu.Unlock() - - c.ldsVersion = metadata.Version - if metadata.ErrState != nil { - c.ldsVersion = metadata.ErrState.Version - } - for name, uErr := range updates { - if s, ok := c.ldsWatchers[name]; ok { - if uErr.Err != nil { - // On error, keep previous version for each resource. But update - // status and error. - mdCopy := c.ldsMD[name] - mdCopy.ErrState = metadata.ErrState - mdCopy.Status = metadata.Status - c.ldsMD[name] = mdCopy - for wi := range s { - wi.newError(uErr.Err) - } - continue - } - // If we get here, it means that the update is a valid one. Notify - // watchers only if this is a first time update or it is different - // from the one currently cached. - if cur, ok := c.ldsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { - for wi := range s { - wi.newUpdate(uErr.Update) - } - } - // Sync cache. - c.logger.Debugf("LDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) - c.ldsCache[name] = uErr.Update - // Set status to ACK, and clear error state. The metadata might be a - // NACK metadata because some other resources in the same response - // are invalid. - mdCopy := metadata - mdCopy.Status = ServiceStatusACKed - mdCopy.ErrState = nil - if metadata.ErrState != nil { - mdCopy.Version = metadata.ErrState.Version - } - c.ldsMD[name] = mdCopy - } - } - // Resources not in the new update were removed by the server, so delete - // them. - for name := range c.ldsCache { - if _, ok := updates[name]; !ok { - // If resource exists in cache, but not in the new update, delete - // the resource from cache, and also send an resource not found - // error to indicate resource removed. - delete(c.ldsCache, name) - c.ldsMD[name] = UpdateMetadata{Status: ServiceStatusNotExist} - for wi := range c.ldsWatchers[name] { - wi.resourceNotFound() - } - } - } - // When LDS resource is removed, we don't delete corresponding RDS cached - // data. The RDS watch will be canceled, and cache entry is removed when the - // last watch is canceled. +func (c *clientImpl) NewListeners(updates map[string]xdsresource.ListenerUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + c.pubsub.NewListeners(updates, metadata) } // NewRouteConfigs is called by the underlying xdsAPIClient when it receives an @@ -148,52 +36,8 @@ func (c *clientImpl) NewListeners(updates map[string]ListenerUpdateErrTuple, met // // A response can contain multiple resources. They will be parsed and put in a // map from resource name to the resource content. -func (c *clientImpl) NewRouteConfigs(updates map[string]RouteConfigUpdateErrTuple, metadata UpdateMetadata) { - c.mu.Lock() - defer c.mu.Unlock() - - // If no error received, the status is ACK. - c.rdsVersion = metadata.Version - if metadata.ErrState != nil { - c.rdsVersion = metadata.ErrState.Version - } - for name, uErr := range updates { - if s, ok := c.rdsWatchers[name]; ok { - if uErr.Err != nil { - // On error, keep previous version for each resource. But update - // status and error. - mdCopy := c.rdsMD[name] - mdCopy.ErrState = metadata.ErrState - mdCopy.Status = metadata.Status - c.rdsMD[name] = mdCopy - for wi := range s { - wi.newError(uErr.Err) - } - continue - } - // If we get here, it means that the update is a valid one. Notify - // watchers only if this is a first time update or it is different - // from the one currently cached. - if cur, ok := c.rdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { - for wi := range s { - wi.newUpdate(uErr.Update) - } - } - // Sync cache. - c.logger.Debugf("RDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) - c.rdsCache[name] = uErr.Update - // Set status to ACK, and clear error state. The metadata might be a - // NACK metadata because some other resources in the same response - // are invalid. - mdCopy := metadata - mdCopy.Status = ServiceStatusACKed - mdCopy.ErrState = nil - if metadata.ErrState != nil { - mdCopy.Version = metadata.ErrState.Version - } - c.rdsMD[name] = mdCopy - } - } +func (c *clientImpl) NewRouteConfigs(updates map[string]xdsresource.RouteConfigUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + c.pubsub.NewRouteConfigs(updates, metadata) } // NewClusters is called by the underlying xdsAPIClient when it receives an xDS @@ -201,70 +45,8 @@ func (c *clientImpl) NewRouteConfigs(updates map[string]RouteConfigUpdateErrTupl // // A response can contain multiple resources. They will be parsed and put in a // map from resource name to the resource content. -func (c *clientImpl) NewClusters(updates map[string]ClusterUpdateErrTuple, metadata UpdateMetadata) { - c.mu.Lock() - defer c.mu.Unlock() - - c.cdsVersion = metadata.Version - if metadata.ErrState != nil { - c.cdsVersion = metadata.ErrState.Version - } - for name, uErr := range updates { - if s, ok := c.cdsWatchers[name]; ok { - if uErr.Err != nil { - // On error, keep previous version for each resource. But update - // status and error. - mdCopy := c.cdsMD[name] - mdCopy.ErrState = metadata.ErrState - mdCopy.Status = metadata.Status - c.cdsMD[name] = mdCopy - for wi := range s { - // Send the watcher the individual error, instead of the - // overall combined error from the metadata.ErrState. - wi.newError(uErr.Err) - } - continue - } - // If we get here, it means that the update is a valid one. Notify - // watchers only if this is a first time update or it is different - // from the one currently cached. - if cur, ok := c.cdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { - for wi := range s { - wi.newUpdate(uErr.Update) - } - } - // Sync cache. - c.logger.Debugf("CDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) - c.cdsCache[name] = uErr.Update - // Set status to ACK, and clear error state. The metadata might be a - // NACK metadata because some other resources in the same response - // are invalid. - mdCopy := metadata - mdCopy.Status = ServiceStatusACKed - mdCopy.ErrState = nil - if metadata.ErrState != nil { - mdCopy.Version = metadata.ErrState.Version - } - c.cdsMD[name] = mdCopy - } - } - // Resources not in the new update were removed by the server, so delete - // them. - for name := range c.cdsCache { - if _, ok := updates[name]; !ok { - // If resource exists in cache, but not in the new update, delete it - // from cache, and also send an resource not found error to indicate - // resource removed. - delete(c.cdsCache, name) - c.ldsMD[name] = UpdateMetadata{Status: ServiceStatusNotExist} - for wi := range c.cdsWatchers[name] { - wi.resourceNotFound() - } - } - } - // When CDS resource is removed, we don't delete corresponding EDS cached - // data. The EDS watch will be canceled, and cache entry is removed when the - // last watch is canceled. +func (c *clientImpl) NewClusters(updates map[string]xdsresource.ClusterUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + c.pubsub.NewClusters(updates, metadata) } // NewEndpoints is called by the underlying xdsAPIClient when it receives an @@ -272,64 +54,12 @@ func (c *clientImpl) NewClusters(updates map[string]ClusterUpdateErrTuple, metad // // A response can contain multiple resources. They will be parsed and put in a // map from resource name to the resource content. -func (c *clientImpl) NewEndpoints(updates map[string]EndpointsUpdateErrTuple, metadata UpdateMetadata) { - c.mu.Lock() - defer c.mu.Unlock() - - c.edsVersion = metadata.Version - if metadata.ErrState != nil { - c.edsVersion = metadata.ErrState.Version - } - for name, uErr := range updates { - if s, ok := c.edsWatchers[name]; ok { - if uErr.Err != nil { - // On error, keep previous version for each resource. But update - // status and error. - mdCopy := c.edsMD[name] - mdCopy.ErrState = metadata.ErrState - mdCopy.Status = metadata.Status - c.edsMD[name] = mdCopy - for wi := range s { - // Send the watcher the individual error, instead of the - // overall combined error from the metadata.ErrState. - wi.newError(uErr.Err) - } - continue - } - // If we get here, it means that the update is a valid one. Notify - // watchers only if this is a first time update or it is different - // from the one currently cached. - if cur, ok := c.edsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { - for wi := range s { - wi.newUpdate(uErr.Update) - } - } - // Sync cache. - c.logger.Debugf("EDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) - c.edsCache[name] = uErr.Update - // Set status to ACK, and clear error state. The metadata might be a - // NACK metadata because some other resources in the same response - // are invalid. - mdCopy := metadata - mdCopy.Status = ServiceStatusACKed - mdCopy.ErrState = nil - if metadata.ErrState != nil { - mdCopy.Version = metadata.ErrState.Version - } - c.edsMD[name] = mdCopy - } - } +func (c *clientImpl) NewEndpoints(updates map[string]xdsresource.EndpointsUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + c.pubsub.NewEndpoints(updates, metadata) } // NewConnectionError is called by the underlying xdsAPIClient when it receives // a connection error. The error will be forwarded to all the resource watchers. func (c *clientImpl) NewConnectionError(err error) { - c.mu.Lock() - defer c.mu.Unlock() - - for _, s := range c.edsWatchers { - for wi := range s { - wi.newError(NewErrorf(ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) - } - } + c.pubsub.NewConnectionError(err) } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/client.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/client.go index 3230c66c0..13e8265b6 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/client.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/client.go @@ -21,567 +21,16 @@ package xdsclient import ( - "context" - "errors" "fmt" - "regexp" - "sync" "time" - v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" - v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - "github.com/golang/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/internal/xds/matcher" - "google.golang.org/grpc/xds/internal/httpfilter" - "google.golang.org/grpc/xds/internal/xdsclient/load" - - "google.golang.org/grpc" - "google.golang.org/grpc/internal/backoff" - "google.golang.org/grpc/internal/buffer" "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcsync" - "google.golang.org/grpc/keepalive" - "google.golang.org/grpc/xds/internal" - "google.golang.org/grpc/xds/internal/version" "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/pubsub" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) -var ( - m = make(map[version.TransportAPI]APIClientBuilder) -) - -// RegisterAPIClientBuilder registers a client builder for xDS transport protocol -// version specified by b.Version(). -// -// NOTE: this function must only be called during initialization time (i.e. in -// an init() function), and is not thread-safe. If multiple builders are -// registered for the same version, the one registered last will take effect. -func RegisterAPIClientBuilder(b APIClientBuilder) { - m[b.Version()] = b -} - -// getAPIClientBuilder returns the client builder registered for the provided -// xDS transport API version. -func getAPIClientBuilder(version version.TransportAPI) APIClientBuilder { - if b, ok := m[version]; ok { - return b - } - return nil -} - -// UpdateValidatorFunc performs validations on update structs using -// context/logic available at the xdsClient layer. Since these validation are -// performed on internal update structs, they can be shared between different -// API clients. -type UpdateValidatorFunc func(interface{}) error - -// BuildOptions contains options to be passed to client builders. -type BuildOptions struct { - // Parent is a top-level xDS client which has the intelligence to take - // appropriate action based on xDS responses received from the management - // server. - Parent UpdateHandler - // Validator performs post unmarshal validation checks. - Validator UpdateValidatorFunc - // NodeProto contains the Node proto to be used in xDS requests. The actual - // type depends on the transport protocol version used. - NodeProto proto.Message - // Backoff returns the amount of time to backoff before retrying broken - // streams. - Backoff func(int) time.Duration - // Logger provides enhanced logging capabilities. - Logger *grpclog.PrefixLogger -} - -// APIClientBuilder creates an xDS client for a specific xDS transport protocol -// version. -type APIClientBuilder interface { - // Build builds a transport protocol specific implementation of the xDS - // client based on the provided clientConn to the management server and the - // provided options. - Build(*grpc.ClientConn, BuildOptions) (APIClient, error) - // Version returns the xDS transport protocol version used by clients build - // using this builder. - Version() version.TransportAPI -} - -// APIClient represents the functionality provided by transport protocol -// version specific implementations of the xDS client. -// -// TODO: unexport this interface and all the methods after the PR to make -// xdsClient sharable by clients. AddWatch and RemoveWatch are exported for -// v2/v3 to override because they need to keep track of LDS name for RDS to use. -// After the share xdsClient change, that's no longer necessary. After that, we -// will still keep this interface for testing purposes. -type APIClient interface { - // AddWatch adds a watch for an xDS resource given its type and name. - AddWatch(ResourceType, string) - - // RemoveWatch cancels an already registered watch for an xDS resource - // given its type and name. - RemoveWatch(ResourceType, string) - - // reportLoad starts an LRS stream to periodically report load using the - // provided ClientConn, which represent a connection to the management - // server. - reportLoad(ctx context.Context, cc *grpc.ClientConn, opts loadReportingOptions) - - // Close cleans up resources allocated by the API client. - Close() -} - -// loadReportingOptions contains configuration knobs for reporting load data. -type loadReportingOptions struct { - loadStore *load.Store -} - -// UpdateHandler receives and processes (by taking appropriate actions) xDS -// resource updates from an APIClient for a specific version. -type UpdateHandler interface { - // NewListeners handles updates to xDS listener resources. - NewListeners(map[string]ListenerUpdateErrTuple, UpdateMetadata) - // NewRouteConfigs handles updates to xDS RouteConfiguration resources. - NewRouteConfigs(map[string]RouteConfigUpdateErrTuple, UpdateMetadata) - // NewClusters handles updates to xDS Cluster resources. - NewClusters(map[string]ClusterUpdateErrTuple, UpdateMetadata) - // NewEndpoints handles updates to xDS ClusterLoadAssignment (or tersely - // referred to as Endpoints) resources. - NewEndpoints(map[string]EndpointsUpdateErrTuple, UpdateMetadata) - // NewConnectionError handles connection errors from the xDS stream. The - // error will be reported to all the resource watchers. - NewConnectionError(err error) -} - -// ServiceStatus is the status of the update. -type ServiceStatus int - -const ( - // ServiceStatusUnknown is the default state, before a watch is started for - // the resource. - ServiceStatusUnknown ServiceStatus = iota - // ServiceStatusRequested is when the watch is started, but before and - // response is received. - ServiceStatusRequested - // ServiceStatusNotExist is when the resource doesn't exist in - // state-of-the-world responses (e.g. LDS and CDS), which means the resource - // is removed by the management server. - ServiceStatusNotExist // Resource is removed in the server, in LDS/CDS. - // ServiceStatusACKed is when the resource is ACKed. - ServiceStatusACKed - // ServiceStatusNACKed is when the resource is NACKed. - ServiceStatusNACKed -) - -// UpdateErrorMetadata is part of UpdateMetadata. It contains the error state -// when a response is NACKed. -type UpdateErrorMetadata struct { - // Version is the version of the NACKed response. - Version string - // Err contains why the response was NACKed. - Err error - // Timestamp is when the NACKed response was received. - Timestamp time.Time -} - -// UpdateMetadata contains the metadata for each update, including timestamp, -// raw message, and so on. -type UpdateMetadata struct { - // Status is the status of this resource, e.g. ACKed, NACKed, or - // Not_exist(removed). - Status ServiceStatus - // Version is the version of the xds response. Note that this is the version - // of the resource in use (previous ACKed). If a response is NACKed, the - // NACKed version is in ErrState. - Version string - // Timestamp is when the response is received. - Timestamp time.Time - // ErrState is set when the update is NACKed. - ErrState *UpdateErrorMetadata -} - -// ListenerUpdate contains information received in an LDS response, which is of -// interest to the registered LDS watcher. -type ListenerUpdate struct { - // RouteConfigName is the route configuration name corresponding to the - // target which is being watched through LDS. - // - // Only one of RouteConfigName and InlineRouteConfig is set. - RouteConfigName string - // InlineRouteConfig is the inline route configuration (RDS response) - // returned inside LDS. - // - // Only one of RouteConfigName and InlineRouteConfig is set. - InlineRouteConfig *RouteConfigUpdate - - // MaxStreamDuration contains the HTTP connection manager's - // common_http_protocol_options.max_stream_duration field, or zero if - // unset. - MaxStreamDuration time.Duration - // HTTPFilters is a list of HTTP filters (name, config) from the LDS - // response. - HTTPFilters []HTTPFilter - // InboundListenerCfg contains inbound listener configuration. - InboundListenerCfg *InboundListenerConfig - - // Raw is the resource from the xds response. - Raw *anypb.Any -} - -// HTTPFilter represents one HTTP filter from an LDS response's HTTP connection -// manager field. -type HTTPFilter struct { - // Name is an arbitrary name of the filter. Used for applying override - // settings in virtual host / route / weighted cluster configuration (not - // yet supported). - Name string - // Filter is the HTTP filter found in the registry for the config type. - Filter httpfilter.Filter - // Config contains the filter's configuration - Config httpfilter.FilterConfig -} - -// InboundListenerConfig contains information about the inbound listener, i.e -// the server-side listener. -type InboundListenerConfig struct { - // Address is the local address on which the inbound listener is expected to - // accept incoming connections. - Address string - // Port is the local port on which the inbound listener is expected to - // accept incoming connections. - Port string - // FilterChains is the list of filter chains associated with this listener. - FilterChains *FilterChainManager -} - -// RouteConfigUpdate contains information received in an RDS response, which is -// of interest to the registered RDS watcher. -type RouteConfigUpdate struct { - VirtualHosts []*VirtualHost - // Raw is the resource from the xds response. - Raw *anypb.Any -} - -// VirtualHost contains the routes for a list of Domains. -// -// Note that the domains in this slice can be a wildcard, not an exact string. -// The consumer of this struct needs to find the best match for its hostname. -type VirtualHost struct { - Domains []string - // Routes contains a list of routes, each containing matchers and - // corresponding action. - Routes []*Route - // HTTPFilterConfigOverride contains any HTTP filter config overrides for - // the virtual host which may be present. An individual filter's override - // may be unused if the matching Route contains an override for that - // filter. - HTTPFilterConfigOverride map[string]httpfilter.FilterConfig - RetryConfig *RetryConfig -} - -// RetryConfig contains all retry-related configuration in either a VirtualHost -// or Route. -type RetryConfig struct { - // RetryOn is a set of status codes on which to retry. Only Canceled, - // DeadlineExceeded, Internal, ResourceExhausted, and Unavailable are - // supported; any other values will be omitted. - RetryOn map[codes.Code]bool - NumRetries uint32 // maximum number of retry attempts - RetryBackoff RetryBackoff // retry backoff policy -} - -// RetryBackoff describes the backoff policy for retries. -type RetryBackoff struct { - BaseInterval time.Duration // initial backoff duration between attempts - MaxInterval time.Duration // maximum backoff duration -} - -// HashPolicyType specifies the type of HashPolicy from a received RDS Response. -type HashPolicyType int - -const ( - // HashPolicyTypeHeader specifies to hash a Header in the incoming request. - HashPolicyTypeHeader HashPolicyType = iota - // HashPolicyTypeChannelID specifies to hash a unique Identifier of the - // Channel. In grpc-go, this will be done using the ClientConn pointer. - HashPolicyTypeChannelID -) - -// HashPolicy specifies the HashPolicy if the upstream cluster uses a hashing -// load balancer. -type HashPolicy struct { - HashPolicyType HashPolicyType - Terminal bool - // Fields used for type HEADER. - HeaderName string - Regex *regexp.Regexp - RegexSubstitution string -} - -// RouteAction is the action of the route from a received RDS response. -type RouteAction int - -const ( - // RouteActionUnsupported are routing types currently unsupported by grpc. - // According to A36, "A Route with an inappropriate action causes RPCs - // matching that route to fail." - RouteActionUnsupported RouteAction = iota - // RouteActionRoute is the expected route type on the client side. Route - // represents routing a request to some upstream cluster. On the client - // side, if an RPC matches to a route that is not RouteActionRoute, the RPC - // will fail according to A36. - RouteActionRoute - // RouteActionNonForwardingAction is the expected route type on the server - // side. NonForwardingAction represents when a route will generate a - // response directly, without forwarding to an upstream host. - RouteActionNonForwardingAction -) - -// Route is both a specification of how to match a request as well as an -// indication of the action to take upon match. -type Route struct { - Path *string - Prefix *string - Regex *regexp.Regexp - // Indicates if prefix/path matching should be case insensitive. The default - // is false (case sensitive). - CaseInsensitive bool - Headers []*HeaderMatcher - Fraction *uint32 - - HashPolicies []*HashPolicy - - // If the matchers above indicate a match, the below configuration is used. - WeightedClusters map[string]WeightedCluster - // If MaxStreamDuration is nil, it indicates neither of the route action's - // max_stream_duration fields (grpc_timeout_header_max nor - // max_stream_duration) were set. In this case, the ListenerUpdate's - // MaxStreamDuration field should be used. If MaxStreamDuration is set to - // an explicit zero duration, the application's deadline should be used. - MaxStreamDuration *time.Duration - // HTTPFilterConfigOverride contains any HTTP filter config overrides for - // the route which may be present. An individual filter's override may be - // unused if the matching WeightedCluster contains an override for that - // filter. - HTTPFilterConfigOverride map[string]httpfilter.FilterConfig - RetryConfig *RetryConfig - - RouteAction RouteAction -} - -// WeightedCluster contains settings for an xds RouteAction.WeightedCluster. -type WeightedCluster struct { - // Weight is the relative weight of the cluster. It will never be zero. - Weight uint32 - // HTTPFilterConfigOverride contains any HTTP filter config overrides for - // the weighted cluster which may be present. - HTTPFilterConfigOverride map[string]httpfilter.FilterConfig -} - -// HeaderMatcher represents header matchers. -type HeaderMatcher struct { - Name string - InvertMatch *bool - ExactMatch *string - RegexMatch *regexp.Regexp - PrefixMatch *string - SuffixMatch *string - RangeMatch *Int64Range - PresentMatch *bool -} - -// Int64Range is a range for header range match. -type Int64Range struct { - Start int64 - End int64 -} - -// SecurityConfig contains the security configuration received as part of the -// Cluster resource on the client-side, and as part of the Listener resource on -// the server-side. -type SecurityConfig struct { - // RootInstanceName identifies the certProvider plugin to be used to fetch - // root certificates. This instance name will be resolved to the plugin name - // and its associated configuration from the certificate_providers field of - // the bootstrap file. - RootInstanceName string - // RootCertName is the certificate name to be passed to the plugin (looked - // up from the bootstrap file) while fetching root certificates. - RootCertName string - // IdentityInstanceName identifies the certProvider plugin to be used to - // fetch identity certificates. This instance name will be resolved to the - // plugin name and its associated configuration from the - // certificate_providers field of the bootstrap file. - IdentityInstanceName string - // IdentityCertName is the certificate name to be passed to the plugin - // (looked up from the bootstrap file) while fetching identity certificates. - IdentityCertName string - // SubjectAltNameMatchers is an optional list of match criteria for SANs - // specified on the peer certificate. Used only on the client-side. - // - // Some intricacies: - // - If this field is empty, then any peer certificate is accepted. - // - If the peer certificate contains a wildcard DNS SAN, and an `exact` - // matcher is configured, a wildcard DNS match is performed instead of a - // regular string comparison. - SubjectAltNameMatchers []matcher.StringMatcher - // RequireClientCert indicates if the server handshake process expects the - // client to present a certificate. Set to true when performing mTLS. Used - // only on the server-side. - RequireClientCert bool -} - -// Equal returns true if sc is equal to other. -func (sc *SecurityConfig) Equal(other *SecurityConfig) bool { - switch { - case sc == nil && other == nil: - return true - case (sc != nil) != (other != nil): - return false - } - switch { - case sc.RootInstanceName != other.RootInstanceName: - return false - case sc.RootCertName != other.RootCertName: - return false - case sc.IdentityInstanceName != other.IdentityInstanceName: - return false - case sc.IdentityCertName != other.IdentityCertName: - return false - case sc.RequireClientCert != other.RequireClientCert: - return false - default: - if len(sc.SubjectAltNameMatchers) != len(other.SubjectAltNameMatchers) { - return false - } - for i := 0; i < len(sc.SubjectAltNameMatchers); i++ { - if !sc.SubjectAltNameMatchers[i].Equal(other.SubjectAltNameMatchers[i]) { - return false - } - } - } - return true -} - -// ClusterType is the type of cluster from a received CDS response. -type ClusterType int - -const ( - // ClusterTypeEDS represents the EDS cluster type, which will delegate endpoint - // discovery to the management server. - ClusterTypeEDS ClusterType = iota - // ClusterTypeLogicalDNS represents the Logical DNS cluster type, which essentially - // maps to the gRPC behavior of using the DNS resolver with pick_first LB policy. - ClusterTypeLogicalDNS - // ClusterTypeAggregate represents the Aggregate Cluster type, which provides a - // prioritized list of clusters to use. It is used for failover between clusters - // with a different configuration. - ClusterTypeAggregate -) - -// ClusterLBPolicyRingHash represents ring_hash lb policy, and also contains its -// config. -type ClusterLBPolicyRingHash struct { - MinimumRingSize uint64 - MaximumRingSize uint64 -} - -// ClusterUpdate contains information from a received CDS response, which is of -// interest to the registered CDS watcher. -type ClusterUpdate struct { - ClusterType ClusterType - // ClusterName is the clusterName being watched for through CDS. - ClusterName string - // EDSServiceName is an optional name for EDS. If it's not set, the balancer - // should watch ClusterName for the EDS resources. - EDSServiceName string - // EnableLRS indicates whether or not load should be reported through LRS. - EnableLRS bool - // SecurityCfg contains security configuration sent by the control plane. - SecurityCfg *SecurityConfig - // MaxRequests for circuit breaking, if any (otherwise nil). - MaxRequests *uint32 - // DNSHostName is used only for cluster type DNS. It's the DNS name to - // resolve in "host:port" form - DNSHostName string - // PrioritizedClusterNames is used only for cluster type aggregate. It represents - // a prioritized list of cluster names. - PrioritizedClusterNames []string - - // LBPolicy is the lb policy for this cluster. - // - // This only support round_robin and ring_hash. - // - if it's nil, the lb policy is round_robin - // - if it's not nil, the lb policy is ring_hash, the this field has the config. - // - // When we add more support policies, this can be made an interface, and - // will be set to different types based on the policy type. - LBPolicy *ClusterLBPolicyRingHash - - // Raw is the resource from the xds response. - Raw *anypb.Any -} - -// OverloadDropConfig contains the config to drop overloads. -type OverloadDropConfig struct { - Category string - Numerator uint32 - Denominator uint32 -} - -// EndpointHealthStatus represents the health status of an endpoint. -type EndpointHealthStatus int32 - -const ( - // EndpointHealthStatusUnknown represents HealthStatus UNKNOWN. - EndpointHealthStatusUnknown EndpointHealthStatus = iota - // EndpointHealthStatusHealthy represents HealthStatus HEALTHY. - EndpointHealthStatusHealthy - // EndpointHealthStatusUnhealthy represents HealthStatus UNHEALTHY. - EndpointHealthStatusUnhealthy - // EndpointHealthStatusDraining represents HealthStatus DRAINING. - EndpointHealthStatusDraining - // EndpointHealthStatusTimeout represents HealthStatus TIMEOUT. - EndpointHealthStatusTimeout - // EndpointHealthStatusDegraded represents HealthStatus DEGRADED. - EndpointHealthStatusDegraded -) - -// Endpoint contains information of an endpoint. -type Endpoint struct { - Address string - HealthStatus EndpointHealthStatus - Weight uint32 -} - -// Locality contains information of a locality. -type Locality struct { - Endpoints []Endpoint - ID internal.LocalityID - Priority uint32 - Weight uint32 -} - -// EndpointsUpdate contains an EDS update. -type EndpointsUpdate struct { - Drops []OverloadDropConfig - Localities []Locality - - // Raw is the resource from the xds response. - Raw *anypb.Any -} - -// Function to be overridden in tests. -var newAPIClient = func(apiVersion version.TransportAPI, cc *grpc.ClientConn, opts BuildOptions) (APIClient, error) { - cb := getAPIClientBuilder(apiVersion) - if cb == nil { - return nil, fmt.Errorf("no client builder for xDS API version: %v", apiVersion) - } - return cb.Build(cc, opts) -} - // clientImpl is the real implementation of the xds client. The exported Client // is a wrapper of this struct with a ref count. // @@ -590,115 +39,40 @@ var newAPIClient = func(apiVersion version.TransportAPI, cc *grpc.ClientConn, op // style of ccBalancerWrapper so that the Client type does not implement these // exported methods. type clientImpl struct { - done *grpcsync.Event - config *bootstrap.Config - cc *grpc.ClientConn // Connection to the management server. - apiClient APIClient - watchExpiryTimeout time.Duration + done *grpcsync.Event + config *bootstrap.Config - logger *grpclog.PrefixLogger - - updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate - // All the following maps are to keep the updates/metadata in a cache. - // TODO: move them to a separate struct/package, to cleanup the xds_client. - // And CSDS handler can be implemented directly by the cache. - mu sync.Mutex - ldsWatchers map[string]map[*watchInfo]bool - ldsVersion string // Only used in CSDS. - ldsCache map[string]ListenerUpdate - ldsMD map[string]UpdateMetadata - rdsWatchers map[string]map[*watchInfo]bool - rdsVersion string // Only used in CSDS. - rdsCache map[string]RouteConfigUpdate - rdsMD map[string]UpdateMetadata - cdsWatchers map[string]map[*watchInfo]bool - cdsVersion string // Only used in CSDS. - cdsCache map[string]ClusterUpdate - cdsMD map[string]UpdateMetadata - edsWatchers map[string]map[*watchInfo]bool - edsVersion string // Only used in CSDS. - edsCache map[string]EndpointsUpdate - edsMD map[string]UpdateMetadata + controller controllerInterface - // Changes to map lrsClients and the lrsClient inside the map need to be - // protected by lrsMu. - lrsMu sync.Mutex - lrsClients map[string]*lrsClient + logger *grpclog.PrefixLogger + pubsub *pubsub.Pubsub } // newWithConfig returns a new xdsClient with the given config. -func newWithConfig(config *bootstrap.Config, watchExpiryTimeout time.Duration) (*clientImpl, error) { - switch { - case config.BalancerName == "": - return nil, errors.New("xds: no xds_server name provided in options") - case config.Creds == nil: - return nil, errors.New("xds: no credentials provided in options") - case config.NodeProto == nil: - return nil, errors.New("xds: no node_proto provided in options") +func newWithConfig(config *bootstrap.Config, watchExpiryTimeout time.Duration) (_ *clientImpl, retErr error) { + c := &clientImpl{ + done: grpcsync.NewEvent(), + config: config, } - switch config.TransportAPI { - case version.TransportV2: - if _, ok := config.NodeProto.(*v2corepb.Node); !ok { - return nil, fmt.Errorf("xds: Node proto type (%T) does not match API version: %v", config.NodeProto, config.TransportAPI) + defer func() { + if retErr != nil { + c.Close() } - case version.TransportV3: - if _, ok := config.NodeProto.(*v3corepb.Node); !ok { - return nil, fmt.Errorf("xds: Node proto type (%T) does not match API version: %v", config.NodeProto, config.TransportAPI) - } - } + }() - dopts := []grpc.DialOption{ - config.Creds, - grpc.WithKeepaliveParams(keepalive.ClientParameters{ - Time: 5 * time.Minute, - Timeout: 20 * time.Second, - }), - } + c.logger = prefixLogger(c) + c.logger.Infof("Created ClientConn to xDS management server: %s", config.XDSServer) - c := &clientImpl{ - done: grpcsync.NewEvent(), - config: config, - watchExpiryTimeout: watchExpiryTimeout, + c.pubsub = pubsub.New(watchExpiryTimeout, c.logger) - updateCh: buffer.NewUnbounded(), - ldsWatchers: make(map[string]map[*watchInfo]bool), - ldsCache: make(map[string]ListenerUpdate), - ldsMD: make(map[string]UpdateMetadata), - rdsWatchers: make(map[string]map[*watchInfo]bool), - rdsCache: make(map[string]RouteConfigUpdate), - rdsMD: make(map[string]UpdateMetadata), - cdsWatchers: make(map[string]map[*watchInfo]bool), - cdsCache: make(map[string]ClusterUpdate), - cdsMD: make(map[string]UpdateMetadata), - edsWatchers: make(map[string]map[*watchInfo]bool), - edsCache: make(map[string]EndpointsUpdate), - edsMD: make(map[string]UpdateMetadata), - lrsClients: make(map[string]*lrsClient), - } - - cc, err := grpc.Dial(config.BalancerName, dopts...) + controller, err := newController(config.XDSServer, c.pubsub, c.updateValidator, c.logger) if err != nil { - // An error from a non-blocking dial indicates something serious. - return nil, fmt.Errorf("xds: failed to dial balancer {%s}: %v", config.BalancerName, err) + return nil, fmt.Errorf("xds: failed to connect to the control plane: %v", err) } - c.cc = cc - c.logger = prefixLogger((c)) - c.logger.Infof("Created ClientConn to xDS management server: %s", config.BalancerName) + c.controller = controller - apiClient, err := newAPIClient(config.TransportAPI, cc, BuildOptions{ - Parent: c, - Validator: c.updateValidator, - NodeProto: config.NodeProto, - Backoff: backoff.DefaultExponential.Backoff, - Logger: c.logger, - }) - if err != nil { - return nil, err - } - c.apiClient = apiClient c.logger.Infof("Created") - go c.run() return c, nil } @@ -708,27 +82,6 @@ func (c *clientRefCounted) BootstrapConfig() *bootstrap.Config { return c.config } -// run is a goroutine for all the callbacks. -// -// Callback can be called in watch(), if an item is found in cache. Without this -// goroutine, the callback will be called inline, which might cause a deadlock -// in user's code. Callbacks also cannot be simple `go callback()` because the -// order matters. -func (c *clientImpl) run() { - for { - select { - case t := <-c.updateCh.Get(): - c.updateCh.Load() - if c.done.HasFired() { - return - } - c.callCallback(t.(*watcherInfoWithUpdate)) - case <-c.done.Done(): - return - } - } -} - // Close closes the gRPC connection to the management server. func (c *clientImpl) Close() { if c.done.HasFired() { @@ -737,19 +90,27 @@ func (c *clientImpl) Close() { c.done.Fire() // TODO: Should we invoke the registered callbacks here with an error that // the client is closed? - c.apiClient.Close() - c.cc.Close() + + // Note that Close needs to check for nils even if some of them are always + // set in the constructor. This is because the constructor defers Close() in + // error cases, and the fields might not be set when the error happens. + if c.controller != nil { + c.controller.Close() + } + if c.pubsub != nil { + c.pubsub.Close() + } c.logger.Infof("Shutdown") } -func (c *clientImpl) filterChainUpdateValidator(fc *FilterChain) error { +func (c *clientImpl) filterChainUpdateValidator(fc *xdsresource.FilterChain) error { if fc == nil { return nil } return c.securityConfigUpdateValidator(fc.SecurityCfg) } -func (c *clientImpl) securityConfigUpdateValidator(sc *SecurityConfig) error { +func (c *clientImpl) securityConfigUpdateValidator(sc *xdsresource.SecurityConfig) error { if sc == nil { return nil } @@ -768,28 +129,12 @@ func (c *clientImpl) securityConfigUpdateValidator(sc *SecurityConfig) error { func (c *clientImpl) updateValidator(u interface{}) error { switch update := u.(type) { - case ListenerUpdate: + case xdsresource.ListenerUpdate: if update.InboundListenerCfg == nil || update.InboundListenerCfg.FilterChains == nil { return nil } - - fcm := update.InboundListenerCfg.FilterChains - for _, dst := range fcm.dstPrefixMap { - for _, srcType := range dst.srcTypeArr { - if srcType == nil { - continue - } - for _, src := range srcType.srcPrefixMap { - for _, fc := range src.srcPortMap { - if err := c.filterChainUpdateValidator(fc); err != nil { - return err - } - } - } - } - } - return c.filterChainUpdateValidator(fcm.def) - case ClusterUpdate: + return update.InboundListenerCfg.FilterChains.Validate(c.filterChainUpdateValidator) + case xdsresource.ClusterUpdate: return c.securityConfigUpdateValidator(update.SecurityCfg) default: // We currently invoke this update validation function only for LDS and @@ -799,65 +144,3 @@ func (c *clientImpl) updateValidator(u interface{}) error { } return nil } - -// ResourceType identifies resources in a transport protocol agnostic way. These -// will be used in transport version agnostic code, while the versioned API -// clients will map these to appropriate version URLs. -type ResourceType int - -// Version agnostic resource type constants. -const ( - UnknownResource ResourceType = iota - ListenerResource - HTTPConnManagerResource - RouteConfigResource - ClusterResource - EndpointsResource -) - -func (r ResourceType) String() string { - switch r { - case ListenerResource: - return "ListenerResource" - case HTTPConnManagerResource: - return "HTTPConnManagerResource" - case RouteConfigResource: - return "RouteConfigResource" - case ClusterResource: - return "ClusterResource" - case EndpointsResource: - return "EndpointsResource" - default: - return "UnknownResource" - } -} - -// IsListenerResource returns true if the provider URL corresponds to an xDS -// Listener resource. -func IsListenerResource(url string) bool { - return url == version.V2ListenerURL || url == version.V3ListenerURL -} - -// IsHTTPConnManagerResource returns true if the provider URL corresponds to an xDS -// HTTPConnManager resource. -func IsHTTPConnManagerResource(url string) bool { - return url == version.V2HTTPConnManagerURL || url == version.V3HTTPConnManagerURL -} - -// IsRouteConfigResource returns true if the provider URL corresponds to an xDS -// RouteConfig resource. -func IsRouteConfigResource(url string) bool { - return url == version.V2RouteConfigURL || url == version.V3RouteConfigURL -} - -// IsClusterResource returns true if the provider URL corresponds to an xDS -// Cluster resource. -func IsClusterResource(url string) bool { - return url == version.V2ClusterURL || url == version.V3ClusterURL -} - -// IsEndpointsResource returns true if the provider URL corresponds to an xDS -// Endpoints resource. -func IsEndpointsResource(url string) bool { - return url == version.V2EndpointsURL || url == version.V3EndpointsURL -} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller.go new file mode 100644 index 000000000..431a14498 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller.go @@ -0,0 +1,38 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsclient + +import ( + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/controller" + "google.golang.org/grpc/xds/internal/xdsclient/load" + "google.golang.org/grpc/xds/internal/xdsclient/pubsub" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) + +type controllerInterface interface { + AddWatch(resourceType xdsresource.ResourceType, resourceName string) + RemoveWatch(resourceType xdsresource.ResourceType, resourceName string) + ReportLoad(server string) (*load.Store, func()) + Close() +} + +var newController = func(config *bootstrap.ServerConfig, pubsub *pubsub.Pubsub, validator xdsresource.UpdateValidatorFunc, logger *grpclog.PrefixLogger) (controllerInterface, error) { + return controller.New(config, pubsub, validator, logger) +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/controller.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/controller.go new file mode 100644 index 000000000..09283d742 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/controller.go @@ -0,0 +1,168 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +// Package controller contains implementation to connect to the control plane. +// Including starting the ClientConn, starting the xDS stream, and +// sending/receiving messages. +// +// All the messages are parsed by the resource package (e.g. +// UnmarshalListener()) and sent to the Pubsub watchers. +package controller + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/internal/backoff" + "google.golang.org/grpc/internal/buffer" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + "google.golang.org/grpc/xds/internal/xdsclient/pubsub" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) + +// Controller manages the connection and stream to the control plane. +// +// It keeps track of what resources are being watched, and send new requests +// when new watches are added. +// +// It takes a pubsub (as an interface) as input. When a response is received, +// it's parsed, and the updates are sent to the pubsub. +type Controller struct { + config *bootstrap.ServerConfig + updateHandler pubsub.UpdateHandler + updateValidator xdsresource.UpdateValidatorFunc + logger *grpclog.PrefixLogger + + cc *grpc.ClientConn // Connection to the management server. + vClient version.VersionedClient + stopRunGoroutine context.CancelFunc + + backoff func(int) time.Duration + streamCh chan grpc.ClientStream + sendCh *buffer.Unbounded + + mu sync.Mutex + // Message specific watch infos, protected by the above mutex. These are + // written to, after successfully reading from the update channel, and are + // read from when recovering from a broken stream to resend the xDS + // messages. When the user of this client object cancels a watch call, + // these are set to nil. All accesses to the map protected and any value + // inside the map should be protected with the above mutex. + watchMap map[xdsresource.ResourceType]map[string]bool + // versionMap contains the version that was acked (the version in the ack + // request that was sent on wire). The key is rType, the value is the + // version string, becaues the versions for different resource types should + // be independent. + versionMap map[xdsresource.ResourceType]string + // nonceMap contains the nonce from the most recent received response. + nonceMap map[xdsresource.ResourceType]string + + // Changes to map lrsClients and the lrsClient inside the map need to be + // protected by lrsMu. + // + // TODO: after LRS refactoring, each controller should only manage the LRS + // stream to its server. LRS streams to other servers should be managed by + // other controllers. + lrsMu sync.Mutex + lrsClients map[string]*lrsClient +} + +// New creates a new controller. +func New(config *bootstrap.ServerConfig, updateHandler pubsub.UpdateHandler, validator xdsresource.UpdateValidatorFunc, logger *grpclog.PrefixLogger) (_ *Controller, retErr error) { + switch { + case config == nil: + return nil, errors.New("xds: no xds_server provided") + case config.ServerURI == "": + return nil, errors.New("xds: no xds_server name provided in options") + case config.Creds == nil: + return nil, errors.New("xds: no credentials provided in options") + case config.NodeProto == nil: + return nil, errors.New("xds: no node_proto provided in options") + } + + dopts := []grpc.DialOption{ + config.Creds, + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: 5 * time.Minute, + Timeout: 20 * time.Second, + }), + } + + ret := &Controller{ + config: config, + updateValidator: validator, + updateHandler: updateHandler, + + backoff: backoff.DefaultExponential.Backoff, // TODO: should this be configurable? + streamCh: make(chan grpc.ClientStream, 1), + sendCh: buffer.NewUnbounded(), + watchMap: make(map[xdsresource.ResourceType]map[string]bool), + versionMap: make(map[xdsresource.ResourceType]string), + nonceMap: make(map[xdsresource.ResourceType]string), + + lrsClients: make(map[string]*lrsClient), + } + + defer func() { + if retErr != nil { + ret.Close() + } + }() + + cc, err := grpc.Dial(config.ServerURI, dopts...) + if err != nil { + // An error from a non-blocking dial indicates something serious. + return nil, fmt.Errorf("xds: failed to dial control plane {%s}: %v", config.ServerURI, err) + } + ret.cc = cc + + builder := version.GetAPIClientBuilder(config.TransportAPI) + if builder == nil { + return nil, fmt.Errorf("no client builder for xDS API version: %v", config.TransportAPI) + } + apiClient, err := builder(version.BuildOptions{NodeProto: config.NodeProto, Logger: logger}) + if err != nil { + return nil, err + } + ret.vClient = apiClient + + ctx, cancel := context.WithCancel(context.Background()) + ret.stopRunGoroutine = cancel + go ret.run(ctx) + + return ret, nil +} + +// Close closes the controller. +func (t *Controller) Close() { + // Note that Close needs to check for nils even if some of them are always + // set in the constructor. This is because the constructor defers Close() in + // error cases, and the fields might not be set when the error happens. + if t.stopRunGoroutine != nil { + t.stopRunGoroutine() + } + if t.cc != nil { + t.cc.Close() + } +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/loadreport.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/loadreport.go new file mode 100644 index 000000000..f8cfd017e --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/loadreport.go @@ -0,0 +1,144 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package controller + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + "google.golang.org/grpc/xds/internal/xdsclient/load" +) + +// ReportLoad starts an load reporting stream to the given server. If the server +// is not an empty string, and is different from the management server, a new +// ClientConn will be created. +// +// The same options used for creating the Client will be used (including +// NodeProto, and dial options if necessary). +// +// It returns a Store for the user to report loads, a function to cancel the +// load reporting stream. +// +// TODO: LRS refactor; maybe a new controller should be created for a separate +// server, so that the same stream can be shared by different reporters to the +// same server, even if they originate from different Controllers. +func (c *Controller) ReportLoad(server string) (*load.Store, func()) { + c.lrsMu.Lock() + defer c.lrsMu.Unlock() + + // If there's already a client to this server, use it. Otherwise, create + // one. + lrsC, ok := c.lrsClients[server] + if !ok { + lrsC = newLRSClient(c, server) + c.lrsClients[server] = lrsC + } + + store := lrsC.ref() + return store, func() { + // This is a callback, need to hold lrsMu. + c.lrsMu.Lock() + defer c.lrsMu.Unlock() + if lrsC.unRef() { + // Delete the lrsClient from map if this is the last reference. + delete(c.lrsClients, server) + } + } +} + +// lrsClient maps to one lrsServer. It contains: +// - a ClientConn to this server (only if it's different from the management +// server) +// - a load.Store that contains loads only for this server +type lrsClient struct { + parent *Controller + server string + + cc *grpc.ClientConn // nil if the server is same as the management server + refCount int + cancelStream func() + loadStore *load.Store +} + +// newLRSClient creates a new LRS stream to the server. +func newLRSClient(parent *Controller, server string) *lrsClient { + return &lrsClient{ + parent: parent, + server: server, + refCount: 0, + } +} + +// ref increments the refCount. If this is the first ref, it starts the LRS stream. +// +// Not thread-safe, caller needs to synchronize. +func (lrsC *lrsClient) ref() *load.Store { + lrsC.refCount++ + if lrsC.refCount == 1 { + lrsC.startStream() + } + return lrsC.loadStore +} + +// unRef decrements the refCount, and closes the stream if refCount reaches 0 +// (and close the cc if cc is not xDS cc). It returns whether refCount reached 0 +// after this call. +// +// Not thread-safe, caller needs to synchronize. +func (lrsC *lrsClient) unRef() (closed bool) { + lrsC.refCount-- + if lrsC.refCount != 0 { + return false + } + lrsC.parent.logger.Infof("Stopping load report to server: %s", lrsC.server) + lrsC.cancelStream() + if lrsC.cc != nil { + lrsC.cc.Close() + } + return true +} + +// startStream starts the LRS stream to the server. If server is not the same +// management server from the parent, it also creates a ClientConn. +func (lrsC *lrsClient) startStream() { + var cc *grpc.ClientConn + + lrsC.parent.logger.Infof("Starting load report to server: %s", lrsC.server) + if lrsC.server == "" || lrsC.server == lrsC.parent.config.ServerURI { + // Reuse the xDS client if server is the same. + cc = lrsC.parent.cc + } else { + lrsC.parent.logger.Infof("LRS server is different from management server, starting a new ClientConn") + ccNew, err := grpc.Dial(lrsC.server, lrsC.parent.config.Creds) + if err != nil { + // An error from a non-blocking dial indicates something serious. + lrsC.parent.logger.Infof("xds: failed to dial load report server {%s}: %v", lrsC.server, err) + return + } + cc = ccNew + lrsC.cc = ccNew + } + + var ctx context.Context + ctx, lrsC.cancelStream = context.WithCancel(context.Background()) + + // Create the store and stream. + lrsC.loadStore = load.NewStore() + go lrsC.parent.reportLoad(ctx, cc, version.LoadReportingOptions{LoadStore: lrsC.loadStore}) +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/transport_helper.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/transport.go similarity index 58% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/transport_helper.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/transport.go index 4c56daaf0..0b982b0d7 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/transport_helper.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/transport.go @@ -16,139 +16,23 @@ * */ -package xdsclient +package controller import ( "context" - "sync" + "fmt" "time" "github.com/golang/protobuf/proto" - "google.golang.org/grpc/xds/internal/xdsclient/load" - "google.golang.org/grpc" - "google.golang.org/grpc/internal/buffer" - "google.golang.org/grpc/internal/grpclog" + controllerversion "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + xdsresourceversion "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + "google.golang.org/grpc/xds/internal/xdsclient/load" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) -// ErrResourceTypeUnsupported is an error used to indicate an unsupported xDS -// resource type. The wrapped ErrStr contains the details. -type ErrResourceTypeUnsupported struct { - ErrStr string -} - -// Error helps implements the error interface. -func (e ErrResourceTypeUnsupported) Error() string { - return e.ErrStr -} - -// VersionedClient is the interface to be provided by the transport protocol -// specific client implementations. This mainly deals with the actual sending -// and receiving of messages. -type VersionedClient interface { - // NewStream returns a new xDS client stream specific to the underlying - // transport protocol version. - NewStream(ctx context.Context) (grpc.ClientStream, error) - - // SendRequest constructs and sends out a DiscoveryRequest message specific - // to the underlying transport protocol version. - SendRequest(s grpc.ClientStream, resourceNames []string, rType ResourceType, version, nonce, errMsg string) error - - // RecvResponse uses the provided stream to receive a response specific to - // the underlying transport protocol version. - RecvResponse(s grpc.ClientStream) (proto.Message, error) - - // HandleResponse parses and validates the received response and notifies - // the top-level client which in turn notifies the registered watchers. - // - // Return values are: resourceType, version, nonce, error. - // If the provided protobuf message contains a resource type which is not - // supported, implementations must return an error of type - // ErrResourceTypeUnsupported. - HandleResponse(proto.Message) (ResourceType, string, string, error) - - // NewLoadStatsStream returns a new LRS client stream specific to the underlying - // transport protocol version. - NewLoadStatsStream(ctx context.Context, cc *grpc.ClientConn) (grpc.ClientStream, error) - - // SendFirstLoadStatsRequest constructs and sends the first request on the - // LRS stream. - SendFirstLoadStatsRequest(s grpc.ClientStream) error - - // HandleLoadStatsResponse receives the first response from the server which - // contains the load reporting interval and the clusters for which the - // server asks the client to report load for. - // - // If the response sets SendAllClusters to true, the returned clusters is - // nil. - HandleLoadStatsResponse(s grpc.ClientStream) (clusters []string, _ time.Duration, _ error) - - // SendLoadStatsRequest will be invoked at regular intervals to send load - // report with load data reported since the last time this method was - // invoked. - SendLoadStatsRequest(s grpc.ClientStream, loads []*load.Data) error -} - -// TransportHelper contains all xDS transport protocol related functionality -// which is common across different versioned client implementations. -// -// TransportHelper takes care of sending and receiving xDS requests and -// responses on an ADS stream. It also takes care of ACK/NACK handling. It -// delegates to the actual versioned client implementations wherever -// appropriate. -// -// Implements the APIClient interface which makes it possible for versioned -// client implementations to embed this type, and thereby satisfy the interface -// requirements. -type TransportHelper struct { - cancelCtx context.CancelFunc - - vClient VersionedClient - logger *grpclog.PrefixLogger - backoff func(int) time.Duration - streamCh chan grpc.ClientStream - sendCh *buffer.Unbounded - - mu sync.Mutex - // Message specific watch infos, protected by the above mutex. These are - // written to, after successfully reading from the update channel, and are - // read from when recovering from a broken stream to resend the xDS - // messages. When the user of this client object cancels a watch call, - // these are set to nil. All accesses to the map protected and any value - // inside the map should be protected with the above mutex. - watchMap map[ResourceType]map[string]bool - // versionMap contains the version that was acked (the version in the ack - // request that was sent on wire). The key is rType, the value is the - // version string, becaues the versions for different resource types should - // be independent. - versionMap map[ResourceType]string - // nonceMap contains the nonce from the most recent received response. - nonceMap map[ResourceType]string -} - -// NewTransportHelper creates a new transport helper to be used by versioned -// client implementations. -func NewTransportHelper(vc VersionedClient, logger *grpclog.PrefixLogger, backoff func(int) time.Duration) *TransportHelper { - ctx, cancelCtx := context.WithCancel(context.Background()) - t := &TransportHelper{ - cancelCtx: cancelCtx, - vClient: vc, - logger: logger, - backoff: backoff, - - streamCh: make(chan grpc.ClientStream, 1), - sendCh: buffer.NewUnbounded(), - watchMap: make(map[ResourceType]map[string]bool), - versionMap: make(map[ResourceType]string), - nonceMap: make(map[ResourceType]string), - } - - go t.run(ctx) - return t -} - // AddWatch adds a watch for an xDS resource given its type and name. -func (t *TransportHelper) AddWatch(rType ResourceType, resourceName string) { +func (t *Controller) AddWatch(rType xdsresource.ResourceType, resourceName string) { t.sendCh.Put(&watchAction{ rType: rType, remove: false, @@ -158,7 +42,7 @@ func (t *TransportHelper) AddWatch(rType ResourceType, resourceName string) { // RemoveWatch cancels an already registered watch for an xDS resource // given its type and name. -func (t *TransportHelper) RemoveWatch(rType ResourceType, resourceName string) { +func (t *Controller) RemoveWatch(rType xdsresource.ResourceType, resourceName string) { t.sendCh.Put(&watchAction{ rType: rType, remove: true, @@ -166,15 +50,10 @@ func (t *TransportHelper) RemoveWatch(rType ResourceType, resourceName string) { }) } -// Close closes the transport helper. -func (t *TransportHelper) Close() { - t.cancelCtx() -} - // run starts an ADS stream (and backs off exponentially, if the previous // stream failed without receiving a single reply) and runs the sender and // receiver routines to send and receive data from the stream respectively. -func (t *TransportHelper) run(ctx context.Context) { +func (t *Controller) run(ctx context.Context) { go t.send(ctx) // TODO: start a goroutine monitoring ClientConn's connectivity state, and // report error (and log) when stats is transient failure. @@ -200,8 +79,9 @@ func (t *TransportHelper) run(ctx context.Context) { } retries++ - stream, err := t.vClient.NewStream(ctx) + stream, err := t.vClient.NewStream(ctx, t.cc) if err != nil { + t.updateHandler.NewConnectionError(err) t.logger.Warningf("xds: ADS stream creation failed: %v", err) continue } @@ -234,7 +114,7 @@ func (t *TransportHelper) run(ctx context.Context) { // Note that this goroutine doesn't do anything to the old stream when there's a // new one. In fact, there should be only one stream in progress, and new one // should only be created when the old one fails (recv returns an error). -func (t *TransportHelper) send(ctx context.Context) { +func (t *Controller) send(ctx context.Context) { var stream grpc.ClientStream for { select { @@ -250,7 +130,7 @@ func (t *TransportHelper) send(ctx context.Context) { var ( target []string - rType ResourceType + rType xdsresource.ResourceType version, nonce, errMsg string send bool ) @@ -287,13 +167,13 @@ func (t *TransportHelper) send(ctx context.Context) { // that here because the stream has just started and Send() usually returns // quickly (once it pushes the message onto the transport layer) and is only // ever blocked if we don't have enough flow control quota. -func (t *TransportHelper) sendExisting(stream grpc.ClientStream) bool { +func (t *Controller) sendExisting(stream grpc.ClientStream) bool { t.mu.Lock() defer t.mu.Unlock() // Reset the ack versions when the stream restarts. - t.versionMap = make(map[ResourceType]string) - t.nonceMap = make(map[ResourceType]string) + t.versionMap = make(map[xdsresource.ResourceType]string) + t.nonceMap = make(map[xdsresource.ResourceType]string) for rType, s := range t.watchMap { if err := t.vClient.SendRequest(stream, mapToSlice(s), rType, "", "", ""); err != nil { @@ -307,16 +187,19 @@ func (t *TransportHelper) sendExisting(stream grpc.ClientStream) bool { // recv receives xDS responses on the provided ADS stream and branches out to // message specific handlers. -func (t *TransportHelper) recv(stream grpc.ClientStream) bool { +func (t *Controller) recv(stream grpc.ClientStream) bool { success := false for { resp, err := t.vClient.RecvResponse(stream) if err != nil { + t.updateHandler.NewConnectionError(err) t.logger.Warningf("ADS stream is closed with error: %v", err) return success } - rType, version, nonce, err := t.vClient.HandleResponse(resp) - if e, ok := err.(ErrResourceTypeUnsupported); ok { + + rType, version, nonce, err := t.handleResponse(resp) + + if e, ok := err.(xdsresourceversion.ErrResourceTypeUnsupported); ok { t.logger.Warningf("%s", e.ErrStr) continue } @@ -342,6 +225,43 @@ func (t *TransportHelper) recv(stream grpc.ClientStream) bool { } } +func (t *Controller) handleResponse(resp proto.Message) (xdsresource.ResourceType, string, string, error) { + rType, resource, version, nonce, err := t.vClient.ParseResponse(resp) + if err != nil { + return rType, version, nonce, err + } + opts := &xdsresource.UnmarshalOptions{ + Version: version, + Resources: resource, + Logger: t.logger, + UpdateValidator: t.updateValidator, + } + var md xdsresource.UpdateMetadata + switch rType { + case xdsresource.ListenerResource: + var update map[string]xdsresource.ListenerUpdateErrTuple + update, md, err = xdsresource.UnmarshalListener(opts) + t.updateHandler.NewListeners(update, md) + case xdsresource.RouteConfigResource: + var update map[string]xdsresource.RouteConfigUpdateErrTuple + update, md, err = xdsresource.UnmarshalRouteConfig(opts) + t.updateHandler.NewRouteConfigs(update, md) + case xdsresource.ClusterResource: + var update map[string]xdsresource.ClusterUpdateErrTuple + update, md, err = xdsresource.UnmarshalCluster(opts) + t.updateHandler.NewClusters(update, md) + case xdsresource.EndpointsResource: + var update map[string]xdsresource.EndpointsUpdateErrTuple + update, md, err = xdsresource.UnmarshalEndpoints(opts) + t.updateHandler.NewEndpoints(update, md) + default: + return rType, "", "", xdsresourceversion.ErrResourceTypeUnsupported{ + ErrStr: fmt.Sprintf("Resource type %v unknown in response from server", rType), + } + } + return rType, version, nonce, err +} + func mapToSlice(m map[string]bool) []string { ret := make([]string, 0, len(m)) for i := range m { @@ -351,7 +271,7 @@ func mapToSlice(m map[string]bool) []string { } type watchAction struct { - rType ResourceType + rType xdsresource.ResourceType remove bool // Whether this is to remove watch for the resource. resource string } @@ -359,7 +279,7 @@ type watchAction struct { // processWatchInfo pulls the fields needed by the request from a watchAction. // // It also updates the watch map. -func (t *TransportHelper) processWatchInfo(w *watchAction) (target []string, rType ResourceType, ver, nonce string) { +func (t *Controller) processWatchInfo(w *watchAction) (target []string, rType xdsresource.ResourceType, ver, nonce string) { t.mu.Lock() defer t.mu.Unlock() @@ -390,7 +310,7 @@ func (t *TransportHelper) processWatchInfo(w *watchAction) (target []string, rTy } type ackAction struct { - rType ResourceType + rType xdsresource.ResourceType version string // NACK if version is an empty string. nonce string errMsg string // Empty unless it's a NACK. @@ -403,13 +323,13 @@ type ackAction struct { // processAckInfo pulls the fields needed by the ack request from a ackAction. // // If no active watch is found for this ack, it returns false for send. -func (t *TransportHelper) processAckInfo(ack *ackAction, stream grpc.ClientStream) (target []string, rType ResourceType, version, nonce string, send bool) { +func (t *Controller) processAckInfo(ack *ackAction, stream grpc.ClientStream) (target []string, rType xdsresource.ResourceType, version, nonce string, send bool) { if ack.stream != stream { // If ACK's stream isn't the current sending stream, this means the ACK // was pushed to queue before the old stream broke, and a new stream has // been started since. Return immediately here so we don't update the // nonce for the new stream. - return nil, UnknownResource, "", "", false + return nil, xdsresource.UnknownResource, "", "", false } rType = ack.rType @@ -429,7 +349,7 @@ func (t *TransportHelper) processAckInfo(ack *ackAction, stream grpc.ClientStrea // canceled while the ackAction is in queue), because there's no resource // name. And if we send a request with empty resource name list, the // server may treat it as a wild card and send us everything. - return nil, UnknownResource, "", "", false + return nil, xdsresource.UnknownResource, "", "", false } send = true target = mapToSlice(s) @@ -449,7 +369,7 @@ func (t *TransportHelper) processAckInfo(ack *ackAction, stream grpc.ClientStrea // reportLoad starts an LRS stream to report load data to the management server. // It blocks until the context is cancelled. -func (t *TransportHelper) reportLoad(ctx context.Context, cc *grpc.ClientConn, opts loadReportingOptions) { +func (t *Controller) reportLoad(ctx context.Context, cc *grpc.ClientConn, opts controllerversion.LoadReportingOptions) { retries := 0 for { if ctx.Err() != nil { @@ -471,28 +391,28 @@ func (t *TransportHelper) reportLoad(ctx context.Context, cc *grpc.ClientConn, o retries++ stream, err := t.vClient.NewLoadStatsStream(ctx, cc) if err != nil { - logger.Warningf("lrs: failed to create stream: %v", err) + t.logger.Warningf("lrs: failed to create stream: %v", err) continue } - logger.Infof("lrs: created LRS stream") + t.logger.Infof("lrs: created LRS stream") if err := t.vClient.SendFirstLoadStatsRequest(stream); err != nil { - logger.Warningf("lrs: failed to send first request: %v", err) + t.logger.Warningf("lrs: failed to send first request: %v", err) continue } clusters, interval, err := t.vClient.HandleLoadStatsResponse(stream) if err != nil { - logger.Warning(err) + t.logger.Warningf("%v", err) continue } retries = 0 - t.sendLoads(ctx, stream, opts.loadStore, clusters, interval) + t.sendLoads(ctx, stream, opts.LoadStore, clusters, interval) } } -func (t *TransportHelper) sendLoads(ctx context.Context, stream grpc.ClientStream, store *load.Store, clusterNames []string, interval time.Duration) { +func (t *Controller) sendLoads(ctx context.Context, stream grpc.ClientStream, store *load.Store, clusterNames []string, interval time.Duration) { tick := time.NewTicker(interval) defer tick.Stop() for { @@ -502,7 +422,7 @@ func (t *TransportHelper) sendLoads(ctx context.Context, stream grpc.ClientStrea return } if err := t.vClient.SendLoadStatsRequest(stream, store.Stats(clusterNames)); err != nil { - logger.Warning(err) + t.logger.Warningf("%v", err) return } } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2/client.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2/client.go new file mode 100644 index 000000000..ae3ae559e --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2/client.go @@ -0,0 +1,155 @@ +/* + * + * Copyright 2019 gRPC authors. + * + * 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. + * + */ + +// Package v2 provides xDS v2 transport protocol specific functionality. +package v2 + +import ( + "context" + "fmt" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + controllerversion "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" + xdsresourceversion "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" + + v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" + v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" + v2adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" + statuspb "google.golang.org/genproto/googleapis/rpc/status" +) + +func init() { + controllerversion.RegisterAPIClientBuilder(xdsresourceversion.TransportV2, newClient) +} + +var ( + resourceTypeToURL = map[xdsresource.ResourceType]string{ + xdsresource.ListenerResource: xdsresourceversion.V2ListenerURL, + xdsresource.RouteConfigResource: xdsresourceversion.V2RouteConfigURL, + xdsresource.ClusterResource: xdsresourceversion.V2ClusterURL, + xdsresource.EndpointsResource: xdsresourceversion.V2EndpointsURL, + } +) + +func newClient(opts controllerversion.BuildOptions) (controllerversion.VersionedClient, error) { + nodeProto, ok := opts.NodeProto.(*v2corepb.Node) + if !ok { + return nil, fmt.Errorf("xds: unsupported Node proto type: %T, want %T", opts.NodeProto, (*v2corepb.Node)(nil)) + } + v2c := &client{nodeProto: nodeProto, logger: opts.Logger} + return v2c, nil +} + +type adsStream v2adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient + +// client performs the actual xDS RPCs using the xDS v2 API. It creates a +// single ADS stream on which the different types of xDS requests and responses +// are multiplexed. +type client struct { + nodeProto *v2corepb.Node + logger *grpclog.PrefixLogger +} + +func (v2c *client) NewStream(ctx context.Context, cc *grpc.ClientConn) (grpc.ClientStream, error) { + return v2adsgrpc.NewAggregatedDiscoveryServiceClient(cc).StreamAggregatedResources(ctx, grpc.WaitForReady(true)) +} + +// SendRequest sends out a DiscoveryRequest for the given resourceNames, of type +// rType, on the provided stream. +// +// version is the ack version to be sent with the request +// - If this is the new request (not an ack/nack), version will be empty. +// - If this is an ack, version will be the version from the response. +// - If this is a nack, version will be the previous acked version (from +// versionMap). If there was no ack before, it will be empty. +func (v2c *client) SendRequest(s grpc.ClientStream, resourceNames []string, rType xdsresource.ResourceType, version, nonce, errMsg string) error { + stream, ok := s.(adsStream) + if !ok { + return fmt.Errorf("xds: Attempt to send request on unsupported stream type: %T", s) + } + req := &v2xdspb.DiscoveryRequest{ + Node: v2c.nodeProto, + TypeUrl: resourceTypeToURL[rType], + ResourceNames: resourceNames, + VersionInfo: version, + ResponseNonce: nonce, + } + if errMsg != "" { + req.ErrorDetail = &statuspb.Status{ + Code: int32(codes.InvalidArgument), Message: errMsg, + } + } + if err := stream.Send(req); err != nil { + return fmt.Errorf("xds: stream.Send(%+v) failed: %v", req, err) + } + v2c.logger.Debugf("ADS request sent: %v", pretty.ToJSON(req)) + return nil +} + +// RecvResponse blocks on the receipt of one response message on the provided +// stream. +func (v2c *client) RecvResponse(s grpc.ClientStream) (proto.Message, error) { + stream, ok := s.(adsStream) + if !ok { + return nil, fmt.Errorf("xds: Attempt to receive response on unsupported stream type: %T", s) + } + + resp, err := stream.Recv() + if err != nil { + return nil, fmt.Errorf("xds: stream.Recv() failed: %v", err) + } + v2c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl()) + v2c.logger.Debugf("ADS response received: %v", pretty.ToJSON(resp)) + return resp, nil +} + +func (v2c *client) ParseResponse(r proto.Message) (xdsresource.ResourceType, []*anypb.Any, string, string, error) { + rType := xdsresource.UnknownResource + resp, ok := r.(*v2xdspb.DiscoveryResponse) + if !ok { + return rType, nil, "", "", fmt.Errorf("xds: unsupported message type: %T", resp) + } + + // Note that the xDS transport protocol is versioned independently of + // the resource types, and it is supported to transfer older versions + // of resource types using new versions of the transport protocol, or + // vice-versa. Hence we need to handle v3 type_urls as well here. + var err error + url := resp.GetTypeUrl() + switch { + case xdsresource.IsListenerResource(url): + rType = xdsresource.ListenerResource + case xdsresource.IsRouteConfigResource(url): + rType = xdsresource.RouteConfigResource + case xdsresource.IsClusterResource(url): + rType = xdsresource.ClusterResource + case xdsresource.IsEndpointsResource(url): + rType = xdsresource.EndpointsResource + default: + return rType, nil, "", "", controllerversion.ErrResourceTypeUnsupported{ + ErrStr: fmt.Sprintf("Resource type %v unknown in response from server", resp.GetTypeUrl()), + } + } + return rType, resp.GetResources(), resp.GetVersionInfo(), resp.GetNonce(), err +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v2/loadreport.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2/loadreport.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/v2/loadreport.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2/loadreport.go diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3/client.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3/client.go new file mode 100644 index 000000000..1c7f11ad2 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3/client.go @@ -0,0 +1,157 @@ +/* + * + * Copyright 2020 gRPC authors. + * + * 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. + * + */ + +// Package v3 provides xDS v3 transport protocol specific functionality. +package v3 + +import ( + "context" + "fmt" + + "github.com/golang/protobuf/proto" + statuspb "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + controllerversion "google.golang.org/grpc/xds/internal/xdsclient/controller/version" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" + xdsresourceversion "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" + + v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + v3adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" +) + +func init() { + controllerversion.RegisterAPIClientBuilder(xdsresourceversion.TransportV3, newClient) +} + +var ( + resourceTypeToURL = map[xdsresource.ResourceType]string{ + xdsresource.ListenerResource: xdsresourceversion.V3ListenerURL, + xdsresource.RouteConfigResource: xdsresourceversion.V3RouteConfigURL, + xdsresource.ClusterResource: xdsresourceversion.V3ClusterURL, + xdsresource.EndpointsResource: xdsresourceversion.V3EndpointsURL, + } +) + +func newClient(opts controllerversion.BuildOptions) (controllerversion.VersionedClient, error) { + nodeProto, ok := opts.NodeProto.(*v3corepb.Node) + if !ok { + return nil, fmt.Errorf("xds: unsupported Node proto type: %T, want %T", opts.NodeProto, v3corepb.Node{}) + } + v3c := &client{ + nodeProto: nodeProto, logger: opts.Logger, + } + return v3c, nil +} + +type adsStream v3adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient + +// client performs the actual xDS RPCs using the xDS v3 API. It creates a +// single ADS stream on which the different types of xDS requests and responses +// are multiplexed. +type client struct { + nodeProto *v3corepb.Node + logger *grpclog.PrefixLogger +} + +func (v3c *client) NewStream(ctx context.Context, cc *grpc.ClientConn) (grpc.ClientStream, error) { + return v3adsgrpc.NewAggregatedDiscoveryServiceClient(cc).StreamAggregatedResources(ctx, grpc.WaitForReady(true)) +} + +// SendRequest sends out a DiscoveryRequest for the given resourceNames, of type +// rType, on the provided stream. +// +// version is the ack version to be sent with the request +// - If this is the new request (not an ack/nack), version will be empty. +// - If this is an ack, version will be the version from the response. +// - If this is a nack, version will be the previous acked version (from +// versionMap). If there was no ack before, it will be empty. +func (v3c *client) SendRequest(s grpc.ClientStream, resourceNames []string, rType xdsresource.ResourceType, version, nonce, errMsg string) error { + stream, ok := s.(adsStream) + if !ok { + return fmt.Errorf("xds: Attempt to send request on unsupported stream type: %T", s) + } + req := &v3discoverypb.DiscoveryRequest{ + Node: v3c.nodeProto, + TypeUrl: resourceTypeToURL[rType], + ResourceNames: resourceNames, + VersionInfo: version, + ResponseNonce: nonce, + } + if errMsg != "" { + req.ErrorDetail = &statuspb.Status{ + Code: int32(codes.InvalidArgument), Message: errMsg, + } + } + if err := stream.Send(req); err != nil { + return fmt.Errorf("xds: stream.Send(%+v) failed: %v", req, err) + } + v3c.logger.Debugf("ADS request sent: %v", pretty.ToJSON(req)) + return nil +} + +// RecvResponse blocks on the receipt of one response message on the provided +// stream. +func (v3c *client) RecvResponse(s grpc.ClientStream) (proto.Message, error) { + stream, ok := s.(adsStream) + if !ok { + return nil, fmt.Errorf("xds: Attempt to receive response on unsupported stream type: %T", s) + } + + resp, err := stream.Recv() + if err != nil { + return nil, fmt.Errorf("xds: stream.Recv() failed: %v", err) + } + v3c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl()) + v3c.logger.Debugf("ADS response received: %+v", pretty.ToJSON(resp)) + return resp, nil +} + +func (v3c *client) ParseResponse(r proto.Message) (xdsresource.ResourceType, []*anypb.Any, string, string, error) { + rType := xdsresource.UnknownResource + resp, ok := r.(*v3discoverypb.DiscoveryResponse) + if !ok { + return rType, nil, "", "", fmt.Errorf("xds: unsupported message type: %T", resp) + } + + // Note that the xDS transport protocol is versioned independently of + // the resource types, and it is supported to transfer older versions + // of resource types using new versions of the transport protocol, or + // vice-versa. Hence we need to handle v3 type_urls as well here. + var err error + url := resp.GetTypeUrl() + switch { + case xdsresource.IsListenerResource(url): + rType = xdsresource.ListenerResource + case xdsresource.IsRouteConfigResource(url): + rType = xdsresource.RouteConfigResource + case xdsresource.IsClusterResource(url): + rType = xdsresource.ClusterResource + case xdsresource.IsEndpointsResource(url): + rType = xdsresource.EndpointsResource + default: + return rType, nil, "", "", controllerversion.ErrResourceTypeUnsupported{ + ErrStr: fmt.Sprintf("Resource type %v unknown in response from server", resp.GetTypeUrl()), + } + } + return rType, resp.GetResources(), resp.GetVersionInfo(), resp.GetNonce(), err +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v3/loadreport.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3/loadreport.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/v3/loadreport.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3/loadreport.go diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/version.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/version.go new file mode 100644 index 000000000..f79a21e29 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/controller/version/version.go @@ -0,0 +1,123 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +// Package version defines APIs to deal with different versions of xDS. +package version + +import ( + "context" + "time" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/xds/internal/xdsclient/load" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" +) + +var ( + m = make(map[version.TransportAPI]func(opts BuildOptions) (VersionedClient, error)) +) + +// RegisterAPIClientBuilder registers a client builder for xDS transport protocol +// version specified by b.Version(). +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple builders are +// registered for the same version, the one registered last will take effect. +func RegisterAPIClientBuilder(v version.TransportAPI, f func(opts BuildOptions) (VersionedClient, error)) { + m[v] = f +} + +// GetAPIClientBuilder returns the client builder registered for the provided +// xDS transport API version. +func GetAPIClientBuilder(version version.TransportAPI) func(opts BuildOptions) (VersionedClient, error) { + if f, ok := m[version]; ok { + return f + } + return nil +} + +// BuildOptions contains options to be passed to client builders. +type BuildOptions struct { + // NodeProto contains the Node proto to be used in xDS requests. The actual + // type depends on the transport protocol version used. + NodeProto proto.Message + // // Backoff returns the amount of time to backoff before retrying broken + // // streams. + // Backoff func(int) time.Duration + // Logger provides enhanced logging capabilities. + Logger *grpclog.PrefixLogger +} + +// LoadReportingOptions contains configuration knobs for reporting load data. +type LoadReportingOptions struct { + LoadStore *load.Store +} + +// ErrResourceTypeUnsupported is an error used to indicate an unsupported xDS +// resource type. The wrapped ErrStr contains the details. +type ErrResourceTypeUnsupported struct { + ErrStr string +} + +// Error helps implements the error interface. +func (e ErrResourceTypeUnsupported) Error() string { + return e.ErrStr +} + +// VersionedClient is the interface to version specific operations of the +// client. +// +// It mainly deals with the type assertion from proto.Message to the real v2/v3 +// types, and grpc.Stream to the versioned stream types. +type VersionedClient interface { + // NewStream returns a new xDS client stream specific to the underlying + // transport protocol version. + NewStream(ctx context.Context, cc *grpc.ClientConn) (grpc.ClientStream, error) + // SendRequest constructs and sends out a DiscoveryRequest message specific + // to the underlying transport protocol version. + SendRequest(s grpc.ClientStream, resourceNames []string, rType xdsresource.ResourceType, version, nonce, errMsg string) error + // RecvResponse uses the provided stream to receive a response specific to + // the underlying transport protocol version. + RecvResponse(s grpc.ClientStream) (proto.Message, error) + // ParseResponse type asserts message to the versioned response, and + // retrieves the fields. + ParseResponse(r proto.Message) (xdsresource.ResourceType, []*anypb.Any, string, string, error) + + // The following are LRS methods. + + // NewLoadStatsStream returns a new LRS client stream specific to the + // underlying transport protocol version. + NewLoadStatsStream(ctx context.Context, cc *grpc.ClientConn) (grpc.ClientStream, error) + // SendFirstLoadStatsRequest constructs and sends the first request on the + // LRS stream. + SendFirstLoadStatsRequest(s grpc.ClientStream) error + // HandleLoadStatsResponse receives the first response from the server which + // contains the load reporting interval and the clusters for which the + // server asks the client to report load for. + // + // If the response sets SendAllClusters to true, the returned clusters is + // nil. + HandleLoadStatsResponse(s grpc.ClientStream) (clusters []string, _ time.Duration, _ error) + // SendLoadStatsRequest will be invoked at regular intervals to send load + // report with load data reported since the last time this method was + // invoked. + SendLoadStatsRequest(s grpc.ClientStream, loads []*load.Data) error +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/dump.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/dump.go index db9b474f3..61c054d25 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/dump.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/dump.go @@ -18,106 +18,26 @@ package xdsclient -import anypb "github.com/golang/protobuf/ptypes/any" - -// UpdateWithMD contains the raw message of the update and the metadata, -// including version, raw message, timestamp. -// -// This is to be used for config dump and CSDS, not directly by users (like -// resolvers/balancers). -type UpdateWithMD struct { - MD UpdateMetadata - Raw *anypb.Any -} - -func rawFromCache(s string, cache interface{}) *anypb.Any { - switch c := cache.(type) { - case map[string]ListenerUpdate: - v, ok := c[s] - if !ok { - return nil - } - return v.Raw - case map[string]RouteConfigUpdate: - v, ok := c[s] - if !ok { - return nil - } - return v.Raw - case map[string]ClusterUpdate: - v, ok := c[s] - if !ok { - return nil - } - return v.Raw - case map[string]EndpointsUpdate: - v, ok := c[s] - if !ok { - return nil - } - return v.Raw - default: - return nil - } -} - -func (c *clientImpl) dump(t ResourceType) (string, map[string]UpdateWithMD) { - c.mu.Lock() - defer c.mu.Unlock() - - var ( - version string - md map[string]UpdateMetadata - cache interface{} - ) - switch t { - case ListenerResource: - version = c.ldsVersion - md = c.ldsMD - cache = c.ldsCache - case RouteConfigResource: - version = c.rdsVersion - md = c.rdsMD - cache = c.rdsCache - case ClusterResource: - version = c.cdsVersion - md = c.cdsMD - cache = c.cdsCache - case EndpointsResource: - version = c.edsVersion - md = c.edsMD - cache = c.edsCache - default: - c.logger.Errorf("dumping resource of unknown type: %v", t) - return "", nil - } - - ret := make(map[string]UpdateWithMD, len(md)) - for s, md := range md { - ret[s] = UpdateWithMD{ - MD: md, - Raw: rawFromCache(s, cache), - } - } - return version, ret -} +import ( + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) // DumpLDS returns the status and contents of LDS. -func (c *clientImpl) DumpLDS() (string, map[string]UpdateWithMD) { - return c.dump(ListenerResource) +func (c *clientImpl) DumpLDS() map[string]xdsresource.UpdateWithMD { + return c.pubsub.Dump(xdsresource.ListenerResource) } // DumpRDS returns the status and contents of RDS. -func (c *clientImpl) DumpRDS() (string, map[string]UpdateWithMD) { - return c.dump(RouteConfigResource) +func (c *clientImpl) DumpRDS() map[string]xdsresource.UpdateWithMD { + return c.pubsub.Dump(xdsresource.RouteConfigResource) } // DumpCDS returns the status and contents of CDS. -func (c *clientImpl) DumpCDS() (string, map[string]UpdateWithMD) { - return c.dump(ClusterResource) +func (c *clientImpl) DumpCDS() map[string]xdsresource.UpdateWithMD { + return c.pubsub.Dump(xdsresource.ClusterResource) } // DumpEDS returns the status and contents of EDS. -func (c *clientImpl) DumpEDS() (string, map[string]UpdateWithMD) { - return c.dump(EndpointsResource) +func (c *clientImpl) DumpEDS() map[string]xdsresource.UpdateWithMD { + return c.pubsub.Dump(xdsresource.EndpointsResource) } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/loadreport.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/loadreport.go index 32a71dada..21400c132 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/loadreport.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/loadreport.go @@ -17,12 +17,7 @@ package xdsclient -import ( - "context" - - "google.golang.org/grpc" - "google.golang.org/grpc/xds/internal/xdsclient/load" -) +import "google.golang.org/grpc/xds/internal/xdsclient/load" // ReportLoad starts an load reporting stream to the given server. If the server // is not an empty string, and is different from the management server, a new @@ -34,106 +29,5 @@ import ( // It returns a Store for the user to report loads, a function to cancel the // load reporting stream. func (c *clientImpl) ReportLoad(server string) (*load.Store, func()) { - c.lrsMu.Lock() - defer c.lrsMu.Unlock() - - // If there's already a client to this server, use it. Otherwise, create - // one. - lrsC, ok := c.lrsClients[server] - if !ok { - lrsC = newLRSClient(c, server) - c.lrsClients[server] = lrsC - } - - store := lrsC.ref() - return store, func() { - // This is a callback, need to hold lrsMu. - c.lrsMu.Lock() - defer c.lrsMu.Unlock() - if lrsC.unRef() { - // Delete the lrsClient from map if this is the last reference. - delete(c.lrsClients, server) - } - } -} - -// lrsClient maps to one lrsServer. It contains: -// - a ClientConn to this server (only if it's different from the management -// server) -// - a load.Store that contains loads only for this server -type lrsClient struct { - parent *clientImpl - server string - - cc *grpc.ClientConn // nil if the server is same as the management server - refCount int - cancelStream func() - loadStore *load.Store -} - -// newLRSClient creates a new LRS stream to the server. -func newLRSClient(parent *clientImpl, server string) *lrsClient { - return &lrsClient{ - parent: parent, - server: server, - refCount: 0, - } -} - -// ref increments the refCount. If this is the first ref, it starts the LRS stream. -// -// Not thread-safe, caller needs to synchronize. -func (lrsC *lrsClient) ref() *load.Store { - lrsC.refCount++ - if lrsC.refCount == 1 { - lrsC.startStream() - } - return lrsC.loadStore -} - -// unRef decrements the refCount, and closes the stream if refCount reaches 0 -// (and close the cc if cc is not xDS cc). It returns whether refCount reached 0 -// after this call. -// -// Not thread-safe, caller needs to synchronize. -func (lrsC *lrsClient) unRef() (closed bool) { - lrsC.refCount-- - if lrsC.refCount != 0 { - return false - } - lrsC.parent.logger.Infof("Stopping load report to server: %s", lrsC.server) - lrsC.cancelStream() - if lrsC.cc != nil { - lrsC.cc.Close() - } - return true -} - -// startStream starts the LRS stream to the server. If server is not the same -// management server from the parent, it also creates a ClientConn. -func (lrsC *lrsClient) startStream() { - var cc *grpc.ClientConn - - lrsC.parent.logger.Infof("Starting load report to server: %s", lrsC.server) - if lrsC.server == "" || lrsC.server == lrsC.parent.config.BalancerName { - // Reuse the xDS client if server is the same. - cc = lrsC.parent.cc - } else { - lrsC.parent.logger.Infof("LRS server is different from management server, starting a new ClientConn") - ccNew, err := grpc.Dial(lrsC.server, lrsC.parent.config.Creds) - if err != nil { - // An error from a non-blocking dial indicates something serious. - lrsC.parent.logger.Infof("xds: failed to dial load report server {%s}: %v", lrsC.server, err) - return - } - cc = ccNew - lrsC.cc = ccNew - } - - var ctx context.Context - ctx, lrsC.cancelStream = context.WithCancel(context.Background()) - - // Create the store and stream. - lrsC.loadStore = load.NewStore() - go lrsC.parent.apiClient.reportLoad(ctx, cc, loadReportingOptions{loadStore: lrsC.loadStore}) + return c.controller.ReportLoad(server) } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/dump.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/dump.go new file mode 100644 index 000000000..2ff19a901 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/dump.go @@ -0,0 +1,87 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package pubsub + +import ( + anypb "github.com/golang/protobuf/ptypes/any" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) + +func rawFromCache(s string, cache interface{}) *anypb.Any { + switch c := cache.(type) { + case map[string]xdsresource.ListenerUpdate: + if v, ok := c[s]; ok { + return v.Raw + } + return nil + case map[string]xdsresource.RouteConfigUpdate: + if v, ok := c[s]; ok { + return v.Raw + } + return nil + case map[string]xdsresource.ClusterUpdate: + if v, ok := c[s]; ok { + return v.Raw + } + return nil + case map[string]xdsresource.EndpointsUpdate: + if v, ok := c[s]; ok { + return v.Raw + } + return nil + default: + return nil + } +} + +// Dump dumps the resource for the given type. +func (pb *Pubsub) Dump(t xdsresource.ResourceType) map[string]xdsresource.UpdateWithMD { + pb.mu.Lock() + defer pb.mu.Unlock() + + var ( + md map[string]xdsresource.UpdateMetadata + cache interface{} + ) + switch t { + case xdsresource.ListenerResource: + md = pb.ldsMD + cache = pb.ldsCache + case xdsresource.RouteConfigResource: + md = pb.rdsMD + cache = pb.rdsCache + case xdsresource.ClusterResource: + md = pb.cdsMD + cache = pb.cdsCache + case xdsresource.EndpointsResource: + md = pb.edsMD + cache = pb.edsCache + default: + pb.logger.Errorf("dumping resource of unknown type: %v", t) + return nil + } + + ret := make(map[string]xdsresource.UpdateWithMD, len(md)) + for s, md := range md { + ret[s] = xdsresource.UpdateWithMD{ + MD: md, + Raw: rawFromCache(s, cache), + } + } + return ret +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/interface.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/interface.go new file mode 100644 index 000000000..334ec101e --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/interface.go @@ -0,0 +1,39 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package pubsub + +import "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" + +// UpdateHandler receives and processes (by taking appropriate actions) xDS +// resource updates from an APIClient for a specific version. +// +// It's a subset of the APIs of a *Pubsub. +type UpdateHandler interface { + // NewListeners handles updates to xDS listener resources. + NewListeners(map[string]xdsresource.ListenerUpdateErrTuple, xdsresource.UpdateMetadata) + // NewRouteConfigs handles updates to xDS RouteConfiguration resources. + NewRouteConfigs(map[string]xdsresource.RouteConfigUpdateErrTuple, xdsresource.UpdateMetadata) + // NewClusters handles updates to xDS Cluster resources. + NewClusters(map[string]xdsresource.ClusterUpdateErrTuple, xdsresource.UpdateMetadata) + // NewEndpoints handles updates to xDS ClusterLoadAssignment (or tersely + // referred to as Endpoints) resources. + NewEndpoints(map[string]xdsresource.EndpointsUpdateErrTuple, xdsresource.UpdateMetadata) + // NewConnectionError handles connection errors from the xDS stream. The + // error will be reported to all the resource watchers. + NewConnectionError(err error) +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/pubsub.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/pubsub.go new file mode 100644 index 000000000..a843fd5f1 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/pubsub.go @@ -0,0 +1,182 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +// Package pubsub implements a utility type to maintain resource watchers and +// the updates. +// +// This package is designed to work with the xds resources. It could be made a +// general system that works with all types. +package pubsub + +import ( + "sync" + "time" + + "google.golang.org/grpc/internal/buffer" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/grpcsync" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) + +// Pubsub maintains resource watchers and resource updates. +// +// There can be multiple watchers for the same resource. An update to a resource +// triggers updates to all the existing watchers. Watchers can be canceled at +// any time. +type Pubsub struct { + done *grpcsync.Event + logger *grpclog.PrefixLogger + watchExpiryTimeout time.Duration + + updateCh *buffer.Unbounded // chan *watcherInfoWithUpdate + // All the following maps are to keep the updates/metadata in a cache. + mu sync.Mutex + ldsWatchers map[string]map[*watchInfo]bool + ldsCache map[string]xdsresource.ListenerUpdate + ldsMD map[string]xdsresource.UpdateMetadata + rdsWatchers map[string]map[*watchInfo]bool + rdsCache map[string]xdsresource.RouteConfigUpdate + rdsMD map[string]xdsresource.UpdateMetadata + cdsWatchers map[string]map[*watchInfo]bool + cdsCache map[string]xdsresource.ClusterUpdate + cdsMD map[string]xdsresource.UpdateMetadata + edsWatchers map[string]map[*watchInfo]bool + edsCache map[string]xdsresource.EndpointsUpdate + edsMD map[string]xdsresource.UpdateMetadata +} + +// New creates a new Pubsub. +func New(watchExpiryTimeout time.Duration, logger *grpclog.PrefixLogger) *Pubsub { + pb := &Pubsub{ + done: grpcsync.NewEvent(), + logger: logger, + watchExpiryTimeout: watchExpiryTimeout, + + updateCh: buffer.NewUnbounded(), + ldsWatchers: make(map[string]map[*watchInfo]bool), + ldsCache: make(map[string]xdsresource.ListenerUpdate), + ldsMD: make(map[string]xdsresource.UpdateMetadata), + rdsWatchers: make(map[string]map[*watchInfo]bool), + rdsCache: make(map[string]xdsresource.RouteConfigUpdate), + rdsMD: make(map[string]xdsresource.UpdateMetadata), + cdsWatchers: make(map[string]map[*watchInfo]bool), + cdsCache: make(map[string]xdsresource.ClusterUpdate), + cdsMD: make(map[string]xdsresource.UpdateMetadata), + edsWatchers: make(map[string]map[*watchInfo]bool), + edsCache: make(map[string]xdsresource.EndpointsUpdate), + edsMD: make(map[string]xdsresource.UpdateMetadata), + } + go pb.run() + return pb +} + +// WatchListener registers a watcher for the LDS resource. +// +// It also returns whether this is the first watch for this resource. +func (pb *Pubsub) WatchListener(serviceName string, cb func(xdsresource.ListenerUpdate, error)) (first bool, cancel func() bool) { + wi := &watchInfo{ + c: pb, + rType: xdsresource.ListenerResource, + target: serviceName, + ldsCallback: cb, + } + + wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { + wi.timeout() + }) + return pb.watch(wi) +} + +// WatchRouteConfig register a watcher for the RDS resource. +// +// It also returns whether this is the first watch for this resource. +func (pb *Pubsub) WatchRouteConfig(routeName string, cb func(xdsresource.RouteConfigUpdate, error)) (first bool, cancel func() bool) { + wi := &watchInfo{ + c: pb, + rType: xdsresource.RouteConfigResource, + target: routeName, + rdsCallback: cb, + } + + wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { + wi.timeout() + }) + return pb.watch(wi) +} + +// WatchCluster register a watcher for the CDS resource. +// +// It also returns whether this is the first watch for this resource. +func (pb *Pubsub) WatchCluster(clusterName string, cb func(xdsresource.ClusterUpdate, error)) (first bool, cancel func() bool) { + wi := &watchInfo{ + c: pb, + rType: xdsresource.ClusterResource, + target: clusterName, + cdsCallback: cb, + } + + wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { + wi.timeout() + }) + return pb.watch(wi) +} + +// WatchEndpoints registers a watcher for the EDS resource. +// +// It also returns whether this is the first watch for this resource. +func (pb *Pubsub) WatchEndpoints(clusterName string, cb func(xdsresource.EndpointsUpdate, error)) (first bool, cancel func() bool) { + wi := &watchInfo{ + c: pb, + rType: xdsresource.EndpointsResource, + target: clusterName, + edsCallback: cb, + } + + wi.expiryTimer = time.AfterFunc(pb.watchExpiryTimeout, func() { + wi.timeout() + }) + return pb.watch(wi) +} + +// Close closes the pubsub. +func (pb *Pubsub) Close() { + if pb.done.HasFired() { + return + } + pb.done.Fire() +} + +// run is a goroutine for all the callbacks. +// +// Callback can be called in watch(), if an item is found in cache. Without this +// goroutine, the callback will be called inline, which might cause a deadlock +// in user's code. Callbacks also cannot be simple `go callback()` because the +// order matters. +func (pb *Pubsub) run() { + for { + select { + case t := <-pb.updateCh.Get(): + pb.updateCh.Load() + if pb.done.HasFired() { + return + } + pb.callCallback(t.(*watcherInfoWithUpdate)) + case <-pb.done.Done(): + return + } + } +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/update.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/update.go new file mode 100644 index 000000000..371405b67 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/update.go @@ -0,0 +1,318 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package pubsub + +import ( + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" + "google.golang.org/protobuf/proto" +) + +type watcherInfoWithUpdate struct { + wi *watchInfo + update interface{} + err error +} + +// scheduleCallback should only be called by methods of watchInfo, which checks +// for watcher states and maintain consistency. +func (pb *Pubsub) scheduleCallback(wi *watchInfo, update interface{}, err error) { + pb.updateCh.Put(&watcherInfoWithUpdate{ + wi: wi, + update: update, + err: err, + }) +} + +func (pb *Pubsub) callCallback(wiu *watcherInfoWithUpdate) { + pb.mu.Lock() + // Use a closure to capture the callback and type assertion, to save one + // more switch case. + // + // The callback must be called without pb.mu. Otherwise if the callback calls + // another watch() inline, it will cause a deadlock. This leaves a small + // window that a watcher's callback could be called after the watcher is + // canceled, and the user needs to take care of it. + var ccb func() + switch wiu.wi.rType { + case xdsresource.ListenerResource: + if s, ok := pb.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] { + ccb = func() { wiu.wi.ldsCallback(wiu.update.(xdsresource.ListenerUpdate), wiu.err) } + } + case xdsresource.RouteConfigResource: + if s, ok := pb.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { + ccb = func() { wiu.wi.rdsCallback(wiu.update.(xdsresource.RouteConfigUpdate), wiu.err) } + } + case xdsresource.ClusterResource: + if s, ok := pb.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] { + ccb = func() { wiu.wi.cdsCallback(wiu.update.(xdsresource.ClusterUpdate), wiu.err) } + } + case xdsresource.EndpointsResource: + if s, ok := pb.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] { + ccb = func() { wiu.wi.edsCallback(wiu.update.(xdsresource.EndpointsUpdate), wiu.err) } + } + } + pb.mu.Unlock() + + if ccb != nil { + ccb() + } +} + +// NewListeners is called when there's a new LDS update. +func (pb *Pubsub) NewListeners(updates map[string]xdsresource.ListenerUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + pb.mu.Lock() + defer pb.mu.Unlock() + + for name, uErr := range updates { + if s, ok := pb.ldsWatchers[name]; ok { + if uErr.Err != nil { + // On error, keep previous version for each resource. But update + // status and error. + mdCopy := pb.ldsMD[name] + mdCopy.ErrState = metadata.ErrState + mdCopy.Status = metadata.Status + pb.ldsMD[name] = mdCopy + for wi := range s { + wi.newError(uErr.Err) + } + continue + } + // If we get here, it means that the update is a valid one. Notify + // watchers only if this is a first time update or it is different + // from the one currently cached. + if cur, ok := pb.ldsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { + for wi := range s { + wi.newUpdate(uErr.Update) + } + } + // Sync cache. + pb.logger.Debugf("LDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) + pb.ldsCache[name] = uErr.Update + // Set status to ACK, and clear error state. The metadata might be a + // NACK metadata because some other resources in the same response + // are invalid. + mdCopy := metadata + mdCopy.Status = xdsresource.ServiceStatusACKed + mdCopy.ErrState = nil + if metadata.ErrState != nil { + mdCopy.Version = metadata.ErrState.Version + } + pb.ldsMD[name] = mdCopy + } + } + // Resources not in the new update were removed by the server, so delete + // them. + for name := range pb.ldsCache { + if _, ok := updates[name]; !ok { + // If resource exists in cache, but not in the new update, delete + // the resource from cache, and also send an resource not found + // error to indicate resource removed. + delete(pb.ldsCache, name) + pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} + for wi := range pb.ldsWatchers[name] { + wi.resourceNotFound() + } + } + } + // When LDS resource is removed, we don't delete corresponding RDS cached + // data. The RDS watch will be canceled, and cache entry is removed when the + // last watch is canceled. +} + +// NewRouteConfigs is called when there's a new RDS update. +func (pb *Pubsub) NewRouteConfigs(updates map[string]xdsresource.RouteConfigUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + pb.mu.Lock() + defer pb.mu.Unlock() + + // If no error received, the status is ACK. + for name, uErr := range updates { + if s, ok := pb.rdsWatchers[name]; ok { + if uErr.Err != nil { + // On error, keep previous version for each resource. But update + // status and error. + mdCopy := pb.rdsMD[name] + mdCopy.ErrState = metadata.ErrState + mdCopy.Status = metadata.Status + pb.rdsMD[name] = mdCopy + for wi := range s { + wi.newError(uErr.Err) + } + continue + } + // If we get here, it means that the update is a valid one. Notify + // watchers only if this is a first time update or it is different + // from the one currently cached. + if cur, ok := pb.rdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { + for wi := range s { + wi.newUpdate(uErr.Update) + } + } + // Sync cache. + pb.logger.Debugf("RDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) + pb.rdsCache[name] = uErr.Update + // Set status to ACK, and clear error state. The metadata might be a + // NACK metadata because some other resources in the same response + // are invalid. + mdCopy := metadata + mdCopy.Status = xdsresource.ServiceStatusACKed + mdCopy.ErrState = nil + if metadata.ErrState != nil { + mdCopy.Version = metadata.ErrState.Version + } + pb.rdsMD[name] = mdCopy + } + } +} + +// NewClusters is called when there's a new CDS update. +func (pb *Pubsub) NewClusters(updates map[string]xdsresource.ClusterUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + pb.mu.Lock() + defer pb.mu.Unlock() + + for name, uErr := range updates { + if s, ok := pb.cdsWatchers[name]; ok { + if uErr.Err != nil { + // On error, keep previous version for each resource. But update + // status and error. + mdCopy := pb.cdsMD[name] + mdCopy.ErrState = metadata.ErrState + mdCopy.Status = metadata.Status + pb.cdsMD[name] = mdCopy + for wi := range s { + // Send the watcher the individual error, instead of the + // overall combined error from the metadata.ErrState. + wi.newError(uErr.Err) + } + continue + } + // If we get here, it means that the update is a valid one. Notify + // watchers only if this is a first time update or it is different + // from the one currently cached. + if cur, ok := pb.cdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { + for wi := range s { + wi.newUpdate(uErr.Update) + } + } + // Sync cache. + pb.logger.Debugf("CDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) + pb.cdsCache[name] = uErr.Update + // Set status to ACK, and clear error state. The metadata might be a + // NACK metadata because some other resources in the same response + // are invalid. + mdCopy := metadata + mdCopy.Status = xdsresource.ServiceStatusACKed + mdCopy.ErrState = nil + if metadata.ErrState != nil { + mdCopy.Version = metadata.ErrState.Version + } + pb.cdsMD[name] = mdCopy + } + } + // Resources not in the new update were removed by the server, so delete + // them. + for name := range pb.cdsCache { + if _, ok := updates[name]; !ok { + // If resource exists in cache, but not in the new update, delete it + // from cache, and also send an resource not found error to indicate + // resource removed. + delete(pb.cdsCache, name) + pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist} + for wi := range pb.cdsWatchers[name] { + wi.resourceNotFound() + } + } + } + // When CDS resource is removed, we don't delete corresponding EDS cached + // data. The EDS watch will be canceled, and cache entry is removed when the + // last watch is canceled. +} + +// NewEndpoints is called when there's anew EDS update. +func (pb *Pubsub) NewEndpoints(updates map[string]xdsresource.EndpointsUpdateErrTuple, metadata xdsresource.UpdateMetadata) { + pb.mu.Lock() + defer pb.mu.Unlock() + + for name, uErr := range updates { + if s, ok := pb.edsWatchers[name]; ok { + if uErr.Err != nil { + // On error, keep previous version for each resource. But update + // status and error. + mdCopy := pb.edsMD[name] + mdCopy.ErrState = metadata.ErrState + mdCopy.Status = metadata.Status + pb.edsMD[name] = mdCopy + for wi := range s { + // Send the watcher the individual error, instead of the + // overall combined error from the metadata.ErrState. + wi.newError(uErr.Err) + } + continue + } + // If we get here, it means that the update is a valid one. Notify + // watchers only if this is a first time update or it is different + // from the one currently cached. + if cur, ok := pb.edsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) { + for wi := range s { + wi.newUpdate(uErr.Update) + } + } + // Sync cache. + pb.logger.Debugf("EDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr)) + pb.edsCache[name] = uErr.Update + // Set status to ACK, and clear error state. The metadata might be a + // NACK metadata because some other resources in the same response + // are invalid. + mdCopy := metadata + mdCopy.Status = xdsresource.ServiceStatusACKed + mdCopy.ErrState = nil + if metadata.ErrState != nil { + mdCopy.Version = metadata.ErrState.Version + } + pb.edsMD[name] = mdCopy + } + } +} + +// NewConnectionError is called by the underlying xdsAPIClient when it receives +// a connection error. The error will be forwarded to all the resource watchers. +func (pb *Pubsub) NewConnectionError(err error) { + pb.mu.Lock() + defer pb.mu.Unlock() + + for _, s := range pb.ldsWatchers { + for wi := range s { + wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) + } + } + for _, s := range pb.rdsWatchers { + for wi := range s { + wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) + } + } + for _, s := range pb.cdsWatchers { + for wi := range s { + wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) + } + } + for _, s := range pb.edsWatchers { + for wi := range s { + wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err)) + } + } +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/watch.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/watch.go new file mode 100644 index 000000000..0baa68317 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/pubsub/watch.go @@ -0,0 +1,232 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package pubsub + +import ( + "fmt" + "sync" + "time" + + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" +) + +type watchInfoState int + +const ( + watchInfoStateStarted watchInfoState = iota + watchInfoStateRespReceived + watchInfoStateTimeout + watchInfoStateCanceled +) + +// watchInfo holds all the information from a watch() call. +type watchInfo struct { + c *Pubsub + rType xdsresource.ResourceType + target string + + ldsCallback func(xdsresource.ListenerUpdate, error) + rdsCallback func(xdsresource.RouteConfigUpdate, error) + cdsCallback func(xdsresource.ClusterUpdate, error) + edsCallback func(xdsresource.EndpointsUpdate, error) + + expiryTimer *time.Timer + + // mu protects state, and c.scheduleCallback(). + // - No callback should be scheduled after watchInfo is canceled. + // - No timeout error should be scheduled after watchInfo is resp received. + mu sync.Mutex + state watchInfoState +} + +func (wi *watchInfo) newUpdate(update interface{}) { + wi.mu.Lock() + defer wi.mu.Unlock() + if wi.state == watchInfoStateCanceled { + return + } + wi.state = watchInfoStateRespReceived + wi.expiryTimer.Stop() + wi.c.scheduleCallback(wi, update, nil) +} + +func (wi *watchInfo) newError(err error) { + wi.mu.Lock() + defer wi.mu.Unlock() + if wi.state == watchInfoStateCanceled { + return + } + wi.state = watchInfoStateRespReceived + wi.expiryTimer.Stop() + wi.sendErrorLocked(err) +} + +func (wi *watchInfo) resourceNotFound() { + wi.mu.Lock() + defer wi.mu.Unlock() + if wi.state == watchInfoStateCanceled { + return + } + wi.state = watchInfoStateRespReceived + wi.expiryTimer.Stop() + wi.sendErrorLocked(xdsresource.NewErrorf(xdsresource.ErrorTypeResourceNotFound, "xds: %v target %s not found in received response", wi.rType, wi.target)) +} + +func (wi *watchInfo) timeout() { + wi.mu.Lock() + defer wi.mu.Unlock() + if wi.state == watchInfoStateCanceled || wi.state == watchInfoStateRespReceived { + return + } + wi.state = watchInfoStateTimeout + wi.sendErrorLocked(fmt.Errorf("xds: %v target %s not found, watcher timeout", wi.rType, wi.target)) +} + +// Caller must hold wi.mu. +func (wi *watchInfo) sendErrorLocked(err error) { + var ( + u interface{} + ) + switch wi.rType { + case xdsresource.ListenerResource: + u = xdsresource.ListenerUpdate{} + case xdsresource.RouteConfigResource: + u = xdsresource.RouteConfigUpdate{} + case xdsresource.ClusterResource: + u = xdsresource.ClusterUpdate{} + case xdsresource.EndpointsResource: + u = xdsresource.EndpointsUpdate{} + } + wi.c.scheduleCallback(wi, u, err) +} + +func (wi *watchInfo) cancel() { + wi.mu.Lock() + defer wi.mu.Unlock() + if wi.state == watchInfoStateCanceled { + return + } + wi.expiryTimer.Stop() + wi.state = watchInfoStateCanceled +} + +func (pb *Pubsub) watch(wi *watchInfo) (first bool, cancel func() bool) { + pb.mu.Lock() + defer pb.mu.Unlock() + pb.logger.Debugf("new watch for type %v, resource name %v", wi.rType, wi.target) + var ( + watchers map[string]map[*watchInfo]bool + mds map[string]xdsresource.UpdateMetadata + ) + switch wi.rType { + case xdsresource.ListenerResource: + watchers = pb.ldsWatchers + mds = pb.ldsMD + case xdsresource.RouteConfigResource: + watchers = pb.rdsWatchers + mds = pb.rdsMD + case xdsresource.ClusterResource: + watchers = pb.cdsWatchers + mds = pb.cdsMD + case xdsresource.EndpointsResource: + watchers = pb.edsWatchers + mds = pb.edsMD + default: + pb.logger.Errorf("unknown watch type: %v", wi.rType) + return false, nil + } + + var firstWatcher bool + resourceName := wi.target + s, ok := watchers[wi.target] + if !ok { + // If this is a new watcher, will ask lower level to send a new request + // with the resource name. + // + // If this (type+name) is already being watched, will not notify the + // underlying versioned apiClient. + pb.logger.Debugf("first watch for type %v, resource name %v, will send a new xDS request", wi.rType, wi.target) + s = make(map[*watchInfo]bool) + watchers[resourceName] = s + mds[resourceName] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusRequested} + firstWatcher = true + } + // No matter what, add the new watcher to the set, so it's callback will be + // call for new responses. + s[wi] = true + + // If the resource is in cache, call the callback with the value. + switch wi.rType { + case xdsresource.ListenerResource: + if v, ok := pb.ldsCache[resourceName]; ok { + pb.logger.Debugf("LDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) + wi.newUpdate(v) + } + case xdsresource.RouteConfigResource: + if v, ok := pb.rdsCache[resourceName]; ok { + pb.logger.Debugf("RDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) + wi.newUpdate(v) + } + case xdsresource.ClusterResource: + if v, ok := pb.cdsCache[resourceName]; ok { + pb.logger.Debugf("CDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) + wi.newUpdate(v) + } + case xdsresource.EndpointsResource: + if v, ok := pb.edsCache[resourceName]; ok { + pb.logger.Debugf("EDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) + wi.newUpdate(v) + } + } + + return firstWatcher, func() bool { + pb.logger.Debugf("watch for type %v, resource name %v canceled", wi.rType, wi.target) + wi.cancel() + pb.mu.Lock() + defer pb.mu.Unlock() + var lastWatcher bool + if s := watchers[resourceName]; s != nil { + // Remove this watcher, so it's callback will not be called in the + // future. + delete(s, wi) + if len(s) == 0 { + pb.logger.Debugf("last watch for type %v, resource name %v canceled, will send a new xDS request", wi.rType, wi.target) + // If this was the last watcher, also tell xdsv2Client to stop + // watching this resource. + delete(watchers, resourceName) + delete(mds, resourceName) + lastWatcher = true + // Remove the resource from cache. When a watch for this + // resource is added later, it will trigger a xDS request with + // resource names, and client will receive new xDS responses. + switch wi.rType { + case xdsresource.ListenerResource: + delete(pb.ldsCache, resourceName) + case xdsresource.RouteConfigResource: + delete(pb.rdsCache, resourceName) + case xdsresource.ClusterResource: + delete(pb.cdsCache, resourceName) + case xdsresource.EndpointsResource: + delete(pb.edsCache, resourceName) + } + } + } + return lastWatcher + } +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v2/client.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/v2/client.go deleted file mode 100644 index dc137f63e..000000000 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v2/client.go +++ /dev/null @@ -1,238 +0,0 @@ -/* - * - * Copyright 2019 gRPC authors. - * - * 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. - * - */ - -// Package v2 provides xDS v2 transport protocol specific functionality. -package v2 - -import ( - "context" - "fmt" - - "github.com/golang/protobuf/proto" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/internal/grpclog" - "google.golang.org/grpc/internal/pretty" - "google.golang.org/grpc/xds/internal/version" - "google.golang.org/grpc/xds/internal/xdsclient" - - v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" - v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" - v2adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2" - statuspb "google.golang.org/genproto/googleapis/rpc/status" -) - -func init() { - xdsclient.RegisterAPIClientBuilder(clientBuilder{}) -} - -var ( - resourceTypeToURL = map[xdsclient.ResourceType]string{ - xdsclient.ListenerResource: version.V2ListenerURL, - xdsclient.RouteConfigResource: version.V2RouteConfigURL, - xdsclient.ClusterResource: version.V2ClusterURL, - xdsclient.EndpointsResource: version.V2EndpointsURL, - } -) - -type clientBuilder struct{} - -func (clientBuilder) Build(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) { - return newClient(cc, opts) -} - -func (clientBuilder) Version() version.TransportAPI { - return version.TransportV2 -} - -func newClient(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) { - nodeProto, ok := opts.NodeProto.(*v2corepb.Node) - if !ok { - return nil, fmt.Errorf("xds: unsupported Node proto type: %T, want %T", opts.NodeProto, (*v2corepb.Node)(nil)) - } - v2c := &client{ - cc: cc, - parent: opts.Parent, - nodeProto: nodeProto, - logger: opts.Logger, - updateValidator: opts.Validator, - } - v2c.ctx, v2c.cancelCtx = context.WithCancel(context.Background()) - v2c.TransportHelper = xdsclient.NewTransportHelper(v2c, opts.Logger, opts.Backoff) - return v2c, nil -} - -type adsStream v2adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient - -// client performs the actual xDS RPCs using the xDS v2 API. It creates a -// single ADS stream on which the different types of xDS requests and responses -// are multiplexed. -type client struct { - *xdsclient.TransportHelper - - ctx context.Context - cancelCtx context.CancelFunc - parent xdsclient.UpdateHandler - logger *grpclog.PrefixLogger - - // ClientConn to the xDS gRPC server. Owned by the parent xdsClient. - cc *grpc.ClientConn - nodeProto *v2corepb.Node - updateValidator xdsclient.UpdateValidatorFunc -} - -func (v2c *client) NewStream(ctx context.Context) (grpc.ClientStream, error) { - return v2adsgrpc.NewAggregatedDiscoveryServiceClient(v2c.cc).StreamAggregatedResources(v2c.ctx, grpc.WaitForReady(true)) -} - -// sendRequest sends out a DiscoveryRequest for the given resourceNames, of type -// rType, on the provided stream. -// -// version is the ack version to be sent with the request -// - If this is the new request (not an ack/nack), version will be empty. -// - If this is an ack, version will be the version from the response. -// - If this is a nack, version will be the previous acked version (from -// versionMap). If there was no ack before, it will be empty. -func (v2c *client) SendRequest(s grpc.ClientStream, resourceNames []string, rType xdsclient.ResourceType, version, nonce, errMsg string) error { - stream, ok := s.(adsStream) - if !ok { - return fmt.Errorf("xds: Attempt to send request on unsupported stream type: %T", s) - } - req := &v2xdspb.DiscoveryRequest{ - Node: v2c.nodeProto, - TypeUrl: resourceTypeToURL[rType], - ResourceNames: resourceNames, - VersionInfo: version, - ResponseNonce: nonce, - } - if errMsg != "" { - req.ErrorDetail = &statuspb.Status{ - Code: int32(codes.InvalidArgument), Message: errMsg, - } - } - if err := stream.Send(req); err != nil { - return fmt.Errorf("xds: stream.Send(%+v) failed: %v", req, err) - } - v2c.logger.Debugf("ADS request sent: %v", pretty.ToJSON(req)) - return nil -} - -// RecvResponse blocks on the receipt of one response message on the provided -// stream. -func (v2c *client) RecvResponse(s grpc.ClientStream) (proto.Message, error) { - stream, ok := s.(adsStream) - if !ok { - return nil, fmt.Errorf("xds: Attempt to receive response on unsupported stream type: %T", s) - } - - resp, err := stream.Recv() - if err != nil { - v2c.parent.NewConnectionError(err) - return nil, fmt.Errorf("xds: stream.Recv() failed: %v", err) - } - v2c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl()) - v2c.logger.Debugf("ADS response received: %v", pretty.ToJSON(resp)) - return resp, nil -} - -func (v2c *client) HandleResponse(r proto.Message) (xdsclient.ResourceType, string, string, error) { - rType := xdsclient.UnknownResource - resp, ok := r.(*v2xdspb.DiscoveryResponse) - if !ok { - return rType, "", "", fmt.Errorf("xds: unsupported message type: %T", resp) - } - - // Note that the xDS transport protocol is versioned independently of - // the resource types, and it is supported to transfer older versions - // of resource types using new versions of the transport protocol, or - // vice-versa. Hence we need to handle v3 type_urls as well here. - var err error - url := resp.GetTypeUrl() - switch { - case xdsclient.IsListenerResource(url): - err = v2c.handleLDSResponse(resp) - rType = xdsclient.ListenerResource - case xdsclient.IsRouteConfigResource(url): - err = v2c.handleRDSResponse(resp) - rType = xdsclient.RouteConfigResource - case xdsclient.IsClusterResource(url): - err = v2c.handleCDSResponse(resp) - rType = xdsclient.ClusterResource - case xdsclient.IsEndpointsResource(url): - err = v2c.handleEDSResponse(resp) - rType = xdsclient.EndpointsResource - default: - return rType, "", "", xdsclient.ErrResourceTypeUnsupported{ - ErrStr: fmt.Sprintf("Resource type %v unknown in response from server", resp.GetTypeUrl()), - } - } - return rType, resp.GetVersionInfo(), resp.GetNonce(), err -} - -// handleLDSResponse processes an LDS response received from the management -// server. On receipt of a good response, it also invokes the registered watcher -// callback. -func (v2c *client) handleLDSResponse(resp *v2xdspb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalListener(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v2c.logger, - UpdateValidator: v2c.updateValidator, - }) - v2c.parent.NewListeners(update, md) - return err -} - -// handleRDSResponse processes an RDS response received from the management -// server. On receipt of a good response, it caches validated resources and also -// invokes the registered watcher callback. -func (v2c *client) handleRDSResponse(resp *v2xdspb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalRouteConfig(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v2c.logger, - UpdateValidator: v2c.updateValidator, - }) - v2c.parent.NewRouteConfigs(update, md) - return err -} - -// handleCDSResponse processes an CDS response received from the management -// server. On receipt of a good response, it also invokes the registered watcher -// callback. -func (v2c *client) handleCDSResponse(resp *v2xdspb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalCluster(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v2c.logger, - UpdateValidator: v2c.updateValidator, - }) - v2c.parent.NewClusters(update, md) - return err -} - -func (v2c *client) handleEDSResponse(resp *v2xdspb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalEndpoints(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v2c.logger, - UpdateValidator: v2c.updateValidator, - }) - v2c.parent.NewEndpoints(update, md) - return err -} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v3/client.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/v3/client.go deleted file mode 100644 index 827c06b74..000000000 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/v3/client.go +++ /dev/null @@ -1,238 +0,0 @@ -/* - * - * Copyright 2020 gRPC authors. - * - * 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. - * - */ - -// Package v3 provides xDS v3 transport protocol specific functionality. -package v3 - -import ( - "context" - "fmt" - - "github.com/golang/protobuf/proto" - statuspb "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/internal/grpclog" - "google.golang.org/grpc/internal/pretty" - "google.golang.org/grpc/xds/internal/version" - "google.golang.org/grpc/xds/internal/xdsclient" - - v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - v3adsgrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" - v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" -) - -func init() { - xdsclient.RegisterAPIClientBuilder(clientBuilder{}) -} - -var ( - resourceTypeToURL = map[xdsclient.ResourceType]string{ - xdsclient.ListenerResource: version.V3ListenerURL, - xdsclient.RouteConfigResource: version.V3RouteConfigURL, - xdsclient.ClusterResource: version.V3ClusterURL, - xdsclient.EndpointsResource: version.V3EndpointsURL, - } -) - -type clientBuilder struct{} - -func (clientBuilder) Build(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) { - return newClient(cc, opts) -} - -func (clientBuilder) Version() version.TransportAPI { - return version.TransportV3 -} - -func newClient(cc *grpc.ClientConn, opts xdsclient.BuildOptions) (xdsclient.APIClient, error) { - nodeProto, ok := opts.NodeProto.(*v3corepb.Node) - if !ok { - return nil, fmt.Errorf("xds: unsupported Node proto type: %T, want %T", opts.NodeProto, v3corepb.Node{}) - } - v3c := &client{ - cc: cc, - parent: opts.Parent, - nodeProto: nodeProto, - logger: opts.Logger, - updateValidator: opts.Validator, - } - v3c.ctx, v3c.cancelCtx = context.WithCancel(context.Background()) - v3c.TransportHelper = xdsclient.NewTransportHelper(v3c, opts.Logger, opts.Backoff) - return v3c, nil -} - -type adsStream v3adsgrpc.AggregatedDiscoveryService_StreamAggregatedResourcesClient - -// client performs the actual xDS RPCs using the xDS v3 API. It creates a -// single ADS stream on which the different types of xDS requests and responses -// are multiplexed. -type client struct { - *xdsclient.TransportHelper - - ctx context.Context - cancelCtx context.CancelFunc - parent xdsclient.UpdateHandler - logger *grpclog.PrefixLogger - - // ClientConn to the xDS gRPC server. Owned by the parent xdsClient. - cc *grpc.ClientConn - nodeProto *v3corepb.Node - updateValidator xdsclient.UpdateValidatorFunc -} - -func (v3c *client) NewStream(ctx context.Context) (grpc.ClientStream, error) { - return v3adsgrpc.NewAggregatedDiscoveryServiceClient(v3c.cc).StreamAggregatedResources(v3c.ctx, grpc.WaitForReady(true)) -} - -// sendRequest sends out a DiscoveryRequest for the given resourceNames, of type -// rType, on the provided stream. -// -// version is the ack version to be sent with the request -// - If this is the new request (not an ack/nack), version will be empty. -// - If this is an ack, version will be the version from the response. -// - If this is a nack, version will be the previous acked version (from -// versionMap). If there was no ack before, it will be empty. -func (v3c *client) SendRequest(s grpc.ClientStream, resourceNames []string, rType xdsclient.ResourceType, version, nonce, errMsg string) error { - stream, ok := s.(adsStream) - if !ok { - return fmt.Errorf("xds: Attempt to send request on unsupported stream type: %T", s) - } - req := &v3discoverypb.DiscoveryRequest{ - Node: v3c.nodeProto, - TypeUrl: resourceTypeToURL[rType], - ResourceNames: resourceNames, - VersionInfo: version, - ResponseNonce: nonce, - } - if errMsg != "" { - req.ErrorDetail = &statuspb.Status{ - Code: int32(codes.InvalidArgument), Message: errMsg, - } - } - if err := stream.Send(req); err != nil { - return fmt.Errorf("xds: stream.Send(%+v) failed: %v", req, err) - } - v3c.logger.Debugf("ADS request sent: %v", pretty.ToJSON(req)) - return nil -} - -// RecvResponse blocks on the receipt of one response message on the provided -// stream. -func (v3c *client) RecvResponse(s grpc.ClientStream) (proto.Message, error) { - stream, ok := s.(adsStream) - if !ok { - return nil, fmt.Errorf("xds: Attempt to receive response on unsupported stream type: %T", s) - } - - resp, err := stream.Recv() - if err != nil { - v3c.parent.NewConnectionError(err) - return nil, fmt.Errorf("xds: stream.Recv() failed: %v", err) - } - v3c.logger.Infof("ADS response received, type: %v", resp.GetTypeUrl()) - v3c.logger.Debugf("ADS response received: %+v", pretty.ToJSON(resp)) - return resp, nil -} - -func (v3c *client) HandleResponse(r proto.Message) (xdsclient.ResourceType, string, string, error) { - rType := xdsclient.UnknownResource - resp, ok := r.(*v3discoverypb.DiscoveryResponse) - if !ok { - return rType, "", "", fmt.Errorf("xds: unsupported message type: %T", resp) - } - - // Note that the xDS transport protocol is versioned independently of - // the resource types, and it is supported to transfer older versions - // of resource types using new versions of the transport protocol, or - // vice-versa. Hence we need to handle v3 type_urls as well here. - var err error - url := resp.GetTypeUrl() - switch { - case xdsclient.IsListenerResource(url): - err = v3c.handleLDSResponse(resp) - rType = xdsclient.ListenerResource - case xdsclient.IsRouteConfigResource(url): - err = v3c.handleRDSResponse(resp) - rType = xdsclient.RouteConfigResource - case xdsclient.IsClusterResource(url): - err = v3c.handleCDSResponse(resp) - rType = xdsclient.ClusterResource - case xdsclient.IsEndpointsResource(url): - err = v3c.handleEDSResponse(resp) - rType = xdsclient.EndpointsResource - default: - return rType, "", "", xdsclient.ErrResourceTypeUnsupported{ - ErrStr: fmt.Sprintf("Resource type %v unknown in response from server", resp.GetTypeUrl()), - } - } - return rType, resp.GetVersionInfo(), resp.GetNonce(), err -} - -// handleLDSResponse processes an LDS response received from the management -// server. On receipt of a good response, it also invokes the registered watcher -// callback. -func (v3c *client) handleLDSResponse(resp *v3discoverypb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalListener(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v3c.logger, - UpdateValidator: v3c.updateValidator, - }) - v3c.parent.NewListeners(update, md) - return err -} - -// handleRDSResponse processes an RDS response received from the management -// server. On receipt of a good response, it caches validated resources and also -// invokes the registered watcher callback. -func (v3c *client) handleRDSResponse(resp *v3discoverypb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalRouteConfig(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v3c.logger, - UpdateValidator: v3c.updateValidator, - }) - v3c.parent.NewRouteConfigs(update, md) - return err -} - -// handleCDSResponse processes an CDS response received from the management -// server. On receipt of a good response, it also invokes the registered watcher -// callback. -func (v3c *client) handleCDSResponse(resp *v3discoverypb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalCluster(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v3c.logger, - UpdateValidator: v3c.updateValidator, - }) - v3c.parent.NewClusters(update, md) - return err -} - -func (v3c *client) handleEDSResponse(resp *v3discoverypb.DiscoveryResponse) error { - update, md, err := xdsclient.UnmarshalEndpoints(&xdsclient.UnmarshalOptions{ - Version: resp.GetVersionInfo(), - Resources: resp.GetResources(), - Logger: v3c.logger, - UpdateValidator: v3c.updateValidator, - }) - v3c.parent.NewEndpoints(update, md) - return err -} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/watchers.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/watchers.go index e26ed3603..fe59dbbd6 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/watchers.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/watchers.go @@ -13,238 +13,29 @@ * 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. - * */ package xdsclient import ( - "fmt" - "sync" - "time" - - "google.golang.org/grpc/internal/pretty" -) - -type watchInfoState int - -const ( - watchInfoStateStarted watchInfoState = iota - watchInfoStateRespReceived - watchInfoStateTimeout - watchInfoStateCanceled + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) -// watchInfo holds all the information from a watch() call. -type watchInfo struct { - c *clientImpl - rType ResourceType - target string - - ldsCallback func(ListenerUpdate, error) - rdsCallback func(RouteConfigUpdate, error) - cdsCallback func(ClusterUpdate, error) - edsCallback func(EndpointsUpdate, error) - - expiryTimer *time.Timer - - // mu protects state, and c.scheduleCallback(). - // - No callback should be scheduled after watchInfo is canceled. - // - No timeout error should be scheduled after watchInfo is resp received. - mu sync.Mutex - state watchInfoState -} - -func (wi *watchInfo) newUpdate(update interface{}) { - wi.mu.Lock() - defer wi.mu.Unlock() - if wi.state == watchInfoStateCanceled { - return - } - wi.state = watchInfoStateRespReceived - wi.expiryTimer.Stop() - wi.c.scheduleCallback(wi, update, nil) -} - -func (wi *watchInfo) newError(err error) { - wi.mu.Lock() - defer wi.mu.Unlock() - if wi.state == watchInfoStateCanceled { - return - } - wi.state = watchInfoStateRespReceived - wi.expiryTimer.Stop() - wi.sendErrorLocked(err) -} - -func (wi *watchInfo) resourceNotFound() { - wi.mu.Lock() - defer wi.mu.Unlock() - if wi.state == watchInfoStateCanceled { - return - } - wi.state = watchInfoStateRespReceived - wi.expiryTimer.Stop() - wi.sendErrorLocked(NewErrorf(ErrorTypeResourceNotFound, "xds: %v target %s not found in received response", wi.rType, wi.target)) -} - -func (wi *watchInfo) timeout() { - wi.mu.Lock() - defer wi.mu.Unlock() - if wi.state == watchInfoStateCanceled || wi.state == watchInfoStateRespReceived { - return - } - wi.state = watchInfoStateTimeout - wi.sendErrorLocked(fmt.Errorf("xds: %v target %s not found, watcher timeout", wi.rType, wi.target)) -} - -// Caller must hold wi.mu. -func (wi *watchInfo) sendErrorLocked(err error) { - var ( - u interface{} - ) - switch wi.rType { - case ListenerResource: - u = ListenerUpdate{} - case RouteConfigResource: - u = RouteConfigUpdate{} - case ClusterResource: - u = ClusterUpdate{} - case EndpointsResource: - u = EndpointsUpdate{} - } - wi.c.scheduleCallback(wi, u, err) -} - -func (wi *watchInfo) cancel() { - wi.mu.Lock() - defer wi.mu.Unlock() - if wi.state == watchInfoStateCanceled { - return - } - wi.expiryTimer.Stop() - wi.state = watchInfoStateCanceled -} - -func (c *clientImpl) watch(wi *watchInfo) (cancel func()) { - c.mu.Lock() - defer c.mu.Unlock() - c.logger.Debugf("new watch for type %v, resource name %v", wi.rType, wi.target) - var ( - watchers map[string]map[*watchInfo]bool - mds map[string]UpdateMetadata - ) - switch wi.rType { - case ListenerResource: - watchers = c.ldsWatchers - mds = c.ldsMD - case RouteConfigResource: - watchers = c.rdsWatchers - mds = c.rdsMD - case ClusterResource: - watchers = c.cdsWatchers - mds = c.cdsMD - case EndpointsResource: - watchers = c.edsWatchers - mds = c.edsMD - default: - c.logger.Errorf("unknown watch type: %v", wi.rType) - return nil - } - - resourceName := wi.target - s, ok := watchers[wi.target] - if !ok { - // If this is a new watcher, will ask lower level to send a new request - // with the resource name. - // - // If this (type+name) is already being watched, will not notify the - // underlying versioned apiClient. - c.logger.Debugf("first watch for type %v, resource name %v, will send a new xDS request", wi.rType, wi.target) - s = make(map[*watchInfo]bool) - watchers[resourceName] = s - mds[resourceName] = UpdateMetadata{Status: ServiceStatusRequested} - c.apiClient.AddWatch(wi.rType, resourceName) - } - // No matter what, add the new watcher to the set, so it's callback will be - // call for new responses. - s[wi] = true - - // If the resource is in cache, call the callback with the value. - switch wi.rType { - case ListenerResource: - if v, ok := c.ldsCache[resourceName]; ok { - c.logger.Debugf("LDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) - wi.newUpdate(v) - } - case RouteConfigResource: - if v, ok := c.rdsCache[resourceName]; ok { - c.logger.Debugf("RDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) - wi.newUpdate(v) - } - case ClusterResource: - if v, ok := c.cdsCache[resourceName]; ok { - c.logger.Debugf("CDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) - wi.newUpdate(v) - } - case EndpointsResource: - if v, ok := c.edsCache[resourceName]; ok { - c.logger.Debugf("EDS resource with name %v found in cache: %+v", wi.target, pretty.ToJSON(v)) - wi.newUpdate(v) - } - } - - return func() { - c.logger.Debugf("watch for type %v, resource name %v canceled", wi.rType, wi.target) - wi.cancel() - c.mu.Lock() - defer c.mu.Unlock() - if s := watchers[resourceName]; s != nil { - // Remove this watcher, so it's callback will not be called in the - // future. - delete(s, wi) - if len(s) == 0 { - c.logger.Debugf("last watch for type %v, resource name %v canceled, will send a new xDS request", wi.rType, wi.target) - // If this was the last watcher, also tell xdsv2Client to stop - // watching this resource. - delete(watchers, resourceName) - delete(mds, resourceName) - c.apiClient.RemoveWatch(wi.rType, resourceName) - // Remove the resource from cache. When a watch for this - // resource is added later, it will trigger a xDS request with - // resource names, and client will receive new xDS responses. - switch wi.rType { - case ListenerResource: - delete(c.ldsCache, resourceName) - case RouteConfigResource: - delete(c.rdsCache, resourceName) - case ClusterResource: - delete(c.cdsCache, resourceName) - case EndpointsResource: - delete(c.edsCache, resourceName) - } - } - } - } -} - // WatchListener uses LDS to discover information about the provided listener. // // Note that during race (e.g. an xDS response is received while the user is // calling cancel()), there's a small window where the callback can be called // after the watcher is canceled. The caller needs to handle this case. -func (c *clientImpl) WatchListener(serviceName string, cb func(ListenerUpdate, error)) (cancel func()) { - wi := &watchInfo{ - c: c, - rType: ListenerResource, - target: serviceName, - ldsCallback: cb, +func (c *clientImpl) WatchListener(serviceName string, cb func(xdsresource.ListenerUpdate, error)) (cancel func()) { + first, cancelF := c.pubsub.WatchListener(serviceName, cb) + if first { + c.controller.AddWatch(xdsresource.ListenerResource, serviceName) + } + return func() { + if cancelF() { + c.controller.RemoveWatch(xdsresource.ListenerResource, serviceName) + } } - - wi.expiryTimer = time.AfterFunc(c.watchExpiryTimeout, func() { - wi.timeout() - }) - return c.watch(wi) } // WatchRouteConfig starts a listener watcher for the service.. @@ -252,18 +43,16 @@ func (c *clientImpl) WatchListener(serviceName string, cb func(ListenerUpdate, e // Note that during race (e.g. an xDS response is received while the user is // calling cancel()), there's a small window where the callback can be called // after the watcher is canceled. The caller needs to handle this case. -func (c *clientImpl) WatchRouteConfig(routeName string, cb func(RouteConfigUpdate, error)) (cancel func()) { - wi := &watchInfo{ - c: c, - rType: RouteConfigResource, - target: routeName, - rdsCallback: cb, +func (c *clientImpl) WatchRouteConfig(routeName string, cb func(xdsresource.RouteConfigUpdate, error)) (cancel func()) { + first, cancelF := c.pubsub.WatchRouteConfig(routeName, cb) + if first { + c.controller.AddWatch(xdsresource.RouteConfigResource, routeName) + } + return func() { + if cancelF() { + c.controller.RemoveWatch(xdsresource.RouteConfigResource, routeName) + } } - - wi.expiryTimer = time.AfterFunc(c.watchExpiryTimeout, func() { - wi.timeout() - }) - return c.watch(wi) } // WatchCluster uses CDS to discover information about the provided @@ -275,18 +64,16 @@ func (c *clientImpl) WatchRouteConfig(routeName string, cb func(RouteConfigUpdat // Note that during race (e.g. an xDS response is received while the user is // calling cancel()), there's a small window where the callback can be called // after the watcher is canceled. The caller needs to handle this case. -func (c *clientImpl) WatchCluster(clusterName string, cb func(ClusterUpdate, error)) (cancel func()) { - wi := &watchInfo{ - c: c, - rType: ClusterResource, - target: clusterName, - cdsCallback: cb, +func (c *clientImpl) WatchCluster(clusterName string, cb func(xdsresource.ClusterUpdate, error)) (cancel func()) { + first, cancelF := c.pubsub.WatchCluster(clusterName, cb) + if first { + c.controller.AddWatch(xdsresource.ClusterResource, clusterName) + } + return func() { + if cancelF() { + c.controller.RemoveWatch(xdsresource.ClusterResource, clusterName) + } } - - wi.expiryTimer = time.AfterFunc(c.watchExpiryTimeout, func() { - wi.timeout() - }) - return c.watch(wi) } // WatchEndpoints uses EDS to discover endpoints in the provided clusterName. @@ -297,16 +84,14 @@ func (c *clientImpl) WatchCluster(clusterName string, cb func(ClusterUpdate, err // Note that during race (e.g. an xDS response is received while the user is // calling cancel()), there's a small window where the callback can be called // after the watcher is canceled. The caller needs to handle this case. -func (c *clientImpl) WatchEndpoints(clusterName string, cb func(EndpointsUpdate, error)) (cancel func()) { - wi := &watchInfo{ - c: c, - rType: EndpointsResource, - target: clusterName, - edsCallback: cb, +func (c *clientImpl) WatchEndpoints(clusterName string, cb func(xdsresource.EndpointsUpdate, error)) (cancel func()) { + first, cancelF := c.pubsub.WatchEndpoints(clusterName, cb) + if first { + c.controller.AddWatch(xdsresource.EndpointsResource, clusterName) + } + return func() { + if cancelF() { + c.controller.RemoveWatch(xdsresource.EndpointsResource, clusterName) + } } - - wi.expiryTimer = time.AfterFunc(c.watchExpiryTimeout, func() { - wi.timeout() - }) - return c.watch(wi) } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xds.go deleted file mode 100644 index 4b4f0680d..000000000 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xds.go +++ /dev/null @@ -1,1345 +0,0 @@ -/* - * - * Copyright 2020 gRPC authors. - * - * 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. - * - */ - -package xdsclient - -import ( - "errors" - "fmt" - "net" - "regexp" - "strconv" - "strings" - "time" - - v1udpatypepb "github.com/cncf/udpa/go/udpa/type/v1" - v3cncftypepb "github.com/cncf/xds/go/xds/type/v3" - v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" - v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" - v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" - v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" - v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" - v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes" - "google.golang.org/protobuf/types/known/anypb" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/internal/pretty" - "google.golang.org/grpc/internal/xds/matcher" - - "google.golang.org/grpc/internal/grpclog" - "google.golang.org/grpc/internal/xds/env" - "google.golang.org/grpc/xds/internal" - "google.golang.org/grpc/xds/internal/httpfilter" - "google.golang.org/grpc/xds/internal/version" -) - -// TransportSocket proto message has a `name` field which is expected to be set -// to this value by the management server. -const transportSocketName = "envoy.transport_sockets.tls" - -// UnmarshalOptions wraps the input parameters for `UnmarshalXxx` functions. -type UnmarshalOptions struct { - // Version is the version of the received response. - Version string - // Resources are the xDS resources resources in the received response. - Resources []*anypb.Any - // Logger is the prefix logger to be used during unmarshaling. - Logger *grpclog.PrefixLogger - // UpdateValidator is a post unmarshal validation check provided by the - // upper layer. - UpdateValidator UpdateValidatorFunc -} - -// UnmarshalListener processes resources received in an LDS response, validates -// them, and transforms them into a native struct which contains only fields we -// are interested in. -func UnmarshalListener(opts *UnmarshalOptions) (map[string]ListenerUpdateErrTuple, UpdateMetadata, error) { - update := make(map[string]ListenerUpdateErrTuple) - md, err := processAllResources(opts, update) - return update, md, err -} - -func unmarshalListenerResource(r *anypb.Any, f UpdateValidatorFunc, logger *grpclog.PrefixLogger) (string, ListenerUpdate, error) { - if !IsListenerResource(r.GetTypeUrl()) { - return "", ListenerUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) - } - // TODO: Pass version.TransportAPI instead of relying upon the type URL - v2 := r.GetTypeUrl() == version.V2ListenerURL - lis := &v3listenerpb.Listener{} - if err := proto.Unmarshal(r.GetValue(), lis); err != nil { - return "", ListenerUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) - } - logger.Infof("Resource with name: %v, type: %T, contains: %v", lis.GetName(), lis, pretty.ToJSON(lis)) - - lu, err := processListener(lis, logger, v2) - if err != nil { - return lis.GetName(), ListenerUpdate{}, err - } - if f != nil { - if err := f(*lu); err != nil { - return lis.GetName(), ListenerUpdate{}, err - } - } - lu.Raw = r - return lis.GetName(), *lu, nil -} - -func processListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger, v2 bool) (*ListenerUpdate, error) { - if lis.GetApiListener() != nil { - return processClientSideListener(lis, logger, v2) - } - return processServerSideListener(lis) -} - -// processClientSideListener checks if the provided Listener proto meets -// the expected criteria. If so, it returns a non-empty routeConfigName. -func processClientSideListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger, v2 bool) (*ListenerUpdate, error) { - update := &ListenerUpdate{} - - apiLisAny := lis.GetApiListener().GetApiListener() - if !IsHTTPConnManagerResource(apiLisAny.GetTypeUrl()) { - return nil, fmt.Errorf("unexpected resource type: %q", apiLisAny.GetTypeUrl()) - } - apiLis := &v3httppb.HttpConnectionManager{} - if err := proto.Unmarshal(apiLisAny.GetValue(), apiLis); err != nil { - return nil, fmt.Errorf("failed to unmarshal api_listner: %v", err) - } - // "HttpConnectionManager.xff_num_trusted_hops must be unset or zero and - // HttpConnectionManager.original_ip_detection_extensions must be empty. If - // either field has an incorrect value, the Listener must be NACKed." - A41 - if apiLis.XffNumTrustedHops != 0 { - return nil, fmt.Errorf("xff_num_trusted_hops must be unset or zero %+v", apiLis) - } - if len(apiLis.OriginalIpDetectionExtensions) != 0 { - return nil, fmt.Errorf("original_ip_detection_extensions must be empty %+v", apiLis) - } - - switch apiLis.RouteSpecifier.(type) { - case *v3httppb.HttpConnectionManager_Rds: - if apiLis.GetRds().GetConfigSource().GetAds() == nil { - return nil, fmt.Errorf("ConfigSource is not ADS: %+v", lis) - } - name := apiLis.GetRds().GetRouteConfigName() - if name == "" { - return nil, fmt.Errorf("empty route_config_name: %+v", lis) - } - update.RouteConfigName = name - case *v3httppb.HttpConnectionManager_RouteConfig: - routeU, err := generateRDSUpdateFromRouteConfiguration(apiLis.GetRouteConfig(), logger, v2) - if err != nil { - return nil, fmt.Errorf("failed to parse inline RDS resp: %v", err) - } - update.InlineRouteConfig = &routeU - case nil: - return nil, fmt.Errorf("no RouteSpecifier: %+v", apiLis) - default: - return nil, fmt.Errorf("unsupported type %T for RouteSpecifier", apiLis.RouteSpecifier) - } - - if v2 { - return update, nil - } - - // The following checks and fields only apply to xDS protocol versions v3+. - - update.MaxStreamDuration = apiLis.GetCommonHttpProtocolOptions().GetMaxStreamDuration().AsDuration() - - var err error - if update.HTTPFilters, err = processHTTPFilters(apiLis.GetHttpFilters(), false); err != nil { - return nil, err - } - - return update, nil -} - -func unwrapHTTPFilterConfig(config *anypb.Any) (proto.Message, string, error) { - switch { - case ptypes.Is(config, &v3cncftypepb.TypedStruct{}): - // The real type name is inside the new TypedStruct message. - s := new(v3cncftypepb.TypedStruct) - if err := ptypes.UnmarshalAny(config, s); err != nil { - return nil, "", fmt.Errorf("error unmarshalling TypedStruct filter config: %v", err) - } - return s, s.GetTypeUrl(), nil - case ptypes.Is(config, &v1udpatypepb.TypedStruct{}): - // The real type name is inside the old TypedStruct message. - s := new(v1udpatypepb.TypedStruct) - if err := ptypes.UnmarshalAny(config, s); err != nil { - return nil, "", fmt.Errorf("error unmarshalling TypedStruct filter config: %v", err) - } - return s, s.GetTypeUrl(), nil - default: - return config, config.GetTypeUrl(), nil - } -} - -func validateHTTPFilterConfig(cfg *anypb.Any, lds, optional bool) (httpfilter.Filter, httpfilter.FilterConfig, error) { - config, typeURL, err := unwrapHTTPFilterConfig(cfg) - if err != nil { - return nil, nil, err - } - filterBuilder := httpfilter.Get(typeURL) - if filterBuilder == nil { - if optional { - return nil, nil, nil - } - return nil, nil, fmt.Errorf("no filter implementation found for %q", typeURL) - } - parseFunc := filterBuilder.ParseFilterConfig - if !lds { - parseFunc = filterBuilder.ParseFilterConfigOverride - } - filterConfig, err := parseFunc(config) - if err != nil { - return nil, nil, fmt.Errorf("error parsing config for filter %q: %v", typeURL, err) - } - return filterBuilder, filterConfig, nil -} - -func processHTTPFilterOverrides(cfgs map[string]*anypb.Any) (map[string]httpfilter.FilterConfig, error) { - if len(cfgs) == 0 { - return nil, nil - } - m := make(map[string]httpfilter.FilterConfig) - for name, cfg := range cfgs { - optional := false - s := new(v3routepb.FilterConfig) - if ptypes.Is(cfg, s) { - if err := ptypes.UnmarshalAny(cfg, s); err != nil { - return nil, fmt.Errorf("filter override %q: error unmarshalling FilterConfig: %v", name, err) - } - cfg = s.GetConfig() - optional = s.GetIsOptional() - } - - httpFilter, config, err := validateHTTPFilterConfig(cfg, false, optional) - if err != nil { - return nil, fmt.Errorf("filter override %q: %v", name, err) - } - if httpFilter == nil { - // Optional configs are ignored. - continue - } - m[name] = config - } - return m, nil -} - -func processHTTPFilters(filters []*v3httppb.HttpFilter, server bool) ([]HTTPFilter, error) { - ret := make([]HTTPFilter, 0, len(filters)) - seenNames := make(map[string]bool, len(filters)) - for _, filter := range filters { - name := filter.GetName() - if name == "" { - return nil, errors.New("filter missing name field") - } - if seenNames[name] { - return nil, fmt.Errorf("duplicate filter name %q", name) - } - seenNames[name] = true - - httpFilter, config, err := validateHTTPFilterConfig(filter.GetTypedConfig(), true, filter.GetIsOptional()) - if err != nil { - return nil, err - } - if httpFilter == nil { - // Optional configs are ignored. - continue - } - if server { - if _, ok := httpFilter.(httpfilter.ServerInterceptorBuilder); !ok { - if filter.GetIsOptional() { - continue - } - return nil, fmt.Errorf("HTTP filter %q not supported server-side", name) - } - } else if _, ok := httpFilter.(httpfilter.ClientInterceptorBuilder); !ok { - if filter.GetIsOptional() { - continue - } - return nil, fmt.Errorf("HTTP filter %q not supported client-side", name) - } - - // Save name/config - ret = append(ret, HTTPFilter{Name: name, Filter: httpFilter, Config: config}) - } - // "Validation will fail if a terminal filter is not the last filter in the - // chain or if a non-terminal filter is the last filter in the chain." - A39 - if len(ret) == 0 { - return nil, fmt.Errorf("http filters list is empty") - } - var i int - for ; i < len(ret)-1; i++ { - if ret[i].Filter.IsTerminal() { - return nil, fmt.Errorf("http filter %q is a terminal filter but it is not last in the filter chain", ret[i].Name) - } - } - if !ret[i].Filter.IsTerminal() { - return nil, fmt.Errorf("http filter %q is not a terminal filter", ret[len(ret)-1].Name) - } - return ret, nil -} - -func processServerSideListener(lis *v3listenerpb.Listener) (*ListenerUpdate, error) { - if n := len(lis.ListenerFilters); n != 0 { - return nil, fmt.Errorf("unsupported field 'listener_filters' contains %d entries", n) - } - if useOrigDst := lis.GetUseOriginalDst(); useOrigDst != nil && useOrigDst.GetValue() { - return nil, errors.New("unsupported field 'use_original_dst' is present and set to true") - } - addr := lis.GetAddress() - if addr == nil { - return nil, fmt.Errorf("no address field in LDS response: %+v", lis) - } - sockAddr := addr.GetSocketAddress() - if sockAddr == nil { - return nil, fmt.Errorf("no socket_address field in LDS response: %+v", lis) - } - lu := &ListenerUpdate{ - InboundListenerCfg: &InboundListenerConfig{ - Address: sockAddr.GetAddress(), - Port: strconv.Itoa(int(sockAddr.GetPortValue())), - }, - } - - fcMgr, err := NewFilterChainManager(lis) - if err != nil { - return nil, err - } - lu.InboundListenerCfg.FilterChains = fcMgr - return lu, nil -} - -// UnmarshalRouteConfig processes resources received in an RDS response, -// validates them, and transforms them into a native struct which contains only -// fields we are interested in. The provided hostname determines the route -// configuration resources of interest. -func UnmarshalRouteConfig(opts *UnmarshalOptions) (map[string]RouteConfigUpdateErrTuple, UpdateMetadata, error) { - update := make(map[string]RouteConfigUpdateErrTuple) - md, err := processAllResources(opts, update) - return update, md, err -} - -func unmarshalRouteConfigResource(r *anypb.Any, logger *grpclog.PrefixLogger) (string, RouteConfigUpdate, error) { - if !IsRouteConfigResource(r.GetTypeUrl()) { - return "", RouteConfigUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) - } - rc := &v3routepb.RouteConfiguration{} - if err := proto.Unmarshal(r.GetValue(), rc); err != nil { - return "", RouteConfigUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) - } - logger.Infof("Resource with name: %v, type: %T, contains: %v.", rc.GetName(), rc, pretty.ToJSON(rc)) - - // TODO: Pass version.TransportAPI instead of relying upon the type URL - v2 := r.GetTypeUrl() == version.V2RouteConfigURL - u, err := generateRDSUpdateFromRouteConfiguration(rc, logger, v2) - if err != nil { - return rc.GetName(), RouteConfigUpdate{}, err - } - u.Raw = r - return rc.GetName(), u, nil -} - -// generateRDSUpdateFromRouteConfiguration checks if the provided -// RouteConfiguration meets the expected criteria. If so, it returns a -// RouteConfigUpdate with nil error. -// -// A RouteConfiguration resource is considered valid when only if it contains a -// VirtualHost whose domain field matches the server name from the URI passed -// to the gRPC channel, and it contains a clusterName or a weighted cluster. -// -// The RouteConfiguration includes a list of virtualHosts, which may have zero -// or more elements. We are interested in the element whose domains field -// matches the server name specified in the "xds:" URI. The only field in the -// VirtualHost proto that the we are interested in is the list of routes. We -// only look at the last route in the list (the default route), whose match -// field must be empty and whose route field must be set. Inside that route -// message, the cluster field will contain the clusterName or weighted clusters -// we are looking for. -func generateRDSUpdateFromRouteConfiguration(rc *v3routepb.RouteConfiguration, logger *grpclog.PrefixLogger, v2 bool) (RouteConfigUpdate, error) { - vhs := make([]*VirtualHost, 0, len(rc.GetVirtualHosts())) - for _, vh := range rc.GetVirtualHosts() { - routes, err := routesProtoToSlice(vh.Routes, logger, v2) - if err != nil { - return RouteConfigUpdate{}, fmt.Errorf("received route is invalid: %v", err) - } - rc, err := generateRetryConfig(vh.GetRetryPolicy()) - if err != nil { - return RouteConfigUpdate{}, fmt.Errorf("received route is invalid: %v", err) - } - vhOut := &VirtualHost{ - Domains: vh.GetDomains(), - Routes: routes, - RetryConfig: rc, - } - if !v2 { - cfgs, err := processHTTPFilterOverrides(vh.GetTypedPerFilterConfig()) - if err != nil { - return RouteConfigUpdate{}, fmt.Errorf("virtual host %+v: %v", vh, err) - } - vhOut.HTTPFilterConfigOverride = cfgs - } - vhs = append(vhs, vhOut) - } - return RouteConfigUpdate{VirtualHosts: vhs}, nil -} - -func generateRetryConfig(rp *v3routepb.RetryPolicy) (*RetryConfig, error) { - if !env.RetrySupport || rp == nil { - return nil, nil - } - - cfg := &RetryConfig{RetryOn: make(map[codes.Code]bool)} - for _, s := range strings.Split(rp.GetRetryOn(), ",") { - switch strings.TrimSpace(strings.ToLower(s)) { - case "cancelled": - cfg.RetryOn[codes.Canceled] = true - case "deadline-exceeded": - cfg.RetryOn[codes.DeadlineExceeded] = true - case "internal": - cfg.RetryOn[codes.Internal] = true - case "resource-exhausted": - cfg.RetryOn[codes.ResourceExhausted] = true - case "unavailable": - cfg.RetryOn[codes.Unavailable] = true - } - } - - if rp.NumRetries == nil { - cfg.NumRetries = 1 - } else { - cfg.NumRetries = rp.GetNumRetries().Value - if cfg.NumRetries < 1 { - return nil, fmt.Errorf("retry_policy.num_retries = %v; must be >= 1", cfg.NumRetries) - } - } - - backoff := rp.GetRetryBackOff() - if backoff == nil { - cfg.RetryBackoff.BaseInterval = 25 * time.Millisecond - } else { - cfg.RetryBackoff.BaseInterval = backoff.GetBaseInterval().AsDuration() - if cfg.RetryBackoff.BaseInterval <= 0 { - return nil, fmt.Errorf("retry_policy.base_interval = %v; must be > 0", cfg.RetryBackoff.BaseInterval) - } - } - if max := backoff.GetMaxInterval(); max == nil { - cfg.RetryBackoff.MaxInterval = 10 * cfg.RetryBackoff.BaseInterval - } else { - cfg.RetryBackoff.MaxInterval = max.AsDuration() - if cfg.RetryBackoff.MaxInterval <= 0 { - return nil, fmt.Errorf("retry_policy.max_interval = %v; must be > 0", cfg.RetryBackoff.MaxInterval) - } - } - - if len(cfg.RetryOn) == 0 { - return &RetryConfig{}, nil - } - return cfg, nil -} - -func routesProtoToSlice(routes []*v3routepb.Route, logger *grpclog.PrefixLogger, v2 bool) ([]*Route, error) { - var routesRet []*Route - for _, r := range routes { - match := r.GetMatch() - if match == nil { - return nil, fmt.Errorf("route %+v doesn't have a match", r) - } - - if len(match.GetQueryParameters()) != 0 { - // Ignore route with query parameters. - logger.Warningf("route %+v has query parameter matchers, the route will be ignored", r) - continue - } - - pathSp := match.GetPathSpecifier() - if pathSp == nil { - return nil, fmt.Errorf("route %+v doesn't have a path specifier", r) - } - - var route Route - switch pt := pathSp.(type) { - case *v3routepb.RouteMatch_Prefix: - route.Prefix = &pt.Prefix - case *v3routepb.RouteMatch_Path: - route.Path = &pt.Path - case *v3routepb.RouteMatch_SafeRegex: - regex := pt.SafeRegex.GetRegex() - re, err := regexp.Compile(regex) - if err != nil { - return nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex) - } - route.Regex = re - default: - return nil, fmt.Errorf("route %+v has an unrecognized path specifier: %+v", r, pt) - } - - if caseSensitive := match.GetCaseSensitive(); caseSensitive != nil { - route.CaseInsensitive = !caseSensitive.Value - } - - for _, h := range match.GetHeaders() { - var header HeaderMatcher - switch ht := h.GetHeaderMatchSpecifier().(type) { - case *v3routepb.HeaderMatcher_ExactMatch: - header.ExactMatch = &ht.ExactMatch - case *v3routepb.HeaderMatcher_SafeRegexMatch: - regex := ht.SafeRegexMatch.GetRegex() - re, err := regexp.Compile(regex) - if err != nil { - return nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex) - } - header.RegexMatch = re - case *v3routepb.HeaderMatcher_RangeMatch: - header.RangeMatch = &Int64Range{ - Start: ht.RangeMatch.Start, - End: ht.RangeMatch.End, - } - case *v3routepb.HeaderMatcher_PresentMatch: - header.PresentMatch = &ht.PresentMatch - case *v3routepb.HeaderMatcher_PrefixMatch: - header.PrefixMatch = &ht.PrefixMatch - case *v3routepb.HeaderMatcher_SuffixMatch: - header.SuffixMatch = &ht.SuffixMatch - default: - return nil, fmt.Errorf("route %+v has an unrecognized header matcher: %+v", r, ht) - } - header.Name = h.GetName() - invert := h.GetInvertMatch() - header.InvertMatch = &invert - route.Headers = append(route.Headers, &header) - } - - if fr := match.GetRuntimeFraction(); fr != nil { - d := fr.GetDefaultValue() - n := d.GetNumerator() - switch d.GetDenominator() { - case v3typepb.FractionalPercent_HUNDRED: - n *= 10000 - case v3typepb.FractionalPercent_TEN_THOUSAND: - n *= 100 - case v3typepb.FractionalPercent_MILLION: - } - route.Fraction = &n - } - - switch r.GetAction().(type) { - case *v3routepb.Route_Route: - route.WeightedClusters = make(map[string]WeightedCluster) - action := r.GetRoute() - - // Hash Policies are only applicable for a Ring Hash LB. - if env.RingHashSupport { - hp, err := hashPoliciesProtoToSlice(action.HashPolicy, logger) - if err != nil { - return nil, err - } - route.HashPolicies = hp - } - - switch a := action.GetClusterSpecifier().(type) { - case *v3routepb.RouteAction_Cluster: - route.WeightedClusters[a.Cluster] = WeightedCluster{Weight: 1} - case *v3routepb.RouteAction_WeightedClusters: - wcs := a.WeightedClusters - var totalWeight uint32 - for _, c := range wcs.Clusters { - w := c.GetWeight().GetValue() - if w == 0 { - continue - } - wc := WeightedCluster{Weight: w} - if !v2 { - cfgs, err := processHTTPFilterOverrides(c.GetTypedPerFilterConfig()) - if err != nil { - return nil, fmt.Errorf("route %+v, action %+v: %v", r, a, err) - } - wc.HTTPFilterConfigOverride = cfgs - } - route.WeightedClusters[c.GetName()] = wc - totalWeight += w - } - // envoy xds doc - // default TotalWeight https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#envoy-v3-api-field-config-route-v3-weightedcluster-total-weight - wantTotalWeight := uint32(100) - if tw := wcs.GetTotalWeight(); tw != nil { - wantTotalWeight = tw.GetValue() - } - if totalWeight != wantTotalWeight { - return nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, expected total weight from response: %v", r, a, totalWeight, wantTotalWeight) - } - if totalWeight == 0 { - return nil, fmt.Errorf("route %+v, action %+v, has no valid cluster in WeightedCluster action", r, a) - } - case *v3routepb.RouteAction_ClusterHeader: - continue - default: - return nil, fmt.Errorf("route %+v, has an unknown ClusterSpecifier: %+v", r, a) - } - - msd := action.GetMaxStreamDuration() - // Prefer grpc_timeout_header_max, if set. - dur := msd.GetGrpcTimeoutHeaderMax() - if dur == nil { - dur = msd.GetMaxStreamDuration() - } - if dur != nil { - d := dur.AsDuration() - route.MaxStreamDuration = &d - } - - var err error - route.RetryConfig, err = generateRetryConfig(action.GetRetryPolicy()) - if err != nil { - return nil, fmt.Errorf("route %+v, action %+v: %v", r, action, err) - } - - route.RouteAction = RouteActionRoute - - case *v3routepb.Route_NonForwardingAction: - // Expected to be used on server side. - route.RouteAction = RouteActionNonForwardingAction - default: - route.RouteAction = RouteActionUnsupported - } - - if !v2 { - cfgs, err := processHTTPFilterOverrides(r.GetTypedPerFilterConfig()) - if err != nil { - return nil, fmt.Errorf("route %+v: %v", r, err) - } - route.HTTPFilterConfigOverride = cfgs - } - routesRet = append(routesRet, &route) - } - return routesRet, nil -} - -func hashPoliciesProtoToSlice(policies []*v3routepb.RouteAction_HashPolicy, logger *grpclog.PrefixLogger) ([]*HashPolicy, error) { - var hashPoliciesRet []*HashPolicy - for _, p := range policies { - policy := HashPolicy{Terminal: p.Terminal} - switch p.GetPolicySpecifier().(type) { - case *v3routepb.RouteAction_HashPolicy_Header_: - policy.HashPolicyType = HashPolicyTypeHeader - policy.HeaderName = p.GetHeader().GetHeaderName() - if rr := p.GetHeader().GetRegexRewrite(); rr != nil { - regex := rr.GetPattern().GetRegex() - re, err := regexp.Compile(regex) - if err != nil { - return nil, fmt.Errorf("hash policy %+v contains an invalid regex %q", p, regex) - } - policy.Regex = re - policy.RegexSubstitution = rr.GetSubstitution() - } - case *v3routepb.RouteAction_HashPolicy_FilterState_: - if p.GetFilterState().GetKey() != "io.grpc.channel_id" { - logger.Infof("hash policy %+v contains an invalid key for filter state policy %q", p, p.GetFilterState().GetKey()) - continue - } - policy.HashPolicyType = HashPolicyTypeChannelID - default: - logger.Infof("hash policy %T is an unsupported hash policy", p.GetPolicySpecifier()) - continue - } - - hashPoliciesRet = append(hashPoliciesRet, &policy) - } - return hashPoliciesRet, nil -} - -// UnmarshalCluster processes resources received in an CDS response, validates -// them, and transforms them into a native struct which contains only fields we -// are interested in. -func UnmarshalCluster(opts *UnmarshalOptions) (map[string]ClusterUpdateErrTuple, UpdateMetadata, error) { - update := make(map[string]ClusterUpdateErrTuple) - md, err := processAllResources(opts, update) - return update, md, err -} - -func unmarshalClusterResource(r *anypb.Any, f UpdateValidatorFunc, logger *grpclog.PrefixLogger) (string, ClusterUpdate, error) { - if !IsClusterResource(r.GetTypeUrl()) { - return "", ClusterUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) - } - - cluster := &v3clusterpb.Cluster{} - if err := proto.Unmarshal(r.GetValue(), cluster); err != nil { - return "", ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) - } - logger.Infof("Resource with name: %v, type: %T, contains: %v", cluster.GetName(), cluster, pretty.ToJSON(cluster)) - cu, err := validateClusterAndConstructClusterUpdate(cluster) - if err != nil { - return cluster.GetName(), ClusterUpdate{}, err - } - cu.Raw = r - if f != nil { - if err := f(cu); err != nil { - return "", ClusterUpdate{}, err - } - } - - return cluster.GetName(), cu, nil -} - -const ( - defaultRingHashMinSize = 1024 - defaultRingHashMaxSize = 8 * 1024 * 1024 // 8M - ringHashSizeUpperBound = 8 * 1024 * 1024 // 8M -) - -func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (ClusterUpdate, error) { - var lbPolicy *ClusterLBPolicyRingHash - switch cluster.GetLbPolicy() { - case v3clusterpb.Cluster_ROUND_ROBIN: - lbPolicy = nil // The default is round_robin, and there's no config to set. - case v3clusterpb.Cluster_RING_HASH: - if !env.RingHashSupport { - return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) - } - rhc := cluster.GetRingHashLbConfig() - if rhc.GetHashFunction() != v3clusterpb.Cluster_RingHashLbConfig_XX_HASH { - return ClusterUpdate{}, fmt.Errorf("unsupported ring_hash hash function %v in response: %+v", rhc.GetHashFunction(), cluster) - } - // Minimum defaults to 1024 entries, and limited to 8M entries Maximum - // defaults to 8M entries, and limited to 8M entries - var minSize, maxSize uint64 = defaultRingHashMinSize, defaultRingHashMaxSize - if min := rhc.GetMinimumRingSize(); min != nil { - if min.GetValue() > ringHashSizeUpperBound { - return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash mininum ring size %v in response: %+v", min.GetValue(), cluster) - } - minSize = min.GetValue() - } - if max := rhc.GetMaximumRingSize(); max != nil { - if max.GetValue() > ringHashSizeUpperBound { - return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash maxinum ring size %v in response: %+v", max.GetValue(), cluster) - } - maxSize = max.GetValue() - } - if minSize > maxSize { - return ClusterUpdate{}, fmt.Errorf("ring_hash config min size %v is greater than max %v", minSize, maxSize) - } - lbPolicy = &ClusterLBPolicyRingHash{MinimumRingSize: minSize, MaximumRingSize: maxSize} - default: - return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) - } - - // Process security configuration received from the control plane iff the - // corresponding environment variable is set. - var sc *SecurityConfig - if env.ClientSideSecuritySupport { - var err error - if sc, err = securityConfigFromCluster(cluster); err != nil { - return ClusterUpdate{}, err - } - } - - ret := ClusterUpdate{ - ClusterName: cluster.GetName(), - EnableLRS: cluster.GetLrsServer().GetSelf() != nil, - SecurityCfg: sc, - MaxRequests: circuitBreakersFromCluster(cluster), - LBPolicy: lbPolicy, - } - - // Validate and set cluster type from the response. - switch { - case cluster.GetType() == v3clusterpb.Cluster_EDS: - if cluster.GetEdsClusterConfig().GetEdsConfig().GetAds() == nil { - return ClusterUpdate{}, fmt.Errorf("unexpected edsConfig in response: %+v", cluster) - } - ret.ClusterType = ClusterTypeEDS - ret.EDSServiceName = cluster.GetEdsClusterConfig().GetServiceName() - return ret, nil - case cluster.GetType() == v3clusterpb.Cluster_LOGICAL_DNS: - if !env.AggregateAndDNSSupportEnv { - return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) - } - ret.ClusterType = ClusterTypeLogicalDNS - dnsHN, err := dnsHostNameFromCluster(cluster) - if err != nil { - return ClusterUpdate{}, err - } - ret.DNSHostName = dnsHN - return ret, nil - case cluster.GetClusterType() != nil && cluster.GetClusterType().Name == "envoy.clusters.aggregate": - if !env.AggregateAndDNSSupportEnv { - return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) - } - clusters := &v3aggregateclusterpb.ClusterConfig{} - if err := proto.Unmarshal(cluster.GetClusterType().GetTypedConfig().GetValue(), clusters); err != nil { - return ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) - } - ret.ClusterType = ClusterTypeAggregate - ret.PrioritizedClusterNames = clusters.Clusters - return ret, nil - default: - return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) - } -} - -// dnsHostNameFromCluster extracts the DNS host name from the cluster's load -// assignment. -// -// There should be exactly one locality, with one endpoint, whose address -// contains the address and port. -func dnsHostNameFromCluster(cluster *v3clusterpb.Cluster) (string, error) { - loadAssignment := cluster.GetLoadAssignment() - if loadAssignment == nil { - return "", fmt.Errorf("load_assignment not present for LOGICAL_DNS cluster") - } - if len(loadAssignment.GetEndpoints()) != 1 { - return "", fmt.Errorf("load_assignment for LOGICAL_DNS cluster must have exactly one locality, got: %+v", loadAssignment) - } - endpoints := loadAssignment.GetEndpoints()[0].GetLbEndpoints() - if len(endpoints) != 1 { - return "", fmt.Errorf("locality for LOGICAL_DNS cluster must have exactly one endpoint, got: %+v", endpoints) - } - endpoint := endpoints[0].GetEndpoint() - if endpoint == nil { - return "", fmt.Errorf("endpoint for LOGICAL_DNS cluster not set") - } - socketAddr := endpoint.GetAddress().GetSocketAddress() - if socketAddr == nil { - return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set") - } - if socketAddr.GetResolverName() != "" { - return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set has unexpected custom resolver name: %v", socketAddr.GetResolverName()) - } - host := socketAddr.GetAddress() - if host == "" { - return "", fmt.Errorf("host for endpoint for LOGICAL_DNS cluster not set") - } - port := socketAddr.GetPortValue() - if port == 0 { - return "", fmt.Errorf("port for endpoint for LOGICAL_DNS cluster not set") - } - return net.JoinHostPort(host, strconv.Itoa(int(port))), nil -} - -// securityConfigFromCluster extracts the relevant security configuration from -// the received Cluster resource. -func securityConfigFromCluster(cluster *v3clusterpb.Cluster) (*SecurityConfig, error) { - if tsm := cluster.GetTransportSocketMatches(); len(tsm) != 0 { - return nil, fmt.Errorf("unsupport transport_socket_matches field is non-empty: %+v", tsm) - } - // The Cluster resource contains a `transport_socket` field, which contains - // a oneof `typed_config` field of type `protobuf.Any`. The any proto - // contains a marshaled representation of an `UpstreamTlsContext` message. - ts := cluster.GetTransportSocket() - if ts == nil { - return nil, nil - } - if name := ts.GetName(); name != transportSocketName { - return nil, fmt.Errorf("transport_socket field has unexpected name: %s", name) - } - any := ts.GetTypedConfig() - if any == nil || any.TypeUrl != version.V3UpstreamTLSContextURL { - return nil, fmt.Errorf("transport_socket field has unexpected typeURL: %s", any.TypeUrl) - } - upstreamCtx := &v3tlspb.UpstreamTlsContext{} - if err := proto.Unmarshal(any.GetValue(), upstreamCtx); err != nil { - return nil, fmt.Errorf("failed to unmarshal UpstreamTlsContext in CDS response: %v", err) - } - // The following fields from `UpstreamTlsContext` are ignored: - // - sni - // - allow_renegotiation - // - max_session_keys - if upstreamCtx.GetCommonTlsContext() == nil { - return nil, errors.New("UpstreamTlsContext in CDS response does not contain a CommonTlsContext") - } - - return securityConfigFromCommonTLSContext(upstreamCtx.GetCommonTlsContext(), false) -} - -// common is expected to be not nil. -// The `alpn_protocols` field is ignored. -func securityConfigFromCommonTLSContext(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { - if common.GetTlsParams() != nil { - return nil, fmt.Errorf("unsupported tls_params field in CommonTlsContext message: %+v", common) - } - if common.GetCustomHandshaker() != nil { - return nil, fmt.Errorf("unsupported custom_handshaker field in CommonTlsContext message: %+v", common) - } - - // For now, if we can't get a valid security config from the new fields, we - // fallback to the old deprecated fields. - // TODO: Drop support for deprecated fields. NACK if err != nil here. - sc, _ := securityConfigFromCommonTLSContextUsingNewFields(common, server) - if sc == nil || sc.Equal(&SecurityConfig{}) { - var err error - sc, err = securityConfigFromCommonTLSContextWithDeprecatedFields(common, server) - if err != nil { - return nil, err - } - } - if sc != nil { - // sc == nil is a valid case where the control plane has not sent us any - // security configuration. xDS creds will use fallback creds. - if server { - if sc.IdentityInstanceName == "" { - return nil, errors.New("security configuration on the server-side does not contain identity certificate provider instance name") - } - } else { - if sc.RootInstanceName == "" { - return nil, errors.New("security configuration on the client-side does not contain root certificate provider instance name") - } - } - } - return sc, nil -} - -func securityConfigFromCommonTLSContextWithDeprecatedFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { - // The `CommonTlsContext` contains a - // `tls_certificate_certificate_provider_instance` field of type - // `CertificateProviderInstance`, which contains the provider instance name - // and the certificate name to fetch identity certs. - sc := &SecurityConfig{} - if identity := common.GetTlsCertificateCertificateProviderInstance(); identity != nil { - sc.IdentityInstanceName = identity.GetInstanceName() - sc.IdentityCertName = identity.GetCertificateName() - } - - // The `CommonTlsContext` contains a `validation_context_type` field which - // is a oneof. We can get the values that we are interested in from two of - // those possible values: - // - combined validation context: - // - contains a default validation context which holds the list of - // matchers for accepted SANs. - // - contains certificate provider instance configuration - // - certificate provider instance configuration - // - in this case, we do not get a list of accepted SANs. - switch t := common.GetValidationContextType().(type) { - case *v3tlspb.CommonTlsContext_CombinedValidationContext: - combined := common.GetCombinedValidationContext() - var matchers []matcher.StringMatcher - if def := combined.GetDefaultValidationContext(); def != nil { - for _, m := range def.GetMatchSubjectAltNames() { - matcher, err := matcher.StringMatcherFromProto(m) - if err != nil { - return nil, err - } - matchers = append(matchers, matcher) - } - } - if server && len(matchers) != 0 { - return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) - } - sc.SubjectAltNameMatchers = matchers - if pi := combined.GetValidationContextCertificateProviderInstance(); pi != nil { - sc.RootInstanceName = pi.GetInstanceName() - sc.RootCertName = pi.GetCertificateName() - } - case *v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance: - pi := common.GetValidationContextCertificateProviderInstance() - sc.RootInstanceName = pi.GetInstanceName() - sc.RootCertName = pi.GetCertificateName() - case nil: - // It is valid for the validation context to be nil on the server side. - default: - return nil, fmt.Errorf("validation context contains unexpected type: %T", t) - } - return sc, nil -} - -// gRFC A29 https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md -// specifies the new way to fetch security configuration and says the following: -// -// Although there are various ways to obtain certificates as per this proto -// (which are supported by Envoy), gRPC supports only one of them and that is -// the `CertificateProviderPluginInstance` proto. -// -// This helper function attempts to fetch security configuration from the -// `CertificateProviderPluginInstance` message, given a CommonTlsContext. -func securityConfigFromCommonTLSContextUsingNewFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { - // The `tls_certificate_provider_instance` field of type - // `CertificateProviderPluginInstance` is used to fetch the identity - // certificate provider. - sc := &SecurityConfig{} - identity := common.GetTlsCertificateProviderInstance() - if identity == nil && len(common.GetTlsCertificates()) != 0 { - return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificates is set in CommonTlsContext message: %+v", common) - } - if identity == nil && common.GetTlsCertificateSdsSecretConfigs() != nil { - return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message: %+v", common) - } - sc.IdentityInstanceName = identity.GetInstanceName() - sc.IdentityCertName = identity.GetCertificateName() - - // The `CommonTlsContext` contains a oneof field `validation_context_type`, - // which contains the `CertificateValidationContext` message in one of the - // following ways: - // - `validation_context` field - // - this is directly of type `CertificateValidationContext` - // - `combined_validation_context` field - // - this is of type `CombinedCertificateValidationContext` and contains - // a `default validation context` field of type - // `CertificateValidationContext` - // - // The `CertificateValidationContext` message has the following fields that - // we are interested in: - // - `ca_certificate_provider_instance` - // - this is of type `CertificateProviderPluginInstance` - // - `match_subject_alt_names` - // - this is a list of string matchers - // - // The `CertificateProviderPluginInstance` message contains two fields - // - instance_name - // - this is the certificate provider instance name to be looked up in - // the bootstrap configuration - // - certificate_name - // - this is an opaque name passed to the certificate provider - var validationCtx *v3tlspb.CertificateValidationContext - switch typ := common.GetValidationContextType().(type) { - case *v3tlspb.CommonTlsContext_ValidationContext: - validationCtx = common.GetValidationContext() - case *v3tlspb.CommonTlsContext_CombinedValidationContext: - validationCtx = common.GetCombinedValidationContext().GetDefaultValidationContext() - case nil: - // It is valid for the validation context to be nil on the server side. - return sc, nil - default: - return nil, fmt.Errorf("validation context contains unexpected type: %T", typ) - } - // If we get here, it means that the `CertificateValidationContext` message - // was found through one of the supported ways. It is an error if the - // validation context is specified, but it does not contain the - // ca_certificate_provider_instance field which contains information about - // the certificate provider to be used for the root certificates. - if validationCtx.GetCaCertificateProviderInstance() == nil { - return nil, fmt.Errorf("expected field ca_certificate_provider_instance is missing in CommonTlsContext message: %+v", common) - } - // The following fields are ignored: - // - trusted_ca - // - watched_directory - // - allow_expired_certificate - // - trust_chain_verification - switch { - case len(validationCtx.GetVerifyCertificateSpki()) != 0: - return nil, fmt.Errorf("unsupported verify_certificate_spki field in CommonTlsContext message: %+v", common) - case len(validationCtx.GetVerifyCertificateHash()) != 0: - return nil, fmt.Errorf("unsupported verify_certificate_hash field in CommonTlsContext message: %+v", common) - case validationCtx.GetRequireSignedCertificateTimestamp().GetValue(): - return nil, fmt.Errorf("unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message: %+v", common) - case validationCtx.GetCrl() != nil: - return nil, fmt.Errorf("unsupported crl field in CommonTlsContext message: %+v", common) - case validationCtx.GetCustomValidatorConfig() != nil: - return nil, fmt.Errorf("unsupported custom_validator_config field in CommonTlsContext message: %+v", common) - } - - if rootProvider := validationCtx.GetCaCertificateProviderInstance(); rootProvider != nil { - sc.RootInstanceName = rootProvider.GetInstanceName() - sc.RootCertName = rootProvider.GetCertificateName() - } - var matchers []matcher.StringMatcher - for _, m := range validationCtx.GetMatchSubjectAltNames() { - matcher, err := matcher.StringMatcherFromProto(m) - if err != nil { - return nil, err - } - matchers = append(matchers, matcher) - } - if server && len(matchers) != 0 { - return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) - } - sc.SubjectAltNameMatchers = matchers - return sc, nil -} - -// circuitBreakersFromCluster extracts the circuit breakers configuration from -// the received cluster resource. Returns nil if no CircuitBreakers or no -// Thresholds in CircuitBreakers. -func circuitBreakersFromCluster(cluster *v3clusterpb.Cluster) *uint32 { - for _, threshold := range cluster.GetCircuitBreakers().GetThresholds() { - if threshold.GetPriority() != v3corepb.RoutingPriority_DEFAULT { - continue - } - maxRequestsPb := threshold.GetMaxRequests() - if maxRequestsPb == nil { - return nil - } - maxRequests := maxRequestsPb.GetValue() - return &maxRequests - } - return nil -} - -// UnmarshalEndpoints processes resources received in an EDS response, -// validates them, and transforms them into a native struct which contains only -// fields we are interested in. -func UnmarshalEndpoints(opts *UnmarshalOptions) (map[string]EndpointsUpdateErrTuple, UpdateMetadata, error) { - update := make(map[string]EndpointsUpdateErrTuple) - md, err := processAllResources(opts, update) - return update, md, err -} - -func unmarshalEndpointsResource(r *anypb.Any, logger *grpclog.PrefixLogger) (string, EndpointsUpdate, error) { - if !IsEndpointsResource(r.GetTypeUrl()) { - return "", EndpointsUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) - } - - cla := &v3endpointpb.ClusterLoadAssignment{} - if err := proto.Unmarshal(r.GetValue(), cla); err != nil { - return "", EndpointsUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) - } - logger.Infof("Resource with name: %v, type: %T, contains: %v", cla.GetClusterName(), cla, pretty.ToJSON(cla)) - - u, err := parseEDSRespProto(cla) - if err != nil { - return cla.GetClusterName(), EndpointsUpdate{}, err - } - u.Raw = r - return cla.GetClusterName(), u, nil -} - -func parseAddress(socketAddress *v3corepb.SocketAddress) string { - return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue()))) -} - -func parseDropPolicy(dropPolicy *v3endpointpb.ClusterLoadAssignment_Policy_DropOverload) OverloadDropConfig { - percentage := dropPolicy.GetDropPercentage() - var ( - numerator = percentage.GetNumerator() - denominator uint32 - ) - switch percentage.GetDenominator() { - case v3typepb.FractionalPercent_HUNDRED: - denominator = 100 - case v3typepb.FractionalPercent_TEN_THOUSAND: - denominator = 10000 - case v3typepb.FractionalPercent_MILLION: - denominator = 1000000 - } - return OverloadDropConfig{ - Category: dropPolicy.GetCategory(), - Numerator: numerator, - Denominator: denominator, - } -} - -func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint) []Endpoint { - endpoints := make([]Endpoint, 0, len(lbEndpoints)) - for _, lbEndpoint := range lbEndpoints { - endpoints = append(endpoints, Endpoint{ - HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()), - Address: parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()), - Weight: lbEndpoint.GetLoadBalancingWeight().GetValue(), - }) - } - return endpoints -} - -func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate, error) { - ret := EndpointsUpdate{} - for _, dropPolicy := range m.GetPolicy().GetDropOverloads() { - ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy)) - } - priorities := make(map[uint32]struct{}) - for _, locality := range m.Endpoints { - l := locality.GetLocality() - if l == nil { - return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality) - } - lid := internal.LocalityID{ - Region: l.Region, - Zone: l.Zone, - SubZone: l.SubZone, - } - priority := locality.GetPriority() - priorities[priority] = struct{}{} - ret.Localities = append(ret.Localities, Locality{ - ID: lid, - Endpoints: parseEndpoints(locality.GetLbEndpoints()), - Weight: locality.GetLoadBalancingWeight().GetValue(), - Priority: priority, - }) - } - for i := 0; i < len(priorities); i++ { - if _, ok := priorities[uint32(i)]; !ok { - return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities) - } - } - return ret, nil -} - -// ListenerUpdateErrTuple is a tuple with the update and error. It contains the -// results from unmarshal functions. It's used to pass unmarshal results of -// multiple resources together, e.g. in maps like `map[string]{Update,error}`. -type ListenerUpdateErrTuple struct { - Update ListenerUpdate - Err error -} - -// RouteConfigUpdateErrTuple is a tuple with the update and error. It contains -// the results from unmarshal functions. It's used to pass unmarshal results of -// multiple resources together, e.g. in maps like `map[string]{Update,error}`. -type RouteConfigUpdateErrTuple struct { - Update RouteConfigUpdate - Err error -} - -// ClusterUpdateErrTuple is a tuple with the update and error. It contains the -// results from unmarshal functions. It's used to pass unmarshal results of -// multiple resources together, e.g. in maps like `map[string]{Update,error}`. -type ClusterUpdateErrTuple struct { - Update ClusterUpdate - Err error -} - -// EndpointsUpdateErrTuple is a tuple with the update and error. It contains the -// results from unmarshal functions. It's used to pass unmarshal results of -// multiple resources together, e.g. in maps like `map[string]{Update,error}`. -type EndpointsUpdateErrTuple struct { - Update EndpointsUpdate - Err error -} - -// processAllResources unmarshals and validates the resources, populates the -// provided ret (a map), and returns metadata and error. -// -// After this function, the ret map will be populated with both valid and -// invalid updates. Invalid resources will have an entry with the key as the -// resource name, value as an empty update. -// -// The type of the resource is determined by the type of ret. E.g. -// map[string]ListenerUpdate means this is for LDS. -func processAllResources(opts *UnmarshalOptions, ret interface{}) (UpdateMetadata, error) { - timestamp := time.Now() - md := UpdateMetadata{ - Version: opts.Version, - Timestamp: timestamp, - } - var topLevelErrors []error - perResourceErrors := make(map[string]error) - - for _, r := range opts.Resources { - switch ret2 := ret.(type) { - case map[string]ListenerUpdateErrTuple: - name, update, err := unmarshalListenerResource(r, opts.UpdateValidator, opts.Logger) - if err == nil { - ret2[name] = ListenerUpdateErrTuple{Update: update} - continue - } - if name == "" { - topLevelErrors = append(topLevelErrors, err) - continue - } - perResourceErrors[name] = err - // Add place holder in the map so we know this resource name was in - // the response. - ret2[name] = ListenerUpdateErrTuple{Err: err} - case map[string]RouteConfigUpdateErrTuple: - name, update, err := unmarshalRouteConfigResource(r, opts.Logger) - if err == nil { - ret2[name] = RouteConfigUpdateErrTuple{Update: update} - continue - } - if name == "" { - topLevelErrors = append(topLevelErrors, err) - continue - } - perResourceErrors[name] = err - // Add place holder in the map so we know this resource name was in - // the response. - ret2[name] = RouteConfigUpdateErrTuple{Err: err} - case map[string]ClusterUpdateErrTuple: - name, update, err := unmarshalClusterResource(r, opts.UpdateValidator, opts.Logger) - if err == nil { - ret2[name] = ClusterUpdateErrTuple{Update: update} - continue - } - if name == "" { - topLevelErrors = append(topLevelErrors, err) - continue - } - perResourceErrors[name] = err - // Add place holder in the map so we know this resource name was in - // the response. - ret2[name] = ClusterUpdateErrTuple{Err: err} - case map[string]EndpointsUpdateErrTuple: - name, update, err := unmarshalEndpointsResource(r, opts.Logger) - if err == nil { - ret2[name] = EndpointsUpdateErrTuple{Update: update} - continue - } - if name == "" { - topLevelErrors = append(topLevelErrors, err) - continue - } - perResourceErrors[name] = err - // Add place holder in the map so we know this resource name was in - // the response. - ret2[name] = EndpointsUpdateErrTuple{Err: err} - } - } - - if len(topLevelErrors) == 0 && len(perResourceErrors) == 0 { - md.Status = ServiceStatusACKed - return md, nil - } - - var typeStr string - switch ret.(type) { - case map[string]ListenerUpdate: - typeStr = "LDS" - case map[string]RouteConfigUpdate: - typeStr = "RDS" - case map[string]ClusterUpdate: - typeStr = "CDS" - case map[string]EndpointsUpdate: - typeStr = "EDS" - } - - md.Status = ServiceStatusNACKed - errRet := combineErrors(typeStr, topLevelErrors, perResourceErrors) - md.ErrState = &UpdateErrorMetadata{ - Version: opts.Version, - Err: errRet, - Timestamp: timestamp, - } - return md, errRet -} - -func combineErrors(rType string, topLevelErrors []error, perResourceErrors map[string]error) error { - var errStrB strings.Builder - errStrB.WriteString(fmt.Sprintf("error parsing %q response: ", rType)) - if len(topLevelErrors) > 0 { - errStrB.WriteString("top level errors: ") - for i, err := range topLevelErrors { - if i != 0 { - errStrB.WriteString(";\n") - } - errStrB.WriteString(err.Error()) - } - } - if len(perResourceErrors) > 0 { - var i int - for name, err := range perResourceErrors { - if i != 0 { - errStrB.WriteString(";\n") - } - i++ - errStrB.WriteString(fmt.Sprintf("resource %q: %v", name, err.Error())) - } - } - return errors.New(errStrB.String()) -} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/errors.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/errors.go similarity index 98% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/errors.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/errors.go index 4d6cdaaf9..cfaf63b30 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/errors.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/errors.go @@ -16,7 +16,7 @@ * */ -package xdsclient +package xdsresource import "fmt" diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/filter_chain.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/filter_chain.go similarity index 94% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/filter_chain.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/filter_chain.go index f2b29f52a..78b2a56e8 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/filter_chain.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/filter_chain.go @@ -13,10 +13,9 @@ * 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. - * */ -package xdsclient +package xdsresource import ( "errors" @@ -28,10 +27,11 @@ import ( v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" + "google.golang.org/grpc/internal/envconfig" + "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/resolver" - "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/xds/internal/httpfilter" - "google.golang.org/grpc/xds/internal/version" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" ) const ( @@ -61,12 +61,12 @@ type FilterChain struct { HTTPFilters []HTTPFilter // RouteConfigName is the route configuration name for this FilterChain. // - // Only one of RouteConfigName and InlineRouteConfig is set. + // Exactly one of RouteConfigName and InlineRouteConfig is set. RouteConfigName string // InlineRouteConfig is the inline route configuration (RDS response) // returned for this filter chain. // - // Only one of RouteConfigName and InlineRouteConfig is set. + // Exactly one of RouteConfigName and InlineRouteConfig is set. InlineRouteConfig *RouteConfigUpdate } @@ -86,8 +86,8 @@ type VirtualHostWithInterceptors struct { type RouteWithInterceptors struct { // M is the matcher used to match to this route. M *CompositeMatcher - // RouteAction is the type of routing action to initiate once matched to. - RouteAction RouteAction + // ActionType is the type of routing action to initiate once matched to. + ActionType RouteActionType // Interceptors are interceptors instantiated for this route. These will be // constructed from a combination of the top level configuration and any // HTTP Filter overrides present in Virtual Host or Route. @@ -112,7 +112,7 @@ func (f *FilterChain) convertVirtualHost(virtualHost *VirtualHost) (VirtualHostW rs := make([]RouteWithInterceptors, len(virtualHost.Routes)) for i, r := range virtualHost.Routes { var err error - rs[i].RouteAction = r.RouteAction + rs[i].ActionType = r.ActionType rs[i].M, err = RouteToMatcher(r) if err != nil { return VirtualHostWithInterceptors{}, fmt.Errorf("matcher construction: %v", err) @@ -177,6 +177,7 @@ const ( // 7. Source IP address. // 8. Source port. type FilterChainManager struct { + logger *grpclog.PrefixLogger // Destination prefix is the first match criteria that we support. // Therefore, this multi-stage map is indexed on destination prefixes // specified in the match criteria. @@ -247,9 +248,10 @@ type sourcePrefixEntry struct { // // This function is only exported so that tests outside of this package can // create a FilterChainManager. -func NewFilterChainManager(lis *v3listenerpb.Listener) (*FilterChainManager, error) { +func NewFilterChainManager(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger) (*FilterChainManager, error) { // Parse all the filter chains and build the internal data structures. fci := &FilterChainManager{ + logger: logger, dstPrefixMap: make(map[string]*destPrefixEntry), RouteConfigNames: make(map[string]bool), } @@ -303,7 +305,7 @@ func (fci *FilterChainManager) addFilterChains(fcs []*v3listenerpb.FilterChain) if fcm.GetDestinationPort().GetValue() != 0 { // Destination port is the first match criteria and we do not // support filter chains which contains this match criteria. - logger.Warningf("Dropping filter chain %+v since it contains unsupported destination_port match field", fc) + fci.logger.Warningf("Dropping filter chain %+v since it contains unsupported destination_port match field", fc) continue } @@ -352,7 +354,7 @@ func (fci *FilterChainManager) addFilterChainsForServerNames(dstEntry *destPrefi // Filter chains specifying server names in their match criteria always fail // a match at connection time. So, these filter chains can be dropped now. if len(fc.GetFilterChainMatch().GetServerNames()) != 0 { - logger.Warningf("Dropping filter chain %+v since it contains unsupported server_names match field", fc) + fci.logger.Warningf("Dropping filter chain %+v since it contains unsupported server_names match field", fc) return nil } @@ -365,13 +367,13 @@ func (fci *FilterChainManager) addFilterChainsForTransportProtocols(dstEntry *de case tp != "" && tp != "raw_buffer": // Only allow filter chains with transport protocol set to empty string // or "raw_buffer". - logger.Warningf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc) + fci.logger.Warningf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc) return nil case tp == "" && dstEntry.rawBufferSeen: // If we have already seen filter chains with transport protocol set to // "raw_buffer", we can drop filter chains with transport protocol set // to empty string, since the former takes precedence. - logger.Warningf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc) + fci.logger.Warningf("Dropping filter chain %+v since it contains unsupported value for transport_protocols match field", fc) return nil case tp != "" && !dstEntry.rawBufferSeen: // This is the first "raw_buffer" that we are seeing. Set the bit and @@ -385,7 +387,7 @@ func (fci *FilterChainManager) addFilterChainsForTransportProtocols(dstEntry *de func (fci *FilterChainManager) addFilterChainsForApplicationProtocols(dstEntry *destPrefixEntry, fc *v3listenerpb.FilterChain) error { if len(fc.GetFilterChainMatch().GetApplicationProtocols()) != 0 { - logger.Warningf("Dropping filter chain %+v since it contains unsupported application_protocols match field", fc) + fci.logger.Warningf("Dropping filter chain %+v since it contains unsupported application_protocols match field", fc) return nil } return fci.addFilterChainsForSourceType(dstEntry, fc) @@ -551,6 +553,25 @@ func (fci *FilterChainManager) filterChainFromProto(fc *v3listenerpb.FilterChain return filterChain, nil } +// Validate takes a function to validate the FilterChains in this manager. +func (fci *FilterChainManager) Validate(f func(fc *FilterChain) error) error { + for _, dst := range fci.dstPrefixMap { + for _, srcType := range dst.srcTypeArr { + if srcType == nil { + continue + } + for _, src := range srcType.srcPrefixMap { + for _, fc := range src.srcPortMap { + if err := f(fc); err != nil { + return err + } + } + } + } + } + return f(fci.def) +} + func processNetworkFilters(filters []*v3listenerpb.Filter) (*FilterChain, error) { filterChain := &FilterChain{} seenNames := make(map[string]bool, len(filters)) @@ -611,7 +632,7 @@ func processNetworkFilters(filters []*v3listenerpb.Filter) (*FilterChain, error) // TODO: Implement terminal filter logic, as per A36. filterChain.HTTPFilters = filters seenHCM = true - if !env.RBACSupport { + if !envconfig.XDSRBAC { continue } switch hcm.RouteSpecifier.(type) { @@ -637,8 +658,7 @@ func processNetworkFilters(filters []*v3listenerpb.Filter) (*FilterChain, error) } filterChain.InlineRouteConfig = &routeU case nil: - // No-op, as no route specifier is a valid configuration on - // the server side. + return nil, fmt.Errorf("no RouteSpecifier: %+v", hcm) default: return nil, fmt.Errorf("unsupported type %T for RouteSpecifier", hcm.RouteSpecifier) } diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher.go similarity index 99% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher.go index 85fff3063..d7da32a75 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher.go @@ -13,10 +13,9 @@ * 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. - * */ -package xdsclient +package xdsresource import ( "fmt" diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher_path.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher_path.go similarity index 99% rename from vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher_path.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher_path.go index 2ca0e4bbc..da487e20c 100644 --- a/vendor/google.golang.org/grpc/xds/internal/xdsclient/matcher_path.go +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/matcher_path.go @@ -13,10 +13,9 @@ * 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. - * */ -package xdsclient +package xdsresource import ( "regexp" diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type.go new file mode 100644 index 000000000..c64f7c609 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type.go @@ -0,0 +1,150 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "time" + + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" +) + +// UpdateValidatorFunc performs validations on update structs using +// context/logic available at the xdsClient layer. Since these validation are +// performed on internal update structs, they can be shared between different +// API clients. +type UpdateValidatorFunc func(interface{}) error + +// UpdateMetadata contains the metadata for each update, including timestamp, +// raw message, and so on. +type UpdateMetadata struct { + // Status is the status of this resource, e.g. ACKed, NACKed, or + // Not_exist(removed). + Status ServiceStatus + // Version is the version of the xds response. Note that this is the version + // of the resource in use (previous ACKed). If a response is NACKed, the + // NACKed version is in ErrState. + Version string + // Timestamp is when the response is received. + Timestamp time.Time + // ErrState is set when the update is NACKed. + ErrState *UpdateErrorMetadata +} + +// IsListenerResource returns true if the provider URL corresponds to an xDS +// Listener resource. +func IsListenerResource(url string) bool { + return url == version.V2ListenerURL || url == version.V3ListenerURL +} + +// IsHTTPConnManagerResource returns true if the provider URL corresponds to an xDS +// HTTPConnManager resource. +func IsHTTPConnManagerResource(url string) bool { + return url == version.V2HTTPConnManagerURL || url == version.V3HTTPConnManagerURL +} + +// IsRouteConfigResource returns true if the provider URL corresponds to an xDS +// RouteConfig resource. +func IsRouteConfigResource(url string) bool { + return url == version.V2RouteConfigURL || url == version.V3RouteConfigURL +} + +// IsClusterResource returns true if the provider URL corresponds to an xDS +// Cluster resource. +func IsClusterResource(url string) bool { + return url == version.V2ClusterURL || url == version.V3ClusterURL +} + +// IsEndpointsResource returns true if the provider URL corresponds to an xDS +// Endpoints resource. +func IsEndpointsResource(url string) bool { + return url == version.V2EndpointsURL || url == version.V3EndpointsURL +} + +// ServiceStatus is the status of the update. +type ServiceStatus int + +const ( + // ServiceStatusUnknown is the default state, before a watch is started for + // the resource. + ServiceStatusUnknown ServiceStatus = iota + // ServiceStatusRequested is when the watch is started, but before and + // response is received. + ServiceStatusRequested + // ServiceStatusNotExist is when the resource doesn't exist in + // state-of-the-world responses (e.g. LDS and CDS), which means the resource + // is removed by the management server. + ServiceStatusNotExist // Resource is removed in the server, in LDS/CDS. + // ServiceStatusACKed is when the resource is ACKed. + ServiceStatusACKed + // ServiceStatusNACKed is when the resource is NACKed. + ServiceStatusNACKed +) + +// UpdateErrorMetadata is part of UpdateMetadata. It contains the error state +// when a response is NACKed. +type UpdateErrorMetadata struct { + // Version is the version of the NACKed response. + Version string + // Err contains why the response was NACKed. + Err error + // Timestamp is when the NACKed response was received. + Timestamp time.Time +} + +// UpdateWithMD contains the raw message of the update and the metadata, +// including version, raw message, timestamp. +// +// This is to be used for config dump and CSDS, not directly by users (like +// resolvers/balancers). +type UpdateWithMD struct { + MD UpdateMetadata + Raw *anypb.Any +} + +// ResourceType identifies resources in a transport protocol agnostic way. These +// will be used in transport version agnostic code, while the versioned API +// clients will map these to appropriate version URLs. +type ResourceType int + +// Version agnostic resource type constants. +const ( + UnknownResource ResourceType = iota + ListenerResource + HTTPConnManagerResource + RouteConfigResource + ClusterResource + EndpointsResource +) + +func (r ResourceType) String() string { + switch r { + case ListenerResource: + return "ListenerResource" + case HTTPConnManagerResource: + return "HTTPConnManagerResource" + case RouteConfigResource: + return "RouteConfigResource" + case ClusterResource: + return "ClusterResource" + case EndpointsResource: + return "EndpointsResource" + default: + return "UnknownResource" + } +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_cds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_cds.go new file mode 100644 index 000000000..c200380be --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_cds.go @@ -0,0 +1,87 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import "google.golang.org/protobuf/types/known/anypb" + +// ClusterType is the type of cluster from a received CDS response. +type ClusterType int + +const ( + // ClusterTypeEDS represents the EDS cluster type, which will delegate endpoint + // discovery to the management server. + ClusterTypeEDS ClusterType = iota + // ClusterTypeLogicalDNS represents the Logical DNS cluster type, which essentially + // maps to the gRPC behavior of using the DNS resolver with pick_first LB policy. + ClusterTypeLogicalDNS + // ClusterTypeAggregate represents the Aggregate Cluster type, which provides a + // prioritized list of clusters to use. It is used for failover between clusters + // with a different configuration. + ClusterTypeAggregate +) + +// ClusterLBPolicyRingHash represents ring_hash lb policy, and also contains its +// config. +type ClusterLBPolicyRingHash struct { + MinimumRingSize uint64 + MaximumRingSize uint64 +} + +// ClusterUpdate contains information from a received CDS response, which is of +// interest to the registered CDS watcher. +type ClusterUpdate struct { + ClusterType ClusterType + // ClusterName is the clusterName being watched for through CDS. + ClusterName string + // EDSServiceName is an optional name for EDS. If it's not set, the balancer + // should watch ClusterName for the EDS resources. + EDSServiceName string + // EnableLRS indicates whether or not load should be reported through LRS. + EnableLRS bool + // SecurityCfg contains security configuration sent by the control plane. + SecurityCfg *SecurityConfig + // MaxRequests for circuit breaking, if any (otherwise nil). + MaxRequests *uint32 + // DNSHostName is used only for cluster type DNS. It's the DNS name to + // resolve in "host:port" form + DNSHostName string + // PrioritizedClusterNames is used only for cluster type aggregate. It represents + // a prioritized list of cluster names. + PrioritizedClusterNames []string + + // LBPolicy is the lb policy for this cluster. + // + // This only support round_robin and ring_hash. + // - if it's nil, the lb policy is round_robin + // - if it's not nil, the lb policy is ring_hash, the this field has the config. + // + // When we add more support policies, this can be made an interface, and + // will be set to different types based on the policy type. + LBPolicy *ClusterLBPolicyRingHash + + // Raw is the resource from the xds response. + Raw *anypb.Any +} + +// ClusterUpdateErrTuple is a tuple with the update and error. It contains the +// results from unmarshal functions. It's used to pass unmarshal results of +// multiple resources together, e.g. in maps like `map[string]{Update,error}`. +type ClusterUpdateErrTuple struct { + Update ClusterUpdate + Err error +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_eds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_eds.go new file mode 100644 index 000000000..ad590160f --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_eds.go @@ -0,0 +1,80 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "google.golang.org/grpc/xds/internal" + "google.golang.org/protobuf/types/known/anypb" +) + +// OverloadDropConfig contains the config to drop overloads. +type OverloadDropConfig struct { + Category string + Numerator uint32 + Denominator uint32 +} + +// EndpointHealthStatus represents the health status of an endpoint. +type EndpointHealthStatus int32 + +const ( + // EndpointHealthStatusUnknown represents HealthStatus UNKNOWN. + EndpointHealthStatusUnknown EndpointHealthStatus = iota + // EndpointHealthStatusHealthy represents HealthStatus HEALTHY. + EndpointHealthStatusHealthy + // EndpointHealthStatusUnhealthy represents HealthStatus UNHEALTHY. + EndpointHealthStatusUnhealthy + // EndpointHealthStatusDraining represents HealthStatus DRAINING. + EndpointHealthStatusDraining + // EndpointHealthStatusTimeout represents HealthStatus TIMEOUT. + EndpointHealthStatusTimeout + // EndpointHealthStatusDegraded represents HealthStatus DEGRADED. + EndpointHealthStatusDegraded +) + +// Endpoint contains information of an endpoint. +type Endpoint struct { + Address string + HealthStatus EndpointHealthStatus + Weight uint32 +} + +// Locality contains information of a locality. +type Locality struct { + Endpoints []Endpoint + ID internal.LocalityID + Priority uint32 + Weight uint32 +} + +// EndpointsUpdate contains an EDS update. +type EndpointsUpdate struct { + Drops []OverloadDropConfig + Localities []Locality + + // Raw is the resource from the xds response. + Raw *anypb.Any +} + +// EndpointsUpdateErrTuple is a tuple with the update and error. It contains the +// results from unmarshal functions. It's used to pass unmarshal results of +// multiple resources together, e.g. in maps like `map[string]{Update,error}`. +type EndpointsUpdateErrTuple struct { + Update EndpointsUpdate + Err error +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_lds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_lds.go new file mode 100644 index 000000000..a2742fb43 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_lds.go @@ -0,0 +1,87 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "time" + + "google.golang.org/grpc/xds/internal/httpfilter" + "google.golang.org/protobuf/types/known/anypb" +) + +// ListenerUpdate contains information received in an LDS response, which is of +// interest to the registered LDS watcher. +type ListenerUpdate struct { + // RouteConfigName is the route configuration name corresponding to the + // target which is being watched through LDS. + // + // Exactly one of RouteConfigName and InlineRouteConfig is set. + RouteConfigName string + // InlineRouteConfig is the inline route configuration (RDS response) + // returned inside LDS. + // + // Exactly one of RouteConfigName and InlineRouteConfig is set. + InlineRouteConfig *RouteConfigUpdate + + // MaxStreamDuration contains the HTTP connection manager's + // common_http_protocol_options.max_stream_duration field, or zero if + // unset. + MaxStreamDuration time.Duration + // HTTPFilters is a list of HTTP filters (name, config) from the LDS + // response. + HTTPFilters []HTTPFilter + // InboundListenerCfg contains inbound listener configuration. + InboundListenerCfg *InboundListenerConfig + + // Raw is the resource from the xds response. + Raw *anypb.Any +} + +// HTTPFilter represents one HTTP filter from an LDS response's HTTP connection +// manager field. +type HTTPFilter struct { + // Name is an arbitrary name of the filter. Used for applying override + // settings in virtual host / route / weighted cluster configuration (not + // yet supported). + Name string + // Filter is the HTTP filter found in the registry for the config type. + Filter httpfilter.Filter + // Config contains the filter's configuration + Config httpfilter.FilterConfig +} + +// InboundListenerConfig contains information about the inbound listener, i.e +// the server-side listener. +type InboundListenerConfig struct { + // Address is the local address on which the inbound listener is expected to + // accept incoming connections. + Address string + // Port is the local port on which the inbound listener is expected to + // accept incoming connections. + Port string + // FilterChains is the list of filter chains associated with this listener. + FilterChains *FilterChainManager +} + +// ListenerUpdateErrTuple is a tuple with the update and error. It contains the +// results from unmarshal functions. It's used to pass unmarshal results of +// multiple resources together, e.g. in maps like `map[string]{Update,error}`. +type ListenerUpdateErrTuple struct { + Update ListenerUpdate + Err error +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_rds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_rds.go new file mode 100644 index 000000000..decffd4ae --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/type_rds.go @@ -0,0 +1,255 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "regexp" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/xds/matcher" + "google.golang.org/grpc/xds/internal/clusterspecifier" + "google.golang.org/grpc/xds/internal/httpfilter" + "google.golang.org/protobuf/types/known/anypb" +) + +// RouteConfigUpdate contains information received in an RDS response, which is +// of interest to the registered RDS watcher. +type RouteConfigUpdate struct { + VirtualHosts []*VirtualHost + // ClusterSpecifierPlugins are the LB Configurations for any + // ClusterSpecifierPlugins referenced by the Route Table. + ClusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig + // Raw is the resource from the xds response. + Raw *anypb.Any +} + +// VirtualHost contains the routes for a list of Domains. +// +// Note that the domains in this slice can be a wildcard, not an exact string. +// The consumer of this struct needs to find the best match for its hostname. +type VirtualHost struct { + Domains []string + // Routes contains a list of routes, each containing matchers and + // corresponding action. + Routes []*Route + // HTTPFilterConfigOverride contains any HTTP filter config overrides for + // the virtual host which may be present. An individual filter's override + // may be unused if the matching Route contains an override for that + // filter. + HTTPFilterConfigOverride map[string]httpfilter.FilterConfig + RetryConfig *RetryConfig +} + +// RetryConfig contains all retry-related configuration in either a VirtualHost +// or Route. +type RetryConfig struct { + // RetryOn is a set of status codes on which to retry. Only Canceled, + // DeadlineExceeded, Internal, ResourceExhausted, and Unavailable are + // supported; any other values will be omitted. + RetryOn map[codes.Code]bool + NumRetries uint32 // maximum number of retry attempts + RetryBackoff RetryBackoff // retry backoff policy +} + +// RetryBackoff describes the backoff policy for retries. +type RetryBackoff struct { + BaseInterval time.Duration // initial backoff duration between attempts + MaxInterval time.Duration // maximum backoff duration +} + +// HashPolicyType specifies the type of HashPolicy from a received RDS Response. +type HashPolicyType int + +const ( + // HashPolicyTypeHeader specifies to hash a Header in the incoming request. + HashPolicyTypeHeader HashPolicyType = iota + // HashPolicyTypeChannelID specifies to hash a unique Identifier of the + // Channel. In grpc-go, this will be done using the ClientConn pointer. + HashPolicyTypeChannelID +) + +// HashPolicy specifies the HashPolicy if the upstream cluster uses a hashing +// load balancer. +type HashPolicy struct { + HashPolicyType HashPolicyType + Terminal bool + // Fields used for type HEADER. + HeaderName string + Regex *regexp.Regexp + RegexSubstitution string +} + +// RouteActionType is the action of the route from a received RDS response. +type RouteActionType int + +const ( + // RouteActionUnsupported are routing types currently unsupported by grpc. + // According to A36, "A Route with an inappropriate action causes RPCs + // matching that route to fail." + RouteActionUnsupported RouteActionType = iota + // RouteActionRoute is the expected route type on the client side. Route + // represents routing a request to some upstream cluster. On the client + // side, if an RPC matches to a route that is not RouteActionRoute, the RPC + // will fail according to A36. + RouteActionRoute + // RouteActionNonForwardingAction is the expected route type on the server + // side. NonForwardingAction represents when a route will generate a + // response directly, without forwarding to an upstream host. + RouteActionNonForwardingAction +) + +// Route is both a specification of how to match a request as well as an +// indication of the action to take upon match. +type Route struct { + Path *string + Prefix *string + Regex *regexp.Regexp + // Indicates if prefix/path matching should be case insensitive. The default + // is false (case sensitive). + CaseInsensitive bool + Headers []*HeaderMatcher + Fraction *uint32 + + HashPolicies []*HashPolicy + + // If the matchers above indicate a match, the below configuration is used. + // If MaxStreamDuration is nil, it indicates neither of the route action's + // max_stream_duration fields (grpc_timeout_header_max nor + // max_stream_duration) were set. In this case, the ListenerUpdate's + // MaxStreamDuration field should be used. If MaxStreamDuration is set to + // an explicit zero duration, the application's deadline should be used. + MaxStreamDuration *time.Duration + // HTTPFilterConfigOverride contains any HTTP filter config overrides for + // the route which may be present. An individual filter's override may be + // unused if the matching WeightedCluster contains an override for that + // filter. + HTTPFilterConfigOverride map[string]httpfilter.FilterConfig + RetryConfig *RetryConfig + + ActionType RouteActionType + + // Only one of the following fields (WeightedClusters or + // ClusterSpecifierPlugin) will be set for a route. + WeightedClusters map[string]WeightedCluster + // ClusterSpecifierPlugin is the name of the Cluster Specifier Plugin that + // this Route is linked to, if specified by xDS. + ClusterSpecifierPlugin string +} + +// WeightedCluster contains settings for an xds ActionType.WeightedCluster. +type WeightedCluster struct { + // Weight is the relative weight of the cluster. It will never be zero. + Weight uint32 + // HTTPFilterConfigOverride contains any HTTP filter config overrides for + // the weighted cluster which may be present. + HTTPFilterConfigOverride map[string]httpfilter.FilterConfig +} + +// HeaderMatcher represents header matchers. +type HeaderMatcher struct { + Name string + InvertMatch *bool + ExactMatch *string + RegexMatch *regexp.Regexp + PrefixMatch *string + SuffixMatch *string + RangeMatch *Int64Range + PresentMatch *bool +} + +// Int64Range is a range for header range match. +type Int64Range struct { + Start int64 + End int64 +} + +// SecurityConfig contains the security configuration received as part of the +// Cluster resource on the client-side, and as part of the Listener resource on +// the server-side. +type SecurityConfig struct { + // RootInstanceName identifies the certProvider plugin to be used to fetch + // root certificates. This instance name will be resolved to the plugin name + // and its associated configuration from the certificate_providers field of + // the bootstrap file. + RootInstanceName string + // RootCertName is the certificate name to be passed to the plugin (looked + // up from the bootstrap file) while fetching root certificates. + RootCertName string + // IdentityInstanceName identifies the certProvider plugin to be used to + // fetch identity certificates. This instance name will be resolved to the + // plugin name and its associated configuration from the + // certificate_providers field of the bootstrap file. + IdentityInstanceName string + // IdentityCertName is the certificate name to be passed to the plugin + // (looked up from the bootstrap file) while fetching identity certificates. + IdentityCertName string + // SubjectAltNameMatchers is an optional list of match criteria for SANs + // specified on the peer certificate. Used only on the client-side. + // + // Some intricacies: + // - If this field is empty, then any peer certificate is accepted. + // - If the peer certificate contains a wildcard DNS SAN, and an `exact` + // matcher is configured, a wildcard DNS match is performed instead of a + // regular string comparison. + SubjectAltNameMatchers []matcher.StringMatcher + // RequireClientCert indicates if the server handshake process expects the + // client to present a certificate. Set to true when performing mTLS. Used + // only on the server-side. + RequireClientCert bool +} + +// Equal returns true if sc is equal to other. +func (sc *SecurityConfig) Equal(other *SecurityConfig) bool { + switch { + case sc == nil && other == nil: + return true + case (sc != nil) != (other != nil): + return false + } + switch { + case sc.RootInstanceName != other.RootInstanceName: + return false + case sc.RootCertName != other.RootCertName: + return false + case sc.IdentityInstanceName != other.IdentityInstanceName: + return false + case sc.IdentityCertName != other.IdentityCertName: + return false + case sc.RequireClientCert != other.RequireClientCert: + return false + default: + if len(sc.SubjectAltNameMatchers) != len(other.SubjectAltNameMatchers) { + return false + } + for i := 0; i < len(sc.SubjectAltNameMatchers); i++ { + if !sc.SubjectAltNameMatchers[i].Equal(other.SubjectAltNameMatchers[i]) { + return false + } + } + } + return true +} + +// RouteConfigUpdateErrTuple is a tuple with the update and error. It contains +// the results from unmarshal functions. It's used to pass unmarshal results of +// multiple resources together, e.g. in maps like `map[string]{Update,error}`. +type RouteConfigUpdateErrTuple struct { + Update RouteConfigUpdate + Err error +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal.go new file mode 100644 index 000000000..7cd9d32dd --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal.go @@ -0,0 +1,174 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +// Package xdsresource contains functions to proto xds updates (unmarshal from +// proto), and types for the resource updates. +package xdsresource + +import ( + "errors" + "fmt" + "strings" + "time" + + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/protobuf/types/known/anypb" +) + +// UnmarshalOptions wraps the input parameters for `UnmarshalXxx` functions. +type UnmarshalOptions struct { + // Version is the version of the received response. + Version string + // Resources are the xDS resources resources in the received response. + Resources []*anypb.Any + // Logger is the prefix logger to be used during unmarshaling. + Logger *grpclog.PrefixLogger + // UpdateValidator is a post unmarshal validation check provided by the + // upper layer. + UpdateValidator UpdateValidatorFunc +} + +// processAllResources unmarshals and validates the resources, populates the +// provided ret (a map), and returns metadata and error. +// +// After this function, the ret map will be populated with both valid and +// invalid updates. Invalid resources will have an entry with the key as the +// resource name, value as an empty update. +// +// The type of the resource is determined by the type of ret. E.g. +// map[string]ListenerUpdate means this is for LDS. +func processAllResources(opts *UnmarshalOptions, ret interface{}) (UpdateMetadata, error) { + timestamp := time.Now() + md := UpdateMetadata{ + Version: opts.Version, + Timestamp: timestamp, + } + var topLevelErrors []error + perResourceErrors := make(map[string]error) + + for _, r := range opts.Resources { + switch ret2 := ret.(type) { + case map[string]ListenerUpdateErrTuple: + name, update, err := unmarshalListenerResource(r, opts.UpdateValidator, opts.Logger) + if err == nil { + ret2[name] = ListenerUpdateErrTuple{Update: update} + continue + } + if name == "" { + topLevelErrors = append(topLevelErrors, err) + continue + } + perResourceErrors[name] = err + // Add place holder in the map so we know this resource name was in + // the response. + ret2[name] = ListenerUpdateErrTuple{Err: err} + case map[string]RouteConfigUpdateErrTuple: + name, update, err := unmarshalRouteConfigResource(r, opts.Logger) + if err == nil { + ret2[name] = RouteConfigUpdateErrTuple{Update: update} + continue + } + if name == "" { + topLevelErrors = append(topLevelErrors, err) + continue + } + perResourceErrors[name] = err + // Add place holder in the map so we know this resource name was in + // the response. + ret2[name] = RouteConfigUpdateErrTuple{Err: err} + case map[string]ClusterUpdateErrTuple: + name, update, err := unmarshalClusterResource(r, opts.UpdateValidator, opts.Logger) + if err == nil { + ret2[name] = ClusterUpdateErrTuple{Update: update} + continue + } + if name == "" { + topLevelErrors = append(topLevelErrors, err) + continue + } + perResourceErrors[name] = err + // Add place holder in the map so we know this resource name was in + // the response. + ret2[name] = ClusterUpdateErrTuple{Err: err} + case map[string]EndpointsUpdateErrTuple: + name, update, err := unmarshalEndpointsResource(r, opts.Logger) + if err == nil { + ret2[name] = EndpointsUpdateErrTuple{Update: update} + continue + } + if name == "" { + topLevelErrors = append(topLevelErrors, err) + continue + } + perResourceErrors[name] = err + // Add place holder in the map so we know this resource name was in + // the response. + ret2[name] = EndpointsUpdateErrTuple{Err: err} + } + } + + if len(topLevelErrors) == 0 && len(perResourceErrors) == 0 { + md.Status = ServiceStatusACKed + return md, nil + } + + var typeStr string + switch ret.(type) { + case map[string]ListenerUpdate: + typeStr = "LDS" + case map[string]RouteConfigUpdate: + typeStr = "RDS" + case map[string]ClusterUpdate: + typeStr = "CDS" + case map[string]EndpointsUpdate: + typeStr = "EDS" + } + + md.Status = ServiceStatusNACKed + errRet := combineErrors(typeStr, topLevelErrors, perResourceErrors) + md.ErrState = &UpdateErrorMetadata{ + Version: opts.Version, + Err: errRet, + Timestamp: timestamp, + } + return md, errRet +} + +func combineErrors(rType string, topLevelErrors []error, perResourceErrors map[string]error) error { + var errStrB strings.Builder + errStrB.WriteString(fmt.Sprintf("error parsing %q response: ", rType)) + if len(topLevelErrors) > 0 { + errStrB.WriteString("top level errors: ") + for i, err := range topLevelErrors { + if i != 0 { + errStrB.WriteString(";\n") + } + errStrB.WriteString(err.Error()) + } + } + if len(perResourceErrors) > 0 { + var i int + for name, err := range perResourceErrors { + if i != 0 { + errStrB.WriteString(";\n") + } + i++ + errStrB.WriteString(fmt.Sprintf("resource %q: %v", name, err.Error())) + } + } + return errors.New(errStrB.String()) +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_cds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_cds.go new file mode 100644 index 000000000..5b34c1ae6 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_cds.go @@ -0,0 +1,456 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "errors" + "fmt" + "net" + "strconv" + + v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" + v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" + v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + "github.com/golang/protobuf/proto" + "google.golang.org/grpc/internal/envconfig" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/internal/xds/matcher" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" +) + +// TransportSocket proto message has a `name` field which is expected to be set +// to this value by the management server. +const transportSocketName = "envoy.transport_sockets.tls" + +// UnmarshalCluster processes resources received in an CDS response, validates +// them, and transforms them into a native struct which contains only fields we +// are interested in. +func UnmarshalCluster(opts *UnmarshalOptions) (map[string]ClusterUpdateErrTuple, UpdateMetadata, error) { + update := make(map[string]ClusterUpdateErrTuple) + md, err := processAllResources(opts, update) + return update, md, err +} + +func unmarshalClusterResource(r *anypb.Any, f UpdateValidatorFunc, logger *grpclog.PrefixLogger) (string, ClusterUpdate, error) { + if !IsClusterResource(r.GetTypeUrl()) { + return "", ClusterUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) + } + + cluster := &v3clusterpb.Cluster{} + if err := proto.Unmarshal(r.GetValue(), cluster); err != nil { + return "", ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) + } + logger.Infof("Resource with name: %v, type: %T, contains: %v", cluster.GetName(), cluster, pretty.ToJSON(cluster)) + cu, err := validateClusterAndConstructClusterUpdate(cluster) + if err != nil { + return cluster.GetName(), ClusterUpdate{}, err + } + cu.Raw = r + if f != nil { + if err := f(cu); err != nil { + return "", ClusterUpdate{}, err + } + } + + return cluster.GetName(), cu, nil +} + +const ( + defaultRingHashMinSize = 1024 + defaultRingHashMaxSize = 8 * 1024 * 1024 // 8M + ringHashSizeUpperBound = 8 * 1024 * 1024 // 8M +) + +func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster) (ClusterUpdate, error) { + var lbPolicy *ClusterLBPolicyRingHash + switch cluster.GetLbPolicy() { + case v3clusterpb.Cluster_ROUND_ROBIN: + lbPolicy = nil // The default is round_robin, and there's no config to set. + case v3clusterpb.Cluster_RING_HASH: + if !envconfig.XDSRingHash { + return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) + } + rhc := cluster.GetRingHashLbConfig() + if rhc.GetHashFunction() != v3clusterpb.Cluster_RingHashLbConfig_XX_HASH { + return ClusterUpdate{}, fmt.Errorf("unsupported ring_hash hash function %v in response: %+v", rhc.GetHashFunction(), cluster) + } + // Minimum defaults to 1024 entries, and limited to 8M entries Maximum + // defaults to 8M entries, and limited to 8M entries + var minSize, maxSize uint64 = defaultRingHashMinSize, defaultRingHashMaxSize + if min := rhc.GetMinimumRingSize(); min != nil { + if min.GetValue() > ringHashSizeUpperBound { + return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash mininum ring size %v in response: %+v", min.GetValue(), cluster) + } + minSize = min.GetValue() + } + if max := rhc.GetMaximumRingSize(); max != nil { + if max.GetValue() > ringHashSizeUpperBound { + return ClusterUpdate{}, fmt.Errorf("unexpected ring_hash maxinum ring size %v in response: %+v", max.GetValue(), cluster) + } + maxSize = max.GetValue() + } + if minSize > maxSize { + return ClusterUpdate{}, fmt.Errorf("ring_hash config min size %v is greater than max %v", minSize, maxSize) + } + lbPolicy = &ClusterLBPolicyRingHash{MinimumRingSize: minSize, MaximumRingSize: maxSize} + default: + return ClusterUpdate{}, fmt.Errorf("unexpected lbPolicy %v in response: %+v", cluster.GetLbPolicy(), cluster) + } + + // Process security configuration received from the control plane iff the + // corresponding environment variable is set. + var sc *SecurityConfig + if envconfig.XDSClientSideSecurity { + var err error + if sc, err = securityConfigFromCluster(cluster); err != nil { + return ClusterUpdate{}, err + } + } + + ret := ClusterUpdate{ + ClusterName: cluster.GetName(), + EnableLRS: cluster.GetLrsServer().GetSelf() != nil, + SecurityCfg: sc, + MaxRequests: circuitBreakersFromCluster(cluster), + LBPolicy: lbPolicy, + } + + // Validate and set cluster type from the response. + switch { + case cluster.GetType() == v3clusterpb.Cluster_EDS: + if cluster.GetEdsClusterConfig().GetEdsConfig().GetAds() == nil { + return ClusterUpdate{}, fmt.Errorf("unexpected edsConfig in response: %+v", cluster) + } + ret.ClusterType = ClusterTypeEDS + ret.EDSServiceName = cluster.GetEdsClusterConfig().GetServiceName() + return ret, nil + case cluster.GetType() == v3clusterpb.Cluster_LOGICAL_DNS: + if !envconfig.XDSAggregateAndDNS { + return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) + } + ret.ClusterType = ClusterTypeLogicalDNS + dnsHN, err := dnsHostNameFromCluster(cluster) + if err != nil { + return ClusterUpdate{}, err + } + ret.DNSHostName = dnsHN + return ret, nil + case cluster.GetClusterType() != nil && cluster.GetClusterType().Name == "envoy.clusters.aggregate": + if !envconfig.XDSAggregateAndDNS { + return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) + } + clusters := &v3aggregateclusterpb.ClusterConfig{} + if err := proto.Unmarshal(cluster.GetClusterType().GetTypedConfig().GetValue(), clusters); err != nil { + return ClusterUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) + } + ret.ClusterType = ClusterTypeAggregate + ret.PrioritizedClusterNames = clusters.Clusters + return ret, nil + default: + return ClusterUpdate{}, fmt.Errorf("unsupported cluster type (%v, %v) in response: %+v", cluster.GetType(), cluster.GetClusterType(), cluster) + } +} + +// dnsHostNameFromCluster extracts the DNS host name from the cluster's load +// assignment. +// +// There should be exactly one locality, with one endpoint, whose address +// contains the address and port. +func dnsHostNameFromCluster(cluster *v3clusterpb.Cluster) (string, error) { + loadAssignment := cluster.GetLoadAssignment() + if loadAssignment == nil { + return "", fmt.Errorf("load_assignment not present for LOGICAL_DNS cluster") + } + if len(loadAssignment.GetEndpoints()) != 1 { + return "", fmt.Errorf("load_assignment for LOGICAL_DNS cluster must have exactly one locality, got: %+v", loadAssignment) + } + endpoints := loadAssignment.GetEndpoints()[0].GetLbEndpoints() + if len(endpoints) != 1 { + return "", fmt.Errorf("locality for LOGICAL_DNS cluster must have exactly one endpoint, got: %+v", endpoints) + } + endpoint := endpoints[0].GetEndpoint() + if endpoint == nil { + return "", fmt.Errorf("endpoint for LOGICAL_DNS cluster not set") + } + socketAddr := endpoint.GetAddress().GetSocketAddress() + if socketAddr == nil { + return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set") + } + if socketAddr.GetResolverName() != "" { + return "", fmt.Errorf("socket address for endpoint for LOGICAL_DNS cluster not set has unexpected custom resolver name: %v", socketAddr.GetResolverName()) + } + host := socketAddr.GetAddress() + if host == "" { + return "", fmt.Errorf("host for endpoint for LOGICAL_DNS cluster not set") + } + port := socketAddr.GetPortValue() + if port == 0 { + return "", fmt.Errorf("port for endpoint for LOGICAL_DNS cluster not set") + } + return net.JoinHostPort(host, strconv.Itoa(int(port))), nil +} + +// securityConfigFromCluster extracts the relevant security configuration from +// the received Cluster resource. +func securityConfigFromCluster(cluster *v3clusterpb.Cluster) (*SecurityConfig, error) { + if tsm := cluster.GetTransportSocketMatches(); len(tsm) != 0 { + return nil, fmt.Errorf("unsupport transport_socket_matches field is non-empty: %+v", tsm) + } + // The Cluster resource contains a `transport_socket` field, which contains + // a oneof `typed_config` field of type `protobuf.Any`. The any proto + // contains a marshaled representation of an `UpstreamTlsContext` message. + ts := cluster.GetTransportSocket() + if ts == nil { + return nil, nil + } + if name := ts.GetName(); name != transportSocketName { + return nil, fmt.Errorf("transport_socket field has unexpected name: %s", name) + } + any := ts.GetTypedConfig() + if any == nil || any.TypeUrl != version.V3UpstreamTLSContextURL { + return nil, fmt.Errorf("transport_socket field has unexpected typeURL: %s", any.TypeUrl) + } + upstreamCtx := &v3tlspb.UpstreamTlsContext{} + if err := proto.Unmarshal(any.GetValue(), upstreamCtx); err != nil { + return nil, fmt.Errorf("failed to unmarshal UpstreamTlsContext in CDS response: %v", err) + } + // The following fields from `UpstreamTlsContext` are ignored: + // - sni + // - allow_renegotiation + // - max_session_keys + if upstreamCtx.GetCommonTlsContext() == nil { + return nil, errors.New("UpstreamTlsContext in CDS response does not contain a CommonTlsContext") + } + + return securityConfigFromCommonTLSContext(upstreamCtx.GetCommonTlsContext(), false) +} + +// common is expected to be not nil. +// The `alpn_protocols` field is ignored. +func securityConfigFromCommonTLSContext(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { + if common.GetTlsParams() != nil { + return nil, fmt.Errorf("unsupported tls_params field in CommonTlsContext message: %+v", common) + } + if common.GetCustomHandshaker() != nil { + return nil, fmt.Errorf("unsupported custom_handshaker field in CommonTlsContext message: %+v", common) + } + + // For now, if we can't get a valid security config from the new fields, we + // fallback to the old deprecated fields. + // TODO: Drop support for deprecated fields. NACK if err != nil here. + sc, _ := securityConfigFromCommonTLSContextUsingNewFields(common, server) + if sc == nil || sc.Equal(&SecurityConfig{}) { + var err error + sc, err = securityConfigFromCommonTLSContextWithDeprecatedFields(common, server) + if err != nil { + return nil, err + } + } + if sc != nil { + // sc == nil is a valid case where the control plane has not sent us any + // security configuration. xDS creds will use fallback creds. + if server { + if sc.IdentityInstanceName == "" { + return nil, errors.New("security configuration on the server-side does not contain identity certificate provider instance name") + } + } else { + if sc.RootInstanceName == "" { + return nil, errors.New("security configuration on the client-side does not contain root certificate provider instance name") + } + } + } + return sc, nil +} + +func securityConfigFromCommonTLSContextWithDeprecatedFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { + // The `CommonTlsContext` contains a + // `tls_certificate_certificate_provider_instance` field of type + // `CertificateProviderInstance`, which contains the provider instance name + // and the certificate name to fetch identity certs. + sc := &SecurityConfig{} + if identity := common.GetTlsCertificateCertificateProviderInstance(); identity != nil { + sc.IdentityInstanceName = identity.GetInstanceName() + sc.IdentityCertName = identity.GetCertificateName() + } + + // The `CommonTlsContext` contains a `validation_context_type` field which + // is a oneof. We can get the values that we are interested in from two of + // those possible values: + // - combined validation context: + // - contains a default validation context which holds the list of + // matchers for accepted SANs. + // - contains certificate provider instance configuration + // - certificate provider instance configuration + // - in this case, we do not get a list of accepted SANs. + switch t := common.GetValidationContextType().(type) { + case *v3tlspb.CommonTlsContext_CombinedValidationContext: + combined := common.GetCombinedValidationContext() + var matchers []matcher.StringMatcher + if def := combined.GetDefaultValidationContext(); def != nil { + for _, m := range def.GetMatchSubjectAltNames() { + matcher, err := matcher.StringMatcherFromProto(m) + if err != nil { + return nil, err + } + matchers = append(matchers, matcher) + } + } + if server && len(matchers) != 0 { + return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) + } + sc.SubjectAltNameMatchers = matchers + if pi := combined.GetValidationContextCertificateProviderInstance(); pi != nil { + sc.RootInstanceName = pi.GetInstanceName() + sc.RootCertName = pi.GetCertificateName() + } + case *v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance: + pi := common.GetValidationContextCertificateProviderInstance() + sc.RootInstanceName = pi.GetInstanceName() + sc.RootCertName = pi.GetCertificateName() + case nil: + // It is valid for the validation context to be nil on the server side. + default: + return nil, fmt.Errorf("validation context contains unexpected type: %T", t) + } + return sc, nil +} + +// gRFC A29 https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md +// specifies the new way to fetch security configuration and says the following: +// +// Although there are various ways to obtain certificates as per this proto +// (which are supported by Envoy), gRPC supports only one of them and that is +// the `CertificateProviderPluginInstance` proto. +// +// This helper function attempts to fetch security configuration from the +// `CertificateProviderPluginInstance` message, given a CommonTlsContext. +func securityConfigFromCommonTLSContextUsingNewFields(common *v3tlspb.CommonTlsContext, server bool) (*SecurityConfig, error) { + // The `tls_certificate_provider_instance` field of type + // `CertificateProviderPluginInstance` is used to fetch the identity + // certificate provider. + sc := &SecurityConfig{} + identity := common.GetTlsCertificateProviderInstance() + if identity == nil && len(common.GetTlsCertificates()) != 0 { + return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificates is set in CommonTlsContext message: %+v", common) + } + if identity == nil && common.GetTlsCertificateSdsSecretConfigs() != nil { + return nil, fmt.Errorf("expected field tls_certificate_provider_instance is not set, while unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message: %+v", common) + } + sc.IdentityInstanceName = identity.GetInstanceName() + sc.IdentityCertName = identity.GetCertificateName() + + // The `CommonTlsContext` contains a oneof field `validation_context_type`, + // which contains the `CertificateValidationContext` message in one of the + // following ways: + // - `validation_context` field + // - this is directly of type `CertificateValidationContext` + // - `combined_validation_context` field + // - this is of type `CombinedCertificateValidationContext` and contains + // a `default validation context` field of type + // `CertificateValidationContext` + // + // The `CertificateValidationContext` message has the following fields that + // we are interested in: + // - `ca_certificate_provider_instance` + // - this is of type `CertificateProviderPluginInstance` + // - `match_subject_alt_names` + // - this is a list of string matchers + // + // The `CertificateProviderPluginInstance` message contains two fields + // - instance_name + // - this is the certificate provider instance name to be looked up in + // the bootstrap configuration + // - certificate_name + // - this is an opaque name passed to the certificate provider + var validationCtx *v3tlspb.CertificateValidationContext + switch typ := common.GetValidationContextType().(type) { + case *v3tlspb.CommonTlsContext_ValidationContext: + validationCtx = common.GetValidationContext() + case *v3tlspb.CommonTlsContext_CombinedValidationContext: + validationCtx = common.GetCombinedValidationContext().GetDefaultValidationContext() + case nil: + // It is valid for the validation context to be nil on the server side. + return sc, nil + default: + return nil, fmt.Errorf("validation context contains unexpected type: %T", typ) + } + // If we get here, it means that the `CertificateValidationContext` message + // was found through one of the supported ways. It is an error if the + // validation context is specified, but it does not contain the + // ca_certificate_provider_instance field which contains information about + // the certificate provider to be used for the root certificates. + if validationCtx.GetCaCertificateProviderInstance() == nil { + return nil, fmt.Errorf("expected field ca_certificate_provider_instance is missing in CommonTlsContext message: %+v", common) + } + // The following fields are ignored: + // - trusted_ca + // - watched_directory + // - allow_expired_certificate + // - trust_chain_verification + switch { + case len(validationCtx.GetVerifyCertificateSpki()) != 0: + return nil, fmt.Errorf("unsupported verify_certificate_spki field in CommonTlsContext message: %+v", common) + case len(validationCtx.GetVerifyCertificateHash()) != 0: + return nil, fmt.Errorf("unsupported verify_certificate_hash field in CommonTlsContext message: %+v", common) + case validationCtx.GetRequireSignedCertificateTimestamp().GetValue(): + return nil, fmt.Errorf("unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message: %+v", common) + case validationCtx.GetCrl() != nil: + return nil, fmt.Errorf("unsupported crl field in CommonTlsContext message: %+v", common) + case validationCtx.GetCustomValidatorConfig() != nil: + return nil, fmt.Errorf("unsupported custom_validator_config field in CommonTlsContext message: %+v", common) + } + + if rootProvider := validationCtx.GetCaCertificateProviderInstance(); rootProvider != nil { + sc.RootInstanceName = rootProvider.GetInstanceName() + sc.RootCertName = rootProvider.GetCertificateName() + } + var matchers []matcher.StringMatcher + for _, m := range validationCtx.GetMatchSubjectAltNames() { + matcher, err := matcher.StringMatcherFromProto(m) + if err != nil { + return nil, err + } + matchers = append(matchers, matcher) + } + if server && len(matchers) != 0 { + return nil, fmt.Errorf("match_subject_alt_names field in validation context is not supported on the server: %v", common) + } + sc.SubjectAltNameMatchers = matchers + return sc, nil +} + +// circuitBreakersFromCluster extracts the circuit breakers configuration from +// the received cluster resource. Returns nil if no CircuitBreakers or no +// Thresholds in CircuitBreakers. +func circuitBreakersFromCluster(cluster *v3clusterpb.Cluster) *uint32 { + for _, threshold := range cluster.GetCircuitBreakers().GetThresholds() { + if threshold.GetPriority() != v3corepb.RoutingPriority_DEFAULT { + continue + } + maxRequestsPb := threshold.GetMaxRequests() + if maxRequestsPb == nil { + return nil + } + maxRequests := maxRequestsPb.GetValue() + return &maxRequests + } + return nil +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_eds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_eds.go new file mode 100644 index 000000000..f1774deda --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_eds.go @@ -0,0 +1,131 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "fmt" + "net" + "strconv" + + v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" + v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/golang/protobuf/proto" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal" + "google.golang.org/protobuf/types/known/anypb" +) + +// UnmarshalEndpoints processes resources received in an EDS response, +// validates them, and transforms them into a native struct which contains only +// fields we are interested in. +func UnmarshalEndpoints(opts *UnmarshalOptions) (map[string]EndpointsUpdateErrTuple, UpdateMetadata, error) { + update := make(map[string]EndpointsUpdateErrTuple) + md, err := processAllResources(opts, update) + return update, md, err +} + +func unmarshalEndpointsResource(r *anypb.Any, logger *grpclog.PrefixLogger) (string, EndpointsUpdate, error) { + if !IsEndpointsResource(r.GetTypeUrl()) { + return "", EndpointsUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) + } + + cla := &v3endpointpb.ClusterLoadAssignment{} + if err := proto.Unmarshal(r.GetValue(), cla); err != nil { + return "", EndpointsUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) + } + logger.Infof("Resource with name: %v, type: %T, contains: %v", cla.GetClusterName(), cla, pretty.ToJSON(cla)) + + u, err := parseEDSRespProto(cla) + if err != nil { + return cla.GetClusterName(), EndpointsUpdate{}, err + } + u.Raw = r + return cla.GetClusterName(), u, nil +} + +func parseAddress(socketAddress *v3corepb.SocketAddress) string { + return net.JoinHostPort(socketAddress.GetAddress(), strconv.Itoa(int(socketAddress.GetPortValue()))) +} + +func parseDropPolicy(dropPolicy *v3endpointpb.ClusterLoadAssignment_Policy_DropOverload) OverloadDropConfig { + percentage := dropPolicy.GetDropPercentage() + var ( + numerator = percentage.GetNumerator() + denominator uint32 + ) + switch percentage.GetDenominator() { + case v3typepb.FractionalPercent_HUNDRED: + denominator = 100 + case v3typepb.FractionalPercent_TEN_THOUSAND: + denominator = 10000 + case v3typepb.FractionalPercent_MILLION: + denominator = 1000000 + } + return OverloadDropConfig{ + Category: dropPolicy.GetCategory(), + Numerator: numerator, + Denominator: denominator, + } +} + +func parseEndpoints(lbEndpoints []*v3endpointpb.LbEndpoint) []Endpoint { + endpoints := make([]Endpoint, 0, len(lbEndpoints)) + for _, lbEndpoint := range lbEndpoints { + endpoints = append(endpoints, Endpoint{ + HealthStatus: EndpointHealthStatus(lbEndpoint.GetHealthStatus()), + Address: parseAddress(lbEndpoint.GetEndpoint().GetAddress().GetSocketAddress()), + Weight: lbEndpoint.GetLoadBalancingWeight().GetValue(), + }) + } + return endpoints +} + +func parseEDSRespProto(m *v3endpointpb.ClusterLoadAssignment) (EndpointsUpdate, error) { + ret := EndpointsUpdate{} + for _, dropPolicy := range m.GetPolicy().GetDropOverloads() { + ret.Drops = append(ret.Drops, parseDropPolicy(dropPolicy)) + } + priorities := make(map[uint32]struct{}) + for _, locality := range m.Endpoints { + l := locality.GetLocality() + if l == nil { + return EndpointsUpdate{}, fmt.Errorf("EDS response contains a locality without ID, locality: %+v", locality) + } + lid := internal.LocalityID{ + Region: l.Region, + Zone: l.Zone, + SubZone: l.SubZone, + } + priority := locality.GetPriority() + priorities[priority] = struct{}{} + ret.Localities = append(ret.Localities, Locality{ + ID: lid, + Endpoints: parseEndpoints(locality.GetLbEndpoints()), + Weight: locality.GetLoadBalancingWeight().GetValue(), + Priority: priority, + }) + } + for i := 0; i < len(priorities); i++ { + if _, ok := priorities[uint32(i)]; !ok { + return EndpointsUpdate{}, fmt.Errorf("priority %v missing (with different priorities %v received)", i, priorities) + } + } + return ret, nil +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_lds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_lds.go new file mode 100644 index 000000000..f9663d05b --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_lds.go @@ -0,0 +1,297 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "errors" + "fmt" + "strconv" + + v1udpatypepb "github.com/cncf/udpa/go/udpa/type/v1" + v3cncftypepb "github.com/cncf/xds/go/xds/type/v3" + v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal/httpfilter" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" +) + +// UnmarshalListener processes resources received in an LDS response, validates +// them, and transforms them into a native struct which contains only fields we +// are interested in. +func UnmarshalListener(opts *UnmarshalOptions) (map[string]ListenerUpdateErrTuple, UpdateMetadata, error) { + update := make(map[string]ListenerUpdateErrTuple) + md, err := processAllResources(opts, update) + return update, md, err +} + +func unmarshalListenerResource(r *anypb.Any, f UpdateValidatorFunc, logger *grpclog.PrefixLogger) (string, ListenerUpdate, error) { + if !IsListenerResource(r.GetTypeUrl()) { + return "", ListenerUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) + } + // TODO: Pass version.TransportAPI instead of relying upon the type URL + v2 := r.GetTypeUrl() == version.V2ListenerURL + lis := &v3listenerpb.Listener{} + if err := proto.Unmarshal(r.GetValue(), lis); err != nil { + return "", ListenerUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) + } + logger.Infof("Resource with name: %v, type: %T, contains: %v", lis.GetName(), lis, pretty.ToJSON(lis)) + + lu, err := processListener(lis, logger, v2) + if err != nil { + return lis.GetName(), ListenerUpdate{}, err + } + if f != nil { + if err := f(*lu); err != nil { + return lis.GetName(), ListenerUpdate{}, err + } + } + lu.Raw = r + return lis.GetName(), *lu, nil +} + +func processListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger, v2 bool) (*ListenerUpdate, error) { + if lis.GetApiListener() != nil { + return processClientSideListener(lis, logger, v2) + } + return processServerSideListener(lis, logger) +} + +// processClientSideListener checks if the provided Listener proto meets +// the expected criteria. If so, it returns a non-empty routeConfigName. +func processClientSideListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger, v2 bool) (*ListenerUpdate, error) { + update := &ListenerUpdate{} + + apiLisAny := lis.GetApiListener().GetApiListener() + if !IsHTTPConnManagerResource(apiLisAny.GetTypeUrl()) { + return nil, fmt.Errorf("unexpected resource type: %q", apiLisAny.GetTypeUrl()) + } + apiLis := &v3httppb.HttpConnectionManager{} + if err := proto.Unmarshal(apiLisAny.GetValue(), apiLis); err != nil { + return nil, fmt.Errorf("failed to unmarshal api_listner: %v", err) + } + // "HttpConnectionManager.xff_num_trusted_hops must be unset or zero and + // HttpConnectionManager.original_ip_detection_extensions must be empty. If + // either field has an incorrect value, the Listener must be NACKed." - A41 + if apiLis.XffNumTrustedHops != 0 { + return nil, fmt.Errorf("xff_num_trusted_hops must be unset or zero %+v", apiLis) + } + if len(apiLis.OriginalIpDetectionExtensions) != 0 { + return nil, fmt.Errorf("original_ip_detection_extensions must be empty %+v", apiLis) + } + + switch apiLis.RouteSpecifier.(type) { + case *v3httppb.HttpConnectionManager_Rds: + if apiLis.GetRds().GetConfigSource().GetAds() == nil { + return nil, fmt.Errorf("ConfigSource is not ADS: %+v", lis) + } + name := apiLis.GetRds().GetRouteConfigName() + if name == "" { + return nil, fmt.Errorf("empty route_config_name: %+v", lis) + } + update.RouteConfigName = name + case *v3httppb.HttpConnectionManager_RouteConfig: + routeU, err := generateRDSUpdateFromRouteConfiguration(apiLis.GetRouteConfig(), logger, v2) + if err != nil { + return nil, fmt.Errorf("failed to parse inline RDS resp: %v", err) + } + update.InlineRouteConfig = &routeU + case nil: + return nil, fmt.Errorf("no RouteSpecifier: %+v", apiLis) + default: + return nil, fmt.Errorf("unsupported type %T for RouteSpecifier", apiLis.RouteSpecifier) + } + + if v2 { + return update, nil + } + + // The following checks and fields only apply to xDS protocol versions v3+. + + update.MaxStreamDuration = apiLis.GetCommonHttpProtocolOptions().GetMaxStreamDuration().AsDuration() + + var err error + if update.HTTPFilters, err = processHTTPFilters(apiLis.GetHttpFilters(), false); err != nil { + return nil, err + } + + return update, nil +} + +func unwrapHTTPFilterConfig(config *anypb.Any) (proto.Message, string, error) { + switch { + case ptypes.Is(config, &v3cncftypepb.TypedStruct{}): + // The real type name is inside the new TypedStruct message. + s := new(v3cncftypepb.TypedStruct) + if err := ptypes.UnmarshalAny(config, s); err != nil { + return nil, "", fmt.Errorf("error unmarshalling TypedStruct filter config: %v", err) + } + return s, s.GetTypeUrl(), nil + case ptypes.Is(config, &v1udpatypepb.TypedStruct{}): + // The real type name is inside the old TypedStruct message. + s := new(v1udpatypepb.TypedStruct) + if err := ptypes.UnmarshalAny(config, s); err != nil { + return nil, "", fmt.Errorf("error unmarshalling TypedStruct filter config: %v", err) + } + return s, s.GetTypeUrl(), nil + default: + return config, config.GetTypeUrl(), nil + } +} + +func validateHTTPFilterConfig(cfg *anypb.Any, lds, optional bool) (httpfilter.Filter, httpfilter.FilterConfig, error) { + config, typeURL, err := unwrapHTTPFilterConfig(cfg) + if err != nil { + return nil, nil, err + } + filterBuilder := httpfilter.Get(typeURL) + if filterBuilder == nil { + if optional { + return nil, nil, nil + } + return nil, nil, fmt.Errorf("no filter implementation found for %q", typeURL) + } + parseFunc := filterBuilder.ParseFilterConfig + if !lds { + parseFunc = filterBuilder.ParseFilterConfigOverride + } + filterConfig, err := parseFunc(config) + if err != nil { + return nil, nil, fmt.Errorf("error parsing config for filter %q: %v", typeURL, err) + } + return filterBuilder, filterConfig, nil +} + +func processHTTPFilterOverrides(cfgs map[string]*anypb.Any) (map[string]httpfilter.FilterConfig, error) { + if len(cfgs) == 0 { + return nil, nil + } + m := make(map[string]httpfilter.FilterConfig) + for name, cfg := range cfgs { + optional := false + s := new(v3routepb.FilterConfig) + if ptypes.Is(cfg, s) { + if err := ptypes.UnmarshalAny(cfg, s); err != nil { + return nil, fmt.Errorf("filter override %q: error unmarshalling FilterConfig: %v", name, err) + } + cfg = s.GetConfig() + optional = s.GetIsOptional() + } + + httpFilter, config, err := validateHTTPFilterConfig(cfg, false, optional) + if err != nil { + return nil, fmt.Errorf("filter override %q: %v", name, err) + } + if httpFilter == nil { + // Optional configs are ignored. + continue + } + m[name] = config + } + return m, nil +} + +func processHTTPFilters(filters []*v3httppb.HttpFilter, server bool) ([]HTTPFilter, error) { + ret := make([]HTTPFilter, 0, len(filters)) + seenNames := make(map[string]bool, len(filters)) + for _, filter := range filters { + name := filter.GetName() + if name == "" { + return nil, errors.New("filter missing name field") + } + if seenNames[name] { + return nil, fmt.Errorf("duplicate filter name %q", name) + } + seenNames[name] = true + + httpFilter, config, err := validateHTTPFilterConfig(filter.GetTypedConfig(), true, filter.GetIsOptional()) + if err != nil { + return nil, err + } + if httpFilter == nil { + // Optional configs are ignored. + continue + } + if server { + if _, ok := httpFilter.(httpfilter.ServerInterceptorBuilder); !ok { + if filter.GetIsOptional() { + continue + } + return nil, fmt.Errorf("HTTP filter %q not supported server-side", name) + } + } else if _, ok := httpFilter.(httpfilter.ClientInterceptorBuilder); !ok { + if filter.GetIsOptional() { + continue + } + return nil, fmt.Errorf("HTTP filter %q not supported client-side", name) + } + + // Save name/config + ret = append(ret, HTTPFilter{Name: name, Filter: httpFilter, Config: config}) + } + // "Validation will fail if a terminal filter is not the last filter in the + // chain or if a non-terminal filter is the last filter in the chain." - A39 + if len(ret) == 0 { + return nil, fmt.Errorf("http filters list is empty") + } + var i int + for ; i < len(ret)-1; i++ { + if ret[i].Filter.IsTerminal() { + return nil, fmt.Errorf("http filter %q is a terminal filter but it is not last in the filter chain", ret[i].Name) + } + } + if !ret[i].Filter.IsTerminal() { + return nil, fmt.Errorf("http filter %q is not a terminal filter", ret[len(ret)-1].Name) + } + return ret, nil +} + +func processServerSideListener(lis *v3listenerpb.Listener, logger *grpclog.PrefixLogger) (*ListenerUpdate, error) { + if n := len(lis.ListenerFilters); n != 0 { + return nil, fmt.Errorf("unsupported field 'listener_filters' contains %d entries", n) + } + if useOrigDst := lis.GetUseOriginalDst(); useOrigDst != nil && useOrigDst.GetValue() { + return nil, errors.New("unsupported field 'use_original_dst' is present and set to true") + } + addr := lis.GetAddress() + if addr == nil { + return nil, fmt.Errorf("no address field in LDS response: %+v", lis) + } + sockAddr := addr.GetSocketAddress() + if sockAddr == nil { + return nil, fmt.Errorf("no socket_address field in LDS response: %+v", lis) + } + lu := &ListenerUpdate{ + InboundListenerCfg: &InboundListenerConfig{ + Address: sockAddr.GetAddress(), + Port: strconv.Itoa(int(sockAddr.GetPortValue())), + }, + } + + fcMgr, err := NewFilterChainManager(lis, logger) + if err != nil { + return nil, err + } + lu.InboundListenerCfg.FilterChains = fcMgr + return lu, nil +} diff --git a/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_rds.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_rds.go new file mode 100644 index 000000000..a6fbf08d4 --- /dev/null +++ b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/unmarshal_rds.go @@ -0,0 +1,433 @@ +/* + * + * Copyright 2021 gRPC authors. + * + * 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. + */ + +package xdsresource + +import ( + "fmt" + "regexp" + "strings" + "time" + + v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3" + "github.com/golang/protobuf/proto" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/envconfig" + "google.golang.org/grpc/internal/grpclog" + "google.golang.org/grpc/internal/pretty" + "google.golang.org/grpc/xds/internal/clusterspecifier" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version" + "google.golang.org/protobuf/types/known/anypb" +) + +// UnmarshalRouteConfig processes resources received in an RDS response, +// validates them, and transforms them into a native struct which contains only +// fields we are interested in. The provided hostname determines the route +// configuration resources of interest. +func UnmarshalRouteConfig(opts *UnmarshalOptions) (map[string]RouteConfigUpdateErrTuple, UpdateMetadata, error) { + update := make(map[string]RouteConfigUpdateErrTuple) + md, err := processAllResources(opts, update) + return update, md, err +} + +func unmarshalRouteConfigResource(r *anypb.Any, logger *grpclog.PrefixLogger) (string, RouteConfigUpdate, error) { + if !IsRouteConfigResource(r.GetTypeUrl()) { + return "", RouteConfigUpdate{}, fmt.Errorf("unexpected resource type: %q ", r.GetTypeUrl()) + } + rc := &v3routepb.RouteConfiguration{} + if err := proto.Unmarshal(r.GetValue(), rc); err != nil { + return "", RouteConfigUpdate{}, fmt.Errorf("failed to unmarshal resource: %v", err) + } + logger.Infof("Resource with name: %v, type: %T, contains: %v.", rc.GetName(), rc, pretty.ToJSON(rc)) + + // TODO: Pass version.TransportAPI instead of relying upon the type URL + v2 := r.GetTypeUrl() == version.V2RouteConfigURL + u, err := generateRDSUpdateFromRouteConfiguration(rc, logger, v2) + if err != nil { + return rc.GetName(), RouteConfigUpdate{}, err + } + u.Raw = r + return rc.GetName(), u, nil +} + +// generateRDSUpdateFromRouteConfiguration checks if the provided +// RouteConfiguration meets the expected criteria. If so, it returns a +// RouteConfigUpdate with nil error. +// +// A RouteConfiguration resource is considered valid when only if it contains a +// VirtualHost whose domain field matches the server name from the URI passed +// to the gRPC channel, and it contains a clusterName or a weighted cluster. +// +// The RouteConfiguration includes a list of virtualHosts, which may have zero +// or more elements. We are interested in the element whose domains field +// matches the server name specified in the "xds:" URI. The only field in the +// VirtualHost proto that the we are interested in is the list of routes. We +// only look at the last route in the list (the default route), whose match +// field must be empty and whose route field must be set. Inside that route +// message, the cluster field will contain the clusterName or weighted clusters +// we are looking for. +func generateRDSUpdateFromRouteConfiguration(rc *v3routepb.RouteConfiguration, logger *grpclog.PrefixLogger, v2 bool) (RouteConfigUpdate, error) { + vhs := make([]*VirtualHost, 0, len(rc.GetVirtualHosts())) + csps, err := processClusterSpecifierPlugins(rc.ClusterSpecifierPlugins) + if err != nil { + return RouteConfigUpdate{}, fmt.Errorf("received route is invalid %v", err) + } + // cspNames represents all the cluster specifiers referenced by Route + // Actions - any cluster specifiers not referenced by a Route Action can be + // ignored and not emitted by the xdsclient. + var cspNames = make(map[string]bool) + for _, vh := range rc.GetVirtualHosts() { + routes, cspNs, err := routesProtoToSlice(vh.Routes, csps, logger, v2) + if err != nil { + return RouteConfigUpdate{}, fmt.Errorf("received route is invalid: %v", err) + } + for n := range cspNs { + cspNames[n] = true + } + rc, err := generateRetryConfig(vh.GetRetryPolicy()) + if err != nil { + return RouteConfigUpdate{}, fmt.Errorf("received route is invalid: %v", err) + } + vhOut := &VirtualHost{ + Domains: vh.GetDomains(), + Routes: routes, + RetryConfig: rc, + } + if !v2 { + cfgs, err := processHTTPFilterOverrides(vh.GetTypedPerFilterConfig()) + if err != nil { + return RouteConfigUpdate{}, fmt.Errorf("virtual host %+v: %v", vh, err) + } + vhOut.HTTPFilterConfigOverride = cfgs + } + vhs = append(vhs, vhOut) + } + + // "For any entry in the RouteConfiguration.cluster_specifier_plugins not + // referenced by an enclosed ActionType's cluster_specifier_plugin, the xDS + // client should not provide it to its consumers." - RLS in xDS Design + for name := range csps { + if !cspNames[name] { + delete(csps, name) + } + } + + return RouteConfigUpdate{VirtualHosts: vhs, ClusterSpecifierPlugins: csps}, nil +} + +func processClusterSpecifierPlugins(csps []*v3routepb.ClusterSpecifierPlugin) (map[string]clusterspecifier.BalancerConfig, error) { + cspCfgs := make(map[string]clusterspecifier.BalancerConfig) + // "The xDS client will inspect all elements of the + // cluster_specifier_plugins field looking up a plugin based on the + // extension.typed_config of each." - RLS in xDS design + for _, csp := range csps { + cs := clusterspecifier.Get(csp.GetExtension().GetTypedConfig().GetTypeUrl()) + if cs == nil { + // "If no plugin is registered for it, the resource will be NACKed." + // - RLS in xDS design + return nil, fmt.Errorf("cluster specifier %q of type %q was not found", csp.GetExtension().GetName(), csp.GetExtension().GetTypedConfig().GetTypeUrl()) + } + lbCfg, err := cs.ParseClusterSpecifierConfig(csp.GetExtension().GetTypedConfig()) + if err != nil { + // "If a plugin is found, the value of the typed_config field will + // be passed to it's conversion method, and if an error is + // encountered, the resource will be NACKED." - RLS in xDS design + return nil, fmt.Errorf("error: %q parsing config %q for cluster specifier %q of type %q", err, csp.GetExtension().GetTypedConfig(), csp.GetExtension().GetName(), csp.GetExtension().GetTypedConfig().GetTypeUrl()) + } + // "If all cluster specifiers are valid, the xDS client will store the + // configurations in a map keyed by the name of the extension instance." - + // RLS in xDS Design + cspCfgs[csp.GetExtension().GetName()] = lbCfg + } + return cspCfgs, nil +} + +func generateRetryConfig(rp *v3routepb.RetryPolicy) (*RetryConfig, error) { + if rp == nil { + return nil, nil + } + + cfg := &RetryConfig{RetryOn: make(map[codes.Code]bool)} + for _, s := range strings.Split(rp.GetRetryOn(), ",") { + switch strings.TrimSpace(strings.ToLower(s)) { + case "cancelled": + cfg.RetryOn[codes.Canceled] = true + case "deadline-exceeded": + cfg.RetryOn[codes.DeadlineExceeded] = true + case "internal": + cfg.RetryOn[codes.Internal] = true + case "resource-exhausted": + cfg.RetryOn[codes.ResourceExhausted] = true + case "unavailable": + cfg.RetryOn[codes.Unavailable] = true + } + } + + if rp.NumRetries == nil { + cfg.NumRetries = 1 + } else { + cfg.NumRetries = rp.GetNumRetries().Value + if cfg.NumRetries < 1 { + return nil, fmt.Errorf("retry_policy.num_retries = %v; must be >= 1", cfg.NumRetries) + } + } + + backoff := rp.GetRetryBackOff() + if backoff == nil { + cfg.RetryBackoff.BaseInterval = 25 * time.Millisecond + } else { + cfg.RetryBackoff.BaseInterval = backoff.GetBaseInterval().AsDuration() + if cfg.RetryBackoff.BaseInterval <= 0 { + return nil, fmt.Errorf("retry_policy.base_interval = %v; must be > 0", cfg.RetryBackoff.BaseInterval) + } + } + if max := backoff.GetMaxInterval(); max == nil { + cfg.RetryBackoff.MaxInterval = 10 * cfg.RetryBackoff.BaseInterval + } else { + cfg.RetryBackoff.MaxInterval = max.AsDuration() + if cfg.RetryBackoff.MaxInterval <= 0 { + return nil, fmt.Errorf("retry_policy.max_interval = %v; must be > 0", cfg.RetryBackoff.MaxInterval) + } + } + + if len(cfg.RetryOn) == 0 { + return &RetryConfig{}, nil + } + return cfg, nil +} + +func routesProtoToSlice(routes []*v3routepb.Route, csps map[string]clusterspecifier.BalancerConfig, logger *grpclog.PrefixLogger, v2 bool) ([]*Route, map[string]bool, error) { + var routesRet []*Route + var cspNames = make(map[string]bool) + for _, r := range routes { + match := r.GetMatch() + if match == nil { + return nil, nil, fmt.Errorf("route %+v doesn't have a match", r) + } + + if len(match.GetQueryParameters()) != 0 { + // Ignore route with query parameters. + logger.Warningf("route %+v has query parameter matchers, the route will be ignored", r) + continue + } + + pathSp := match.GetPathSpecifier() + if pathSp == nil { + return nil, nil, fmt.Errorf("route %+v doesn't have a path specifier", r) + } + + var route Route + switch pt := pathSp.(type) { + case *v3routepb.RouteMatch_Prefix: + route.Prefix = &pt.Prefix + case *v3routepb.RouteMatch_Path: + route.Path = &pt.Path + case *v3routepb.RouteMatch_SafeRegex: + regex := pt.SafeRegex.GetRegex() + re, err := regexp.Compile(regex) + if err != nil { + return nil, nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex) + } + route.Regex = re + default: + return nil, nil, fmt.Errorf("route %+v has an unrecognized path specifier: %+v", r, pt) + } + + if caseSensitive := match.GetCaseSensitive(); caseSensitive != nil { + route.CaseInsensitive = !caseSensitive.Value + } + + for _, h := range match.GetHeaders() { + var header HeaderMatcher + switch ht := h.GetHeaderMatchSpecifier().(type) { + case *v3routepb.HeaderMatcher_ExactMatch: + header.ExactMatch = &ht.ExactMatch + case *v3routepb.HeaderMatcher_SafeRegexMatch: + regex := ht.SafeRegexMatch.GetRegex() + re, err := regexp.Compile(regex) + if err != nil { + return nil, nil, fmt.Errorf("route %+v contains an invalid regex %q", r, regex) + } + header.RegexMatch = re + case *v3routepb.HeaderMatcher_RangeMatch: + header.RangeMatch = &Int64Range{ + Start: ht.RangeMatch.Start, + End: ht.RangeMatch.End, + } + case *v3routepb.HeaderMatcher_PresentMatch: + header.PresentMatch = &ht.PresentMatch + case *v3routepb.HeaderMatcher_PrefixMatch: + header.PrefixMatch = &ht.PrefixMatch + case *v3routepb.HeaderMatcher_SuffixMatch: + header.SuffixMatch = &ht.SuffixMatch + default: + return nil, nil, fmt.Errorf("route %+v has an unrecognized header matcher: %+v", r, ht) + } + header.Name = h.GetName() + invert := h.GetInvertMatch() + header.InvertMatch = &invert + route.Headers = append(route.Headers, &header) + } + + if fr := match.GetRuntimeFraction(); fr != nil { + d := fr.GetDefaultValue() + n := d.GetNumerator() + switch d.GetDenominator() { + case v3typepb.FractionalPercent_HUNDRED: + n *= 10000 + case v3typepb.FractionalPercent_TEN_THOUSAND: + n *= 100 + case v3typepb.FractionalPercent_MILLION: + } + route.Fraction = &n + } + + switch r.GetAction().(type) { + case *v3routepb.Route_Route: + route.WeightedClusters = make(map[string]WeightedCluster) + action := r.GetRoute() + + // Hash Policies are only applicable for a Ring Hash LB. + if envconfig.XDSRingHash { + hp, err := hashPoliciesProtoToSlice(action.HashPolicy, logger) + if err != nil { + return nil, nil, err + } + route.HashPolicies = hp + } + + switch a := action.GetClusterSpecifier().(type) { + case *v3routepb.RouteAction_Cluster: + route.WeightedClusters[a.Cluster] = WeightedCluster{Weight: 1} + case *v3routepb.RouteAction_WeightedClusters: + wcs := a.WeightedClusters + var totalWeight uint32 + for _, c := range wcs.Clusters { + w := c.GetWeight().GetValue() + if w == 0 { + continue + } + wc := WeightedCluster{Weight: w} + if !v2 { + cfgs, err := processHTTPFilterOverrides(c.GetTypedPerFilterConfig()) + if err != nil { + return nil, nil, fmt.Errorf("route %+v, action %+v: %v", r, a, err) + } + wc.HTTPFilterConfigOverride = cfgs + } + route.WeightedClusters[c.GetName()] = wc + totalWeight += w + } + // envoy xds doc + // default TotalWeight https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html#envoy-v3-api-field-config-route-v3-weightedcluster-total-weight + wantTotalWeight := uint32(100) + if tw := wcs.GetTotalWeight(); tw != nil { + wantTotalWeight = tw.GetValue() + } + if totalWeight != wantTotalWeight { + return nil, nil, fmt.Errorf("route %+v, action %+v, weights of clusters do not add up to total total weight, got: %v, expected total weight from response: %v", r, a, totalWeight, wantTotalWeight) + } + if totalWeight == 0 { + return nil, nil, fmt.Errorf("route %+v, action %+v, has no valid cluster in WeightedCluster action", r, a) + } + case *v3routepb.RouteAction_ClusterHeader: + continue + case *v3routepb.RouteAction_ClusterSpecifierPlugin: + if _, ok := csps[a.ClusterSpecifierPlugin]; !ok { + // "When processing RouteActions, if any action includes a + // cluster_specifier_plugin value that is not in + // RouteConfiguration.cluster_specifier_plugins, the + // resource will be NACKed." - RLS in xDS design + return nil, nil, fmt.Errorf("route %+v, action %+v, specifies a cluster specifier plugin %+v that is not in Route Configuration", r, a, a.ClusterSpecifierPlugin) + } + cspNames[a.ClusterSpecifierPlugin] = true + route.ClusterSpecifierPlugin = a.ClusterSpecifierPlugin + default: + return nil, nil, fmt.Errorf("route %+v, has an unknown ClusterSpecifier: %+v", r, a) + } + + msd := action.GetMaxStreamDuration() + // Prefer grpc_timeout_header_max, if set. + dur := msd.GetGrpcTimeoutHeaderMax() + if dur == nil { + dur = msd.GetMaxStreamDuration() + } + if dur != nil { + d := dur.AsDuration() + route.MaxStreamDuration = &d + } + + var err error + route.RetryConfig, err = generateRetryConfig(action.GetRetryPolicy()) + if err != nil { + return nil, nil, fmt.Errorf("route %+v, action %+v: %v", r, action, err) + } + + route.ActionType = RouteActionRoute + + case *v3routepb.Route_NonForwardingAction: + // Expected to be used on server side. + route.ActionType = RouteActionNonForwardingAction + default: + route.ActionType = RouteActionUnsupported + } + + if !v2 { + cfgs, err := processHTTPFilterOverrides(r.GetTypedPerFilterConfig()) + if err != nil { + return nil, nil, fmt.Errorf("route %+v: %v", r, err) + } + route.HTTPFilterConfigOverride = cfgs + } + routesRet = append(routesRet, &route) + } + return routesRet, cspNames, nil +} + +func hashPoliciesProtoToSlice(policies []*v3routepb.RouteAction_HashPolicy, logger *grpclog.PrefixLogger) ([]*HashPolicy, error) { + var hashPoliciesRet []*HashPolicy + for _, p := range policies { + policy := HashPolicy{Terminal: p.Terminal} + switch p.GetPolicySpecifier().(type) { + case *v3routepb.RouteAction_HashPolicy_Header_: + policy.HashPolicyType = HashPolicyTypeHeader + policy.HeaderName = p.GetHeader().GetHeaderName() + if rr := p.GetHeader().GetRegexRewrite(); rr != nil { + regex := rr.GetPattern().GetRegex() + re, err := regexp.Compile(regex) + if err != nil { + return nil, fmt.Errorf("hash policy %+v contains an invalid regex %q", p, regex) + } + policy.Regex = re + policy.RegexSubstitution = rr.GetSubstitution() + } + case *v3routepb.RouteAction_HashPolicy_FilterState_: + if p.GetFilterState().GetKey() != "io.grpc.channel_id" { + logger.Infof("hash policy %+v contains an invalid key for filter state policy %q", p, p.GetFilterState().GetKey()) + continue + } + policy.HashPolicyType = HashPolicyTypeChannelID + default: + logger.Infof("hash policy %T is an unsupported hash policy", p.GetPolicySpecifier()) + continue + } + + hashPoliciesRet = append(hashPoliciesRet, &policy) + } + return hashPoliciesRet, nil +} diff --git a/vendor/google.golang.org/grpc/xds/internal/version/version.go b/vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version/version.go similarity index 100% rename from vendor/google.golang.org/grpc/xds/internal/version/version.go rename to vendor/google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version/version.go diff --git a/vendor/google.golang.org/grpc/xds/server.go b/vendor/google.golang.org/grpc/xds/server.go index b36fa64b5..7d2c404ac 100644 --- a/vendor/google.golang.org/grpc/xds/server.go +++ b/vendor/google.golang.org/grpc/xds/server.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "net" - "strings" "sync" "google.golang.org/grpc" @@ -33,15 +32,17 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" "google.golang.org/grpc/internal/buffer" + "google.golang.org/grpc/internal/envconfig" internalgrpclog "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcsync" iresolver "google.golang.org/grpc/internal/resolver" "google.golang.org/grpc/internal/transport" - "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/grpc/xds/internal/server" "google.golang.org/grpc/xds/internal/xdsclient" + "google.golang.org/grpc/xds/internal/xdsclient/bootstrap" + "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" ) const serverPrefix = "[xds-server %p] " @@ -216,10 +217,7 @@ func (s *GRPCServer) Serve(lis net.Listener) error { if cfg.ServerListenerResourceNameTemplate == "" { return errors.New("missing server_listener_resource_name_template in the bootstrap configuration") } - name := cfg.ServerListenerResourceNameTemplate - if strings.Contains(cfg.ServerListenerResourceNameTemplate, "%s") { - name = strings.Replace(cfg.ServerListenerResourceNameTemplate, "%s", lis.Addr().String(), -1) - } + name := bootstrap.PopulateResourceTemplate(cfg.ServerListenerResourceNameTemplate, lis.Addr().String()) modeUpdateCh := buffer.NewUnbounded() go func() { @@ -330,7 +328,7 @@ func (s *GRPCServer) GracefulStop() { func routeAndProcess(ctx context.Context) error { conn := transport.GetConnection(ctx) cw, ok := conn.(interface { - VirtualHosts() []xdsclient.VirtualHostWithInterceptors + VirtualHosts() []xdsresource.VirtualHostWithInterceptors }) if !ok { return errors.New("missing virtual hosts in incoming context") @@ -347,12 +345,12 @@ func routeAndProcess(ctx context.Context) error { // the RPC gets to this point, there will be a single, unambiguous authority // present in the header map. authority := md.Get(":authority") - vh := xdsclient.FindBestMatchingVirtualHostServer(authority[0], cw.VirtualHosts()) + vh := xdsresource.FindBestMatchingVirtualHostServer(authority[0], cw.VirtualHosts()) if vh == nil { return status.Error(codes.Unavailable, "the incoming RPC did not match a configured Virtual Host") } - var rwi *xdsclient.RouteWithInterceptors + var rwi *xdsresource.RouteWithInterceptors rpcInfo := iresolver.RPCInfo{ Context: ctx, Method: mn, @@ -361,7 +359,7 @@ func routeAndProcess(ctx context.Context) error { if r.M.Match(rpcInfo) { // "NonForwardingAction is expected for all Routes used on server-side; a route with an inappropriate action causes // RPCs matching that route to fail with UNAVAILABLE." - A36 - if r.RouteAction != xdsclient.RouteActionNonForwardingAction { + if r.ActionType != xdsresource.RouteActionNonForwardingAction { return status.Error(codes.Unavailable, "the incoming RPC matched to a route that was not of action type non forwarding") } rwi = &r @@ -382,7 +380,7 @@ func routeAndProcess(ctx context.Context) error { // xdsUnaryInterceptor is the unary interceptor added to the gRPC server to // perform any xDS specific functionality on unary RPCs. func xdsUnaryInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { - if env.RBACSupport { + if envconfig.XDSRBAC { if err := routeAndProcess(ctx); err != nil { return nil, err } @@ -393,7 +391,7 @@ func xdsUnaryInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServ // xdsStreamInterceptor is the stream interceptor added to the gRPC server to // perform any xDS specific functionality on streaming RPCs. func xdsStreamInterceptor(srv interface{}, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - if env.RBACSupport { + if envconfig.XDSRBAC { if err := routeAndProcess(ss.Context()); err != nil { return err } diff --git a/vendor/google.golang.org/grpc/xds/xds.go b/vendor/google.golang.org/grpc/xds/xds.go index 27547b56d..818af0367 100644 --- a/vendor/google.golang.org/grpc/xds/xds.go +++ b/vendor/google.golang.org/grpc/xds/xds.go @@ -36,14 +36,14 @@ import ( "google.golang.org/grpc/resolver" "google.golang.org/grpc/xds/csds" - _ "google.golang.org/grpc/credentials/tls/certprovider/pemfile" // Register the file watcher certificate provider plugin. - _ "google.golang.org/grpc/xds/internal/balancer" // Register the balancers. - _ "google.golang.org/grpc/xds/internal/httpfilter/fault" // Register the fault injection filter. - _ "google.golang.org/grpc/xds/internal/httpfilter/rbac" // Register the RBAC filter. - _ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter. - xdsresolver "google.golang.org/grpc/xds/internal/resolver" // Register the xds_resolver. - _ "google.golang.org/grpc/xds/internal/xdsclient/v2" // Register the v2 xDS API client. - _ "google.golang.org/grpc/xds/internal/xdsclient/v3" // Register the v3 xDS API client. + _ "google.golang.org/grpc/credentials/tls/certprovider/pemfile" // Register the file watcher certificate provider plugin. + _ "google.golang.org/grpc/xds/internal/balancer" // Register the balancers. + _ "google.golang.org/grpc/xds/internal/httpfilter/fault" // Register the fault injection filter. + _ "google.golang.org/grpc/xds/internal/httpfilter/rbac" // Register the RBAC filter. + _ "google.golang.org/grpc/xds/internal/httpfilter/router" // Register the router filter. + xdsresolver "google.golang.org/grpc/xds/internal/resolver" // Register the xds_resolver. + _ "google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2" // Register the v2 xDS API client. + _ "google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3" // Register the v3 xDS API client. ) func init() { diff --git a/vendor/modules.txt b/vendor/modules.txt index f703daf49..9206112fb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -335,7 +335,7 @@ google.golang.org/genproto/googleapis/logging/v2 google.golang.org/genproto/googleapis/rpc/code google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.42.0 +# google.golang.org/grpc v1.43.0 google.golang.org/grpc google.golang.org/grpc/attributes google.golang.org/grpc/backoff @@ -346,6 +346,8 @@ google.golang.org/grpc/balancer/grpclb/grpc_lb_v1 google.golang.org/grpc/balancer/grpclb/state google.golang.org/grpc/balancer/roundrobin google.golang.org/grpc/balancer/weightedroundrobin +google.golang.org/grpc/balancer/weightedtarget +google.golang.org/grpc/balancer/weightedtarget/weightedaggregator google.golang.org/grpc/binarylog/grpc_binarylog_v1 google.golang.org/grpc/codes google.golang.org/grpc/connectivity @@ -368,6 +370,7 @@ google.golang.org/grpc/grpclog google.golang.org/grpc/internal google.golang.org/grpc/internal/admin google.golang.org/grpc/internal/backoff +google.golang.org/grpc/internal/balancergroup google.golang.org/grpc/internal/balancerload google.golang.org/grpc/internal/binarylog google.golang.org/grpc/internal/buffer @@ -394,7 +397,6 @@ google.golang.org/grpc/internal/syscall google.golang.org/grpc/internal/transport google.golang.org/grpc/internal/transport/networktype google.golang.org/grpc/internal/wrr -google.golang.org/grpc/internal/xds/env google.golang.org/grpc/internal/xds/matcher google.golang.org/grpc/internal/xds/rbac google.golang.org/grpc/keepalive @@ -410,7 +412,6 @@ google.golang.org/grpc/xds/csds google.golang.org/grpc/xds/googledirectpath google.golang.org/grpc/xds/internal google.golang.org/grpc/xds/internal/balancer -google.golang.org/grpc/xds/internal/balancer/balancergroup google.golang.org/grpc/xds/internal/balancer/cdsbalancer google.golang.org/grpc/xds/internal/balancer/clusterimpl google.golang.org/grpc/xds/internal/balancer/clustermanager @@ -418,20 +419,23 @@ google.golang.org/grpc/xds/internal/balancer/clusterresolver google.golang.org/grpc/xds/internal/balancer/loadstore google.golang.org/grpc/xds/internal/balancer/priority google.golang.org/grpc/xds/internal/balancer/ringhash -google.golang.org/grpc/xds/internal/balancer/weightedtarget -google.golang.org/grpc/xds/internal/balancer/weightedtarget/weightedaggregator +google.golang.org/grpc/xds/internal/clusterspecifier google.golang.org/grpc/xds/internal/httpfilter google.golang.org/grpc/xds/internal/httpfilter/fault google.golang.org/grpc/xds/internal/httpfilter/rbac google.golang.org/grpc/xds/internal/httpfilter/router google.golang.org/grpc/xds/internal/resolver google.golang.org/grpc/xds/internal/server -google.golang.org/grpc/xds/internal/version google.golang.org/grpc/xds/internal/xdsclient google.golang.org/grpc/xds/internal/xdsclient/bootstrap +google.golang.org/grpc/xds/internal/xdsclient/controller +google.golang.org/grpc/xds/internal/xdsclient/controller/version +google.golang.org/grpc/xds/internal/xdsclient/controller/version/v2 +google.golang.org/grpc/xds/internal/xdsclient/controller/version/v3 google.golang.org/grpc/xds/internal/xdsclient/load -google.golang.org/grpc/xds/internal/xdsclient/v2 -google.golang.org/grpc/xds/internal/xdsclient/v3 +google.golang.org/grpc/xds/internal/xdsclient/pubsub +google.golang.org/grpc/xds/internal/xdsclient/xdsresource +google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version # google.golang.org/protobuf v1.27.1 google.golang.org/protobuf/encoding/protojson google.golang.org/protobuf/encoding/prototext @@ -523,7 +527,7 @@ istio.io/client-go/pkg/listers/security/v1beta1 istio.io/client-go/pkg/listers/telemetry/v1alpha1 # istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e istio.io/gogo-genproto/googleapis/google/api -# istio.io/istio v0.0.0-20211211014447-a9f4988c313b +# istio.io/istio v0.0.0-20211216225035-26e13f954263 ## explicit istio.io/istio/operator/pkg/apis istio.io/istio/operator/pkg/apis/istio/v1alpha1