From 6e2bf400153ba1c2fa689e8cd2330ff489f6dddf Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 19 Mar 2018 10:08:06 -0700 Subject: [PATCH 01/39] vendor: upgrade "grpc/grpc-go" to v1.11.1 Signed-off-by: Gyuho Lee --- Gopkg.lock | 12 +- vendor/google.golang.org/grpc/backoff.go | 14 +- vendor/google.golang.org/grpc/balancer.go | 3 +- .../grpc/balancer/balancer.go | 35 +- .../grpc/balancer/base/balancer.go | 209 +++++ .../grpc/balancer/base/base.go | 52 ++ .../grpc/balancer/roundrobin/roundrobin.go | 79 ++ .../grpc/balancer_conn_wrappers.go | 66 +- .../grpc/balancer_v1_wrapper.go | 42 +- vendor/google.golang.org/grpc/call.go | 308 +------ vendor/google.golang.org/grpc/clientconn.go | 754 ++++++++++------ vendor/google.golang.org/grpc/codec.go | 88 +- .../grpc/codes/code_string.go | 66 +- vendor/google.golang.org/grpc/codes/codes.go | 66 +- .../grpc/credentials/credentials.go | 27 +- .../grpc/encoding/encoding.go | 118 +++ .../grpc/encoding/proto/proto.go | 110 +++ vendor/google.golang.org/grpc/go16.go | 70 ++ vendor/google.golang.org/grpc/go17.go | 71 ++ vendor/google.golang.org/grpc/grpclb.go | 820 +++++------------- .../google.golang.org/grpc/grpclb_picker.go | 159 ++++ .../grpc/grpclb_remote_balancer.go | 254 ++++++ vendor/google.golang.org/grpc/grpclb_util.go | 90 ++ .../google.golang.org/grpc/health/health.go | 4 +- vendor/google.golang.org/grpc/interceptor.go | 4 +- .../grpc/internal/internal.go | 7 - .../grpc/metadata/metadata.go | 61 +- vendor/google.golang.org/grpc/naming/go17.go | 2 +- .../google.golang.org/grpc/picker_wrapper.go | 23 +- vendor/google.golang.org/grpc/pickfirst.go | 17 +- vendor/google.golang.org/grpc/proxy.go | 3 +- .../grpc/resolver/dns/dns_resolver.go | 377 ++++++++ .../grpc/resolver/dns/go17.go | 35 + .../doc.go => resolver/dns/go18.go} | 14 +- .../grpc/resolver/passthrough/passthrough.go | 57 ++ .../grpc/resolver/resolver.go | 37 +- .../grpc/resolver_conn_wrapper.go | 65 +- vendor/google.golang.org/grpc/rpc_util.go | 524 ++++++----- vendor/google.golang.org/grpc/server.go | 387 ++++++--- .../google.golang.org/grpc/service_config.go | 226 +++++ vendor/google.golang.org/grpc/stats/stats.go | 2 + .../google.golang.org/grpc/status/status.go | 31 +- vendor/google.golang.org/grpc/stream.go | 615 +++++++------ .../grpc/transport/bdp_estimator.go | 9 +- .../grpc/transport/control.go | 113 ++- .../google.golang.org/grpc/transport/go16.go | 51 ++ .../google.golang.org/grpc/transport/go17.go | 52 ++ .../grpc/transport/handler_server.go | 78 +- .../grpc/transport/http2_client.go | 314 ++++--- .../grpc/transport/http2_server.go | 190 ++-- .../grpc/transport/http_util.go | 71 +- .../grpc/transport/transport.go | 238 +++-- 52 files changed, 4789 insertions(+), 2331 deletions(-) create mode 100644 vendor/google.golang.org/grpc/balancer/base/balancer.go create mode 100644 vendor/google.golang.org/grpc/balancer/base/base.go create mode 100644 vendor/google.golang.org/grpc/balancer/roundrobin/roundrobin.go create mode 100644 vendor/google.golang.org/grpc/encoding/encoding.go create mode 100644 vendor/google.golang.org/grpc/encoding/proto/proto.go create mode 100644 vendor/google.golang.org/grpc/go16.go create mode 100644 vendor/google.golang.org/grpc/go17.go create mode 100644 vendor/google.golang.org/grpc/grpclb_picker.go create mode 100644 vendor/google.golang.org/grpc/grpclb_remote_balancer.go create mode 100644 vendor/google.golang.org/grpc/grpclb_util.go create mode 100644 vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go create mode 100644 vendor/google.golang.org/grpc/resolver/dns/go17.go rename vendor/google.golang.org/grpc/{grpclb/grpc_lb_v1/doc.go => resolver/dns/go18.go} (76%) create mode 100644 vendor/google.golang.org/grpc/resolver/passthrough/passthrough.go create mode 100644 vendor/google.golang.org/grpc/service_config.go create mode 100644 vendor/google.golang.org/grpc/transport/go16.go create mode 100644 vendor/google.golang.org/grpc/transport/go17.go diff --git a/Gopkg.lock b/Gopkg.lock index 9aec626a676..9ca9a6596d5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -335,9 +335,13 @@ packages = [ ".", "balancer", + "balancer/base", + "balancer/roundrobin", "codes", "connectivity", "credentials", + "encoding", + "encoding/proto", "grpclb/grpc_lb_v1/messages", "grpclog", "health", @@ -348,13 +352,15 @@ "naming", "peer", "resolver", + "resolver/dns", + "resolver/passthrough", "stats", "status", "tap", "transport" ] - revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" - version = "v1.7.5" + revision = "1e2570b1b19ade82d8dbb31bba4e65e9f9ef5b34" + version = "v1.11.1" [[projects]] name = "gopkg.in/cheggaaa/pb.v1" @@ -371,6 +377,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "943bf7648c0129f59546321f569622e933f24a103b5d68525b82d5e47d52733f" + inputs-digest = "b747b3fd3120687183829e5d2d5b2d10bba1719402c9bcc7c955d27ab5f884a0" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/google.golang.org/grpc/backoff.go b/vendor/google.golang.org/grpc/backoff.go index 090fbe87c52..c40facce510 100644 --- a/vendor/google.golang.org/grpc/backoff.go +++ b/vendor/google.golang.org/grpc/backoff.go @@ -25,14 +25,12 @@ import ( // DefaultBackoffConfig uses values specified for backoff in // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. -var ( - DefaultBackoffConfig = BackoffConfig{ - MaxDelay: 120 * time.Second, - baseDelay: 1.0 * time.Second, - factor: 1.6, - jitter: 0.2, - } -) +var DefaultBackoffConfig = BackoffConfig{ + MaxDelay: 120 * time.Second, + baseDelay: 1.0 * time.Second, + factor: 1.6, + jitter: 0.2, +} // backoffStrategy defines the methodology for backing off after a grpc // connection failure. diff --git a/vendor/google.golang.org/grpc/balancer.go b/vendor/google.golang.org/grpc/balancer.go index ab65049ddc1..300da6c5e87 100644 --- a/vendor/google.golang.org/grpc/balancer.go +++ b/vendor/google.golang.org/grpc/balancer.go @@ -28,6 +28,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/naming" + "google.golang.org/grpc/status" ) // Address represents a server the client connects to. @@ -310,7 +311,7 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad if !opts.BlockingWait { if len(rr.addrs) == 0 { rr.mu.Unlock() - err = Errorf(codes.Unavailable, "there is no address available") + err = status.Errorf(codes.Unavailable, "there is no address available") return } // Returns the next addr on rr.addrs for failfast RPCs. diff --git a/vendor/google.golang.org/grpc/balancer/balancer.go b/vendor/google.golang.org/grpc/balancer/balancer.go index 84e10b630e7..219a2940c65 100644 --- a/vendor/google.golang.org/grpc/balancer/balancer.go +++ b/vendor/google.golang.org/grpc/balancer/balancer.go @@ -23,6 +23,7 @@ package balancer import ( "errors" "net" + "strings" "golang.org/x/net/context" "google.golang.org/grpc/connectivity" @@ -33,24 +34,23 @@ import ( var ( // m is a map from name to balancer builder. m = make(map[string]Builder) - // defaultBuilder is the default balancer to use. - defaultBuilder Builder // TODO(bar) install pickfirst as default. ) // Register registers the balancer builder to the balancer map. -// b.Name will be used as the name registered with this builder. +// b.Name (lowercased) will be used as the name registered with +// this builder. func Register(b Builder) { - m[b.Name()] = b + m[strings.ToLower(b.Name())] = b } // Get returns the resolver builder registered with the given name. -// If no builder is register with the name, the default pickfirst will -// be used. +// Note that the compare is done in a case-insenstive fashion. +// If no builder is register with the name, nil will be returned. func Get(name string) Builder { - if b, ok := m[name]; ok { + if b, ok := m[strings.ToLower(name)]; ok { return b } - return defaultBuilder + return nil } // SubConn represents a gRPC sub connection. @@ -66,6 +66,11 @@ func Get(name string) Builder { // When the connection encounters an error, it will reconnect immediately. // When the connection becomes IDLE, it will not reconnect unless Connect is // called. +// +// This interface is to be implemented by gRPC. Users should not need a +// brand new implementation of this interface. For the situations like +// testing, the new implementation should embed this interface. This allows +// gRPC to add new methods to this interface. type SubConn interface { // UpdateAddresses updates the addresses used in this SubConn. // gRPC checks if currently-connected address is still in the new list. @@ -83,6 +88,11 @@ type SubConn interface { type NewSubConnOptions struct{} // ClientConn represents a gRPC ClientConn. +// +// This interface is to be implemented by gRPC. Users should not need a +// brand new implementation of this interface. For the situations like +// testing, the new implementation should embed this interface. This allows +// gRPC to add new methods to this interface. type ClientConn interface { // NewSubConn is called by balancer to create a new SubConn. // It doesn't block and wait for the connections to be established. @@ -99,6 +109,9 @@ type ClientConn interface { // on the new picker to pick new SubConn. UpdateBalancerState(s connectivity.State, p Picker) + // ResolveNow is called by balancer to notify gRPC to do a name resolving. + ResolveNow(resolver.ResolveNowOption) + // Target returns the dial target for this ClientConn. Target() string } @@ -131,6 +144,10 @@ type PickOptions struct{} type DoneInfo struct { // Err is the rpc error the RPC finished with. It could be nil. Err error + // BytesSent indicates if any bytes have been sent to the server. + BytesSent bool + // BytesReceived indicates if any byte has been received from the server. + BytesReceived bool } var ( @@ -161,7 +178,7 @@ type Picker interface { // If a SubConn is returned: // - If it is READY, gRPC will send the RPC on it; // - If it is not ready, or becomes not ready after it's returned, gRPC will block - // this call until a new picker is updated and will call pick on the new picker. + // until UpdateBalancerState() is called and will call pick on the new picker. // // If the returned error is not nil: // - If the error is ErrNoSubConnAvailable, gRPC will block until UpdateBalancerState() diff --git a/vendor/google.golang.org/grpc/balancer/base/balancer.go b/vendor/google.golang.org/grpc/balancer/base/balancer.go new file mode 100644 index 00000000000..1e962b72403 --- /dev/null +++ b/vendor/google.golang.org/grpc/balancer/base/balancer.go @@ -0,0 +1,209 @@ +/* + * + * Copyright 2017 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 base + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/resolver" +) + +type baseBuilder struct { + name string + pickerBuilder PickerBuilder +} + +func (bb *baseBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { + return &baseBalancer{ + cc: cc, + pickerBuilder: bb.pickerBuilder, + + subConns: make(map[resolver.Address]balancer.SubConn), + scStates: make(map[balancer.SubConn]connectivity.State), + csEvltr: &connectivityStateEvaluator{}, + // Initialize picker to a picker that always return + // ErrNoSubConnAvailable, because when state of a SubConn changes, we + // may call UpdateBalancerState with this picker. + picker: NewErrPicker(balancer.ErrNoSubConnAvailable), + } +} + +func (bb *baseBuilder) Name() string { + return bb.name +} + +type baseBalancer struct { + cc balancer.ClientConn + pickerBuilder PickerBuilder + + csEvltr *connectivityStateEvaluator + state connectivity.State + + subConns map[resolver.Address]balancer.SubConn + scStates map[balancer.SubConn]connectivity.State + picker balancer.Picker +} + +func (b *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) { + if err != nil { + grpclog.Infof("base.baseBalancer: HandleResolvedAddrs called with error %v", err) + return + } + grpclog.Infoln("base.baseBalancer: got new resolved addresses: ", addrs) + // addrsSet is the set converted from addrs, it's used for quick lookup of an address. + addrsSet := make(map[resolver.Address]struct{}) + for _, a := range addrs { + addrsSet[a] = struct{}{} + if _, ok := b.subConns[a]; !ok { + // a is a new address (not existing in b.subConns). + sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{}) + if err != nil { + grpclog.Warningf("base.baseBalancer: failed to create new SubConn: %v", err) + continue + } + b.subConns[a] = sc + b.scStates[sc] = connectivity.Idle + sc.Connect() + } + } + for a, sc := range b.subConns { + // a was removed by resolver. + if _, ok := addrsSet[a]; !ok { + b.cc.RemoveSubConn(sc) + delete(b.subConns, a) + // Keep the state of this sc in b.scStates until sc's state becomes Shutdown. + // The entry will be deleted in HandleSubConnStateChange. + } + } +} + +// regeneratePicker takes a snapshot of the balancer, and generates a picker +// from it. The picker is +// - errPicker with ErrTransientFailure if the balancer is in TransientFailure, +// - built by the pickerBuilder with all READY SubConns otherwise. +func (b *baseBalancer) regeneratePicker() { + if b.state == connectivity.TransientFailure { + b.picker = NewErrPicker(balancer.ErrTransientFailure) + return + } + readySCs := make(map[resolver.Address]balancer.SubConn) + + // Filter out all ready SCs from full subConn map. + for addr, sc := range b.subConns { + if st, ok := b.scStates[sc]; ok && st == connectivity.Ready { + readySCs[addr] = sc + } + } + b.picker = b.pickerBuilder.Build(readySCs) +} + +func (b *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { + grpclog.Infof("base.baseBalancer: handle SubConn state change: %p, %v", sc, s) + oldS, ok := b.scStates[sc] + if !ok { + grpclog.Infof("base.baseBalancer: got state changes for an unknown SubConn: %p, %v", sc, s) + return + } + b.scStates[sc] = s + switch s { + case connectivity.Idle: + sc.Connect() + case connectivity.Shutdown: + // When an address was removed by resolver, b called RemoveSubConn but + // kept the sc's state in scStates. Remove state for this sc here. + delete(b.scStates, sc) + } + + oldAggrState := b.state + b.state = b.csEvltr.recordTransition(oldS, s) + + // Regenerate picker when one of the following happens: + // - this sc became ready from not-ready + // - this sc became not-ready from ready + // - the aggregated state of balancer became TransientFailure from non-TransientFailure + // - the aggregated state of balancer became non-TransientFailure from TransientFailure + if (s == connectivity.Ready) != (oldS == connectivity.Ready) || + (b.state == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) { + b.regeneratePicker() + } + + b.cc.UpdateBalancerState(b.state, b.picker) + return +} + +// Close is a nop because base balancer doesn't have internal state to clean up, +// and it doesn't need to call RemoveSubConn for the SubConns. +func (b *baseBalancer) Close() { +} + +// NewErrPicker returns a picker that always returns err on Pick(). +func NewErrPicker(err error) balancer.Picker { + return &errPicker{err: err} +} + +type errPicker struct { + err error // Pick() always returns this err. +} + +func (p *errPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + return nil, nil, p.err +} + +// connectivityStateEvaluator gets updated by addrConns when their +// states transition, based on which it evaluates the state of +// ClientConn. +type connectivityStateEvaluator struct { + numReady uint64 // Number of addrConns in ready state. + numConnecting uint64 // Number of addrConns in connecting state. + numTransientFailure uint64 // Number of addrConns in transientFailure. +} + +// recordTransition records state change happening in every subConn and based on +// that it evaluates what aggregated state should be. +// It can only transition between Ready, Connecting and TransientFailure. Other states, +// Idle and Shutdown are transitioned into by ClientConn; in the beginning of the connection +// before any subConn is created ClientConn is in idle state. In the end when ClientConn +// closes it is in Shutdown state. +// +// recordTransition should only be called synchronously from the same goroutine. +func (cse *connectivityStateEvaluator) recordTransition(oldState, newState connectivity.State) connectivity.State { + // Update counters. + for idx, state := range []connectivity.State{oldState, newState} { + updateVal := 2*uint64(idx) - 1 // -1 for oldState and +1 for new. + switch state { + case connectivity.Ready: + cse.numReady += updateVal + case connectivity.Connecting: + cse.numConnecting += updateVal + case connectivity.TransientFailure: + cse.numTransientFailure += updateVal + } + } + + // Evaluate. + if cse.numReady > 0 { + return connectivity.Ready + } + if cse.numConnecting > 0 { + return connectivity.Connecting + } + return connectivity.TransientFailure +} diff --git a/vendor/google.golang.org/grpc/balancer/base/base.go b/vendor/google.golang.org/grpc/balancer/base/base.go new file mode 100644 index 00000000000..012ace2f2f7 --- /dev/null +++ b/vendor/google.golang.org/grpc/balancer/base/base.go @@ -0,0 +1,52 @@ +/* + * + * Copyright 2017 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 base defines a balancer base that can be used to build balancers with +// different picking algorithms. +// +// The base balancer creates a new SubConn for each resolved address. The +// provided picker will only be notified about READY SubConns. +// +// This package is the base of round_robin balancer, its purpose is to be used +// to build round_robin like balancers with complex picking algorithms. +// Balancers with more complicated logic should try to implement a balancer +// builder from scratch. +// +// All APIs in this package are experimental. +package base + +import ( + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +// PickerBuilder creates balancer.Picker. +type PickerBuilder interface { + // Build takes a slice of ready SubConns, and returns a picker that will be + // used by gRPC to pick a SubConn. + Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker +} + +// NewBalancerBuilder returns a balancer builder. The balancers +// built by this builder will use the picker builder to build pickers. +func NewBalancerBuilder(name string, pb PickerBuilder) balancer.Builder { + return &baseBuilder{ + name: name, + pickerBuilder: pb, + } +} diff --git a/vendor/google.golang.org/grpc/balancer/roundrobin/roundrobin.go b/vendor/google.golang.org/grpc/balancer/roundrobin/roundrobin.go new file mode 100644 index 00000000000..2eda0a1c210 --- /dev/null +++ b/vendor/google.golang.org/grpc/balancer/roundrobin/roundrobin.go @@ -0,0 +1,79 @@ +/* + * + * Copyright 2017 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 roundrobin defines a roundrobin balancer. Roundrobin balancer is +// installed as one of the default balancers in gRPC, users don't need to +// explicitly install this balancer. +package roundrobin + +import ( + "sync" + + "golang.org/x/net/context" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/balancer/base" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/resolver" +) + +// Name is the name of round_robin balancer. +const Name = "round_robin" + +// newBuilder creates a new roundrobin balancer builder. +func newBuilder() balancer.Builder { + return base.NewBalancerBuilder(Name, &rrPickerBuilder{}) +} + +func init() { + balancer.Register(newBuilder()) +} + +type rrPickerBuilder struct{} + +func (*rrPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker { + grpclog.Infof("roundrobinPicker: newPicker called with readySCs: %v", readySCs) + var scs []balancer.SubConn + for _, sc := range readySCs { + scs = append(scs, sc) + } + return &rrPicker{ + subConns: scs, + } +} + +type rrPicker struct { + // subConns is the snapshot of the roundrobin balancer when this picker was + // created. The slice is immutable. Each Get() will do a round robin + // selection from it and return the selected SubConn. + subConns []balancer.SubConn + + mu sync.Mutex + next int +} + +func (p *rrPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + if len(p.subConns) <= 0 { + return nil, nil, balancer.ErrNoSubConnAvailable + } + + p.mu.Lock() + sc := p.subConns[p.next] + p.next = (p.next + 1) % len(p.subConns) + p.mu.Unlock() + return sc, nil, nil +} diff --git a/vendor/google.golang.org/grpc/balancer_conn_wrappers.go b/vendor/google.golang.org/grpc/balancer_conn_wrappers.go index f5dbc4ba201..db6f0ae3f09 100644 --- a/vendor/google.golang.org/grpc/balancer_conn_wrappers.go +++ b/vendor/google.golang.org/grpc/balancer_conn_wrappers.go @@ -19,6 +19,7 @@ package grpc import ( + "fmt" "sync" "google.golang.org/grpc/balancer" @@ -73,7 +74,7 @@ func (b *scStateUpdateBuffer) load() { } } -// get returns the channel that receives a recvMsg in the buffer. +// get returns the channel that the scStateUpdate will be sent to. // // Upon receiving, the caller should call load to send another // scStateChangeTuple onto the channel if there is any. @@ -96,6 +97,9 @@ type ccBalancerWrapper struct { stateChangeQueue *scStateUpdateBuffer resolverUpdateCh chan *resolverUpdate done chan struct{} + + mu sync.Mutex + subConns map[*acBalancerWrapper]struct{} } func newCCBalancerWrapper(cc *ClientConn, b balancer.Builder, bopts balancer.BuildOptions) *ccBalancerWrapper { @@ -104,6 +108,7 @@ func newCCBalancerWrapper(cc *ClientConn, b balancer.Builder, bopts balancer.Bui stateChangeQueue: newSCStateUpdateBuffer(), resolverUpdateCh: make(chan *resolverUpdate, 1), done: make(chan struct{}), + subConns: make(map[*acBalancerWrapper]struct{}), } go ccb.watcher() ccb.balancer = b.Build(ccb, bopts) @@ -117,8 +122,20 @@ func (ccb *ccBalancerWrapper) watcher() { select { case t := <-ccb.stateChangeQueue.get(): ccb.stateChangeQueue.load() + select { + case <-ccb.done: + ccb.balancer.Close() + return + default: + } ccb.balancer.HandleSubConnStateChange(t.sc, t.state) case t := <-ccb.resolverUpdateCh: + select { + case <-ccb.done: + ccb.balancer.Close() + return + default: + } ccb.balancer.HandleResolvedAddrs(t.addrs, t.err) case <-ccb.done: } @@ -126,6 +143,13 @@ func (ccb *ccBalancerWrapper) watcher() { select { case <-ccb.done: ccb.balancer.Close() + ccb.mu.Lock() + scs := ccb.subConns + ccb.subConns = nil + ccb.mu.Unlock() + for acbw := range scs { + ccb.cc.removeAddrConn(acbw.getAddrConn(), errConnDrain) + } return default: } @@ -165,33 +189,54 @@ func (ccb *ccBalancerWrapper) handleResolvedAddrs(addrs []resolver.Address, err } func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { - grpclog.Infof("ccBalancerWrapper: new subconn: %v", addrs) + if len(addrs) <= 0 { + return nil, fmt.Errorf("grpc: cannot create SubConn with empty address list") + } + ccb.mu.Lock() + defer ccb.mu.Unlock() + if ccb.subConns == nil { + return nil, fmt.Errorf("grpc: ClientConn balancer wrapper was closed") + } ac, err := ccb.cc.newAddrConn(addrs) if err != nil { return nil, err } acbw := &acBalancerWrapper{ac: ac} - ac.mu.Lock() + acbw.ac.mu.Lock() ac.acbw = acbw - ac.mu.Unlock() + acbw.ac.mu.Unlock() + ccb.subConns[acbw] = struct{}{} return acbw, nil } func (ccb *ccBalancerWrapper) RemoveSubConn(sc balancer.SubConn) { - grpclog.Infof("ccBalancerWrapper: removing subconn") acbw, ok := sc.(*acBalancerWrapper) if !ok { return } + ccb.mu.Lock() + defer ccb.mu.Unlock() + if ccb.subConns == nil { + return + } + delete(ccb.subConns, acbw) ccb.cc.removeAddrConn(acbw.getAddrConn(), errConnDrain) } func (ccb *ccBalancerWrapper) UpdateBalancerState(s connectivity.State, p balancer.Picker) { - grpclog.Infof("ccBalancerWrapper: updating state and picker called by balancer: %v, %p", s, p) + ccb.mu.Lock() + defer ccb.mu.Unlock() + if ccb.subConns == nil { + return + } ccb.cc.csMgr.updateState(s) ccb.cc.blockingpicker.updatePicker(p) } +func (ccb *ccBalancerWrapper) ResolveNow(o resolver.ResolveNowOption) { + ccb.cc.resolveNow(o) +} + func (ccb *ccBalancerWrapper) Target() string { return ccb.cc.target } @@ -204,9 +249,12 @@ type acBalancerWrapper struct { } func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) { - grpclog.Infof("acBalancerWrapper: UpdateAddresses called with %v", addrs) acbw.mu.Lock() defer acbw.mu.Unlock() + if len(addrs) <= 0 { + acbw.ac.tearDown(errConnDrain) + return + } if !acbw.ac.tryUpdateAddrs(addrs) { cc := acbw.ac.cc acbw.ac.mu.Lock() @@ -234,7 +282,7 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) { ac.acbw = acbw ac.mu.Unlock() if acState != connectivity.Idle { - ac.connect(false) + ac.connect() } } } @@ -242,7 +290,7 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) { func (acbw *acBalancerWrapper) Connect() { acbw.mu.Lock() defer acbw.mu.Unlock() - acbw.ac.connect(false) + acbw.ac.connect() } func (acbw *acBalancerWrapper) getAddrConn() *addrConn { diff --git a/vendor/google.golang.org/grpc/balancer_v1_wrapper.go b/vendor/google.golang.org/grpc/balancer_v1_wrapper.go index 9d0616080a1..faabf87d001 100644 --- a/vendor/google.golang.org/grpc/balancer_v1_wrapper.go +++ b/vendor/google.golang.org/grpc/balancer_v1_wrapper.go @@ -19,6 +19,7 @@ package grpc import ( + "strings" "sync" "golang.org/x/net/context" @@ -27,6 +28,7 @@ import ( "google.golang.org/grpc/connectivity" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/resolver" + "google.golang.org/grpc/status" ) type balancerWrapperBuilder struct { @@ -34,20 +36,27 @@ type balancerWrapperBuilder struct { } func (bwb *balancerWrapperBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer { - bwb.b.Start(cc.Target(), BalancerConfig{ + targetAddr := cc.Target() + targetSplitted := strings.Split(targetAddr, ":///") + if len(targetSplitted) >= 2 { + targetAddr = targetSplitted[1] + } + + bwb.b.Start(targetAddr, BalancerConfig{ DialCreds: opts.DialCreds, Dialer: opts.Dialer, }) _, pickfirst := bwb.b.(*pickFirst) bw := &balancerWrapper{ - balancer: bwb.b, - pickfirst: pickfirst, - cc: cc, - startCh: make(chan struct{}), - conns: make(map[resolver.Address]balancer.SubConn), - connSt: make(map[balancer.SubConn]*scState), - csEvltr: &connectivityStateEvaluator{}, - state: connectivity.Idle, + balancer: bwb.b, + pickfirst: pickfirst, + cc: cc, + targetAddr: targetAddr, + startCh: make(chan struct{}), + conns: make(map[resolver.Address]balancer.SubConn), + connSt: make(map[balancer.SubConn]*scState), + csEvltr: &connectivityStateEvaluator{}, + state: connectivity.Idle, } cc.UpdateBalancerState(connectivity.Idle, bw) go bw.lbWatcher() @@ -68,7 +77,8 @@ type balancerWrapper struct { balancer Balancer // The v1 balancer. pickfirst bool - cc balancer.ClientConn + cc balancer.ClientConn + targetAddr string // Target without the scheme. // To aggregate the connectivity state. csEvltr *connectivityStateEvaluator @@ -88,12 +98,11 @@ type balancerWrapper struct { // connections accordingly. func (bw *balancerWrapper) lbWatcher() { <-bw.startCh - grpclog.Infof("balancerWrapper: is pickfirst: %v\n", bw.pickfirst) notifyCh := bw.balancer.Notify() if notifyCh == nil { // There's no resolver in the balancer. Connect directly. a := resolver.Address{ - Addr: bw.cc.Target(), + Addr: bw.targetAddr, Type: resolver.Backend, } sc, err := bw.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{}) @@ -103,7 +112,7 @@ func (bw *balancerWrapper) lbWatcher() { bw.mu.Lock() bw.conns[a] = sc bw.connSt[sc] = &scState{ - addr: Address{Addr: bw.cc.Target()}, + addr: Address{Addr: bw.targetAddr}, s: connectivity.Idle, } bw.mu.Unlock() @@ -165,10 +174,10 @@ func (bw *balancerWrapper) lbWatcher() { sc.Connect() } } else { - oldSC.UpdateAddresses(newAddrs) bw.mu.Lock() bw.connSt[oldSC].addr = addrs[0] bw.mu.Unlock() + oldSC.UpdateAddresses(newAddrs) } } else { var ( @@ -221,7 +230,6 @@ func (bw *balancerWrapper) lbWatcher() { } func (bw *balancerWrapper) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { - grpclog.Infof("balancerWrapper: handle subconn state change: %p, %v", sc, s) bw.mu.Lock() defer bw.mu.Unlock() scSt, ok := bw.connSt[sc] @@ -310,12 +318,12 @@ func (bw *balancerWrapper) Pick(ctx context.Context, opts balancer.PickOptions) Metadata: a.Metadata, }] if !ok && failfast { - return nil, nil, Errorf(codes.Unavailable, "there is no connection available") + return nil, nil, status.Errorf(codes.Unavailable, "there is no connection available") } if s, ok := bw.connSt[sc]; failfast && (!ok || s.s != connectivity.Ready) { // If the returned sc is not ready and RPC is failfast, // return error, and this RPC will fail. - return nil, nil, Errorf(codes.Unavailable, "there is no connection available") + return nil, nil, status.Errorf(codes.Unavailable, "there is no connection available") } } diff --git a/vendor/google.golang.org/grpc/call.go b/vendor/google.golang.org/grpc/call.go index 1ef2507c35f..f73b7d5528f 100644 --- a/vendor/google.golang.org/grpc/call.go +++ b/vendor/google.golang.org/grpc/call.go @@ -19,289 +19,75 @@ package grpc import ( - "bytes" - "io" - "time" - "golang.org/x/net/context" - "golang.org/x/net/trace" - "google.golang.org/grpc/balancer" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/stats" - "google.golang.org/grpc/status" - "google.golang.org/grpc/transport" ) -// recvResponse receives and parses an RPC response. -// On error, it returns the error and indicates whether the call should be retried. +// Invoke sends the RPC request on the wire and returns after response is +// received. This is typically called by generated code. // -// TODO(zhaoq): Check whether the received message sequence is valid. -// TODO ctx is used for stats collection and processing. It is the context passed from the application. -func recvResponse(ctx context.Context, dopts dialOptions, t transport.ClientTransport, c *callInfo, stream *transport.Stream, reply interface{}) (err error) { - // Try to acquire header metadata from the server if there is any. - defer func() { - if err != nil { - if _, ok := err.(transport.ConnectionError); !ok { - t.CloseStream(stream, err) - } - } - }() - c.headerMD, err = stream.Header() - if err != nil { - return - } - p := &parser{r: stream} - var inPayload *stats.InPayload - if dopts.copts.StatsHandler != nil { - inPayload = &stats.InPayload{ - Client: true, - } - } - for { - if c.maxReceiveMessageSize == nil { - return Errorf(codes.Internal, "callInfo maxReceiveMessageSize field uninitialized(nil)") - } - if err = recv(p, dopts.codec, stream, dopts.dc, reply, *c.maxReceiveMessageSize, inPayload); err != nil { - if err == io.EOF { - break - } - return - } - } - if inPayload != nil && err == io.EOF && stream.Status().Code() == codes.OK { - // TODO in the current implementation, inTrailer may be handled before inPayload in some cases. - // Fix the order if necessary. - dopts.copts.StatsHandler.HandleRPC(ctx, inPayload) - } - c.trailerMD = stream.Trailer() - return nil -} +// All errors returned by Invoke are compatible with the status package. +func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error { + // allow interceptor to see all applicable call options, which means those + // configured as defaults from dial option as well as per-call options + opts = combine(cc.dopts.callOptions, opts) -// sendRequest writes out various information of an RPC such as Context and Message. -func sendRequest(ctx context.Context, dopts dialOptions, compressor Compressor, c *callInfo, callHdr *transport.CallHdr, stream *transport.Stream, t transport.ClientTransport, args interface{}, opts *transport.Options) (err error) { - defer func() { - if err != nil { - // If err is connection error, t will be closed, no need to close stream here. - if _, ok := err.(transport.ConnectionError); !ok { - t.CloseStream(stream, err) - } - } - }() - var ( - cbuf *bytes.Buffer - outPayload *stats.OutPayload - ) - if compressor != nil { - cbuf = new(bytes.Buffer) - } - if dopts.copts.StatsHandler != nil { - outPayload = &stats.OutPayload{ - Client: true, - } - } - hdr, data, err := encode(dopts.codec, args, compressor, cbuf, outPayload) - if err != nil { - return err - } - if c.maxSendMessageSize == nil { - return Errorf(codes.Internal, "callInfo maxSendMessageSize field uninitialized(nil)") - } - if len(data) > *c.maxSendMessageSize { - return Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max (%d vs. %d)", len(data), *c.maxSendMessageSize) - } - err = t.Write(stream, hdr, data, opts) - if err == nil && outPayload != nil { - outPayload.SentTime = time.Now() - dopts.copts.StatsHandler.HandleRPC(ctx, outPayload) - } - // t.NewStream(...) could lead to an early rejection of the RPC (e.g., the service/method - // does not exist.) so that t.Write could get io.EOF from wait(...). Leave the following - // recvResponse to get the final status. - if err != nil && err != io.EOF { - return err - } - // Sent successfully. - return nil -} - -// Invoke sends the RPC request on the wire and returns after response is received. -// Invoke is called by generated code. Also users can call Invoke directly when it -// is really needed in their use cases. -func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) error { if cc.dopts.unaryInt != nil { return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...) } return invoke(ctx, method, args, reply, cc, opts...) } -func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (e error) { - c := defaultCallInfo() - mc := cc.GetMethodConfig(method) - if mc.WaitForReady != nil { - c.failFast = !*mc.WaitForReady - } - - if mc.Timeout != nil && *mc.Timeout >= 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *mc.Timeout) - defer cancel() - } +func combine(o1 []CallOption, o2 []CallOption) []CallOption { + // we don't use append because o1 could have extra capacity whose + // elements would be overwritten, which could cause inadvertent + // sharing (and race connditions) between concurrent calls + if len(o1) == 0 { + return o2 + } else if len(o2) == 0 { + return o1 + } + ret := make([]CallOption, len(o1)+len(o2)) + copy(ret, o1) + copy(ret[len(o1):], o2) + return ret +} - opts = append(cc.dopts.callOptions, opts...) - for _, o := range opts { - if err := o.before(c); err != nil { - return toRPCErr(err) - } - } - defer func() { - for _, o := range opts { - o.after(c) - } - }() +// Invoke sends the RPC request on the wire and returns after response is +// received. This is typically called by generated code. +// +// DEPRECATED: Use ClientConn.Invoke instead. +func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) error { + return cc.Invoke(ctx, method, args, reply, opts...) +} - c.maxSendMessageSize = getMaxSize(mc.MaxReqSize, c.maxSendMessageSize, defaultClientMaxSendMessageSize) - c.maxReceiveMessageSize = getMaxSize(mc.MaxRespSize, c.maxReceiveMessageSize, defaultClientMaxReceiveMessageSize) +var unaryStreamDesc = &StreamDesc{ServerStreams: false, ClientStreams: false} - if EnableTracing { - c.traceInfo.tr = trace.New("grpc.Sent."+methodFamily(method), method) - defer c.traceInfo.tr.Finish() - c.traceInfo.firstLine.client = true - if deadline, ok := ctx.Deadline(); ok { - c.traceInfo.firstLine.deadline = deadline.Sub(time.Now()) - } - c.traceInfo.tr.LazyLog(&c.traceInfo.firstLine, false) - // TODO(dsymonds): Arrange for c.traceInfo.firstLine.remoteAddr to be set. - defer func() { - if e != nil { - c.traceInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{e}}, true) - c.traceInfo.tr.SetError() - } - }() - } - ctx = newContextWithRPCInfo(ctx, c.failFast) - sh := cc.dopts.copts.StatsHandler - if sh != nil { - ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method, FailFast: c.failFast}) - begin := &stats.Begin{ - Client: true, - BeginTime: time.Now(), - FailFast: c.failFast, - } - sh.HandleRPC(ctx, begin) - defer func() { - end := &stats.End{ - Client: true, - EndTime: time.Now(), - Error: e, - } - sh.HandleRPC(ctx, end) - }() - } - topts := &transport.Options{ - Last: true, - Delay: false, - } +func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error { + // TODO: implement retries in clientStream and make this simply + // newClientStream, SendMsg, RecvMsg. + firstAttempt := true for { - var ( - err error - t transport.ClientTransport - stream *transport.Stream - // Record the done handler from Balancer.Get(...). It is called once the - // RPC has completed or failed. - done func(balancer.DoneInfo) - ) - // TODO(zhaoq): Need a formal spec of fail-fast. - callHdr := &transport.CallHdr{ - Host: cc.authority, - Method: method, - } - if cc.dopts.cp != nil { - callHdr.SendCompress = cc.dopts.cp.Type() - } - if c.creds != nil { - callHdr.Creds = c.creds - } - - t, done, err = cc.getTransport(ctx, c.failFast) + csInt, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...) if err != nil { - // TODO(zhaoq): Probably revisit the error handling. - if _, ok := status.FromError(err); ok { - return err - } - if err == errConnClosing || err == errConnUnavailable { - if c.failFast { - return Errorf(codes.Unavailable, "%v", err) - } - continue - } - // All the other errors are treated as Internal errors. - return Errorf(codes.Internal, "%v", err) - } - if c.traceInfo.tr != nil { - c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true) - } - stream, err = t.NewStream(ctx, callHdr) - if err != nil { - if done != nil { - if _, ok := err.(transport.ConnectionError); ok { - // If error is connection error, transport was sending data on wire, - // and we are not sure if anything has been sent on wire. - // If error is not connection error, we are sure nothing has been sent. - updateRPCInfoInContext(ctx, rpcInfo{bytesSent: true, bytesReceived: false}) - } - done(balancer.DoneInfo{Err: err}) - } - if _, ok := err.(transport.ConnectionError); (ok || err == transport.ErrStreamDrain) && !c.failFast { - continue - } - return toRPCErr(err) - } - if peer, ok := peer.FromContext(stream.Context()); ok { - c.peer = peer + return err } - err = sendRequest(ctx, cc.dopts, cc.dopts.cp, c, callHdr, stream, t, args, topts) - if err != nil { - if done != nil { - updateRPCInfoInContext(ctx, rpcInfo{ - bytesSent: stream.BytesSent(), - bytesReceived: stream.BytesReceived(), - }) - done(balancer.DoneInfo{Err: err}) - } - // Retry a non-failfast RPC when - // i) there is a connection error; or - // ii) the server started to drain before this RPC was initiated. - if _, ok := err.(transport.ConnectionError); (ok || err == transport.ErrStreamDrain) && !c.failFast { + cs := csInt.(*clientStream) + if err := cs.SendMsg(req); err != nil { + if !cs.c.failFast && cs.attempt.s.Unprocessed() && firstAttempt { + // TODO: Add a field to header for grpc-transparent-retry-attempts + firstAttempt = false continue } - return toRPCErr(err) + return err } - err = recvResponse(ctx, cc.dopts, t, c, stream, reply) - if err != nil { - if done != nil { - updateRPCInfoInContext(ctx, rpcInfo{ - bytesSent: stream.BytesSent(), - bytesReceived: stream.BytesReceived(), - }) - done(balancer.DoneInfo{Err: err}) - } - if _, ok := err.(transport.ConnectionError); (ok || err == transport.ErrStreamDrain) && !c.failFast { + if err := cs.RecvMsg(reply); err != nil { + if !cs.c.failFast && cs.attempt.s.Unprocessed() && firstAttempt { + // TODO: Add a field to header for grpc-transparent-retry-attempts + firstAttempt = false continue } - return toRPCErr(err) - } - if c.traceInfo.tr != nil { - c.traceInfo.tr.LazyLog(&payload{sent: false, msg: reply}, true) - } - t.CloseStream(stream, nil) - if done != nil { - updateRPCInfoInContext(ctx, rpcInfo{ - bytesSent: stream.BytesSent(), - bytesReceived: stream.BytesReceived(), - }) - done(balancer.DoneInfo{Err: err}) + return err } - return stream.Status().Err() + return nil } } diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 71de2e50d2b..64a7982fad1 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -31,24 +31,49 @@ import ( "golang.org/x/net/context" "golang.org/x/net/trace" "google.golang.org/grpc/balancer" + _ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin. + "google.golang.org/grpc/codes" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/resolver" + _ "google.golang.org/grpc/resolver/dns" // To register dns resolver. + _ "google.golang.org/grpc/resolver/passthrough" // To register passthrough resolver. "google.golang.org/grpc/stats" + "google.golang.org/grpc/status" "google.golang.org/grpc/transport" ) +const ( + // minimum time to give a connection to complete + minConnectTimeout = 20 * time.Second +) + var ( // ErrClientConnClosing indicates that the operation is illegal because // the ClientConn is closing. - ErrClientConnClosing = errors.New("grpc: the client connection is closing") - // ErrClientConnTimeout indicates that the ClientConn cannot establish the - // underlying connections within the specified timeout. - // DEPRECATED: Please use context.DeadlineExceeded instead. - ErrClientConnTimeout = errors.New("grpc: timed out when dialing") + // + // Deprecated: this error should not be relied upon by users; use the status + // code of Canceled instead. + ErrClientConnClosing = status.Error(codes.Canceled, "grpc: the client connection is closing") + // errConnDrain indicates that the connection starts to be drained and does not accept any new RPCs. + errConnDrain = errors.New("grpc: the connection is drained") + // errConnClosing indicates that the connection is closing. + errConnClosing = errors.New("grpc: the connection is closing") + // errConnUnavailable indicates that the connection is unavailable. + errConnUnavailable = errors.New("grpc: the connection is unavailable") + // errBalancerClosed indicates that the balancer is closed. + errBalancerClosed = errors.New("grpc: balancer is closed") + // We use an accessor so that minConnectTimeout can be + // atomically read and updated while testing. + getMinConnectTimeout = func() time.Duration { + return minConnectTimeout + } +) +// The following errors are returned from Dial and DialContext +var ( // errNoTransportSecurity indicates that there is no transport security // being set for ClientConn. Users should either set one or explicitly // call WithInsecure DialOption to disable security. @@ -62,16 +87,6 @@ var ( errCredentialsConflict = errors.New("grpc: transport credentials are set for an insecure connection (grpc.WithTransportCredentials() and grpc.WithInsecure() are both called)") // errNetworkIO indicates that the connection is down due to some network I/O error. errNetworkIO = errors.New("grpc: failed with network I/O error") - // errConnDrain indicates that the connection starts to be drained and does not accept any new RPCs. - errConnDrain = errors.New("grpc: the connection is drained") - // errConnClosing indicates that the connection is closing. - errConnClosing = errors.New("grpc: the connection is closing") - // errConnUnavailable indicates that the connection is unavailable. - errConnUnavailable = errors.New("grpc: the connection is unavailable") - // errBalancerClosed indicates that the balancer is closed. - errBalancerClosed = errors.New("grpc: balancer is closed") - // minimum time to give a connection to complete - minConnectTimeout = 20 * time.Second ) // dialOptions configure a Dial call. dialOptions are set by the DialOption @@ -79,7 +94,6 @@ var ( type dialOptions struct { unaryInt UnaryClientInterceptor streamInt StreamClientInterceptor - codec Codec cp Compressor dc Decompressor bs backoffStrategy @@ -89,8 +103,12 @@ type dialOptions struct { scChan <-chan ServiceConfig copts transport.ConnectOptions callOptions []CallOption - // This is to support v1 balancer. + // This is used by v1 balancer dial option WithBalancer to support v1 + // balancer, and also by WithBalancerName dial option. balancerBuilder balancer.Builder + // This is to support grpclb. + resolverBuilder resolver.Builder + waitForHandshake bool } const ( @@ -101,6 +119,15 @@ const ( // DialOption configures how we set up the connection. type DialOption func(*dialOptions) +// WithWaitForHandshake blocks until the initial settings frame is received from the +// server before assigning RPCs to the connection. +// Experimental API. +func WithWaitForHandshake() DialOption { + return func(o *dialOptions) { + o.waitForHandshake = true + } +} + // WithWriteBufferSize lets you set the size of write buffer, this determines how much data can be batched // before doing a write on the wire. func WithWriteBufferSize(s int) DialOption { @@ -146,22 +173,32 @@ func WithDefaultCallOptions(cos ...CallOption) DialOption { } // WithCodec returns a DialOption which sets a codec for message marshaling and unmarshaling. +// +// Deprecated: use WithDefaultCallOptions(CallCustomCodec(c)) instead. func WithCodec(c Codec) DialOption { - return func(o *dialOptions) { - o.codec = c - } + return WithDefaultCallOptions(CallCustomCodec(c)) } -// WithCompressor returns a DialOption which sets a CompressorGenerator for generating message -// compressor. +// WithCompressor returns a DialOption which sets a Compressor to use for +// message compression. It has lower priority than the compressor set by +// the UseCompressor CallOption. +// +// Deprecated: use UseCompressor instead. func WithCompressor(cp Compressor) DialOption { return func(o *dialOptions) { o.cp = cp } } -// WithDecompressor returns a DialOption which sets a DecompressorGenerator for generating -// message decompressor. +// WithDecompressor returns a DialOption which sets a Decompressor to use for +// incoming message decompression. If incoming response messages are encoded +// using the decompressor's Type(), it will be used. Otherwise, the message +// encoding will be used to look up the compressor registered via +// encoding.RegisterCompressor, which will then be used to decompress the +// message. If no compressor is registered for the encoding, an Unimplemented +// status error will be returned. +// +// Deprecated: use encoding.RegisterCompressor instead. func WithDecompressor(dc Decompressor) DialOption { return func(o *dialOptions) { o.dc = dc @@ -170,7 +207,8 @@ func WithDecompressor(dc Decompressor) DialOption { // WithBalancer returns a DialOption which sets a load balancer with the v1 API. // Name resolver will be ignored if this DialOption is specified. -// Deprecated: use the new balancer APIs in balancer package instead. +// +// Deprecated: use the new balancer APIs in balancer package and WithBalancerName. func WithBalancer(b Balancer) DialOption { return func(o *dialOptions) { o.balancerBuilder = &balancerWrapperBuilder{ @@ -179,16 +217,34 @@ func WithBalancer(b Balancer) DialOption { } } -// WithBalancerBuilder is for testing only. Users using custom balancers should -// register their balancer and use service config to choose the balancer to use. -func WithBalancerBuilder(b balancer.Builder) DialOption { - // TODO(bar) remove this when switching balancer is done. +// WithBalancerName sets the balancer that the ClientConn will be initialized +// with. Balancer registered with balancerName will be used. This function +// panics if no balancer was registered by balancerName. +// +// The balancer cannot be overridden by balancer option specified by service +// config. +// +// This is an EXPERIMENTAL API. +func WithBalancerName(balancerName string) DialOption { + builder := balancer.Get(balancerName) + if builder == nil { + panic(fmt.Sprintf("grpc.WithBalancerName: no balancer is registered for name %v", balancerName)) + } return func(o *dialOptions) { - o.balancerBuilder = b + o.balancerBuilder = builder + } +} + +// withResolverBuilder is only for grpclb. +func withResolverBuilder(b resolver.Builder) DialOption { + return func(o *dialOptions) { + o.resolverBuilder = b } } // WithServiceConfig returns a DialOption which has a channel to read the service configuration. +// DEPRECATED: service config should be received through name resolver, as specified here. +// https://github.com/grpc/grpc/blob/master/doc/service_config.md func WithServiceConfig(c <-chan ServiceConfig) DialOption { return func(o *dialOptions) { o.scChan = c @@ -213,7 +269,7 @@ func WithBackoffConfig(b BackoffConfig) DialOption { return withBackoff(b) } -// withBackoff sets the backoff strategy used for retries after a +// withBackoff sets the backoff strategy used for connectRetryNum after a // failed connection attempt. // // This can be exported if arbitrary backoff strategies are allowed by gRPC. @@ -265,18 +321,23 @@ func WithTimeout(d time.Duration) DialOption { } } +func withContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption { + return func(o *dialOptions) { + o.copts.Dialer = f + } +} + // WithDialer returns a DialOption that specifies a function to use for dialing network addresses. // If FailOnNonTempDialError() is set to true, and an error is returned by f, gRPC checks the error's // Temporary() method to decide if it should try to reconnect to the network address. func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption { - return func(o *dialOptions) { - o.copts.Dialer = func(ctx context.Context, addr string) (net.Conn, error) { + return withContextDialer( + func(ctx context.Context, addr string) (net.Conn, error) { if deadline, ok := ctx.Deadline(); ok { return f(addr, deadline.Sub(time.Now())) } return f(addr, 0) - } - } + }) } // WithStatsHandler returns a DialOption that specifies the stats handler @@ -344,6 +405,10 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) { // cancel or expire the pending connection. Once this function returns, the // cancellation and expiration of ctx will be noop. Users should call ClientConn.Close // to terminate all the pending operations after this function returns. +// +// The target name syntax is defined in +// https://github.com/grpc/grpc/blob/master/doc/naming.md. +// e.g. to use dns resolver, a "dns:///" prefix should be applied to the target. func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { cc := &ClientConn{ target: target, @@ -378,7 +443,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if cc.dopts.copts.Dialer == nil { cc.dopts.copts.Dialer = newProxyDialer( func(ctx context.Context, addr string) (net.Conn, error) { - return (&net.Dialer{}).DialContext(ctx, "tcp", addr) + return dialContext(ctx, "tcp", addr) }, ) } @@ -419,58 +484,39 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * default: } } - // Set defaults. - if cc.dopts.codec == nil { - cc.dopts.codec = protoCodec{} - } if cc.dopts.bs == nil { cc.dopts.bs = DefaultBackoffConfig } + if cc.dopts.resolverBuilder == nil { + // Only try to parse target when resolver builder is not already set. + cc.parsedTarget = parseTarget(cc.target) + grpclog.Infof("parsed scheme: %q", cc.parsedTarget.Scheme) + cc.dopts.resolverBuilder = resolver.Get(cc.parsedTarget.Scheme) + if cc.dopts.resolverBuilder == nil { + // If resolver builder is still nil, the parse target's scheme is + // not registered. Fallback to default resolver and set Endpoint to + // the original unparsed target. + grpclog.Infof("scheme %q not registered, fallback to default scheme", cc.parsedTarget.Scheme) + cc.parsedTarget = resolver.Target{ + Scheme: resolver.GetDefaultScheme(), + Endpoint: target, + } + cc.dopts.resolverBuilder = resolver.Get(cc.parsedTarget.Scheme) + } + } else { + cc.parsedTarget = resolver.Target{Endpoint: target} + } creds := cc.dopts.copts.TransportCredentials if creds != nil && creds.Info().ServerName != "" { cc.authority = creds.Info().ServerName } else if cc.dopts.insecure && cc.dopts.copts.Authority != "" { cc.authority = cc.dopts.copts.Authority } else { - cc.authority = target + // Use endpoint from "scheme://authority/endpoint" as the default + // authority for ClientConn. + cc.authority = cc.parsedTarget.Endpoint } - if cc.dopts.balancerBuilder != nil { - var credsClone credentials.TransportCredentials - if creds != nil { - credsClone = creds.Clone() - } - buildOpts := balancer.BuildOptions{ - DialCreds: credsClone, - Dialer: cc.dopts.copts.Dialer, - } - // Build should not take long time. So it's ok to not have a goroutine for it. - // TODO(bar) init balancer after first resolver result to support service config balancer. - cc.balancerWrapper = newCCBalancerWrapper(cc, cc.dopts.balancerBuilder, buildOpts) - } else { - waitC := make(chan error, 1) - go func() { - defer close(waitC) - // No balancer, or no resolver within the balancer. Connect directly. - ac, err := cc.newAddrConn([]resolver.Address{{Addr: target}}) - if err != nil { - waitC <- err - return - } - if err := ac.connect(cc.dopts.block); err != nil { - waitC <- err - return - } - }() - select { - case <-ctx.Done(): - return nil, ctx.Err() - case err := <-waitC: - if err != nil { - return nil, err - } - } - } if cc.dopts.scChan != nil && !scSet { // Blocking wait for the initial service config. select { @@ -486,19 +532,28 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * go cc.scWatcher() } + var credsClone credentials.TransportCredentials + if creds := cc.dopts.copts.TransportCredentials; creds != nil { + credsClone = creds.Clone() + } + cc.balancerBuildOpts = balancer.BuildOptions{ + DialCreds: credsClone, + Dialer: cc.dopts.copts.Dialer, + } + // Build the resolver. cc.resolverWrapper, err = newCCResolverWrapper(cc) if err != nil { return nil, fmt.Errorf("failed to build resolver: %v", err) } - - if cc.balancerWrapper != nil && cc.resolverWrapper == nil { - // TODO(bar) there should always be a resolver (DNS as the default). - // Unblock balancer initialization with a fake resolver update if there's no resolver. - // The balancer wrapper will not read the addresses, so an empty list works. - // TODO(bar) remove this after the real resolver is started. - cc.balancerWrapper.handleResolvedAddrs([]resolver.Address{}, nil) - } + // Start the resolver wrapper goroutine after resolverWrapper is created. + // + // If the goroutine is started before resolverWrapper is ready, the + // following may happen: The goroutine sends updates to cc. cc forwards + // those to balancer. Balancer creates new addrConn. addrConn fails to + // connect, and calls resolveNow(). resolveNow() tries to use the non-ready + // resolverWrapper. + cc.resolverWrapper.start() // A blocking dial blocks until the clientConn is ready. if cc.dopts.block { @@ -565,21 +620,26 @@ type ClientConn struct { ctx context.Context cancel context.CancelFunc - target string - authority string - dopts dialOptions - csMgr *connectivityStateManager + target string + parsedTarget resolver.Target + authority string + dopts dialOptions + csMgr *connectivityStateManager - balancerWrapper *ccBalancerWrapper - resolverWrapper *ccResolverWrapper - - blockingpicker *pickerWrapper + balancerBuildOpts balancer.BuildOptions + resolverWrapper *ccResolverWrapper + blockingpicker *pickerWrapper mu sync.RWMutex sc ServiceConfig + scRaw string conns map[*addrConn]struct{} // Keepalive parameter can be updated if a GoAway is received. - mkp keepalive.ClientParameters + mkp keepalive.ClientParameters + curBalancerName string + preBalancerName string // previous balancer name. + curAddresses []resolver.Address + balancerWrapper *ccBalancerWrapper } // WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or @@ -615,6 +675,7 @@ func (cc *ClientConn) scWatcher() { // TODO: load balance policy runtime change is ignored. // We may revist this decision in the future. cc.sc = sc + cc.scRaw = "" cc.mu.Unlock() case <-cc.ctx.Done(): return @@ -622,7 +683,113 @@ func (cc *ClientConn) scWatcher() { } } +func (cc *ClientConn) handleResolvedAddrs(addrs []resolver.Address, err error) { + cc.mu.Lock() + defer cc.mu.Unlock() + if cc.conns == nil { + // cc was closed. + return + } + + if reflect.DeepEqual(cc.curAddresses, addrs) { + return + } + + cc.curAddresses = addrs + + if cc.dopts.balancerBuilder == nil { + // Only look at balancer types and switch balancer if balancer dial + // option is not set. + var isGRPCLB bool + for _, a := range addrs { + if a.Type == resolver.GRPCLB { + isGRPCLB = true + break + } + } + var newBalancerName string + if isGRPCLB { + newBalancerName = grpclbName + } else { + // Address list doesn't contain grpclb address. Try to pick a + // non-grpclb balancer. + newBalancerName = cc.curBalancerName + // If current balancer is grpclb, switch to the previous one. + if newBalancerName == grpclbName { + newBalancerName = cc.preBalancerName + } + // The following could be true in two cases: + // - the first time handling resolved addresses + // (curBalancerName="") + // - the first time handling non-grpclb addresses + // (curBalancerName="grpclb", preBalancerName="") + if newBalancerName == "" { + newBalancerName = PickFirstBalancerName + } + } + cc.switchBalancer(newBalancerName) + } else if cc.balancerWrapper == nil { + // Balancer dial option was set, and this is the first time handling + // resolved addresses. Build a balancer with dopts.balancerBuilder. + cc.balancerWrapper = newCCBalancerWrapper(cc, cc.dopts.balancerBuilder, cc.balancerBuildOpts) + } + + cc.balancerWrapper.handleResolvedAddrs(addrs, nil) +} + +// switchBalancer starts the switching from current balancer to the balancer +// with the given name. +// +// It will NOT send the current address list to the new balancer. If needed, +// caller of this function should send address list to the new balancer after +// this function returns. +// +// Caller must hold cc.mu. +func (cc *ClientConn) switchBalancer(name string) { + if cc.conns == nil { + return + } + + if strings.ToLower(cc.curBalancerName) == strings.ToLower(name) { + return + } + + grpclog.Infof("ClientConn switching balancer to %q", name) + if cc.dopts.balancerBuilder != nil { + grpclog.Infoln("ignoring balancer switching: Balancer DialOption used instead") + return + } + // TODO(bar switching) change this to two steps: drain and close. + // Keep track of sc in wrapper. + if cc.balancerWrapper != nil { + cc.balancerWrapper.close() + } + + builder := balancer.Get(name) + if builder == nil { + grpclog.Infof("failed to get balancer builder for: %v, using pick_first instead", name) + builder = newPickfirstBuilder() + } + cc.preBalancerName = cc.curBalancerName + cc.curBalancerName = builder.Name() + cc.balancerWrapper = newCCBalancerWrapper(cc, builder, cc.balancerBuildOpts) +} + +func (cc *ClientConn) handleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { + cc.mu.Lock() + if cc.conns == nil { + cc.mu.Unlock() + return + } + // TODO(bar switching) send updates to all balancer wrappers when balancer + // gracefully switching is supported. + cc.balancerWrapper.handleSubConnStateChange(sc, s) + cc.mu.Unlock() +} + // newAddrConn creates an addrConn for addrs and adds it to cc.conns. +// +// Caller needs to make sure len(addrs) > 0. func (cc *ClientConn) newAddrConn(addrs []resolver.Address) (*addrConn, error) { ac := &addrConn{ cc: cc, @@ -659,7 +826,7 @@ func (cc *ClientConn) removeAddrConn(ac *addrConn, err error) { // It does nothing if the ac is not IDLE. // TODO(bar) Move this to the addrConn section. // This was part of resetAddrConn, keep it here to make the diff look clean. -func (ac *addrConn) connect(block bool) error { +func (ac *addrConn) connect() error { ac.mu.Lock() if ac.state == connectivity.Shutdown { ac.mu.Unlock() @@ -670,39 +837,21 @@ func (ac *addrConn) connect(block bool) error { return nil } ac.state = connectivity.Connecting - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) ac.mu.Unlock() - if block { + // Start a goroutine connecting to the server asynchronously. + go func() { if err := ac.resetTransport(); err != nil { + grpclog.Warningf("Failed to dial %s: %v; please retry.", ac.addrs[0].Addr, err) if err != errConnClosing { + // Keep this ac in cc.conns, to get the reason it's torn down. ac.tearDown(err) } - if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() { - return e.Origin() - } - return err + return } - // Start to monitor the error status of transport. - go ac.transportMonitor() - } else { - // Start a goroutine connecting to the server asynchronously. - go func() { - if err := ac.resetTransport(); err != nil { - grpclog.Warningf("Failed to dial %s: %v; please retry.", ac.addrs[0].Addr, err) - if err != errConnClosing { - // Keep this ac in cc.conns, to get the reason it's torn down. - ac.tearDown(err) - } - return - } - ac.transportMonitor() - }() - } + ac.transportMonitor() + }() return nil } @@ -731,6 +880,7 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool { grpclog.Infof("addrConn: tryUpdateAddrs curAddrFound: %v", curAddrFound) if curAddrFound { ac.addrs = addrs + ac.reconnectIdx = 0 // Start reconnecting from beginning in the new list. } return curAddrFound @@ -741,7 +891,7 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool { // the corresponding MethodConfig. // If there isn't an exact match for the input method, we look for the default config // under the service (i.e /service/). If there is a default MethodConfig for -// the serivce, we return it. +// the service, we return it. // Otherwise, we return an empty MethodConfig. func (cc *ClientConn) GetMethodConfig(method string) MethodConfig { // TODO: Avoid the locking here. @@ -756,31 +906,6 @@ func (cc *ClientConn) GetMethodConfig(method string) MethodConfig { } func (cc *ClientConn) getTransport(ctx context.Context, failfast bool) (transport.ClientTransport, func(balancer.DoneInfo), error) { - if cc.balancerWrapper == nil { - // If balancer is nil, there should be only one addrConn available. - cc.mu.RLock() - if cc.conns == nil { - cc.mu.RUnlock() - // TODO this function returns toRPCErr and non-toRPCErr. Clean up - // the errors in ClientConn. - return nil, nil, toRPCErr(ErrClientConnClosing) - } - var ac *addrConn - for ac = range cc.conns { - // Break after the first iteration to get the first addrConn. - break - } - cc.mu.RUnlock() - if ac == nil { - return nil, nil, errConnClosing - } - t, err := ac.wait(ctx, false /*hasBalancer*/, failfast) - if err != nil { - return nil, nil, err - } - return t, nil, nil - } - t, done, err := cc.blockingpicker.pick(ctx, failfast, balancer.PickOptions{}) if err != nil { return nil, nil, toRPCErr(err) @@ -788,9 +913,46 @@ func (cc *ClientConn) getTransport(ctx context.Context, failfast bool) (transpor return t, done, nil } +// handleServiceConfig parses the service config string in JSON format to Go native +// struct ServiceConfig, and store both the struct and the JSON string in ClientConn. +func (cc *ClientConn) handleServiceConfig(js string) error { + sc, err := parseServiceConfig(js) + if err != nil { + return err + } + cc.mu.Lock() + cc.scRaw = js + cc.sc = sc + if sc.LB != nil && *sc.LB != grpclbName { // "grpclb" is not a valid balancer option in service config. + if cc.curBalancerName == grpclbName { + // If current balancer is grpclb, there's at least one grpclb + // balancer address in the resolved list. Don't switch the balancer, + // but change the previous balancer name, so if a new resolved + // address list doesn't contain grpclb address, balancer will be + // switched to *sc.LB. + cc.preBalancerName = *sc.LB + } else { + cc.switchBalancer(*sc.LB) + cc.balancerWrapper.handleResolvedAddrs(cc.curAddresses, nil) + } + } + cc.mu.Unlock() + return nil +} + +func (cc *ClientConn) resolveNow(o resolver.ResolveNowOption) { + cc.mu.Lock() + r := cc.resolverWrapper + cc.mu.Unlock() + if r == nil { + return + } + go r.resolveNow(o) +} + // Close tears down the ClientConn and all underlying connections. func (cc *ClientConn) Close() error { - cc.cancel() + defer cc.cancel() cc.mu.Lock() if cc.conns == nil { @@ -800,13 +962,18 @@ func (cc *ClientConn) Close() error { conns := cc.conns cc.conns = nil cc.csMgr.updateState(connectivity.Shutdown) + + rWrapper := cc.resolverWrapper + cc.resolverWrapper = nil + bWrapper := cc.balancerWrapper + cc.balancerWrapper = nil cc.mu.Unlock() cc.blockingpicker.close() - if cc.resolverWrapper != nil { - cc.resolverWrapper.close() + if rWrapper != nil { + rWrapper.close() } - if cc.balancerWrapper != nil { - cc.balancerWrapper.close() + if bWrapper != nil { + bWrapper.close() } for ac := range conns { ac.tearDown(ErrClientConnClosing) @@ -819,15 +986,16 @@ type addrConn struct { ctx context.Context cancel context.CancelFunc - cc *ClientConn - curAddr resolver.Address - addrs []resolver.Address - dopts dialOptions - events trace.EventLog - acbw balancer.SubConn + cc *ClientConn + addrs []resolver.Address + dopts dialOptions + events trace.EventLog + acbw balancer.SubConn - mu sync.Mutex - state connectivity.State + mu sync.Mutex + curAddr resolver.Address + reconnectIdx int // The index in addrs list to start reconnecting from. + state connectivity.State // ready is closed and becomes nil when a new transport is up or failed // due to timeout. ready chan struct{} @@ -835,13 +1003,21 @@ type addrConn struct { // The reason this addrConn is torn down. tearDownErr error + + connectRetryNum int + // backoffDeadline is the time until which resetTransport needs to + // wait before increasing connectRetryNum count. + backoffDeadline time.Time + // connectDeadline is the time by which all connection + // negotiations must complete. + connectDeadline time.Time } // adjustParams updates parameters used to create transports upon // receiving a GoAway. func (ac *addrConn) adjustParams(r transport.GoAwayReason) { switch r { - case transport.TooManyPings: + case transport.GoAwayTooManyPings: v := 2 * ac.dopts.copts.KeepaliveParams.Time ac.cc.mu.Lock() if v > ac.cc.mkp.Time { @@ -869,6 +1045,15 @@ func (ac *addrConn) errorf(format string, a ...interface{}) { // resetTransport recreates a transport to the address for ac. The old // transport will close itself on error or when the clientconn is closed. +// The created transport must receive initial settings frame from the server. +// In case that doesnt happen, transportMonitor will kill the newly created +// transport after connectDeadline has expired. +// In case there was an error on the transport before the settings frame was +// received, resetTransport resumes connecting to backends after the one that +// was previously connected to. In case end of the list is reached, resetTransport +// backs off until the original deadline. +// If the DialOption WithWaitForHandshake was set, resetTrasport returns +// successfully only after server settings are received. // // TODO(bar) make sure all state transitions are valid. func (ac *addrConn) resetTransport() error { @@ -882,19 +1067,38 @@ func (ac *addrConn) resetTransport() error { ac.ready = nil } ac.transport = nil - ac.curAddr = resolver.Address{} + ridx := ac.reconnectIdx ac.mu.Unlock() ac.cc.mu.RLock() ac.dopts.copts.KeepaliveParams = ac.cc.mkp ac.cc.mu.RUnlock() - for retries := 0; ; retries++ { - sleepTime := ac.dopts.bs.backoff(retries) - timeout := minConnectTimeout + var backoffDeadline, connectDeadline time.Time + for connectRetryNum := 0; ; connectRetryNum++ { ac.mu.Lock() - if timeout < time.Duration(int(sleepTime)/len(ac.addrs)) { - timeout = time.Duration(int(sleepTime) / len(ac.addrs)) + if ac.backoffDeadline.IsZero() { + // This means either a successful HTTP2 connection was established + // or this is the first time this addrConn is trying to establish a + // connection. + backoffFor := ac.dopts.bs.backoff(connectRetryNum) // time.Duration. + // This will be the duration that dial gets to finish. + dialDuration := getMinConnectTimeout() + if backoffFor > dialDuration { + // Give dial more time as we keep failing to connect. + dialDuration = backoffFor + } + start := time.Now() + backoffDeadline = start.Add(backoffFor) + connectDeadline = start.Add(dialDuration) + ridx = 0 // Start connecting from the beginning. + } else { + // Continue trying to conect with the same deadlines. + connectRetryNum = ac.connectRetryNum + backoffDeadline = ac.backoffDeadline + connectDeadline = ac.connectDeadline + ac.backoffDeadline = time.Time{} + ac.connectDeadline = time.Time{} + ac.connectRetryNum = 0 } - connectTime := time.Now() if ac.state == connectivity.Shutdown { ac.mu.Unlock() return errConnClosing @@ -902,116 +1106,158 @@ func (ac *addrConn) resetTransport() error { ac.printf("connecting") if ac.state != connectivity.Connecting { ac.state = connectivity.Connecting - // TODO(bar) remove condition once we always have a balancer. - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) } // copy ac.addrs in case of race addrsIter := make([]resolver.Address, len(ac.addrs)) copy(addrsIter, ac.addrs) copts := ac.dopts.copts ac.mu.Unlock() - for _, addr := range addrsIter { + connected, err := ac.createTransport(connectRetryNum, ridx, backoffDeadline, connectDeadline, addrsIter, copts) + if err != nil { + return err + } + if connected { + return nil + } + } +} + +// createTransport creates a connection to one of the backends in addrs. +// It returns true if a connection was established. +func (ac *addrConn) createTransport(connectRetryNum, ridx int, backoffDeadline, connectDeadline time.Time, addrs []resolver.Address, copts transport.ConnectOptions) (bool, error) { + for i := ridx; i < len(addrs); i++ { + addr := addrs[i] + target := transport.TargetInfo{ + Addr: addr.Addr, + Metadata: addr.Metadata, + Authority: ac.cc.authority, + } + done := make(chan struct{}) + onPrefaceReceipt := func() { ac.mu.Lock() - if ac.state == connectivity.Shutdown { - // ac.tearDown(...) has been invoked. - ac.mu.Unlock() - return errConnClosing + close(done) + if !ac.backoffDeadline.IsZero() { + // If we haven't already started reconnecting to + // other backends. + // Note, this can happen when writer notices an error + // and triggers resetTransport while at the same time + // reader receives the preface and invokes this closure. + ac.backoffDeadline = time.Time{} + ac.connectDeadline = time.Time{} + ac.connectRetryNum = 0 } ac.mu.Unlock() - sinfo := transport.TargetInfo{ - Addr: addr.Addr, - Metadata: addr.Metadata, - } - newTransport, err := transport.NewClientTransport(ac.cc.ctx, sinfo, copts, timeout) - if err != nil { - if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() { - ac.mu.Lock() - if ac.state != connectivity.Shutdown { - ac.state = connectivity.TransientFailure - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } - } - ac.mu.Unlock() - return err - } - grpclog.Warningf("grpc: addrConn.resetTransport failed to create client transport: %v; Reconnecting to %v", err, addr) - ac.mu.Lock() - if ac.state == connectivity.Shutdown { - // ac.tearDown(...) has been invoked. - ac.mu.Unlock() - return errConnClosing - } - ac.mu.Unlock() - continue - } + } + // Do not cancel in the success path because of + // this issue in Go1.6: https://github.com/golang/go/issues/15078. + connectCtx, cancel := context.WithDeadline(ac.ctx, connectDeadline) + newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, target, copts, onPrefaceReceipt) + if err != nil { + cancel() + ac.cc.blockingpicker.updateConnectionError(err) ac.mu.Lock() - ac.printf("ready") if ac.state == connectivity.Shutdown { // ac.tearDown(...) has been invoked. ac.mu.Unlock() - newTransport.Close() - return errConnClosing - } - ac.state = connectivity.Ready - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } - t := ac.transport - ac.transport = newTransport - if t != nil { - t.Close() - } - ac.curAddr = addr - if ac.ready != nil { - close(ac.ready) - ac.ready = nil + return false, errConnClosing } ac.mu.Unlock() - return nil + grpclog.Warningf("grpc: addrConn.createTransport failed to connect to %v. Err :%v. Reconnecting...", addr, err) + continue + } + if ac.dopts.waitForHandshake { + select { + case <-done: + case <-connectCtx.Done(): + // Didn't receive server preface, must kill this new transport now. + grpclog.Warningf("grpc: addrConn.createTransport failed to receive server preface before deadline.") + newTr.Close() + break + case <-ac.ctx.Done(): + } } ac.mu.Lock() - ac.state = connectivity.TransientFailure - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) + if ac.state == connectivity.Shutdown { + ac.mu.Unlock() + // ac.tearDonn(...) has been invoked. + newTr.Close() + return false, errConnClosing } + ac.printf("ready") + ac.state = connectivity.Ready + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) + ac.transport = newTr + ac.curAddr = addr if ac.ready != nil { close(ac.ready) ac.ready = nil } - ac.mu.Unlock() - timer := time.NewTimer(sleepTime - time.Since(connectTime)) select { - case <-timer.C: - case <-ac.ctx.Done(): - timer.Stop() - return ac.ctx.Err() + case <-done: + // If the server has responded back with preface already, + // don't set the reconnect parameters. + default: + ac.connectRetryNum = connectRetryNum + ac.backoffDeadline = backoffDeadline + ac.connectDeadline = connectDeadline + ac.reconnectIdx = i + 1 // Start reconnecting from the next backend in the list. } + ac.mu.Unlock() + return true, nil + } + ac.mu.Lock() + ac.state = connectivity.TransientFailure + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) + ac.cc.resolveNow(resolver.ResolveNowOption{}) + if ac.ready != nil { + close(ac.ready) + ac.ready = nil + } + ac.mu.Unlock() + timer := time.NewTimer(backoffDeadline.Sub(time.Now())) + select { + case <-timer.C: + case <-ac.ctx.Done(): timer.Stop() + return false, ac.ctx.Err() } + return false, nil } // Run in a goroutine to track the error in transport and create the // new transport if an error happens. It returns when the channel is closing. func (ac *addrConn) transportMonitor() { for { + var timer *time.Timer + var cdeadline <-chan time.Time ac.mu.Lock() t := ac.transport + if !ac.connectDeadline.IsZero() { + timer = time.NewTimer(ac.connectDeadline.Sub(time.Now())) + cdeadline = timer.C + } ac.mu.Unlock() // Block until we receive a goaway or an error occurs. select { case <-t.GoAway(): case <-t.Error(): + case <-cdeadline: + ac.mu.Lock() + // This implies that client received server preface. + if ac.backoffDeadline.IsZero() { + ac.mu.Unlock() + continue + } + ac.mu.Unlock() + timer = nil + // No server preface received until deadline. + // Kill the connection. + grpclog.Warningf("grpc: addrConn.transportMonitor didn't get server preface after waiting. Closing the new transport now.") + t.Close() + } + if timer != nil { + timer.Stop() } // If a GoAway happened, regardless of error, adjust our keepalive // parameters as appropriate. @@ -1028,11 +1274,8 @@ func (ac *addrConn) transportMonitor() { // Set connectivity state to TransientFailure before calling // resetTransport. Transition READY->CONNECTING is not valid. ac.state = connectivity.TransientFailure - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) + ac.cc.resolveNow(resolver.ResolveNowOption{}) ac.curAddr = resolver.Address{} ac.mu.Unlock() if err := ac.resetTransport(); err != nil { @@ -1106,7 +1349,7 @@ func (ac *addrConn) getReadyTransport() (transport.ClientTransport, bool) { ac.mu.Unlock() // Trigger idle ac to connect. if idle { - ac.connect(false) + ac.connect() } return nil, false } @@ -1119,8 +1362,11 @@ func (ac *addrConn) getReadyTransport() (transport.ClientTransport, bool) { func (ac *addrConn) tearDown(err error) { ac.cancel() ac.mu.Lock() - ac.curAddr = resolver.Address{} defer ac.mu.Unlock() + if ac.state == connectivity.Shutdown { + return + } + ac.curAddr = resolver.Address{} if err == errConnDrain && ac.transport != nil { // GracefulClose(...) may be executed multiple times when // i) receiving multiple GoAway frames from the server; or @@ -1128,16 +1374,9 @@ func (ac *addrConn) tearDown(err error) { // address removal and GoAway. ac.transport.GracefulClose() } - if ac.state == connectivity.Shutdown { - return - } ac.state = connectivity.Shutdown ac.tearDownErr = err - if ac.cc.balancerWrapper != nil { - ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state) - } else { - ac.cc.csMgr.updateState(ac.state) - } + ac.cc.handleSubConnStateChange(ac.acbw, ac.state) if ac.events != nil { ac.events.Finish() ac.events = nil @@ -1154,3 +1393,10 @@ func (ac *addrConn) getState() connectivity.State { defer ac.mu.Unlock() return ac.state } + +// ErrClientConnTimeout indicates that the ClientConn cannot establish the +// underlying connections within the specified timeout. +// +// Deprecated: This error is never returned by grpc and should not be +// referenced by users. +var ErrClientConnTimeout = errors.New("grpc: timed out when dialing") diff --git a/vendor/google.golang.org/grpc/codec.go b/vendor/google.golang.org/grpc/codec.go index 905b048e2ac..12977654781 100644 --- a/vendor/google.golang.org/grpc/codec.go +++ b/vendor/google.golang.org/grpc/codec.go @@ -19,86 +19,32 @@ package grpc import ( - "math" - "sync" - - "github.com/golang/protobuf/proto" + "google.golang.org/grpc/encoding" + _ "google.golang.org/grpc/encoding/proto" // to register the Codec for "proto" ) +// baseCodec contains the functionality of both Codec and encoding.Codec, but +// omits the name/string, which vary between the two and are not needed for +// anything besides the registry in the encoding package. +type baseCodec interface { + Marshal(v interface{}) ([]byte, error) + Unmarshal(data []byte, v interface{}) error +} + +var _ baseCodec = Codec(nil) +var _ baseCodec = encoding.Codec(nil) + // Codec defines the interface gRPC uses to encode and decode messages. // Note that implementations of this interface must be thread safe; // a Codec's methods can be called from concurrent goroutines. +// +// Deprecated: use encoding.Codec instead. type Codec interface { // Marshal returns the wire format of v. Marshal(v interface{}) ([]byte, error) // Unmarshal parses the wire format into v. Unmarshal(data []byte, v interface{}) error - // String returns the name of the Codec implementation. The returned - // string will be used as part of content type in transmission. + // String returns the name of the Codec implementation. This is unused by + // gRPC. String() string } - -// protoCodec is a Codec implementation with protobuf. It is the default codec for gRPC. -type protoCodec struct { -} - -type cachedProtoBuffer struct { - lastMarshaledSize uint32 - proto.Buffer -} - -func capToMaxInt32(val int) uint32 { - if val > math.MaxInt32 { - return uint32(math.MaxInt32) - } - return uint32(val) -} - -func (p protoCodec) marshal(v interface{}, cb *cachedProtoBuffer) ([]byte, error) { - protoMsg := v.(proto.Message) - newSlice := make([]byte, 0, cb.lastMarshaledSize) - - cb.SetBuf(newSlice) - cb.Reset() - if err := cb.Marshal(protoMsg); err != nil { - return nil, err - } - out := cb.Bytes() - cb.lastMarshaledSize = capToMaxInt32(len(out)) - return out, nil -} - -func (p protoCodec) Marshal(v interface{}) ([]byte, error) { - cb := protoBufferPool.Get().(*cachedProtoBuffer) - out, err := p.marshal(v, cb) - - // put back buffer and lose the ref to the slice - cb.SetBuf(nil) - protoBufferPool.Put(cb) - return out, err -} - -func (p protoCodec) Unmarshal(data []byte, v interface{}) error { - cb := protoBufferPool.Get().(*cachedProtoBuffer) - cb.SetBuf(data) - v.(proto.Message).Reset() - err := cb.Unmarshal(v.(proto.Message)) - cb.SetBuf(nil) - protoBufferPool.Put(cb) - return err -} - -func (protoCodec) String() string { - return "proto" -} - -var ( - protoBufferPool = &sync.Pool{ - New: func() interface{} { - return &cachedProtoBuffer{ - Buffer: proto.Buffer{}, - lastMarshaledSize: 16, - } - }, - } -) diff --git a/vendor/google.golang.org/grpc/codes/code_string.go b/vendor/google.golang.org/grpc/codes/code_string.go index 259837060ab..0b206a57822 100644 --- a/vendor/google.golang.org/grpc/codes/code_string.go +++ b/vendor/google.golang.org/grpc/codes/code_string.go @@ -1,16 +1,62 @@ -// Code generated by "stringer -type=Code"; DO NOT EDIT. +/* + * + * Copyright 2017 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 codes -import "fmt" +import "strconv" -const _Code_name = "OKCanceledUnknownInvalidArgumentDeadlineExceededNotFoundAlreadyExistsPermissionDeniedResourceExhaustedFailedPreconditionAbortedOutOfRangeUnimplementedInternalUnavailableDataLossUnauthenticated" - -var _Code_index = [...]uint8{0, 2, 10, 17, 32, 48, 56, 69, 85, 102, 120, 127, 137, 150, 158, 169, 177, 192} - -func (i Code) String() string { - if i >= Code(len(_Code_index)-1) { - return fmt.Sprintf("Code(%d)", i) +func (c Code) String() string { + switch c { + case OK: + return "OK" + case Canceled: + return "Canceled" + case Unknown: + return "Unknown" + case InvalidArgument: + return "InvalidArgument" + case DeadlineExceeded: + return "DeadlineExceeded" + case NotFound: + return "NotFound" + case AlreadyExists: + return "AlreadyExists" + case PermissionDenied: + return "PermissionDenied" + case ResourceExhausted: + return "ResourceExhausted" + case FailedPrecondition: + return "FailedPrecondition" + case Aborted: + return "Aborted" + case OutOfRange: + return "OutOfRange" + case Unimplemented: + return "Unimplemented" + case Internal: + return "Internal" + case Unavailable: + return "Unavailable" + case DataLoss: + return "DataLoss" + case Unauthenticated: + return "Unauthenticated" + default: + return "Code(" + strconv.FormatInt(int64(c), 10) + ")" } - return _Code_name[_Code_index[i]:_Code_index[i+1]] } diff --git a/vendor/google.golang.org/grpc/codes/codes.go b/vendor/google.golang.org/grpc/codes/codes.go index 21e7733a5fe..a8280ae660d 100644 --- a/vendor/google.golang.org/grpc/codes/codes.go +++ b/vendor/google.golang.org/grpc/codes/codes.go @@ -20,11 +20,13 @@ // consistent across various languages. package codes // import "google.golang.org/grpc/codes" +import ( + "fmt" +) + // A Code is an unsigned 32-bit error code as defined in the gRPC spec. type Code uint32 -//go:generate stringer -type=Code - const ( // OK is returned on success. OK Code = 0 @@ -32,9 +34,9 @@ const ( // Canceled indicates the operation was canceled (typically by the caller). Canceled Code = 1 - // Unknown error. An example of where this error may be returned is + // Unknown error. An example of where this error may be returned is // if a Status value received from another address space belongs to - // an error-space that is not known in this address space. Also + // an error-space that is not known in this address space. Also // errors raised by APIs that do not return enough error information // may be converted to this error. Unknown Code = 2 @@ -63,15 +65,11 @@ const ( // PermissionDenied indicates the caller does not have permission to // execute the specified operation. It must not be used for rejections // caused by exhausting some resource (use ResourceExhausted - // instead for those errors). It must not be + // instead for those errors). It must not be // used if the caller cannot be identified (use Unauthenticated // instead for those errors). PermissionDenied Code = 7 - // Unauthenticated indicates the request does not have valid - // authentication credentials for the operation. - Unauthenticated Code = 16 - // ResourceExhausted indicates some resource has been exhausted, perhaps // a per-user quota, or perhaps the entire file system is out of space. ResourceExhausted Code = 8 @@ -87,7 +85,7 @@ const ( // (b) Use Aborted if the client should retry at a higher-level // (e.g., restarting a read-modify-write sequence). // (c) Use FailedPrecondition if the client should not retry until - // the system state has been explicitly fixed. E.g., if an "rmdir" + // the system state has been explicitly fixed. E.g., if an "rmdir" // fails because the directory is non-empty, FailedPrecondition // should be returned since the client should not retry unless // they have first fixed up the directory by deleting files from it. @@ -116,7 +114,7 @@ const ( // file size. // // There is a fair bit of overlap between FailedPrecondition and - // OutOfRange. We recommend using OutOfRange (the more specific + // OutOfRange. We recommend using OutOfRange (the more specific // error) when it applies so that callers who are iterating through // a space can easily look for an OutOfRange error to detect when // they are done. @@ -126,8 +124,8 @@ const ( // supported/enabled in this service. Unimplemented Code = 12 - // Internal errors. Means some invariants expected by underlying - // system has been broken. If you see one of these errors, + // Internal errors. Means some invariants expected by underlying + // system has been broken. If you see one of these errors, // something is very broken. Internal Code = 13 @@ -141,4 +139,46 @@ const ( // DataLoss indicates unrecoverable data loss or corruption. DataLoss Code = 15 + + // Unauthenticated indicates the request does not have valid + // authentication credentials for the operation. + Unauthenticated Code = 16 ) + +var strToCode = map[string]Code{ + `"OK"`: OK, + `"CANCELLED"`:/* [sic] */ Canceled, + `"UNKNOWN"`: Unknown, + `"INVALID_ARGUMENT"`: InvalidArgument, + `"DEADLINE_EXCEEDED"`: DeadlineExceeded, + `"NOT_FOUND"`: NotFound, + `"ALREADY_EXISTS"`: AlreadyExists, + `"PERMISSION_DENIED"`: PermissionDenied, + `"RESOURCE_EXHAUSTED"`: ResourceExhausted, + `"FAILED_PRECONDITION"`: FailedPrecondition, + `"ABORTED"`: Aborted, + `"OUT_OF_RANGE"`: OutOfRange, + `"UNIMPLEMENTED"`: Unimplemented, + `"INTERNAL"`: Internal, + `"UNAVAILABLE"`: Unavailable, + `"DATA_LOSS"`: DataLoss, + `"UNAUTHENTICATED"`: Unauthenticated, +} + +// UnmarshalJSON unmarshals b into the Code. +func (c *Code) UnmarshalJSON(b []byte) error { + // From json.Unmarshaler: By convention, to approximate the behavior of + // Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as + // a no-op. + if string(b) == "null" { + return nil + } + if c == nil { + return fmt.Errorf("nil receiver passed to UnmarshalJSON") + } + if jc, ok := strToCode[string(b)]; ok { + *c = jc + return nil + } + return fmt.Errorf("invalid code: %q", string(b)) +} diff --git a/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/google.golang.org/grpc/credentials/credentials.go index 946aa1f2b85..3351bf0ee5f 100644 --- a/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/google.golang.org/grpc/credentials/credentials.go @@ -34,10 +34,8 @@ import ( "golang.org/x/net/context" ) -var ( - // alpnProtoStr are the specified application level protocols for gRPC. - alpnProtoStr = []string{"h2"} -) +// alpnProtoStr are the specified application level protocols for gRPC. +var alpnProtoStr = []string{"h2"} // PerRPCCredentials defines the common interface for the credentials which need to // attach security information to every RPC (e.g., oauth2). @@ -45,8 +43,9 @@ type PerRPCCredentials interface { // GetRequestMetadata gets the current request metadata, refreshing // tokens if required. This should be called by the transport layer on // each request, and the data should be populated in headers or other - // context. uri is the URI of the entry point for the request. When - // supported by the underlying implementation, ctx can be used for + // context. If a status code is returned, it will be used as the status + // for the RPC. uri is the URI of the entry point for the request. + // When supported by the underlying implementation, ctx can be used for // timeout and cancellation. // TODO(zhaoq): Define the set of the qualified keys instead of leaving // it as an arbitrary string. @@ -74,11 +73,9 @@ type AuthInfo interface { AuthType() string } -var ( - // ErrConnDispatched indicates that rawConn has been dispatched out of gRPC - // and the caller should not close rawConn. - ErrConnDispatched = errors.New("credentials: rawConn is dispatched out of gRPC") -) +// ErrConnDispatched indicates that rawConn has been dispatched out of gRPC +// and the caller should not close rawConn. +var ErrConnDispatched = errors.New("credentials: rawConn is dispatched out of gRPC") // TransportCredentials defines the common interface for all the live gRPC wire // protocols and supported transport security protocols (e.g., TLS, SSL). @@ -135,15 +132,15 @@ func (c tlsCreds) Info() ProtocolInfo { } } -func (c *tlsCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) { +func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) { // use local cfg to avoid clobbering ServerName if using multiple endpoints cfg := cloneTLSConfig(c.config) if cfg.ServerName == "" { - colonPos := strings.LastIndex(addr, ":") + colonPos := strings.LastIndex(authority, ":") if colonPos == -1 { - colonPos = len(addr) + colonPos = len(authority) } - cfg.ServerName = addr[:colonPos] + cfg.ServerName = authority[:colonPos] } conn := tls.Client(rawConn, cfg) errChannel := make(chan error, 1) diff --git a/vendor/google.golang.org/grpc/encoding/encoding.go b/vendor/google.golang.org/grpc/encoding/encoding.go new file mode 100644 index 00000000000..8e26c194364 --- /dev/null +++ b/vendor/google.golang.org/grpc/encoding/encoding.go @@ -0,0 +1,118 @@ +/* + * + * Copyright 2017 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 encoding defines the interface for the compressor and codec, and +// functions to register and retrieve compressors and codecs. +// +// This package is EXPERIMENTAL. +package encoding + +import ( + "io" + "strings" +) + +// Identity specifies the optional encoding for uncompressed streams. +// It is intended for grpc internal use only. +const Identity = "identity" + +// Compressor is used for compressing and decompressing when sending or +// receiving messages. +type Compressor interface { + // Compress writes the data written to wc to w after compressing it. If an + // error occurs while initializing the compressor, that error is returned + // instead. + Compress(w io.Writer) (io.WriteCloser, error) + // Decompress reads data from r, decompresses it, and provides the + // uncompressed data via the returned io.Reader. If an error occurs while + // initializing the decompressor, that error is returned instead. + Decompress(r io.Reader) (io.Reader, error) + // Name is the name of the compression codec and is used to set the content + // coding header. The result must be static; the result cannot change + // between calls. + Name() string +} + +var registeredCompressor = make(map[string]Compressor) + +// RegisterCompressor registers the compressor with gRPC by its name. It can +// be activated when sending an RPC via grpc.UseCompressor(). It will be +// automatically accessed when receiving a message based on the content coding +// header. Servers also use it to send a response with the same encoding as +// the request. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple Compressors are +// registered with the same name, the one registered last will take effect. +func RegisterCompressor(c Compressor) { + registeredCompressor[c.Name()] = c +} + +// GetCompressor returns Compressor for the given compressor name. +func GetCompressor(name string) Compressor { + return registeredCompressor[name] +} + +// Codec defines the interface gRPC uses to encode and decode messages. Note +// that implementations of this interface must be thread safe; a Codec's +// methods can be called from concurrent goroutines. +type Codec interface { + // Marshal returns the wire format of v. + Marshal(v interface{}) ([]byte, error) + // Unmarshal parses the wire format into v. + Unmarshal(data []byte, v interface{}) error + // Name returns the name of the Codec implementation. The returned string + // will be used as part of content type in transmission. The result must be + // static; the result cannot change between calls. + Name() string +} + +var registeredCodecs = make(map[string]Codec, 0) + +// RegisterCodec registers the provided Codec for use with all gRPC clients and +// servers. +// +// The Codec will be stored and looked up by result of its Name() method, which +// should match the content-subtype of the encoding handled by the Codec. This +// is case-insensitive, and is stored and looked up as lowercase. If the +// result of calling Name() is an empty string, RegisterCodec will panic. See +// Content-Type on +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple Compressors are +// registered with the same name, the one registered last will take effect. +func RegisterCodec(codec Codec) { + if codec == nil { + panic("cannot register a nil Codec") + } + contentSubtype := strings.ToLower(codec.Name()) + if contentSubtype == "" { + panic("cannot register Codec with empty string result for String()") + } + registeredCodecs[contentSubtype] = codec +} + +// GetCodec gets a registered Codec by content-subtype, or nil if no Codec is +// registered for the content-subtype. +// +// The content-subtype is expected to be lowercase. +func GetCodec(contentSubtype string) Codec { + return registeredCodecs[contentSubtype] +} diff --git a/vendor/google.golang.org/grpc/encoding/proto/proto.go b/vendor/google.golang.org/grpc/encoding/proto/proto.go new file mode 100644 index 00000000000..66b97a6f692 --- /dev/null +++ b/vendor/google.golang.org/grpc/encoding/proto/proto.go @@ -0,0 +1,110 @@ +/* + * + * Copyright 2018 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 proto defines the protobuf codec. Importing this package will +// register the codec. +package proto + +import ( + "math" + "sync" + + "github.com/golang/protobuf/proto" + "google.golang.org/grpc/encoding" +) + +// Name is the name registered for the proto compressor. +const Name = "proto" + +func init() { + encoding.RegisterCodec(codec{}) +} + +// codec is a Codec implementation with protobuf. It is the default codec for gRPC. +type codec struct{} + +type cachedProtoBuffer struct { + lastMarshaledSize uint32 + proto.Buffer +} + +func capToMaxInt32(val int) uint32 { + if val > math.MaxInt32 { + return uint32(math.MaxInt32) + } + return uint32(val) +} + +func marshal(v interface{}, cb *cachedProtoBuffer) ([]byte, error) { + protoMsg := v.(proto.Message) + newSlice := make([]byte, 0, cb.lastMarshaledSize) + + cb.SetBuf(newSlice) + cb.Reset() + if err := cb.Marshal(protoMsg); err != nil { + return nil, err + } + out := cb.Bytes() + cb.lastMarshaledSize = capToMaxInt32(len(out)) + return out, nil +} + +func (codec) Marshal(v interface{}) ([]byte, error) { + if pm, ok := v.(proto.Marshaler); ok { + // object can marshal itself, no need for buffer + return pm.Marshal() + } + + cb := protoBufferPool.Get().(*cachedProtoBuffer) + out, err := marshal(v, cb) + + // put back buffer and lose the ref to the slice + cb.SetBuf(nil) + protoBufferPool.Put(cb) + return out, err +} + +func (codec) Unmarshal(data []byte, v interface{}) error { + protoMsg := v.(proto.Message) + protoMsg.Reset() + + if pu, ok := protoMsg.(proto.Unmarshaler); ok { + // object can unmarshal itself, no need for buffer + return pu.Unmarshal(data) + } + + cb := protoBufferPool.Get().(*cachedProtoBuffer) + cb.SetBuf(data) + err := cb.Unmarshal(protoMsg) + cb.SetBuf(nil) + protoBufferPool.Put(cb) + return err +} + +func (codec) Name() string { + return Name +} + +var protoBufferPool = &sync.Pool{ + New: func() interface{} { + return &cachedProtoBuffer{ + Buffer: proto.Buffer{}, + lastMarshaledSize: 16, + } + }, +} diff --git a/vendor/google.golang.org/grpc/go16.go b/vendor/google.golang.org/grpc/go16.go new file mode 100644 index 00000000000..535ee9356f3 --- /dev/null +++ b/vendor/google.golang.org/grpc/go16.go @@ -0,0 +1,70 @@ +// +build go1.6,!go1.7 + +/* + * + * Copyright 2016 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 grpc + +import ( + "fmt" + "io" + "net" + "net/http" + + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/grpc/transport" +) + +// dialContext connects to the address on the named network. +func dialContext(ctx context.Context, network, address string) (net.Conn, error) { + return (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address) +} + +func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error { + req.Cancel = ctx.Done() + if err := req.Write(conn); err != nil { + return fmt.Errorf("failed to write the HTTP request: %v", err) + } + return nil +} + +// toRPCErr converts an error into an error from the status package. +func toRPCErr(err error) error { + if err == nil || err == io.EOF { + return err + } + if _, ok := status.FromError(err); ok { + return err + } + switch e := err.(type) { + case transport.StreamError: + return status.Error(e.Code, e.Desc) + case transport.ConnectionError: + return status.Error(codes.Unavailable, e.Desc) + default: + switch err { + case context.DeadlineExceeded: + return status.Error(codes.DeadlineExceeded, err.Error()) + case context.Canceled: + return status.Error(codes.Canceled, err.Error()) + } + } + return status.Error(codes.Unknown, err.Error()) +} diff --git a/vendor/google.golang.org/grpc/go17.go b/vendor/google.golang.org/grpc/go17.go new file mode 100644 index 00000000000..ec676a93c39 --- /dev/null +++ b/vendor/google.golang.org/grpc/go17.go @@ -0,0 +1,71 @@ +// +build go1.7 + +/* + * + * Copyright 2016 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 grpc + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + + netctx "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/grpc/transport" +) + +// dialContext connects to the address on the named network. +func dialContext(ctx context.Context, network, address string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, address) +} + +func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error { + req = req.WithContext(ctx) + if err := req.Write(conn); err != nil { + return fmt.Errorf("failed to write the HTTP request: %v", err) + } + return nil +} + +// toRPCErr converts an error into an error from the status package. +func toRPCErr(err error) error { + if err == nil || err == io.EOF { + return err + } + if _, ok := status.FromError(err); ok { + return err + } + switch e := err.(type) { + case transport.StreamError: + return status.Error(e.Code, e.Desc) + case transport.ConnectionError: + return status.Error(codes.Unavailable, e.Desc) + default: + switch err { + case context.DeadlineExceeded, netctx.DeadlineExceeded: + return status.Error(codes.DeadlineExceeded, err.Error()) + case context.Canceled, netctx.Canceled: + return status.Error(codes.Canceled, err.Error()) + } + } + return status.Error(codes.Unknown, err.Error()) +} diff --git a/vendor/google.golang.org/grpc/grpclb.go b/vendor/google.golang.org/grpc/grpclb.go index db56ff36217..d14a5d4090f 100644 --- a/vendor/google.golang.org/grpc/grpclb.go +++ b/vendor/google.golang.org/grpc/grpclb.go @@ -19,21 +19,32 @@ package grpc import ( - "errors" - "fmt" - "math/rand" - "net" + "strconv" + "strings" "sync" "time" "golang.org/x/net/context" - "google.golang.org/grpc/codes" - lbmpb "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" + lbpb "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/naming" + "google.golang.org/grpc/resolver" ) +const ( + lbTokeyKey = "lb-token" + defaultFallbackTimeout = 10 * time.Second + grpclbName = "grpclb" +) + +func convertDuration(d *lbpb.Duration) time.Duration { + if d == nil { + return 0 + } + return time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond +} + // Client API for LoadBalancer service. // Mostly copied from generated pb.go file. // To avoid circular dependency. @@ -59,646 +70,273 @@ type balanceLoadClientStream struct { ClientStream } -func (x *balanceLoadClientStream) Send(m *lbmpb.LoadBalanceRequest) error { +func (x *balanceLoadClientStream) Send(m *lbpb.LoadBalanceRequest) error { return x.ClientStream.SendMsg(m) } -func (x *balanceLoadClientStream) Recv() (*lbmpb.LoadBalanceResponse, error) { - m := new(lbmpb.LoadBalanceResponse) +func (x *balanceLoadClientStream) Recv() (*lbpb.LoadBalanceResponse, error) { + m := new(lbpb.LoadBalanceResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } -// NewGRPCLBBalancer creates a grpclb load balancer. -func NewGRPCLBBalancer(r naming.Resolver) Balancer { - return &grpclbBalancer{ - r: r, - } +func init() { + balancer.Register(newLBBuilder()) } -type remoteBalancerInfo struct { - addr string - // the server name used for authentication with the remote LB server. - name string +// newLBBuilder creates a builder for grpclb. +func newLBBuilder() balancer.Builder { + return NewLBBuilderWithFallbackTimeout(defaultFallbackTimeout) } -// grpclbAddrInfo consists of the information of a backend server. -type grpclbAddrInfo struct { - addr Address - connected bool - // dropForRateLimiting indicates whether this particular request should be - // dropped by the client for rate limiting. - dropForRateLimiting bool - // dropForLoadBalancing indicates whether this particular request should be - // dropped by the client for load balancing. - dropForLoadBalancing bool +// NewLBBuilderWithFallbackTimeout creates a grpclb builder with the given +// fallbackTimeout. If no response is received from the remote balancer within +// fallbackTimeout, the backend addresses from the resolved address list will be +// used. +// +// Only call this function when a non-default fallback timeout is needed. +func NewLBBuilderWithFallbackTimeout(fallbackTimeout time.Duration) balancer.Builder { + return &lbBuilder{ + fallbackTimeout: fallbackTimeout, + } } -type grpclbBalancer struct { - r naming.Resolver - target string - mu sync.Mutex - seq int // a sequence number to make sure addrCh does not get stale addresses. - w naming.Watcher - addrCh chan []Address - rbs []remoteBalancerInfo - addrs []*grpclbAddrInfo - next int - waitCh chan struct{} - done bool - rand *rand.Rand - - clientStats lbmpb.ClientStats +type lbBuilder struct { + fallbackTimeout time.Duration } -func (b *grpclbBalancer) watchAddrUpdates(w naming.Watcher, ch chan []remoteBalancerInfo) error { - updates, err := w.Next() - if err != nil { - grpclog.Warningf("grpclb: failed to get next addr update from watcher: %v", err) - return err - } - b.mu.Lock() - defer b.mu.Unlock() - if b.done { - return ErrClientConnClosing - } - for _, update := range updates { - switch update.Op { - case naming.Add: - var exist bool - for _, v := range b.rbs { - // TODO: Is the same addr with different server name a different balancer? - if update.Addr == v.addr { - exist = true - break - } - } - if exist { - continue - } - md, ok := update.Metadata.(*naming.AddrMetadataGRPCLB) - if !ok { - // TODO: Revisit the handling here and may introduce some fallback mechanism. - grpclog.Errorf("The name resolution contains unexpected metadata %v", update.Metadata) - continue - } - switch md.AddrType { - case naming.Backend: - // TODO: Revisit the handling here and may introduce some fallback mechanism. - grpclog.Errorf("The name resolution does not give grpclb addresses") - continue - case naming.GRPCLB: - b.rbs = append(b.rbs, remoteBalancerInfo{ - addr: update.Addr, - name: md.ServerName, - }) - default: - grpclog.Errorf("Received unknow address type %d", md.AddrType) - continue - } - case naming.Delete: - for i, v := range b.rbs { - if update.Addr == v.addr { - copy(b.rbs[i:], b.rbs[i+1:]) - b.rbs = b.rbs[:len(b.rbs)-1] - break - } - } - default: - grpclog.Errorf("Unknown update.Op %v", update.Op) - } +func (b *lbBuilder) Name() string { + return grpclbName +} + +func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { + // This generates a manual resolver builder with a random scheme. This + // scheme will be used to dial to remote LB, so we can send filtered address + // updates to remote LB ClientConn using this manual resolver. + scheme := "grpclb_internal_" + strconv.FormatInt(time.Now().UnixNano(), 36) + r := &lbManualResolver{scheme: scheme, ccb: cc} + + var target string + targetSplitted := strings.Split(cc.Target(), ":///") + if len(targetSplitted) < 2 { + target = cc.Target() + } else { + target = targetSplitted[1] } - // TODO: Fall back to the basic round-robin load balancing if the resulting address is - // not a load balancer. - select { - case <-ch: - default: + + lb := &lbBalancer{ + cc: cc, + target: target, + opt: opt, + fallbackTimeout: b.fallbackTimeout, + doneCh: make(chan struct{}), + + manualResolver: r, + csEvltr: &connectivityStateEvaluator{}, + subConns: make(map[resolver.Address]balancer.SubConn), + scStates: make(map[balancer.SubConn]connectivity.State), + picker: &errPicker{err: balancer.ErrNoSubConnAvailable}, + clientStats: &rpcStats{}, } - ch <- b.rbs - return nil + + return lb } -func convertDuration(d *lbmpb.Duration) time.Duration { - if d == nil { - return 0 - } - return time.Duration(d.Seconds)*time.Second + time.Duration(d.Nanos)*time.Nanosecond +type lbBalancer struct { + cc balancer.ClientConn + target string + opt balancer.BuildOptions + fallbackTimeout time.Duration + doneCh chan struct{} + + // manualResolver is used in the remote LB ClientConn inside grpclb. When + // resolved address updates are received by grpclb, filtered updates will be + // send to remote LB ClientConn through this resolver. + manualResolver *lbManualResolver + // The ClientConn to talk to the remote balancer. + ccRemoteLB *ClientConn + + // Support client side load reporting. Each picker gets a reference to this, + // and will update its content. + clientStats *rpcStats + + mu sync.Mutex // guards everything following. + // The full server list including drops, used to check if the newly received + // serverList contains anything new. Each generate picker will also have + // reference to this list to do the first layer pick. + fullServerList []*lbpb.Server + // All backends addresses, with metadata set to nil. This list contains all + // backend addresses in the same order and with the same duplicates as in + // serverlist. When generating picker, a SubConn slice with the same order + // but with only READY SCs will be gerenated. + backendAddrs []resolver.Address + // Roundrobin functionalities. + csEvltr *connectivityStateEvaluator + state connectivity.State + subConns map[resolver.Address]balancer.SubConn // Used to new/remove SubConn. + scStates map[balancer.SubConn]connectivity.State // Used to filter READY SubConns. + picker balancer.Picker + // Support fallback to resolved backend addresses if there's no response + // from remote balancer within fallbackTimeout. + fallbackTimerExpired bool + serverListReceived bool + // resolvedBackendAddrs is resolvedAddrs minus remote balancers. It's set + // when resolved address updates are received, and read in the goroutine + // handling fallback. + resolvedBackendAddrs []resolver.Address } -func (b *grpclbBalancer) processServerList(l *lbmpb.ServerList, seq int) { - if l == nil { +// regeneratePicker takes a snapshot of the balancer, and generates a picker from +// it. The picker +// - always returns ErrTransientFailure if the balancer is in TransientFailure, +// - does two layer roundrobin pick otherwise. +// Caller must hold lb.mu. +func (lb *lbBalancer) regeneratePicker() { + if lb.state == connectivity.TransientFailure { + lb.picker = &errPicker{err: balancer.ErrTransientFailure} return } - servers := l.GetServers() - var ( - sl []*grpclbAddrInfo - addrs []Address - ) - for _, s := range servers { - md := metadata.Pairs("lb-token", s.LoadBalanceToken) - ip := net.IP(s.IpAddress) - ipStr := ip.String() - if ip.To4() == nil { - // Add square brackets to ipv6 addresses, otherwise net.Dial() and - // net.SplitHostPort() will return too many colons error. - ipStr = fmt.Sprintf("[%s]", ipStr) - } - addr := Address{ - Addr: fmt.Sprintf("%s:%d", ipStr, s.Port), - Metadata: &md, + var readySCs []balancer.SubConn + for _, a := range lb.backendAddrs { + if sc, ok := lb.subConns[a]; ok { + if st, ok := lb.scStates[sc]; ok && st == connectivity.Ready { + readySCs = append(readySCs, sc) + } } - sl = append(sl, &grpclbAddrInfo{ - addr: addr, - dropForRateLimiting: s.DropForRateLimiting, - dropForLoadBalancing: s.DropForLoadBalancing, - }) - addrs = append(addrs, addr) } - b.mu.Lock() - defer b.mu.Unlock() - if b.done || seq < b.seq { - return - } - if len(sl) > 0 { - // reset b.next to 0 when replacing the server list. - b.next = 0 - b.addrs = sl - b.addrCh <- addrs - } - return -} -func (b *grpclbBalancer) sendLoadReport(s *balanceLoadClientStream, interval time.Duration, done <-chan struct{}) { - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-ticker.C: - case <-done: - return - } - b.mu.Lock() - stats := b.clientStats - b.clientStats = lbmpb.ClientStats{} // Clear the stats. - b.mu.Unlock() - t := time.Now() - stats.Timestamp = &lbmpb.Timestamp{ - Seconds: t.Unix(), - Nanos: int32(t.Nanosecond()), - } - if err := s.Send(&lbmpb.LoadBalanceRequest{ - LoadBalanceRequestType: &lbmpb.LoadBalanceRequest_ClientStats{ - ClientStats: &stats, - }, - }); err != nil { - grpclog.Errorf("grpclb: failed to send load report: %v", err) + if len(lb.fullServerList) <= 0 { + if len(readySCs) <= 0 { + lb.picker = &errPicker{err: balancer.ErrNoSubConnAvailable} return } - } -} - -func (b *grpclbBalancer) callRemoteBalancer(lbc *loadBalancerClient, seq int) (retry bool) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - stream, err := lbc.BalanceLoad(ctx) - if err != nil { - grpclog.Errorf("grpclb: failed to perform RPC to the remote balancer %v", err) + lb.picker = &rrPicker{subConns: readySCs} return } - b.mu.Lock() - if b.done { - b.mu.Unlock() - return - } - b.mu.Unlock() - initReq := &lbmpb.LoadBalanceRequest{ - LoadBalanceRequestType: &lbmpb.LoadBalanceRequest_InitialRequest{ - InitialRequest: &lbmpb.InitialLoadBalanceRequest{ - Name: b.target, - }, - }, + lb.picker = &lbPicker{ + serverList: lb.fullServerList, + subConns: readySCs, + stats: lb.clientStats, } - if err := stream.Send(initReq); err != nil { - grpclog.Errorf("grpclb: failed to send init request: %v", err) - // TODO: backoff on retry? - return true - } - reply, err := stream.Recv() - if err != nil { - grpclog.Errorf("grpclb: failed to recv init response: %v", err) - // TODO: backoff on retry? - return true - } - initResp := reply.GetInitialResponse() - if initResp == nil { - grpclog.Errorf("grpclb: reply from remote balancer did not include initial response.") - return - } - // TODO: Support delegation. - if initResp.LoadBalancerDelegate != "" { - // delegation - grpclog.Errorf("TODO: Delegation is not supported yet.") - return - } - streamDone := make(chan struct{}) - defer close(streamDone) - b.mu.Lock() - b.clientStats = lbmpb.ClientStats{} // Clear client stats. - b.mu.Unlock() - if d := convertDuration(initResp.ClientStatsReportInterval); d > 0 { - go b.sendLoadReport(stream, d, streamDone) - } - // Retrieve the server list. - for { - reply, err := stream.Recv() - if err != nil { - grpclog.Errorf("grpclb: failed to recv server list: %v", err) - break - } - b.mu.Lock() - if b.done || seq < b.seq { - b.mu.Unlock() - return - } - b.seq++ // tick when receiving a new list of servers. - seq = b.seq - b.mu.Unlock() - if serverList := reply.GetServerList(); serverList != nil { - b.processServerList(serverList, seq) - } - } - return true + return } -func (b *grpclbBalancer) Start(target string, config BalancerConfig) error { - b.rand = rand.New(rand.NewSource(time.Now().Unix())) - // TODO: Fall back to the basic direct connection if there is no name resolver. - if b.r == nil { - return errors.New("there is no name resolver installed") +func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { + grpclog.Infof("lbBalancer: handle SubConn state change: %p, %v", sc, s) + lb.mu.Lock() + defer lb.mu.Unlock() + + oldS, ok := lb.scStates[sc] + if !ok { + grpclog.Infof("lbBalancer: got state changes for an unknown SubConn: %p, %v", sc, s) + return } - b.target = target - b.mu.Lock() - if b.done { - b.mu.Unlock() - return ErrClientConnClosing + lb.scStates[sc] = s + switch s { + case connectivity.Idle: + sc.Connect() + case connectivity.Shutdown: + // When an address was removed by resolver, b called RemoveSubConn but + // kept the sc's state in scStates. Remove state for this sc here. + delete(lb.scStates, sc) } - b.addrCh = make(chan []Address) - w, err := b.r.Resolve(target) - if err != nil { - b.mu.Unlock() - grpclog.Errorf("grpclb: failed to resolve address: %v, err: %v", target, err) - return err - } - b.w = w - b.mu.Unlock() - balancerAddrsCh := make(chan []remoteBalancerInfo, 1) - // Spawn a goroutine to monitor the name resolution of remote load balancer. - go func() { - for { - if err := b.watchAddrUpdates(w, balancerAddrsCh); err != nil { - grpclog.Warningf("grpclb: the naming watcher stops working due to %v.\n", err) - close(balancerAddrsCh) - return - } - } - }() - // Spawn a goroutine to talk to the remote load balancer. - go func() { - var ( - cc *ClientConn - // ccError is closed when there is an error in the current cc. - // A new rb should be picked from rbs and connected. - ccError chan struct{} - rb *remoteBalancerInfo - rbs []remoteBalancerInfo - rbIdx int - ) - - defer func() { - if ccError != nil { - select { - case <-ccError: - default: - close(ccError) - } - } - if cc != nil { - cc.Close() - } - }() - - for { - var ok bool - select { - case rbs, ok = <-balancerAddrsCh: - if !ok { - return - } - foundIdx := -1 - if rb != nil { - for i, trb := range rbs { - if trb == *rb { - foundIdx = i - break - } - } - } - if foundIdx >= 0 { - if foundIdx >= 1 { - // Move the address in use to the beginning of the list. - b.rbs[0], b.rbs[foundIdx] = b.rbs[foundIdx], b.rbs[0] - rbIdx = 0 - } - continue // If found, don't dial new cc. - } else if len(rbs) > 0 { - // Pick a random one from the list, instead of always using the first one. - if l := len(rbs); l > 1 && rb != nil { - tmpIdx := b.rand.Intn(l - 1) - b.rbs[0], b.rbs[tmpIdx] = b.rbs[tmpIdx], b.rbs[0] - } - rbIdx = 0 - rb = &rbs[0] - } else { - // foundIdx < 0 && len(rbs) <= 0. - rb = nil - } - case <-ccError: - ccError = nil - if rbIdx < len(rbs)-1 { - rbIdx++ - rb = &rbs[rbIdx] - } else { - rb = nil - } - } - - if rb == nil { - continue - } - if cc != nil { - cc.Close() - } - // Talk to the remote load balancer to get the server list. - var ( - err error - dopts []DialOption - ) - if creds := config.DialCreds; creds != nil { - if rb.name != "" { - if err := creds.OverrideServerName(rb.name); err != nil { - grpclog.Warningf("grpclb: failed to override the server name in the credentials: %v", err) - continue - } - } - dopts = append(dopts, WithTransportCredentials(creds)) - } else { - dopts = append(dopts, WithInsecure()) - } - if dialer := config.Dialer; dialer != nil { - // WithDialer takes a different type of function, so we instead use a special DialOption here. - dopts = append(dopts, func(o *dialOptions) { o.copts.Dialer = dialer }) - } - dopts = append(dopts, WithBlock()) - ccError = make(chan struct{}) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - cc, err = DialContext(ctx, rb.addr, dopts...) - cancel() - if err != nil { - grpclog.Warningf("grpclb: failed to setup a connection to the remote balancer %v: %v", rb.addr, err) - close(ccError) - continue - } - b.mu.Lock() - b.seq++ // tick when getting a new balancer address - seq := b.seq - b.next = 0 - b.mu.Unlock() - go func(cc *ClientConn, ccError chan struct{}) { - lbc := &loadBalancerClient{cc} - b.callRemoteBalancer(lbc, seq) - cc.Close() - select { - case <-ccError: - default: - close(ccError) - } - }(cc, ccError) - } - }() - return nil -} + oldAggrState := lb.state + lb.state = lb.csEvltr.recordTransition(oldS, s) -func (b *grpclbBalancer) down(addr Address, err error) { - b.mu.Lock() - defer b.mu.Unlock() - for _, a := range b.addrs { - if addr == a.addr { - a.connected = false - break - } + // Regenerate picker when one of the following happens: + // - this sc became ready from not-ready + // - this sc became not-ready from ready + // - the aggregated state of balancer became TransientFailure from non-TransientFailure + // - the aggregated state of balancer became non-TransientFailure from TransientFailure + if (oldS == connectivity.Ready) != (s == connectivity.Ready) || + (lb.state == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) { + lb.regeneratePicker() } + + lb.cc.UpdateBalancerState(lb.state, lb.picker) + return } -func (b *grpclbBalancer) Up(addr Address) func(error) { - b.mu.Lock() - defer b.mu.Unlock() - if b.done { - return nil - } - var cnt int - for _, a := range b.addrs { - if a.addr == addr { - if a.connected { - return nil - } - a.connected = true - } - if a.connected && !a.dropForRateLimiting && !a.dropForLoadBalancing { - cnt++ - } - } - // addr is the only one which is connected. Notify the Get() callers who are blocking. - if cnt == 1 && b.waitCh != nil { - close(b.waitCh) - b.waitCh = nil +// fallbackToBackendsAfter blocks for fallbackTimeout and falls back to use +// resolved backends (backends received from resolver, not from remote balancer) +// if no connection to remote balancers was successful. +func (lb *lbBalancer) fallbackToBackendsAfter(fallbackTimeout time.Duration) { + timer := time.NewTimer(fallbackTimeout) + defer timer.Stop() + select { + case <-timer.C: + case <-lb.doneCh: + return } - return func(err error) { - b.down(addr, err) + lb.mu.Lock() + if lb.serverListReceived { + lb.mu.Unlock() + return } + lb.fallbackTimerExpired = true + lb.refreshSubConns(lb.resolvedBackendAddrs) + lb.mu.Unlock() } -func (b *grpclbBalancer) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) { - var ch chan struct{} - b.mu.Lock() - if b.done { - b.mu.Unlock() - err = ErrClientConnClosing +// HandleResolvedAddrs sends the updated remoteLB addresses to remoteLB +// clientConn. The remoteLB clientConn will handle creating/removing remoteLB +// connections. +func (lb *lbBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) { + grpclog.Infof("lbBalancer: handleResolvedResult: %+v", addrs) + if len(addrs) <= 0 { return } - seq := b.seq - defer func() { - if err != nil { - return - } - put = func() { - s, ok := rpcInfoFromContext(ctx) - if !ok { - return - } - b.mu.Lock() - defer b.mu.Unlock() - if b.done || seq < b.seq { - return - } - b.clientStats.NumCallsFinished++ - if !s.bytesSent { - b.clientStats.NumCallsFinishedWithClientFailedToSend++ - } else if s.bytesReceived { - b.clientStats.NumCallsFinishedKnownReceived++ - } + var remoteBalancerAddrs, backendAddrs []resolver.Address + for _, a := range addrs { + if a.Type == resolver.GRPCLB { + remoteBalancerAddrs = append(remoteBalancerAddrs, a) + } else { + backendAddrs = append(backendAddrs, a) } - }() - - b.clientStats.NumCallsStarted++ - if len(b.addrs) > 0 { - if b.next >= len(b.addrs) { - b.next = 0 - } - next := b.next - for { - a := b.addrs[next] - next = (next + 1) % len(b.addrs) - if a.connected { - if !a.dropForRateLimiting && !a.dropForLoadBalancing { - addr = a.addr - b.next = next - b.mu.Unlock() - return - } - if !opts.BlockingWait { - b.next = next - if a.dropForLoadBalancing { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithDropForLoadBalancing++ - } else if a.dropForRateLimiting { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithDropForRateLimiting++ - } - b.mu.Unlock() - err = Errorf(codes.Unavailable, "%s drops requests", a.addr.Addr) - return - } - } - if next == b.next { - // Has iterated all the possible address but none is connected. - break - } - } - } - if !opts.BlockingWait { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithClientFailedToSend++ - b.mu.Unlock() - err = Errorf(codes.Unavailable, "there is no address available") - return } - // Wait on b.waitCh for non-failfast RPCs. - if b.waitCh == nil { - ch = make(chan struct{}) - b.waitCh = ch - } else { - ch = b.waitCh - } - b.mu.Unlock() - for { - select { - case <-ctx.Done(): - b.mu.Lock() - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithClientFailedToSend++ - b.mu.Unlock() - err = ctx.Err() - return - case <-ch: - b.mu.Lock() - if b.done { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithClientFailedToSend++ - b.mu.Unlock() - err = ErrClientConnClosing - return - } - if len(b.addrs) > 0 { - if b.next >= len(b.addrs) { - b.next = 0 - } - next := b.next - for { - a := b.addrs[next] - next = (next + 1) % len(b.addrs) - if a.connected { - if !a.dropForRateLimiting && !a.dropForLoadBalancing { - addr = a.addr - b.next = next - b.mu.Unlock() - return - } - if !opts.BlockingWait { - b.next = next - if a.dropForLoadBalancing { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithDropForLoadBalancing++ - } else if a.dropForRateLimiting { - b.clientStats.NumCallsFinished++ - b.clientStats.NumCallsFinishedWithDropForRateLimiting++ - } - b.mu.Unlock() - err = Errorf(codes.Unavailable, "drop requests for the addreess %s", a.addr.Addr) - return - } - } - if next == b.next { - // Has iterated all the possible address but none is connected. - break - } - } - } - // The newly added addr got removed by Down() again. - if b.waitCh == nil { - ch = make(chan struct{}) - b.waitCh = ch - } else { - ch = b.waitCh - } - b.mu.Unlock() + if lb.ccRemoteLB == nil { + if len(remoteBalancerAddrs) <= 0 { + grpclog.Errorf("grpclb: no remote balancer address is available, should never happen") + return } + // First time receiving resolved addresses, create a cc to remote + // balancers. + lb.dialRemoteLB(remoteBalancerAddrs[0].ServerName) + // Start the fallback goroutine. + go lb.fallbackToBackendsAfter(lb.fallbackTimeout) } -} -func (b *grpclbBalancer) Notify() <-chan []Address { - return b.addrCh + // cc to remote balancers uses lb.manualResolver. Send the updated remote + // balancer addresses to it through manualResolver. + lb.manualResolver.NewAddress(remoteBalancerAddrs) + + lb.mu.Lock() + lb.resolvedBackendAddrs = backendAddrs + // If serverListReceived is true, connection to remote balancer was + // successful and there's no need to do fallback anymore. + // If fallbackTimerExpired is false, fallback hasn't happened yet. + if !lb.serverListReceived && lb.fallbackTimerExpired { + // This means we received a new list of resolved backends, and we are + // still in fallback mode. Need to update the list of backends we are + // using to the new list of backends. + lb.refreshSubConns(lb.resolvedBackendAddrs) + } + lb.mu.Unlock() } -func (b *grpclbBalancer) Close() error { - b.mu.Lock() - defer b.mu.Unlock() - if b.done { - return errBalancerClosed - } - b.done = true - if b.waitCh != nil { - close(b.waitCh) - } - if b.addrCh != nil { - close(b.addrCh) +func (lb *lbBalancer) Close() { + select { + case <-lb.doneCh: + return + default: } - if b.w != nil { - b.w.Close() + close(lb.doneCh) + if lb.ccRemoteLB != nil { + lb.ccRemoteLB.Close() } - return nil } diff --git a/vendor/google.golang.org/grpc/grpclb_picker.go b/vendor/google.golang.org/grpc/grpclb_picker.go new file mode 100644 index 00000000000..872c7ccea0e --- /dev/null +++ b/vendor/google.golang.org/grpc/grpclb_picker.go @@ -0,0 +1,159 @@ +/* + * + * Copyright 2017 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 grpc + +import ( + "sync" + "sync/atomic" + + "golang.org/x/net/context" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/codes" + lbpb "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" + "google.golang.org/grpc/status" +) + +type rpcStats struct { + NumCallsStarted int64 + NumCallsFinished int64 + NumCallsFinishedWithDropForRateLimiting int64 + NumCallsFinishedWithDropForLoadBalancing int64 + NumCallsFinishedWithClientFailedToSend int64 + NumCallsFinishedKnownReceived int64 +} + +// toClientStats converts rpcStats to lbpb.ClientStats, and clears rpcStats. +func (s *rpcStats) toClientStats() *lbpb.ClientStats { + stats := &lbpb.ClientStats{ + NumCallsStarted: atomic.SwapInt64(&s.NumCallsStarted, 0), + NumCallsFinished: atomic.SwapInt64(&s.NumCallsFinished, 0), + NumCallsFinishedWithDropForRateLimiting: atomic.SwapInt64(&s.NumCallsFinishedWithDropForRateLimiting, 0), + NumCallsFinishedWithDropForLoadBalancing: atomic.SwapInt64(&s.NumCallsFinishedWithDropForLoadBalancing, 0), + NumCallsFinishedWithClientFailedToSend: atomic.SwapInt64(&s.NumCallsFinishedWithClientFailedToSend, 0), + NumCallsFinishedKnownReceived: atomic.SwapInt64(&s.NumCallsFinishedKnownReceived, 0), + } + return stats +} + +func (s *rpcStats) dropForRateLimiting() { + atomic.AddInt64(&s.NumCallsStarted, 1) + atomic.AddInt64(&s.NumCallsFinishedWithDropForRateLimiting, 1) + atomic.AddInt64(&s.NumCallsFinished, 1) +} + +func (s *rpcStats) dropForLoadBalancing() { + atomic.AddInt64(&s.NumCallsStarted, 1) + atomic.AddInt64(&s.NumCallsFinishedWithDropForLoadBalancing, 1) + atomic.AddInt64(&s.NumCallsFinished, 1) +} + +func (s *rpcStats) failedToSend() { + atomic.AddInt64(&s.NumCallsStarted, 1) + atomic.AddInt64(&s.NumCallsFinishedWithClientFailedToSend, 1) + atomic.AddInt64(&s.NumCallsFinished, 1) +} + +func (s *rpcStats) knownReceived() { + atomic.AddInt64(&s.NumCallsStarted, 1) + atomic.AddInt64(&s.NumCallsFinishedKnownReceived, 1) + atomic.AddInt64(&s.NumCallsFinished, 1) +} + +type errPicker struct { + // Pick always returns this err. + err error +} + +func (p *errPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + return nil, nil, p.err +} + +// rrPicker does roundrobin on subConns. It's typically used when there's no +// response from remote balancer, and grpclb falls back to the resolved +// backends. +// +// It guaranteed that len(subConns) > 0. +type rrPicker struct { + mu sync.Mutex + subConns []balancer.SubConn // The subConns that were READY when taking the snapshot. + subConnsNext int +} + +func (p *rrPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + p.mu.Lock() + defer p.mu.Unlock() + sc := p.subConns[p.subConnsNext] + p.subConnsNext = (p.subConnsNext + 1) % len(p.subConns) + return sc, nil, nil +} + +// lbPicker does two layers of picks: +// +// First layer: roundrobin on all servers in serverList, including drops and backends. +// - If it picks a drop, the RPC will fail as being dropped. +// - If it picks a backend, do a second layer pick to pick the real backend. +// +// Second layer: roundrobin on all READY backends. +// +// It's guaranteed that len(serverList) > 0. +type lbPicker struct { + mu sync.Mutex + serverList []*lbpb.Server + serverListNext int + subConns []balancer.SubConn // The subConns that were READY when taking the snapshot. + subConnsNext int + + stats *rpcStats +} + +func (p *lbPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + p.mu.Lock() + defer p.mu.Unlock() + + // Layer one roundrobin on serverList. + s := p.serverList[p.serverListNext] + p.serverListNext = (p.serverListNext + 1) % len(p.serverList) + + // If it's a drop, return an error and fail the RPC. + if s.DropForRateLimiting { + p.stats.dropForRateLimiting() + return nil, nil, status.Errorf(codes.Unavailable, "request dropped by grpclb") + } + if s.DropForLoadBalancing { + p.stats.dropForLoadBalancing() + return nil, nil, status.Errorf(codes.Unavailable, "request dropped by grpclb") + } + + // If not a drop but there's no ready subConns. + if len(p.subConns) <= 0 { + return nil, nil, balancer.ErrNoSubConnAvailable + } + + // Return the next ready subConn in the list, also collect rpc stats. + sc := p.subConns[p.subConnsNext] + p.subConnsNext = (p.subConnsNext + 1) % len(p.subConns) + done := func(info balancer.DoneInfo) { + if !info.BytesSent { + p.stats.failedToSend() + } else if info.BytesReceived { + p.stats.knownReceived() + } + } + return sc, done, nil +} diff --git a/vendor/google.golang.org/grpc/grpclb_remote_balancer.go b/vendor/google.golang.org/grpc/grpclb_remote_balancer.go new file mode 100644 index 00000000000..1b580df26dd --- /dev/null +++ b/vendor/google.golang.org/grpc/grpclb_remote_balancer.go @@ -0,0 +1,254 @@ +/* + * + * Copyright 2017 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 grpc + +import ( + "fmt" + "net" + "reflect" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" + lbpb "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/resolver" +) + +// processServerList updates balaner's internal state, create/remove SubConns +// and regenerates picker using the received serverList. +func (lb *lbBalancer) processServerList(l *lbpb.ServerList) { + grpclog.Infof("lbBalancer: processing server list: %+v", l) + lb.mu.Lock() + defer lb.mu.Unlock() + + // Set serverListReceived to true so fallback will not take effect if it has + // not hit timeout. + lb.serverListReceived = true + + // If the new server list == old server list, do nothing. + if reflect.DeepEqual(lb.fullServerList, l.Servers) { + grpclog.Infof("lbBalancer: new serverlist same as the previous one, ignoring") + return + } + lb.fullServerList = l.Servers + + var backendAddrs []resolver.Address + for _, s := range l.Servers { + if s.DropForLoadBalancing || s.DropForRateLimiting { + continue + } + + md := metadata.Pairs(lbTokeyKey, s.LoadBalanceToken) + ip := net.IP(s.IpAddress) + ipStr := ip.String() + if ip.To4() == nil { + // Add square brackets to ipv6 addresses, otherwise net.Dial() and + // net.SplitHostPort() will return too many colons error. + ipStr = fmt.Sprintf("[%s]", ipStr) + } + addr := resolver.Address{ + Addr: fmt.Sprintf("%s:%d", ipStr, s.Port), + Metadata: &md, + } + + backendAddrs = append(backendAddrs, addr) + } + + // Call refreshSubConns to create/remove SubConns. + backendsUpdated := lb.refreshSubConns(backendAddrs) + // If no backend was updated, no SubConn will be newed/removed. But since + // the full serverList was different, there might be updates in drops or + // pick weights(different number of duplicates). We need to update picker + // with the fulllist. + if !backendsUpdated { + lb.regeneratePicker() + lb.cc.UpdateBalancerState(lb.state, lb.picker) + } +} + +// refreshSubConns creates/removes SubConns with backendAddrs. It returns a bool +// indicating whether the backendAddrs are different from the cached +// backendAddrs (whether any SubConn was newed/removed). +// Caller must hold lb.mu. +func (lb *lbBalancer) refreshSubConns(backendAddrs []resolver.Address) bool { + lb.backendAddrs = nil + var backendsUpdated bool + // addrsSet is the set converted from backendAddrs, it's used to quick + // lookup for an address. + addrsSet := make(map[resolver.Address]struct{}) + // Create new SubConns. + for _, addr := range backendAddrs { + addrWithoutMD := addr + addrWithoutMD.Metadata = nil + addrsSet[addrWithoutMD] = struct{}{} + lb.backendAddrs = append(lb.backendAddrs, addrWithoutMD) + + if _, ok := lb.subConns[addrWithoutMD]; !ok { + backendsUpdated = true + + // Use addrWithMD to create the SubConn. + sc, err := lb.cc.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{}) + if err != nil { + grpclog.Warningf("roundrobinBalancer: failed to create new SubConn: %v", err) + continue + } + lb.subConns[addrWithoutMD] = sc // Use the addr without MD as key for the map. + lb.scStates[sc] = connectivity.Idle + sc.Connect() + } + } + + for a, sc := range lb.subConns { + // a was removed by resolver. + if _, ok := addrsSet[a]; !ok { + backendsUpdated = true + + lb.cc.RemoveSubConn(sc) + delete(lb.subConns, a) + // Keep the state of this sc in b.scStates until sc's state becomes Shutdown. + // The entry will be deleted in HandleSubConnStateChange. + } + } + + return backendsUpdated +} + +func (lb *lbBalancer) readServerList(s *balanceLoadClientStream) error { + for { + reply, err := s.Recv() + if err != nil { + return fmt.Errorf("grpclb: failed to recv server list: %v", err) + } + if serverList := reply.GetServerList(); serverList != nil { + lb.processServerList(serverList) + } + } +} + +func (lb *lbBalancer) sendLoadReport(s *balanceLoadClientStream, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + case <-s.Context().Done(): + return + } + stats := lb.clientStats.toClientStats() + t := time.Now() + stats.Timestamp = &lbpb.Timestamp{ + Seconds: t.Unix(), + Nanos: int32(t.Nanosecond()), + } + if err := s.Send(&lbpb.LoadBalanceRequest{ + LoadBalanceRequestType: &lbpb.LoadBalanceRequest_ClientStats{ + ClientStats: stats, + }, + }); err != nil { + return + } + } +} +func (lb *lbBalancer) callRemoteBalancer() error { + lbClient := &loadBalancerClient{cc: lb.ccRemoteLB} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + stream, err := lbClient.BalanceLoad(ctx, FailFast(false)) + if err != nil { + return fmt.Errorf("grpclb: failed to perform RPC to the remote balancer %v", err) + } + + // grpclb handshake on the stream. + initReq := &lbpb.LoadBalanceRequest{ + LoadBalanceRequestType: &lbpb.LoadBalanceRequest_InitialRequest{ + InitialRequest: &lbpb.InitialLoadBalanceRequest{ + Name: lb.target, + }, + }, + } + if err := stream.Send(initReq); err != nil { + return fmt.Errorf("grpclb: failed to send init request: %v", err) + } + reply, err := stream.Recv() + if err != nil { + return fmt.Errorf("grpclb: failed to recv init response: %v", err) + } + initResp := reply.GetInitialResponse() + if initResp == nil { + return fmt.Errorf("grpclb: reply from remote balancer did not include initial response") + } + if initResp.LoadBalancerDelegate != "" { + return fmt.Errorf("grpclb: Delegation is not supported") + } + + go func() { + if d := convertDuration(initResp.ClientStatsReportInterval); d > 0 { + lb.sendLoadReport(stream, d) + } + }() + return lb.readServerList(stream) +} + +func (lb *lbBalancer) watchRemoteBalancer() { + for { + err := lb.callRemoteBalancer() + select { + case <-lb.doneCh: + return + default: + if err != nil { + grpclog.Error(err) + } + } + + } +} + +func (lb *lbBalancer) dialRemoteLB(remoteLBName string) { + var dopts []DialOption + if creds := lb.opt.DialCreds; creds != nil { + if err := creds.OverrideServerName(remoteLBName); err == nil { + dopts = append(dopts, WithTransportCredentials(creds)) + } else { + grpclog.Warningf("grpclb: failed to override the server name in the credentials: %v, using Insecure", err) + dopts = append(dopts, WithInsecure()) + } + } else { + dopts = append(dopts, WithInsecure()) + } + if lb.opt.Dialer != nil { + // WithDialer takes a different type of function, so we instead use a + // special DialOption here. + dopts = append(dopts, withContextDialer(lb.opt.Dialer)) + } + // Explicitly set pickfirst as the balancer. + dopts = append(dopts, WithBalancerName(PickFirstBalancerName)) + dopts = append(dopts, withResolverBuilder(lb.manualResolver)) + // Dial using manualResolver.Scheme, which is a random scheme generated + // when init grpclb. The target name is not important. + cc, err := Dial("grpclb:///grpclb.server", dopts...) + if err != nil { + grpclog.Fatalf("failed to dial: %v", err) + } + lb.ccRemoteLB = cc + go lb.watchRemoteBalancer() +} diff --git a/vendor/google.golang.org/grpc/grpclb_util.go b/vendor/google.golang.org/grpc/grpclb_util.go new file mode 100644 index 00000000000..93ab2db3234 --- /dev/null +++ b/vendor/google.golang.org/grpc/grpclb_util.go @@ -0,0 +1,90 @@ +/* + * + * Copyright 2016 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 grpc + +import ( + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +// The parent ClientConn should re-resolve when grpclb loses connection to the +// remote balancer. When the ClientConn inside grpclb gets a TransientFailure, +// it calls lbManualResolver.ResolveNow(), which calls parent ClientConn's +// ResolveNow, and eventually results in re-resolve happening in parent +// ClientConn's resolver (DNS for example). +// +// parent +// ClientConn +// +-----------------------------------------------------------------+ +// | parent +---------------------------------+ | +// | DNS ClientConn | grpclb | | +// | resolver balancerWrapper | | | +// | + + | grpclb grpclb | | +// | | | | ManualResolver ClientConn | | +// | | | | + + | | +// | | | | | | Transient | | +// | | | | | | Failure | | +// | | | | | <--------- | | | +// | | | <--------------- | ResolveNow | | | +// | | <--------- | ResolveNow | | | | | +// | | ResolveNow | | | | | | +// | | | | | | | | +// | + + | + + | | +// | +---------------------------------+ | +// +-----------------------------------------------------------------+ + +// lbManualResolver is used by the ClientConn inside grpclb. It's a manual +// resolver with a special ResolveNow() function. +// +// When ResolveNow() is called, it calls ResolveNow() on the parent ClientConn, +// so when grpclb client lose contact with remote balancers, the parent +// ClientConn's resolver will re-resolve. +type lbManualResolver struct { + scheme string + ccr resolver.ClientConn + + ccb balancer.ClientConn +} + +func (r *lbManualResolver) Build(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOption) (resolver.Resolver, error) { + r.ccr = cc + return r, nil +} + +func (r *lbManualResolver) Scheme() string { + return r.scheme +} + +// ResolveNow calls resolveNow on the parent ClientConn. +func (r *lbManualResolver) ResolveNow(o resolver.ResolveNowOption) { + r.ccb.ResolveNow(o) +} + +// Close is a noop for Resolver. +func (*lbManualResolver) Close() {} + +// NewAddress calls cc.NewAddress. +func (r *lbManualResolver) NewAddress(addrs []resolver.Address) { + r.ccr.NewAddress(addrs) +} + +// NewServiceConfig calls cc.NewServiceConfig. +func (r *lbManualResolver) NewServiceConfig(sc string) { + r.ccr.NewServiceConfig(sc) +} diff --git a/vendor/google.golang.org/grpc/health/health.go b/vendor/google.golang.org/grpc/health/health.go index c6212f406f7..30a78667e60 100644 --- a/vendor/google.golang.org/grpc/health/health.go +++ b/vendor/google.golang.org/grpc/health/health.go @@ -26,9 +26,9 @@ import ( "sync" "golang.org/x/net/context" - "google.golang.org/grpc" "google.golang.org/grpc/codes" healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/status" ) // Server implements `service Health`. @@ -60,7 +60,7 @@ func (s *Server) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*h Status: status, }, nil } - return nil, grpc.Errorf(codes.NotFound, "unknown service") + return nil, status.Error(codes.NotFound, "unknown service") } // SetServingStatus is called when need to reset the serving status of a service diff --git a/vendor/google.golang.org/grpc/interceptor.go b/vendor/google.golang.org/grpc/interceptor.go index 06dc825b9fb..1f6ef678035 100644 --- a/vendor/google.golang.org/grpc/interceptor.go +++ b/vendor/google.golang.org/grpc/interceptor.go @@ -48,7 +48,9 @@ type UnaryServerInfo struct { } // UnaryHandler defines the handler invoked by UnaryServerInterceptor to complete the normal -// execution of a unary RPC. +// execution of a unary RPC. If a UnaryHandler returns an error, it should be produced by the +// status package, or else gRPC will use codes.Unknown as the status code and err.Error() as +// the status message of the RPC. type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error) // UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info diff --git a/vendor/google.golang.org/grpc/internal/internal.go b/vendor/google.golang.org/grpc/internal/internal.go index 07083832c3c..53f1775201c 100644 --- a/vendor/google.golang.org/grpc/internal/internal.go +++ b/vendor/google.golang.org/grpc/internal/internal.go @@ -19,13 +19,6 @@ // the godoc of the top-level grpc package. package internal -// TestingCloseConns closes all existing transports but keeps -// grpcServer.lis accepting new connections. -// -// The provided grpcServer must be of type *grpc.Server. It is untyped -// for circular dependency reasons. -var TestingCloseConns func(grpcServer interface{}) - // TestingUseHandlerImpl enables the http.Handler-based server implementation. // It must be called before Serve and requires TLS credentials. // diff --git a/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/google.golang.org/grpc/metadata/metadata.go index ccfea5d4530..e7c994673c0 100644 --- a/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/google.golang.org/grpc/metadata/metadata.go @@ -17,7 +17,8 @@ */ // Package metadata define the structure of the metadata supported by gRPC library. -// Please refer to https://grpc.io/docs/guides/wire.html for more information about custom-metadata. +// Please refer to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +// for more information about custom-metadata. package metadata // import "google.golang.org/grpc/metadata" import ( @@ -115,9 +116,26 @@ func NewIncomingContext(ctx context.Context, md MD) context.Context { return context.WithValue(ctx, mdIncomingKey{}, md) } -// NewOutgoingContext creates a new context with outgoing md attached. +// NewOutgoingContext creates a new context with outgoing md attached. If used +// in conjunction with AppendToOutgoingContext, NewOutgoingContext will +// overwrite any previously-appended metadata. func NewOutgoingContext(ctx context.Context, md MD) context.Context { - return context.WithValue(ctx, mdOutgoingKey{}, md) + return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md}) +} + +// AppendToOutgoingContext returns a new context with the provided kv merged +// with any existing metadata in the context. Please refer to the +// documentation of Pairs for a description of kv. +func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context { + if len(kv)%2 == 1 { + panic(fmt.Sprintf("metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d", len(kv))) + } + md, _ := ctx.Value(mdOutgoingKey{}).(rawMD) + added := make([][]string, len(md.added)+1) + copy(added, md.added) + added[len(added)-1] = make([]string, len(kv)) + copy(added[len(added)-1], kv) + return context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added}) } // FromIncomingContext returns the incoming metadata in ctx if it exists. The @@ -128,10 +146,39 @@ func FromIncomingContext(ctx context.Context) (md MD, ok bool) { return } +// FromOutgoingContextRaw returns the un-merged, intermediary contents +// of rawMD. Remember to perform strings.ToLower on the keys. The returned +// MD should not be modified. Writing to it may cause races. Modification +// should be made to copies of the returned MD. +// +// This is intended for gRPC-internal use ONLY. +func FromOutgoingContextRaw(ctx context.Context) (MD, [][]string, bool) { + raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) + if !ok { + return nil, nil, false + } + + return raw.md, raw.added, true +} + // FromOutgoingContext returns the outgoing metadata in ctx if it exists. The // returned MD should not be modified. Writing to it may cause races. -// Modification should be made to the copies of the returned MD. -func FromOutgoingContext(ctx context.Context) (md MD, ok bool) { - md, ok = ctx.Value(mdOutgoingKey{}).(MD) - return +// Modification should be made to copies of the returned MD. +func FromOutgoingContext(ctx context.Context) (MD, bool) { + raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) + if !ok { + return nil, false + } + + mds := make([]MD, 0, len(raw.added)+1) + mds = append(mds, raw.md) + for _, vv := range raw.added { + mds = append(mds, Pairs(vv...)) + } + return Join(mds...), ok +} + +type rawMD struct { + md MD + added [][]string } diff --git a/vendor/google.golang.org/grpc/naming/go17.go b/vendor/google.golang.org/grpc/naming/go17.go index 8bdf21e7998..57b65d7b889 100644 --- a/vendor/google.golang.org/grpc/naming/go17.go +++ b/vendor/google.golang.org/grpc/naming/go17.go @@ -1,4 +1,4 @@ -// +build go1.7, !go1.8 +// +build go1.6,!go1.8 /* * diff --git a/vendor/google.golang.org/grpc/picker_wrapper.go b/vendor/google.golang.org/grpc/picker_wrapper.go index 9085dbc9c98..4d0082593d1 100644 --- a/vendor/google.golang.org/grpc/picker_wrapper.go +++ b/vendor/google.golang.org/grpc/picker_wrapper.go @@ -36,6 +36,10 @@ type pickerWrapper struct { done bool blockingCh chan struct{} picker balancer.Picker + + // The latest connection happened. + connErrMu sync.Mutex + connErr error } func newPickerWrapper() *pickerWrapper { @@ -43,6 +47,19 @@ func newPickerWrapper() *pickerWrapper { return bp } +func (bp *pickerWrapper) updateConnectionError(err error) { + bp.connErrMu.Lock() + bp.connErr = err + bp.connErrMu.Unlock() +} + +func (bp *pickerWrapper) connectionError() error { + bp.connErrMu.Lock() + err := bp.connErr + bp.connErrMu.Unlock() + return err +} + // updatePicker is called by UpdateBalancerState. It unblocks all blocked pick. func (bp *pickerWrapper) updatePicker(p balancer.Picker) { bp.mu.Lock() @@ -97,7 +114,7 @@ func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer. p = bp.picker bp.mu.Unlock() - subConn, put, err := p.Pick(ctx, opts) + subConn, done, err := p.Pick(ctx, opts) if err != nil { switch err { @@ -107,7 +124,7 @@ func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer. if !failfast { continue } - return nil, nil, status.Errorf(codes.Unavailable, "%v", err) + return nil, nil, status.Errorf(codes.Unavailable, "%v, latest connection error: %v", err, bp.connectionError()) default: // err is some other error. return nil, nil, toRPCErr(err) @@ -120,7 +137,7 @@ func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer. continue } if t, ok := acw.getAddrConn().getReadyTransport(); ok { - return t, put, nil + return t, done, nil } grpclog.Infof("blockingPicker: the picked transport is not ready, loop back to repick") // If ok == false, ac.state is not READY. diff --git a/vendor/google.golang.org/grpc/pickfirst.go b/vendor/google.golang.org/grpc/pickfirst.go index 7f993ef5a38..bf659d49d2f 100644 --- a/vendor/google.golang.org/grpc/pickfirst.go +++ b/vendor/google.golang.org/grpc/pickfirst.go @@ -26,6 +26,9 @@ import ( "google.golang.org/grpc/resolver" ) +// PickFirstBalancerName is the name of the pick_first balancer. +const PickFirstBalancerName = "pick_first" + func newPickfirstBuilder() balancer.Builder { return &pickfirstBuilder{} } @@ -37,7 +40,7 @@ func (*pickfirstBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions } func (*pickfirstBuilder) Name() string { - return "pickfirst" + return PickFirstBalancerName } type pickfirstBalancer struct { @@ -57,14 +60,20 @@ func (b *pickfirstBalancer) HandleResolvedAddrs(addrs []resolver.Address, err er return } b.cc.UpdateBalancerState(connectivity.Idle, &picker{sc: b.sc}) + b.sc.Connect() } else { b.sc.UpdateAddresses(addrs) + b.sc.Connect() } } func (b *pickfirstBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { grpclog.Infof("pickfirstBalancer: HandleSubConnStateChange: %p, %v", sc, s) - if b.sc != sc || s == connectivity.Shutdown { + if b.sc != sc { + grpclog.Infof("pickfirstBalancer: ignored state change because sc is not recognized") + return + } + if s == connectivity.Shutdown { b.sc = nil return } @@ -93,3 +102,7 @@ func (p *picker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer. } return p.sc, nil, nil } + +func init() { + balancer.Register(newPickfirstBuilder()) +} diff --git a/vendor/google.golang.org/grpc/proxy.go b/vendor/google.golang.org/grpc/proxy.go index 3e17efec61b..2d40236e218 100644 --- a/vendor/google.golang.org/grpc/proxy.go +++ b/vendor/google.golang.org/grpc/proxy.go @@ -82,8 +82,7 @@ func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, addr string) (_ Header: map[string][]string{"User-Agent": {grpcUA}}, }) - req = req.WithContext(ctx) - if err := req.Write(conn); err != nil { + if err := sendHTTPRequest(ctx, req, conn); err != nil { return nil, fmt.Errorf("failed to write the HTTP request: %v", err) } diff --git a/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go b/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go new file mode 100644 index 00000000000..a543a709a62 --- /dev/null +++ b/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go @@ -0,0 +1,377 @@ +/* + * + * Copyright 2017 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 dns implements a dns resolver to be installed as the default resolver +// in grpc. +package dns + +import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "net" + "os" + "strconv" + "strings" + "sync" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/resolver" +) + +func init() { + resolver.Register(NewBuilder()) +} + +const ( + defaultPort = "443" + defaultFreq = time.Minute * 30 + golang = "GO" + // In DNS, service config is encoded in a TXT record via the mechanism + // described in RFC-1464 using the attribute name grpc_config. + txtAttribute = "grpc_config=" +) + +var errMissingAddr = errors.New("missing address") + +// NewBuilder creates a dnsBuilder which is used to factory DNS resolvers. +func NewBuilder() resolver.Builder { + return &dnsBuilder{freq: defaultFreq} +} + +type dnsBuilder struct { + // frequency of polling the DNS server. + freq time.Duration +} + +// Build creates and starts a DNS resolver that watches the name resolution of the target. +func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + host, port, err := parseTarget(target.Endpoint) + if err != nil { + return nil, err + } + + // IP address. + if net.ParseIP(host) != nil { + host, _ = formatIP(host) + addr := []resolver.Address{{Addr: host + ":" + port}} + i := &ipResolver{ + cc: cc, + ip: addr, + rn: make(chan struct{}, 1), + q: make(chan struct{}), + } + cc.NewAddress(addr) + go i.watcher() + return i, nil + } + + // DNS address (non-IP). + ctx, cancel := context.WithCancel(context.Background()) + d := &dnsResolver{ + freq: b.freq, + host: host, + port: port, + ctx: ctx, + cancel: cancel, + cc: cc, + t: time.NewTimer(0), + rn: make(chan struct{}, 1), + } + + d.wg.Add(1) + go d.watcher() + return d, nil +} + +// Scheme returns the naming scheme of this resolver builder, which is "dns". +func (b *dnsBuilder) Scheme() string { + return "dns" +} + +// ipResolver watches for the name resolution update for an IP address. +type ipResolver struct { + cc resolver.ClientConn + ip []resolver.Address + // rn channel is used by ResolveNow() to force an immediate resolution of the target. + rn chan struct{} + q chan struct{} +} + +// ResolveNow resend the address it stores, no resolution is needed. +func (i *ipResolver) ResolveNow(opt resolver.ResolveNowOption) { + select { + case i.rn <- struct{}{}: + default: + } +} + +// Close closes the ipResolver. +func (i *ipResolver) Close() { + close(i.q) +} + +func (i *ipResolver) watcher() { + for { + select { + case <-i.rn: + i.cc.NewAddress(i.ip) + case <-i.q: + return + } + } +} + +// dnsResolver watches for the name resolution update for a non-IP target. +type dnsResolver struct { + freq time.Duration + host string + port string + ctx context.Context + cancel context.CancelFunc + cc resolver.ClientConn + // rn channel is used by ResolveNow() to force an immediate resolution of the target. + rn chan struct{} + t *time.Timer + // wg is used to enforce Close() to return after the watcher() goroutine has finished. + // Otherwise, data race will be possible. [Race Example] in dns_resolver_test we + // replace the real lookup functions with mocked ones to facilitate testing. + // If Close() doesn't wait for watcher() goroutine finishes, race detector sometimes + // will warns lookup (READ the lookup function pointers) inside watcher() goroutine + // has data race with replaceNetFunc (WRITE the lookup function pointers). + wg sync.WaitGroup +} + +// ResolveNow invoke an immediate resolution of the target that this dnsResolver watches. +func (d *dnsResolver) ResolveNow(opt resolver.ResolveNowOption) { + select { + case d.rn <- struct{}{}: + default: + } +} + +// Close closes the dnsResolver. +func (d *dnsResolver) Close() { + d.cancel() + d.wg.Wait() + d.t.Stop() +} + +func (d *dnsResolver) watcher() { + defer d.wg.Done() + for { + select { + case <-d.ctx.Done(): + return + case <-d.t.C: + case <-d.rn: + } + result, sc := d.lookup() + // Next lookup should happen after an interval defined by d.freq. + d.t.Reset(d.freq) + d.cc.NewServiceConfig(string(sc)) + d.cc.NewAddress(result) + } +} + +func (d *dnsResolver) lookupSRV() []resolver.Address { + var newAddrs []resolver.Address + _, srvs, err := lookupSRV(d.ctx, "grpclb", "tcp", d.host) + if err != nil { + grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err) + return nil + } + for _, s := range srvs { + lbAddrs, err := lookupHost(d.ctx, s.Target) + if err != nil { + grpclog.Warningf("grpc: failed load banlacer address dns lookup due to %v.\n", err) + continue + } + for _, a := range lbAddrs { + a, ok := formatIP(a) + if !ok { + grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err) + continue + } + addr := a + ":" + strconv.Itoa(int(s.Port)) + newAddrs = append(newAddrs, resolver.Address{Addr: addr, Type: resolver.GRPCLB, ServerName: s.Target}) + } + } + return newAddrs +} + +func (d *dnsResolver) lookupTXT() string { + ss, err := lookupTXT(d.ctx, d.host) + if err != nil { + grpclog.Warningf("grpc: failed dns TXT record lookup due to %v.\n", err) + return "" + } + var res string + for _, s := range ss { + res += s + } + + // TXT record must have "grpc_config=" attribute in order to be used as service config. + if !strings.HasPrefix(res, txtAttribute) { + grpclog.Warningf("grpc: TXT record %v missing %v attribute", res, txtAttribute) + return "" + } + return strings.TrimPrefix(res, txtAttribute) +} + +func (d *dnsResolver) lookupHost() []resolver.Address { + var newAddrs []resolver.Address + addrs, err := lookupHost(d.ctx, d.host) + if err != nil { + grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err) + return nil + } + for _, a := range addrs { + a, ok := formatIP(a) + if !ok { + grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err) + continue + } + addr := a + ":" + d.port + newAddrs = append(newAddrs, resolver.Address{Addr: addr}) + } + return newAddrs +} + +func (d *dnsResolver) lookup() ([]resolver.Address, string) { + var newAddrs []resolver.Address + newAddrs = d.lookupSRV() + // Support fallback to non-balancer address. + newAddrs = append(newAddrs, d.lookupHost()...) + sc := d.lookupTXT() + return newAddrs, canaryingSC(sc) +} + +// formatIP returns ok = false if addr is not a valid textual representation of an IP address. +// If addr is an IPv4 address, return the addr and ok = true. +// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true. +func formatIP(addr string) (addrIP string, ok bool) { + ip := net.ParseIP(addr) + if ip == nil { + return "", false + } + if ip.To4() != nil { + return addr, true + } + return "[" + addr + "]", true +} + +// parseTarget takes the user input target string, returns formatted host and port info. +// If target doesn't specify a port, set the port to be the defaultPort. +// If target is in IPv6 format and host-name is enclosed in sqarue brackets, brackets +// are strippd when setting the host. +// examples: +// target: "www.google.com" returns host: "www.google.com", port: "443" +// target: "ipv4-host:80" returns host: "ipv4-host", port: "80" +// target: "[ipv6-host]" returns host: "ipv6-host", port: "443" +// target: ":80" returns host: "localhost", port: "80" +// target: ":" returns host: "localhost", port: "443" +func parseTarget(target string) (host, port string, err error) { + if target == "" { + return "", "", errMissingAddr + } + if ip := net.ParseIP(target); ip != nil { + // target is an IPv4 or IPv6(without brackets) address + return target, defaultPort, nil + } + if host, port, err = net.SplitHostPort(target); err == nil { + // target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port + if host == "" { + // Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed. + host = "localhost" + } + if port == "" { + // If the port field is empty(target ends with colon), e.g. "[::1]:", defaultPort is used. + port = defaultPort + } + return host, port, nil + } + if host, port, err = net.SplitHostPort(target + ":" + defaultPort); err == nil { + // target doesn't have port + return host, port, nil + } + return "", "", fmt.Errorf("invalid target address %v, error info: %v", target, err) +} + +type rawChoice struct { + ClientLanguage *[]string `json:"clientLanguage,omitempty"` + Percentage *int `json:"percentage,omitempty"` + ClientHostName *[]string `json:"clientHostName,omitempty"` + ServiceConfig *json.RawMessage `json:"serviceConfig,omitempty"` +} + +func containsString(a *[]string, b string) bool { + if a == nil { + return true + } + for _, c := range *a { + if c == b { + return true + } + } + return false +} + +func chosenByPercentage(a *int) bool { + if a == nil { + return true + } + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + if r.Intn(100)+1 > *a { + return false + } + return true +} + +func canaryingSC(js string) string { + if js == "" { + return "" + } + var rcs []rawChoice + err := json.Unmarshal([]byte(js), &rcs) + if err != nil { + grpclog.Warningf("grpc: failed to parse service config json string due to %v.\n", err) + return "" + } + cliHostname, err := os.Hostname() + if err != nil { + grpclog.Warningf("grpc: failed to get client hostname due to %v.\n", err) + return "" + } + var sc string + for _, c := range rcs { + if !containsString(c.ClientLanguage, golang) || + !chosenByPercentage(c.Percentage) || + !containsString(c.ClientHostName, cliHostname) || + c.ServiceConfig == nil { + continue + } + sc = string(*c.ServiceConfig) + break + } + return sc +} diff --git a/vendor/google.golang.org/grpc/resolver/dns/go17.go b/vendor/google.golang.org/grpc/resolver/dns/go17.go new file mode 100644 index 00000000000..b466bc8f6d4 --- /dev/null +++ b/vendor/google.golang.org/grpc/resolver/dns/go17.go @@ -0,0 +1,35 @@ +// +build go1.6, !go1.8 + +/* + * + * Copyright 2017 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 dns + +import ( + "net" + + "golang.org/x/net/context" +) + +var ( + lookupHost = func(ctx context.Context, host string) ([]string, error) { return net.LookupHost(host) } + lookupSRV = func(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) { + return net.LookupSRV(service, proto, name) + } + lookupTXT = func(ctx context.Context, name string) ([]string, error) { return net.LookupTXT(name) } +) diff --git a/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/doc.go b/vendor/google.golang.org/grpc/resolver/dns/go18.go similarity index 76% rename from vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/doc.go rename to vendor/google.golang.org/grpc/resolver/dns/go18.go index aba962840c8..fa34f14cad4 100644 --- a/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/doc.go +++ b/vendor/google.golang.org/grpc/resolver/dns/go18.go @@ -1,3 +1,5 @@ +// +build go1.8 + /* * * Copyright 2017 gRPC authors. @@ -16,6 +18,12 @@ * */ -// Package grpc_lb_v1 is the parent package of all gRPC loadbalancer -// message and service protobuf definitions. -package grpc_lb_v1 +package dns + +import "net" + +var ( + lookupHost = net.DefaultResolver.LookupHost + lookupSRV = net.DefaultResolver.LookupSRV + lookupTXT = net.DefaultResolver.LookupTXT +) diff --git a/vendor/google.golang.org/grpc/resolver/passthrough/passthrough.go b/vendor/google.golang.org/grpc/resolver/passthrough/passthrough.go new file mode 100644 index 00000000000..b76010d74d1 --- /dev/null +++ b/vendor/google.golang.org/grpc/resolver/passthrough/passthrough.go @@ -0,0 +1,57 @@ +/* + * + * Copyright 2017 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 passthrough implements a pass-through resolver. It sends the target +// name without scheme back to gRPC as resolved address. +package passthrough + +import "google.golang.org/grpc/resolver" + +const scheme = "passthrough" + +type passthroughBuilder struct{} + +func (*passthroughBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + r := &passthroughResolver{ + target: target, + cc: cc, + } + r.start() + return r, nil +} + +func (*passthroughBuilder) Scheme() string { + return scheme +} + +type passthroughResolver struct { + target resolver.Target + cc resolver.ClientConn +} + +func (r *passthroughResolver) start() { + r.cc.NewAddress([]resolver.Address{{Addr: r.target.Endpoint}}) +} + +func (*passthroughResolver) ResolveNow(o resolver.ResolveNowOption) {} + +func (*passthroughResolver) Close() {} + +func init() { + resolver.Register(&passthroughBuilder{}) +} diff --git a/vendor/google.golang.org/grpc/resolver/resolver.go b/vendor/google.golang.org/grpc/resolver/resolver.go index 49307e8fe9e..775ee4d0d27 100644 --- a/vendor/google.golang.org/grpc/resolver/resolver.go +++ b/vendor/google.golang.org/grpc/resolver/resolver.go @@ -24,7 +24,7 @@ var ( // m is a map from scheme to resolver builder. m = make(map[string]Builder) // defaultScheme is the default scheme to use. - defaultScheme string + defaultScheme = "passthrough" ) // TODO(bar) install dns resolver in init(){}. @@ -36,30 +36,26 @@ func Register(b Builder) { } // Get returns the resolver builder registered with the given scheme. -// If no builder is register with the scheme, the default scheme will -// be used. -// If the default scheme is not modified, "dns" will be the default -// scheme, and the preinstalled dns resolver will be used. -// If the default scheme is modified, and a resolver is registered with -// the scheme, that resolver will be returned. -// If the default scheme is modified, and no resolver is registered with -// the scheme, nil will be returned. +// +// If no builder is register with the scheme, nil will be returned. func Get(scheme string) Builder { if b, ok := m[scheme]; ok { return b } - if b, ok := m[defaultScheme]; ok { - return b - } return nil } // SetDefaultScheme sets the default scheme that will be used. -// The default default scheme is "dns". +// The default default scheme is "passthrough". func SetDefaultScheme(scheme string) { defaultScheme = scheme } +// GetDefaultScheme gets the default scheme that will be used. +func GetDefaultScheme() string { + return defaultScheme +} + // AddressType indicates the address type returned by name resolution. type AddressType uint8 @@ -78,7 +74,9 @@ type Address struct { // Type is the type of this address. Type AddressType // ServerName is the name of this address. - // It's the name of the grpc load balancer, which will be used for authentication. + // + // e.g. if Type is GRPCLB, ServerName should be the name of the remote load + // balancer, not the name of the backend. ServerName string // Metadata is the information associated with Addr, which may be used // to make load balancing decision. @@ -92,6 +90,11 @@ type BuildOption struct { // ClientConn contains the callbacks for resolver to notify any updates // to the gRPC ClientConn. +// +// This interface is to be implemented by gRPC. Users should not need a +// brand new implementation of this interface. For the situations like +// testing, the new implementation should embed this interface. This allows +// gRPC to add new methods to this interface. type ClientConn interface { // NewAddress is called by resolver to notify ClientConn a new list // of resolved addresses. @@ -128,8 +131,10 @@ type ResolveNowOption struct{} // Resolver watches for the updates on the specified target. // Updates include address updates and service config updates. type Resolver interface { - // ResolveNow will be called by gRPC to try to resolve the target name again. - // It's just a hint, resolver can ignore this if it's not necessary. + // ResolveNow will be called by gRPC to try to resolve the target name + // again. It's just a hint, resolver can ignore this if it's not necessary. + // + // It could be called multiple times concurrently. ResolveNow(ResolveNowOption) // Close closes the resolver. Close() diff --git a/vendor/google.golang.org/grpc/resolver_conn_wrapper.go b/vendor/google.golang.org/grpc/resolver_conn_wrapper.go index 7d53964d094..75b8ce1eb6c 100644 --- a/vendor/google.golang.org/grpc/resolver_conn_wrapper.go +++ b/vendor/google.golang.org/grpc/resolver_conn_wrapper.go @@ -19,6 +19,7 @@ package grpc import ( + "fmt" "strings" "google.golang.org/grpc/grpclog" @@ -36,20 +37,30 @@ type ccResolverWrapper struct { } // split2 returns the values from strings.SplitN(s, sep, 2). -// If sep is not found, it returns "", s instead. -func split2(s, sep string) (string, string) { +// If sep is not found, it returns ("", s, false) instead. +func split2(s, sep string) (string, string, bool) { spl := strings.SplitN(s, sep, 2) if len(spl) < 2 { - return "", s + return "", "", false } - return spl[0], spl[1] + return spl[0], spl[1], true } // parseTarget splits target into a struct containing scheme, authority and // endpoint. +// +// If target is not a valid scheme://authority/endpoint, it returns {Endpoint: +// target}. func parseTarget(target string) (ret resolver.Target) { - ret.Scheme, ret.Endpoint = split2(target, "://") - ret.Authority, ret.Endpoint = split2(ret.Endpoint, "/") + var ok bool + ret.Scheme, ret.Endpoint, ok = split2(target, "://") + if !ok { + return resolver.Target{Endpoint: target} + } + ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/") + if !ok { + return resolver.Target{Endpoint: target} + } return ret } @@ -57,18 +68,12 @@ func parseTarget(target string) (ret resolver.Target) { // builder for this scheme. It then builds the resolver and starts the // monitoring goroutine for it. // -// This function could return nil, nil, in tests for old behaviors. -// TODO(bar) never return nil, nil when DNS becomes the default resolver. +// If withResolverBuilder dial option is set, the specified resolver will be +// used instead. func newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error) { - target := parseTarget(cc.target) - grpclog.Infof("dialing to target with scheme: %q", target.Scheme) - - rb := resolver.Get(target.Scheme) + rb := cc.dopts.resolverBuilder if rb == nil { - // TODO(bar) return error when DNS becomes the default (implemented and - // registered by DNS package). - grpclog.Infof("could not get resolver for scheme: %q", target.Scheme) - return nil, nil + return nil, fmt.Errorf("could not get resolver for scheme: %q", cc.parsedTarget.Scheme) } ccr := &ccResolverWrapper{ @@ -79,14 +84,17 @@ func newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error) { } var err error - ccr.resolver, err = rb.Build(target, ccr, resolver.BuildOption{}) + ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, resolver.BuildOption{}) if err != nil { return nil, err } - go ccr.watcher() return ccr, nil } +func (ccr *ccResolverWrapper) start() { + go ccr.watcher() +} + // watcher processes address updates and service config updates sequencially. // Otherwise, we need to resolve possible races between address and service // config (e.g. they specify different balancer types). @@ -100,20 +108,31 @@ func (ccr *ccResolverWrapper) watcher() { select { case addrs := <-ccr.addrCh: - grpclog.Infof("ccResolverWrapper: sending new addresses to balancer wrapper: %v", addrs) - // TODO(bar switching) this should never be nil. Pickfirst should be default. - if ccr.cc.balancerWrapper != nil { - // TODO(bar switching) create balancer if it's nil? - ccr.cc.balancerWrapper.handleResolvedAddrs(addrs, nil) + select { + case <-ccr.done: + return + default: } + grpclog.Infof("ccResolverWrapper: sending new addresses to cc: %v", addrs) + ccr.cc.handleResolvedAddrs(addrs, nil) case sc := <-ccr.scCh: + select { + case <-ccr.done: + return + default: + } grpclog.Infof("ccResolverWrapper: got new service config: %v", sc) + ccr.cc.handleServiceConfig(sc) case <-ccr.done: return } } } +func (ccr *ccResolverWrapper) resolveNow(o resolver.ResolveNowOption) { + ccr.resolver.ResolveNow(o) +} + func (ccr *ccResolverWrapper) close() { ccr.resolver.Close() close(ccr.done) diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 188a75fff94..5ef9cb2f7d9 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -21,18 +21,20 @@ package grpc import ( "bytes" "compress/gzip" - stdctx "context" "encoding/binary" + "fmt" "io" "io/ioutil" "math" - "os" + "strings" "sync" "time" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/encoding/proto" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" @@ -54,13 +56,29 @@ type gzipCompressor struct { // NewGZIPCompressor creates a Compressor based on GZIP. func NewGZIPCompressor() Compressor { + c, _ := NewGZIPCompressorWithLevel(gzip.DefaultCompression) + return c +} + +// NewGZIPCompressorWithLevel is like NewGZIPCompressor but specifies the gzip compression level instead +// of assuming DefaultCompression. +// +// The error returned will be nil if the level is valid. +func NewGZIPCompressorWithLevel(level int) (Compressor, error) { + if level < gzip.DefaultCompression || level > gzip.BestCompression { + return nil, fmt.Errorf("grpc: invalid compression level: %d", level) + } return &gzipCompressor{ pool: sync.Pool{ New: func() interface{} { - return gzip.NewWriter(ioutil.Discard) + w, err := gzip.NewWriterLevel(ioutil.Discard, level) + if err != nil { + panic(err) + } + return w }, }, - } + }, nil } func (c *gzipCompressor) Do(w io.Writer, p []byte) error { @@ -124,14 +142,15 @@ func (d *gzipDecompressor) Type() string { // callInfo contains all related configuration and information about an RPC. type callInfo struct { + compressorType string failFast bool - headerMD metadata.MD - trailerMD metadata.MD - peer *peer.Peer + stream *clientStream traceInfo traceInfo // in trace.go maxReceiveMessageSize *int maxSendMessageSize *int creds credentials.PerRPCCredentials + contentSubtype string + codec baseCodec } func defaultCallInfo() *callInfo { @@ -158,80 +177,232 @@ type EmptyCallOption struct{} func (EmptyCallOption) before(*callInfo) error { return nil } func (EmptyCallOption) after(*callInfo) {} -type beforeCall func(c *callInfo) error - -func (o beforeCall) before(c *callInfo) error { return o(c) } -func (o beforeCall) after(c *callInfo) {} - -type afterCall func(c *callInfo) - -func (o afterCall) before(c *callInfo) error { return nil } -func (o afterCall) after(c *callInfo) { o(c) } - // Header returns a CallOptions that retrieves the header metadata // for a unary RPC. func Header(md *metadata.MD) CallOption { - return afterCall(func(c *callInfo) { - *md = c.headerMD - }) + return HeaderCallOption{HeaderAddr: md} +} + +// HeaderCallOption is a CallOption for collecting response header metadata. +// The metadata field will be populated *after* the RPC completes. +// This is an EXPERIMENTAL API. +type HeaderCallOption struct { + HeaderAddr *metadata.MD +} + +func (o HeaderCallOption) before(c *callInfo) error { return nil } +func (o HeaderCallOption) after(c *callInfo) { + if c.stream != nil { + *o.HeaderAddr, _ = c.stream.Header() + } } // Trailer returns a CallOptions that retrieves the trailer metadata // for a unary RPC. func Trailer(md *metadata.MD) CallOption { - return afterCall(func(c *callInfo) { - *md = c.trailerMD - }) + return TrailerCallOption{TrailerAddr: md} +} + +// TrailerCallOption is a CallOption for collecting response trailer metadata. +// The metadata field will be populated *after* the RPC completes. +// This is an EXPERIMENTAL API. +type TrailerCallOption struct { + TrailerAddr *metadata.MD +} + +func (o TrailerCallOption) before(c *callInfo) error { return nil } +func (o TrailerCallOption) after(c *callInfo) { + if c.stream != nil { + *o.TrailerAddr = c.stream.Trailer() + } } // Peer returns a CallOption that retrieves peer information for a // unary RPC. -func Peer(peer *peer.Peer) CallOption { - return afterCall(func(c *callInfo) { - if c.peer != nil { - *peer = *c.peer +func Peer(p *peer.Peer) CallOption { + return PeerCallOption{PeerAddr: p} +} + +// PeerCallOption is a CallOption for collecting the identity of the remote +// peer. The peer field will be populated *after* the RPC completes. +// This is an EXPERIMENTAL API. +type PeerCallOption struct { + PeerAddr *peer.Peer +} + +func (o PeerCallOption) before(c *callInfo) error { return nil } +func (o PeerCallOption) after(c *callInfo) { + if c.stream != nil { + if x, ok := peer.FromContext(c.stream.Context()); ok { + *o.PeerAddr = *x } - }) + } } // FailFast configures the action to take when an RPC is attempted on broken -// connections or unreachable servers. If failfast is true, the RPC will fail +// connections or unreachable servers. If failFast is true, the RPC will fail // immediately. Otherwise, the RPC client will block the call until a -// connection is available (or the call is canceled or times out) and will retry -// the call if it fails due to a transient error. Please refer to +// connection is available (or the call is canceled or times out) and will +// retry the call if it fails due to a transient error. gRPC will not retry if +// data was written to the wire unless the server indicates it did not process +// the data. Please refer to // https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md. -// Note: failFast is default to true. +// +// By default, RPCs are "Fail Fast". func FailFast(failFast bool) CallOption { - return beforeCall(func(c *callInfo) error { - c.failFast = failFast - return nil - }) + return FailFastCallOption{FailFast: failFast} +} + +// FailFastCallOption is a CallOption for indicating whether an RPC should fail +// fast or not. +// This is an EXPERIMENTAL API. +type FailFastCallOption struct { + FailFast bool +} + +func (o FailFastCallOption) before(c *callInfo) error { + c.failFast = o.FailFast + return nil } +func (o FailFastCallOption) after(c *callInfo) { return } // MaxCallRecvMsgSize returns a CallOption which sets the maximum message size the client can receive. func MaxCallRecvMsgSize(s int) CallOption { - return beforeCall(func(o *callInfo) error { - o.maxReceiveMessageSize = &s - return nil - }) + return MaxRecvMsgSizeCallOption{MaxRecvMsgSize: s} +} + +// MaxRecvMsgSizeCallOption is a CallOption that indicates the maximum message +// size the client can receive. +// This is an EXPERIMENTAL API. +type MaxRecvMsgSizeCallOption struct { + MaxRecvMsgSize int } +func (o MaxRecvMsgSizeCallOption) before(c *callInfo) error { + c.maxReceiveMessageSize = &o.MaxRecvMsgSize + return nil +} +func (o MaxRecvMsgSizeCallOption) after(c *callInfo) { return } + // MaxCallSendMsgSize returns a CallOption which sets the maximum message size the client can send. func MaxCallSendMsgSize(s int) CallOption { - return beforeCall(func(o *callInfo) error { - o.maxSendMessageSize = &s - return nil - }) + return MaxSendMsgSizeCallOption{MaxSendMsgSize: s} } +// MaxSendMsgSizeCallOption is a CallOption that indicates the maximum message +// size the client can send. +// This is an EXPERIMENTAL API. +type MaxSendMsgSizeCallOption struct { + MaxSendMsgSize int +} + +func (o MaxSendMsgSizeCallOption) before(c *callInfo) error { + c.maxSendMessageSize = &o.MaxSendMsgSize + return nil +} +func (o MaxSendMsgSizeCallOption) after(c *callInfo) { return } + // PerRPCCredentials returns a CallOption that sets credentials.PerRPCCredentials // for a call. func PerRPCCredentials(creds credentials.PerRPCCredentials) CallOption { - return beforeCall(func(c *callInfo) error { - c.creds = creds - return nil - }) + return PerRPCCredsCallOption{Creds: creds} +} + +// PerRPCCredsCallOption is a CallOption that indicates the per-RPC +// credentials to use for the call. +// This is an EXPERIMENTAL API. +type PerRPCCredsCallOption struct { + Creds credentials.PerRPCCredentials +} + +func (o PerRPCCredsCallOption) before(c *callInfo) error { + c.creds = o.Creds + return nil } +func (o PerRPCCredsCallOption) after(c *callInfo) { return } + +// UseCompressor returns a CallOption which sets the compressor used when +// sending the request. If WithCompressor is also set, UseCompressor has +// higher priority. +// +// This API is EXPERIMENTAL. +func UseCompressor(name string) CallOption { + return CompressorCallOption{CompressorType: name} +} + +// CompressorCallOption is a CallOption that indicates the compressor to use. +// This is an EXPERIMENTAL API. +type CompressorCallOption struct { + CompressorType string +} + +func (o CompressorCallOption) before(c *callInfo) error { + c.compressorType = o.CompressorType + return nil +} +func (o CompressorCallOption) after(c *callInfo) { return } + +// CallContentSubtype returns a CallOption that will set the content-subtype +// for a call. For example, if content-subtype is "json", the Content-Type over +// the wire will be "application/grpc+json". The content-subtype is converted +// to lowercase before being included in Content-Type. See Content-Type on +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. +// +// If CallCustomCodec is not also used, the content-subtype will be used to +// look up the Codec to use in the registry controlled by RegisterCodec. See +// the documention on RegisterCodec for details on registration. The lookup +// of content-subtype is case-insensitive. If no such Codec is found, the call +// will result in an error with code codes.Internal. +// +// If CallCustomCodec is also used, that Codec will be used for all request and +// response messages, with the content-subtype set to the given contentSubtype +// here for requests. +func CallContentSubtype(contentSubtype string) CallOption { + return ContentSubtypeCallOption{ContentSubtype: strings.ToLower(contentSubtype)} +} + +// ContentSubtypeCallOption is a CallOption that indicates the content-subtype +// used for marshaling messages. +// This is an EXPERIMENTAL API. +type ContentSubtypeCallOption struct { + ContentSubtype string +} + +func (o ContentSubtypeCallOption) before(c *callInfo) error { + c.contentSubtype = o.ContentSubtype + return nil +} +func (o ContentSubtypeCallOption) after(c *callInfo) { return } + +// CallCustomCodec returns a CallOption that will set the given Codec to be +// used for all request and response messages for a call. The result of calling +// String() will be used as the content-subtype in a case-insensitive manner. +// +// See Content-Type on +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. Also see the documentation on RegisterCodec and +// CallContentSubtype for more details on the interaction between Codec and +// content-subtype. +// +// This function is provided for advanced users; prefer to use only +// CallContentSubtype to select a registered codec instead. +func CallCustomCodec(codec Codec) CallOption { + return CustomCodecCallOption{Codec: codec} +} + +// CustomCodecCallOption is a CallOption that indicates the codec used for +// marshaling messages. +// This is an EXPERIMENTAL API. +type CustomCodecCallOption struct { + Codec Codec +} + +func (o CustomCodecCallOption) before(c *callInfo) error { + c.codec = o.Codec + return nil +} +func (o CustomCodecCallOption) after(c *callInfo) { return } // The format of the payload: compressed or not? type payloadFormat uint8 @@ -248,8 +419,8 @@ type parser struct { // error types. r io.Reader - // The header of a gRPC message. Find more detail - // at https://grpc.io/docs/guides/wire.html. + // The header of a gRPC message. Find more detail at + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md header [5]byte } @@ -277,8 +448,11 @@ func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byt if length == 0 { return pf, nil, nil } - if length > uint32(maxReceiveMessageSize) { - return 0, nil, Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", length, maxReceiveMessageSize) + if int64(length) > int64(maxInt) { + return 0, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max length allowed on current machine (%d vs. %d)", length, maxInt) + } + if int(length) > maxReceiveMessageSize { + return 0, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", length, maxReceiveMessageSize) } // TODO(bradfitz,zhaoq): garbage. reuse buffer after proto decoding instead // of making it for each message: @@ -294,18 +468,21 @@ func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byt // encode serializes msg and returns a buffer of message header and a buffer of msg. // If msg is nil, it generates the message header and an empty msg buffer. -func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayload *stats.OutPayload) ([]byte, []byte, error) { - var b []byte +// TODO(ddyihai): eliminate extra Compressor parameter. +func encode(c baseCodec, msg interface{}, cp Compressor, outPayload *stats.OutPayload, compressor encoding.Compressor) ([]byte, []byte, error) { + var ( + b []byte + cbuf *bytes.Buffer + ) const ( payloadLen = 1 sizeLen = 4 ) - if msg != nil { var err error b, err = c.Marshal(msg) if err != nil { - return nil, nil, Errorf(codes.Internal, "grpc: error while marshaling: %v", err.Error()) + return nil, nil, status.Errorf(codes.Internal, "grpc: error while marshaling: %v", err.Error()) } if outPayload != nil { outPayload.Payload = msg @@ -313,24 +490,35 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayl outPayload.Data = b outPayload.Length = len(b) } - if cp != nil { - if err := cp.Do(cbuf, b); err != nil { - return nil, nil, Errorf(codes.Internal, "grpc: error while compressing: %v", err.Error()) + if compressor != nil || cp != nil { + cbuf = new(bytes.Buffer) + // Has compressor, check Compressor is set by UseCompressor first. + if compressor != nil { + z, _ := compressor.Compress(cbuf) + if _, err := z.Write(b); err != nil { + return nil, nil, status.Errorf(codes.Internal, "grpc: error while compressing: %v", err.Error()) + } + z.Close() + } else { + // If Compressor is not set by UseCompressor, use default Compressor + if err := cp.Do(cbuf, b); err != nil { + return nil, nil, status.Errorf(codes.Internal, "grpc: error while compressing: %v", err.Error()) + } } b = cbuf.Bytes() } } - if uint(len(b)) > math.MaxUint32 { - return nil, nil, Errorf(codes.ResourceExhausted, "grpc: message too large (%d bytes)", len(b)) + return nil, nil, status.Errorf(codes.ResourceExhausted, "grpc: message too large (%d bytes)", len(b)) } bufHeader := make([]byte, payloadLen+sizeLen) - if cp == nil { - bufHeader[0] = byte(compressionNone) - } else { + if compressor != nil || cp != nil { bufHeader[0] = byte(compressionMade) + } else { + bufHeader[0] = byte(compressionNone) } + // Write length of b into buf binary.BigEndian.PutUint32(bufHeader[payloadLen:], uint32(len(b))) if outPayload != nil { @@ -339,20 +527,26 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayl return bufHeader, b, nil } -func checkRecvPayload(pf payloadFormat, recvCompress string, dc Decompressor) error { +func checkRecvPayload(pf payloadFormat, recvCompress string, haveCompressor bool) *status.Status { switch pf { case compressionNone: case compressionMade: - if dc == nil || recvCompress != dc.Type() { - return Errorf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress) + if recvCompress == "" || recvCompress == encoding.Identity { + return status.New(codes.Internal, "grpc: compressed flag set with identity or empty encoding") + } + if !haveCompressor { + return status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", recvCompress) } default: - return Errorf(codes.Internal, "grpc: received unexpected payload format %d", pf) + return status.Newf(codes.Internal, "grpc: received unexpected payload format %d", pf) } return nil } -func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int, inPayload *stats.InPayload) error { +// For the two compressor parameters, both should not be set, but if they are, +// dc takes precedence over compressor. +// TODO(dfawley): wrap the old compressor/decompressor using the new API? +func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int, inPayload *stats.InPayload, compressor encoding.Compressor) error { pf, d, err := p.recvMsg(maxReceiveMessageSize) if err != nil { return err @@ -360,22 +554,37 @@ func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{ if inPayload != nil { inPayload.WireLength = len(d) } - if err := checkRecvPayload(pf, s.RecvCompress(), dc); err != nil { - return err + + if st := checkRecvPayload(pf, s.RecvCompress(), compressor != nil || dc != nil); st != nil { + return st.Err() } + if pf == compressionMade { - d, err = dc.Do(bytes.NewReader(d)) - if err != nil { - return Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) + // To match legacy behavior, if the decompressor is set by WithDecompressor or RPCDecompressor, + // use this decompressor as the default. + if dc != nil { + d, err = dc.Do(bytes.NewReader(d)) + if err != nil { + return status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) + } + } else { + dcReader, err := compressor.Decompress(bytes.NewReader(d)) + if err != nil { + return status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) + } + d, err = ioutil.ReadAll(dcReader) + if err != nil { + return status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) + } } } if len(d) > maxReceiveMessageSize { // TODO: Revisit the error code. Currently keep it consistent with java // implementation. - return Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", len(d), maxReceiveMessageSize) + return status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", len(d), maxReceiveMessageSize) } if err := c.Unmarshal(d, m); err != nil { - return Errorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err) + return status.Errorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err) } if inPayload != nil { inPayload.RecvTime = time.Now() @@ -388,9 +597,7 @@ func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{ } type rpcInfo struct { - failfast bool - bytesSent bool - bytesReceived bool + failfast bool } type rpcInfoContextKey struct{} @@ -404,69 +611,10 @@ func rpcInfoFromContext(ctx context.Context) (s *rpcInfo, ok bool) { return } -func updateRPCInfoInContext(ctx context.Context, s rpcInfo) { - if ss, ok := rpcInfoFromContext(ctx); ok { - ss.bytesReceived = s.bytesReceived - ss.bytesSent = s.bytesSent - } - return -} - -// toRPCErr converts an error into an error from the status package. -func toRPCErr(err error) error { - if _, ok := status.FromError(err); ok { - return err - } - switch e := err.(type) { - case transport.StreamError: - return status.Error(e.Code, e.Desc) - case transport.ConnectionError: - return status.Error(codes.Unavailable, e.Desc) - default: - switch err { - case context.DeadlineExceeded, stdctx.DeadlineExceeded: - return status.Error(codes.DeadlineExceeded, err.Error()) - case context.Canceled, stdctx.Canceled: - return status.Error(codes.Canceled, err.Error()) - case ErrClientConnClosing: - return status.Error(codes.FailedPrecondition, err.Error()) - } - } - return status.Error(codes.Unknown, err.Error()) -} - -// convertCode converts a standard Go error into its canonical code. Note that -// this is only used to translate the error returned by the server applications. -func convertCode(err error) codes.Code { - switch err { - case nil: - return codes.OK - case io.EOF: - return codes.OutOfRange - case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: - return codes.FailedPrecondition - case os.ErrInvalid: - return codes.InvalidArgument - case context.Canceled, stdctx.Canceled: - return codes.Canceled - case context.DeadlineExceeded, stdctx.DeadlineExceeded: - return codes.DeadlineExceeded - } - switch { - case os.IsExist(err): - return codes.AlreadyExists - case os.IsNotExist(err): - return codes.NotFound - case os.IsPermission(err): - return codes.PermissionDenied - } - return codes.Unknown -} - // Code returns the error code for err if it was produced by the rpc system. // Otherwise, it returns codes.Unknown. // -// Deprecated; use status.FromError and Code method instead. +// Deprecated: use status.FromError and Code method instead. func Code(err error) codes.Code { if s, ok := status.FromError(err); ok { return s.Code() @@ -477,7 +625,7 @@ func Code(err error) codes.Code { // ErrorDesc returns the error description of err if it was produced by the rpc system. // Otherwise, it returns err.Error() or empty string when err is nil. // -// Deprecated; use status.FromError and Message method instead. +// Deprecated: use status.FromError and Message method instead. func ErrorDesc(err error) string { if s, ok := status.FromError(err); ok { return s.Message() @@ -488,85 +636,47 @@ func ErrorDesc(err error) string { // Errorf returns an error containing an error code and a description; // Errorf returns nil if c is OK. // -// Deprecated; use status.Errorf instead. +// Deprecated: use status.Errorf instead. func Errorf(c codes.Code, format string, a ...interface{}) error { return status.Errorf(c, format, a...) } -// MethodConfig defines the configuration recommended by the service providers for a -// particular method. -// This is EXPERIMENTAL and subject to change. -type MethodConfig struct { - // WaitForReady indicates whether RPCs sent to this method should wait until - // the connection is ready by default (!failfast). The value specified via the - // gRPC client API will override the value set here. - WaitForReady *bool - // Timeout is the default timeout for RPCs sent to this method. The actual - // deadline used will be the minimum of the value specified here and the value - // set by the application via the gRPC client API. If either one is not set, - // then the other will be used. If neither is set, then the RPC has no deadline. - Timeout *time.Duration - // MaxReqSize is the maximum allowed payload size for an individual request in a - // stream (client->server) in bytes. The size which is measured is the serialized - // payload after per-message compression (but before stream compression) in bytes. - // The actual value used is the minimum of the value specified here and the value set - // by the application via the gRPC client API. If either one is not set, then the other - // will be used. If neither is set, then the built-in default is used. - MaxReqSize *int - // MaxRespSize is the maximum allowed payload size for an individual response in a - // stream (server->client) in bytes. - MaxRespSize *int -} - -// ServiceConfig is provided by the service provider and contains parameters for how -// clients that connect to the service should behave. -// This is EXPERIMENTAL and subject to change. -type ServiceConfig struct { - // LB is the load balancer the service providers recommends. The balancer specified - // via grpc.WithBalancer will override this. - LB Balancer - // Methods contains a map for the methods in this service. - // If there is an exact match for a method (i.e. /service/method) in the map, use the corresponding MethodConfig. - // If there's no exact match, look for the default config for the service (/service/) and use the corresponding MethodConfig if it exists. - // Otherwise, the method has no MethodConfig to use. - Methods map[string]MethodConfig -} - -func min(a, b *int) *int { - if *a < *b { - return a +// setCallInfoCodec should only be called after CallOptions have been applied. +func setCallInfoCodec(c *callInfo) error { + if c.codec != nil { + // codec was already set by a CallOption; use it. + return nil } - return b -} -func getMaxSize(mcMax, doptMax *int, defaultVal int) *int { - if mcMax == nil && doptMax == nil { - return &defaultVal - } - if mcMax != nil && doptMax != nil { - return min(mcMax, doptMax) + if c.contentSubtype == "" { + // No codec specified in CallOptions; use proto by default. + c.codec = encoding.GetCodec(proto.Name) + return nil } - if mcMax != nil { - return mcMax + + // c.contentSubtype is already lowercased in CallContentSubtype + c.codec = encoding.GetCodec(c.contentSubtype) + if c.codec == nil { + return status.Errorf(codes.Internal, "no codec registered for content-subtype %s", c.contentSubtype) } - return doptMax + return nil } -// SupportPackageIsVersion3 is referenced from generated protocol buffer files. -// The latest support package version is 4. -// SupportPackageIsVersion3 is kept for compatibility. It will be removed in the -// next support package version update. -const SupportPackageIsVersion3 = true - -// SupportPackageIsVersion4 is referenced from generated protocol buffer files -// to assert that that code is compatible with this version of the grpc package. +// The SupportPackageIsVersion variables are referenced from generated protocol +// buffer files to ensure compatibility with the gRPC version used. The latest +// support package version is 5. // -// This constant may be renamed in the future if a change in the generated code -// requires a synchronised update of grpc-go and protoc-gen-go. This constant -// should not be referenced from any other code. -const SupportPackageIsVersion4 = true +// Older versions are kept for compatibility. They may be removed if +// compatibility cannot be maintained. +// +// These constants should not be referenced from any other code. +const ( + SupportPackageIsVersion3 = true + SupportPackageIsVersion4 = true + SupportPackageIsVersion5 = true +) // Version is the current grpc version. -const Version = "1.7.5" +const Version = "1.11.1" const grpcUA = "grpc-go/" + Version diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 787665dfeb3..0063906e469 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -32,11 +32,15 @@ import ( "sync" "time" + "io/ioutil" + "golang.org/x/net/context" "golang.org/x/net/http2" "golang.org/x/net/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding" + "google.golang.org/grpc/encoding/proto" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" "google.golang.org/grpc/keepalive" @@ -89,18 +93,20 @@ type Server struct { conns map[io.Closer]bool serve bool drain bool - ctx context.Context - cancel context.CancelFunc - // A CondVar to let GracefulStop() blocks until all the pending RPCs are finished - // and all the transport goes away. - cv *sync.Cond + cv *sync.Cond // signaled when connections close for GracefulStop m map[string]*service // service name -> service info events trace.EventLog + + quit chan struct{} + done chan struct{} + quitOnce sync.Once + doneOnce sync.Once + serveWG sync.WaitGroup // counts active Serve goroutines for GracefulStop } type options struct { creds credentials.TransportCredentials - codec Codec + codec baseCodec cp Compressor dc Decompressor unaryInt UnaryServerInterceptor @@ -177,20 +183,32 @@ func KeepaliveEnforcementPolicy(kep keepalive.EnforcementPolicy) ServerOption { } // CustomCodec returns a ServerOption that sets a codec for message marshaling and unmarshaling. +// +// This will override any lookups by content-subtype for Codecs registered with RegisterCodec. func CustomCodec(codec Codec) ServerOption { return func(o *options) { o.codec = codec } } -// RPCCompressor returns a ServerOption that sets a compressor for outbound messages. +// RPCCompressor returns a ServerOption that sets a compressor for outbound +// messages. For backward compatibility, all outbound messages will be sent +// using this compressor, regardless of incoming message compression. By +// default, server messages will be sent using the same compressor with which +// request messages were sent. +// +// Deprecated: use encoding.RegisterCompressor instead. func RPCCompressor(cp Compressor) ServerOption { return func(o *options) { o.cp = cp } } -// RPCDecompressor returns a ServerOption that sets a decompressor for inbound messages. +// RPCDecompressor returns a ServerOption that sets a decompressor for inbound +// messages. It has higher priority than decompressors registered via +// encoding.RegisterCompressor. +// +// Deprecated: use encoding.RegisterCompressor instead. func RPCDecompressor(dc Decompressor) ServerOption { return func(o *options) { o.dc = dc @@ -297,6 +315,8 @@ func UnknownServiceHandler(streamHandler StreamHandler) ServerOption { // connection establishment (up to and including HTTP/2 handshaking) for all // new connections. If this is not set, the default is 120 seconds. A zero or // negative value will result in an immediate timeout. +// +// This API is EXPERIMENTAL. func ConnectionTimeout(d time.Duration) ServerOption { return func(o *options) { o.connectionTimeout = d @@ -310,18 +330,15 @@ func NewServer(opt ...ServerOption) *Server { for _, o := range opt { o(&opts) } - if opts.codec == nil { - // Set the default codec. - opts.codec = protoCodec{} - } s := &Server{ lis: make(map[net.Listener]bool), opts: opts, conns: make(map[io.Closer]bool), m: make(map[string]*service), + quit: make(chan struct{}), + done: make(chan struct{}), } s.cv = sync.NewCond(&s.mu) - s.ctx, s.cancel = context.WithCancel(context.Background()) if EnableTracing { _, file, line, _ := runtime.Caller(1) s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line)) @@ -430,11 +447,9 @@ func (s *Server) GetServiceInfo() map[string]ServiceInfo { return ret } -var ( - // ErrServerStopped indicates that the operation is now illegal because of - // the server being stopped. - ErrServerStopped = errors.New("grpc: the server has been stopped") -) +// ErrServerStopped indicates that the operation is now illegal because of +// the server being stopped. +var ErrServerStopped = errors.New("grpc: the server has been stopped") func (s *Server) useTransportAuthenticator(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { if s.opts.creds == nil { @@ -448,16 +463,29 @@ func (s *Server) useTransportAuthenticator(rawConn net.Conn) (net.Conn, credenti // read gRPC requests and then call the registered handlers to reply to them. // Serve returns when lis.Accept fails with fatal errors. lis will be closed when // this method returns. -// Serve always returns non-nil error. +// Serve will return a non-nil error unless Stop or GracefulStop is called. func (s *Server) Serve(lis net.Listener) error { s.mu.Lock() s.printf("serving") s.serve = true if s.lis == nil { + // Serve called after Stop or GracefulStop. s.mu.Unlock() lis.Close() return ErrServerStopped } + + s.serveWG.Add(1) + defer func() { + s.serveWG.Done() + select { + // Stop or GracefulStop called; block until done and return nil. + case <-s.quit: + <-s.done + default: + } + }() + s.lis[lis] = true s.mu.Unlock() defer func() { @@ -491,25 +519,39 @@ func (s *Server) Serve(lis net.Listener) error { timer := time.NewTimer(tempDelay) select { case <-timer.C: - case <-s.ctx.Done(): + case <-s.quit: + timer.Stop() + return nil } - timer.Stop() continue } s.mu.Lock() s.printf("done serving; Accept = %v", err) s.mu.Unlock() + + select { + case <-s.quit: + return nil + default: + } return err } tempDelay = 0 - // Start a new goroutine to deal with rawConn - // so we don't stall this Accept loop goroutine. - go s.handleRawConn(rawConn) + // Start a new goroutine to deal with rawConn so we don't stall this Accept + // loop goroutine. + // + // Make sure we account for the goroutine so GracefulStop doesn't nil out + // s.conns before this conn can be added. + s.serveWG.Add(1) + go func() { + s.handleRawConn(rawConn) + s.serveWG.Done() + }() } } -// handleRawConn is run in its own goroutine and handles a just-accepted -// connection that has not had any I/O performed on it yet. +// handleRawConn forks a goroutine to handle a just-accepted connection that +// has not had any I/O performed on it yet. func (s *Server) handleRawConn(rawConn net.Conn) { rawConn.SetDeadline(time.Now().Add(s.opts.connectionTimeout)) conn, authInfo, err := s.useTransportAuthenticator(rawConn) @@ -534,17 +576,28 @@ func (s *Server) handleRawConn(rawConn net.Conn) { } s.mu.Unlock() + var serve func() + c := conn.(io.Closer) if s.opts.useHandlerImpl { - rawConn.SetDeadline(time.Time{}) - s.serveUsingHandler(conn) + serve = func() { s.serveUsingHandler(conn) } } else { + // Finish handshaking (HTTP2) st := s.newHTTP2Transport(conn, authInfo) if st == nil { return } - rawConn.SetDeadline(time.Time{}) - s.serveStreams(st) + c = st + serve = func() { s.serveStreams(st) } } + + rawConn.SetDeadline(time.Time{}) + if !s.addConn(c) { + return + } + go func() { + serve() + s.removeConn(c) + }() } // newHTTP2Transport sets up a http/2 transport (using the @@ -571,15 +624,10 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr grpclog.Warningln("grpc: Server.Serve failed to create ServerTransport: ", err) return nil } - if !s.addConn(st) { - st.Close() - return nil - } return st } func (s *Server) serveStreams(st transport.ServerTransport) { - defer s.removeConn(st) defer st.Close() var wg sync.WaitGroup st.HandleStreams(func(stream *transport.Stream) { @@ -613,11 +661,6 @@ var _ http.Handler = (*Server)(nil) // // conn is the *tls.Conn that's already been authenticated. func (s *Server) serveUsingHandler(conn net.Conn) { - if !s.addConn(conn) { - conn.Close() - return - } - defer s.removeConn(conn) h2s := &http2.Server{ MaxConcurrentStreams: s.opts.maxConcurrentStreams, } @@ -651,13 +694,12 @@ func (s *Server) serveUsingHandler(conn net.Conn) { // available through grpc-go's HTTP/2 server, and it is currently EXPERIMENTAL // and subject to change. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - st, err := transport.NewServerHandlerTransport(w, r) + st, err := transport.NewServerHandlerTransport(w, r, s.opts.statsHandler) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if !s.addConn(st) { - st.Close() return } defer s.removeConn(st) @@ -687,9 +729,15 @@ func (s *Server) traceInfo(st transport.ServerTransport, stream *transport.Strea func (s *Server) addConn(c io.Closer) bool { s.mu.Lock() defer s.mu.Unlock() - if s.conns == nil || s.drain { + if s.conns == nil { + c.Close() return false } + if s.drain { + // Transport added after we drained our existing conns: drain it + // immediately. + c.(transport.ServerTransport).Drain() + } s.conns[c] = true return true } @@ -703,18 +751,14 @@ func (s *Server) removeConn(c io.Closer) { } } -func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options) error { +func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error { var ( - cbuf *bytes.Buffer outPayload *stats.OutPayload ) - if cp != nil { - cbuf = new(bytes.Buffer) - } if s.opts.statsHandler != nil { outPayload = &stats.OutPayload{} } - hdr, data, err := encode(s.opts.codec, msg, cp, cbuf, outPayload) + hdr, data, err := encode(s.getCodec(stream.ContentSubtype()), msg, cp, outPayload, comp) if err != nil { grpclog.Errorln("grpc: server failed to encode response: ", err) return err @@ -733,13 +777,15 @@ func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Str func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc, trInfo *traceInfo) (err error) { sh := s.opts.statsHandler if sh != nil { + beginTime := time.Now() begin := &stats.Begin{ - BeginTime: time.Now(), + BeginTime: beginTime, } sh.HandleRPC(stream.Context(), begin) defer func() { end := &stats.End{ - EndTime: time.Now(), + BeginTime: beginTime, + EndTime: time.Now(), } if err != nil && err != io.EOF { end.Error = toRPCErr(err) @@ -758,10 +804,43 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } }() } + + // comp and cp are used for compression. decomp and dc are used for + // decompression. If comp and decomp are both set, they are the same; + // however they are kept separate to ensure that at most one of the + // compressor/decompressor variable pairs are set for use later. + var comp, decomp encoding.Compressor + var cp Compressor + var dc Decompressor + + // If dc is set and matches the stream's compression, use it. Otherwise, try + // to find a matching registered compressor for decomp. + if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc { + dc = s.opts.dc + } else if rc != "" && rc != encoding.Identity { + decomp = encoding.GetCompressor(rc) + if decomp == nil { + st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc) + t.WriteStatus(stream, st) + return st.Err() + } + } + + // If cp is set, use it. Otherwise, attempt to compress the response using + // the incoming message compression method. + // + // NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686. if s.opts.cp != nil { - // NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686. - stream.SetSendCompress(s.opts.cp.Type()) + cp = s.opts.cp + stream.SetSendCompress(cp.Type()) + } else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity { + // Legacy compressor not specified; attempt to respond with same encoding. + comp = encoding.GetCompressor(rc) + if comp != nil { + stream.SetSendCompress(rc) + } } + p := &parser{r: stream} pf, req, err := p.recvMsg(s.opts.maxReceiveMessageSize) if err == io.EOF { @@ -769,7 +848,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. return err } if err == io.ErrUnexpectedEOF { - err = Errorf(codes.Internal, io.ErrUnexpectedEOF.Error()) + err = status.Errorf(codes.Internal, io.ErrUnexpectedEOF.Error()) } if err != nil { if st, ok := status.FromError(err); ok { @@ -790,19 +869,11 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } return err } - - if err := checkRecvPayload(pf, stream.RecvCompress(), s.opts.dc); err != nil { - if st, ok := status.FromError(err); ok { - if e := t.WriteStatus(stream, st); e != nil { - grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status %v", e) - } - return err - } - if e := t.WriteStatus(stream, status.New(codes.Internal, err.Error())); e != nil { + if st := checkRecvPayload(pf, stream.RecvCompress(), dc != nil || decomp != nil); st != nil { + if e := t.WriteStatus(stream, st); e != nil { grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status %v", e) } - - // TODO checkRecvPayload always return RPC error. Add a return here if necessary. + return st.Err() } var inPayload *stats.InPayload if sh != nil { @@ -816,9 +887,17 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } if pf == compressionMade { var err error - req, err = s.opts.dc.Do(bytes.NewReader(req)) - if err != nil { - return Errorf(codes.Internal, err.Error()) + if dc != nil { + req, err = dc.Do(bytes.NewReader(req)) + if err != nil { + return status.Errorf(codes.Internal, err.Error()) + } + } else { + tmp, _ := decomp.Decompress(bytes.NewReader(req)) + req, err = ioutil.ReadAll(tmp) + if err != nil { + return status.Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err) + } } } if len(req) > s.opts.maxReceiveMessageSize { @@ -826,7 +905,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. // java implementation. return status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", len(req), s.opts.maxReceiveMessageSize) } - if err := s.opts.codec.Unmarshal(req, v); err != nil { + if err := s.getCodec(stream.ContentSubtype()).Unmarshal(req, v); err != nil { return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err) } if inPayload != nil { @@ -840,12 +919,13 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } return nil } - reply, appErr := md.Handler(srv.server, stream.Context(), df, s.opts.unaryInt) + ctx := NewContextWithServerTransportStream(stream.Context(), stream) + reply, appErr := md.Handler(srv.server, ctx, df, s.opts.unaryInt) if appErr != nil { appStatus, ok := status.FromError(appErr) if !ok { // Convert appErr if it is not a grpc status error. - appErr = status.Error(convertCode(appErr), appErr.Error()) + appErr = status.Error(codes.Unknown, appErr.Error()) appStatus, _ = status.FromError(appErr) } if trInfo != nil { @@ -864,7 +944,8 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. Last: true, Delay: false, } - if err := s.sendResponse(t, stream, reply, s.opts.cp, opts); err != nil { + + if err := s.sendResponse(t, stream, reply, cp, opts, comp); err != nil { if err == io.EOF { // The entire stream is done (for unary RPC only). return err @@ -899,13 +980,15 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc, trInfo *traceInfo) (err error) { sh := s.opts.statsHandler if sh != nil { + beginTime := time.Now() begin := &stats.Begin{ - BeginTime: time.Now(), + BeginTime: beginTime, } sh.HandleRPC(stream.Context(), begin) defer func() { end := &stats.End{ - EndTime: time.Now(), + BeginTime: beginTime, + EndTime: time.Now(), } if err != nil && err != io.EOF { end.Error = toRPCErr(err) @@ -913,21 +996,47 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp sh.HandleRPC(stream.Context(), end) }() } - if s.opts.cp != nil { - stream.SetSendCompress(s.opts.cp.Type()) - } + ctx := NewContextWithServerTransportStream(stream.Context(), stream) ss := &serverStream{ + ctx: ctx, t: t, s: stream, p: &parser{r: stream}, - codec: s.opts.codec, - cp: s.opts.cp, - dc: s.opts.dc, + codec: s.getCodec(stream.ContentSubtype()), maxReceiveMessageSize: s.opts.maxReceiveMessageSize, maxSendMessageSize: s.opts.maxSendMessageSize, trInfo: trInfo, statsHandler: sh, } + + // If dc is set and matches the stream's compression, use it. Otherwise, try + // to find a matching registered compressor for decomp. + if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc { + ss.dc = s.opts.dc + } else if rc != "" && rc != encoding.Identity { + ss.decomp = encoding.GetCompressor(rc) + if ss.decomp == nil { + st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc) + t.WriteStatus(ss.s, st) + return st.Err() + } + } + + // If cp is set, use it. Otherwise, attempt to compress the response using + // the incoming message compression method. + // + // NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686. + if s.opts.cp != nil { + ss.cp = s.opts.cp + stream.SetSendCompress(s.opts.cp.Type()) + } else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity { + // Legacy compressor not specified; attempt to respond with same encoding. + ss.comp = encoding.GetCompressor(rc) + if ss.comp != nil { + stream.SetSendCompress(rc) + } + } + if trInfo != nil { trInfo.tr.LazyLog(&trInfo.firstLine, false) defer func() { @@ -963,7 +1072,7 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp case transport.StreamError: appStatus = status.New(err.Code, err.Desc) default: - appStatus = status.New(convertCode(appErr), appErr.Error()) + appStatus = status.New(codes.Unknown, appErr.Error()) } appErr = appStatus.Err() } @@ -983,7 +1092,6 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp ss.mu.Unlock() } return t.WriteStatus(ss.s, status.New(codes.OK, "")) - } func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) { @@ -1065,12 +1173,57 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str } } +// The key to save ServerTransportStream in the context. +type streamKey struct{} + +// NewContextWithServerTransportStream creates a new context from ctx and +// attaches stream to it. +// +// This API is EXPERIMENTAL. +func NewContextWithServerTransportStream(ctx context.Context, stream ServerTransportStream) context.Context { + return context.WithValue(ctx, streamKey{}, stream) +} + +// ServerTransportStream is a minimal interface that a transport stream must +// implement. This can be used to mock an actual transport stream for tests of +// handler code that use, for example, grpc.SetHeader (which requires some +// stream to be in context). +// +// See also NewContextWithServerTransportStream. +// +// This API is EXPERIMENTAL. +type ServerTransportStream interface { + Method() string + SetHeader(md metadata.MD) error + SendHeader(md metadata.MD) error + SetTrailer(md metadata.MD) error +} + +// serverStreamFromContext returns the server stream saved in ctx. Returns +// nil if the given context has no stream associated with it (which implies +// it is not an RPC invocation context). +func serverTransportStreamFromContext(ctx context.Context) ServerTransportStream { + s, _ := ctx.Value(streamKey{}).(ServerTransportStream) + return s +} + // Stop stops the gRPC server. It immediately closes all open // connections and listeners. // It cancels all active RPCs on the server side and the corresponding // pending RPCs on the client side will get notified by connection // errors. func (s *Server) Stop() { + s.quitOnce.Do(func() { + close(s.quit) + }) + + defer func() { + s.serveWG.Wait() + s.doneOnce.Do(func() { + close(s.done) + }) + }() + s.mu.Lock() listeners := s.lis s.lis = nil @@ -1088,7 +1241,6 @@ func (s *Server) Stop() { } s.mu.Lock() - s.cancel() if s.events != nil { s.events.Finish() s.events = nil @@ -1100,22 +1252,38 @@ func (s *Server) Stop() { // accepting new connections and RPCs and blocks until all the pending RPCs are // finished. func (s *Server) GracefulStop() { + s.quitOnce.Do(func() { + close(s.quit) + }) + + defer func() { + s.doneOnce.Do(func() { + close(s.done) + }) + }() + s.mu.Lock() - defer s.mu.Unlock() if s.conns == nil { + s.mu.Unlock() return } for lis := range s.lis { lis.Close() } s.lis = nil - s.cancel() if !s.drain { for c := range s.conns { c.(transport.ServerTransport).Drain() } s.drain = true } + + // Wait for serving threads to be ready to exit. Only then can we be sure no + // new conns will be created. + s.mu.Unlock() + s.serveWG.Wait() + s.mu.Lock() + for len(s.conns) != 0 { s.cv.Wait() } @@ -1124,26 +1292,29 @@ func (s *Server) GracefulStop() { s.events.Finish() s.events = nil } + s.mu.Unlock() } func init() { - internal.TestingCloseConns = func(arg interface{}) { - arg.(*Server).testingCloseConns() - } internal.TestingUseHandlerImpl = func(arg interface{}) { arg.(*Server).opts.useHandlerImpl = true } } -// testingCloseConns closes all existing transports but keeps s.lis -// accepting new connections. -func (s *Server) testingCloseConns() { - s.mu.Lock() - for c := range s.conns { - c.Close() - delete(s.conns, c) +// contentSubtype must be lowercase +// cannot return nil +func (s *Server) getCodec(contentSubtype string) baseCodec { + if s.opts.codec != nil { + return s.opts.codec } - s.mu.Unlock() + if contentSubtype == "" { + return encoding.GetCodec(proto.Name) + } + codec := encoding.GetCodec(contentSubtype) + if codec == nil { + return encoding.GetCodec(proto.Name) + } + return codec } // SetHeader sets the header metadata. @@ -1156,9 +1327,9 @@ func SetHeader(ctx context.Context, md metadata.MD) error { if md.Len() == 0 { return nil } - stream, ok := transport.StreamFromContext(ctx) - if !ok { - return Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) + stream := serverTransportStreamFromContext(ctx) + if stream == nil { + return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } return stream.SetHeader(md) } @@ -1166,15 +1337,11 @@ func SetHeader(ctx context.Context, md metadata.MD) error { // SendHeader sends header metadata. It may be called at most once. // The provided md and headers set by SetHeader() will be sent. func SendHeader(ctx context.Context, md metadata.MD) error { - stream, ok := transport.StreamFromContext(ctx) - if !ok { - return Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) - } - t := stream.ServerTransport() - if t == nil { - grpclog.Fatalf("grpc: SendHeader: %v has no ServerTransport to send header metadata.", stream) + stream := serverTransportStreamFromContext(ctx) + if stream == nil { + return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } - if err := t.WriteHeader(stream, md); err != nil { + if err := stream.SendHeader(md); err != nil { return toRPCErr(err) } return nil @@ -1186,9 +1353,9 @@ func SetTrailer(ctx context.Context, md metadata.MD) error { if md.Len() == 0 { return nil } - stream, ok := transport.StreamFromContext(ctx) - if !ok { - return Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) + stream := serverTransportStreamFromContext(ctx) + if stream == nil { + return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } return stream.SetTrailer(md) } diff --git a/vendor/google.golang.org/grpc/service_config.go b/vendor/google.golang.org/grpc/service_config.go new file mode 100644 index 00000000000..53fa88f3793 --- /dev/null +++ b/vendor/google.golang.org/grpc/service_config.go @@ -0,0 +1,226 @@ +/* + * + * Copyright 2017 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 grpc + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "google.golang.org/grpc/grpclog" +) + +const maxInt = int(^uint(0) >> 1) + +// MethodConfig defines the configuration recommended by the service providers for a +// particular method. +// DEPRECATED: Users should not use this struct. Service config should be received +// through name resolver, as specified here +// https://github.com/grpc/grpc/blob/master/doc/service_config.md +type MethodConfig struct { + // WaitForReady indicates whether RPCs sent to this method should wait until + // the connection is ready by default (!failfast). The value specified via the + // gRPC client API will override the value set here. + WaitForReady *bool + // Timeout is the default timeout for RPCs sent to this method. The actual + // deadline used will be the minimum of the value specified here and the value + // set by the application via the gRPC client API. If either one is not set, + // then the other will be used. If neither is set, then the RPC has no deadline. + Timeout *time.Duration + // MaxReqSize is the maximum allowed payload size for an individual request in a + // stream (client->server) in bytes. The size which is measured is the serialized + // payload after per-message compression (but before stream compression) in bytes. + // The actual value used is the minimum of the value specified here and the value set + // by the application via the gRPC client API. If either one is not set, then the other + // will be used. If neither is set, then the built-in default is used. + MaxReqSize *int + // MaxRespSize is the maximum allowed payload size for an individual response in a + // stream (server->client) in bytes. + MaxRespSize *int +} + +// ServiceConfig is provided by the service provider and contains parameters for how +// clients that connect to the service should behave. +// DEPRECATED: Users should not use this struct. Service config should be received +// through name resolver, as specified here +// https://github.com/grpc/grpc/blob/master/doc/service_config.md +type ServiceConfig struct { + // LB is the load balancer the service providers recommends. The balancer specified + // via grpc.WithBalancer will override this. + LB *string + // Methods contains a map for the methods in this service. + // If there is an exact match for a method (i.e. /service/method) in the map, use the corresponding MethodConfig. + // If there's no exact match, look for the default config for the service (/service/) and use the corresponding MethodConfig if it exists. + // Otherwise, the method has no MethodConfig to use. + Methods map[string]MethodConfig +} + +func parseDuration(s *string) (*time.Duration, error) { + if s == nil { + return nil, nil + } + if !strings.HasSuffix(*s, "s") { + return nil, fmt.Errorf("malformed duration %q", *s) + } + ss := strings.SplitN((*s)[:len(*s)-1], ".", 3) + if len(ss) > 2 { + return nil, fmt.Errorf("malformed duration %q", *s) + } + // hasDigits is set if either the whole or fractional part of the number is + // present, since both are optional but one is required. + hasDigits := false + var d time.Duration + if len(ss[0]) > 0 { + i, err := strconv.ParseInt(ss[0], 10, 32) + if err != nil { + return nil, fmt.Errorf("malformed duration %q: %v", *s, err) + } + d = time.Duration(i) * time.Second + hasDigits = true + } + if len(ss) == 2 && len(ss[1]) > 0 { + if len(ss[1]) > 9 { + return nil, fmt.Errorf("malformed duration %q", *s) + } + f, err := strconv.ParseInt(ss[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("malformed duration %q: %v", *s, err) + } + for i := 9; i > len(ss[1]); i-- { + f *= 10 + } + d += time.Duration(f) + hasDigits = true + } + if !hasDigits { + return nil, fmt.Errorf("malformed duration %q", *s) + } + + return &d, nil +} + +type jsonName struct { + Service *string + Method *string +} + +func (j jsonName) generatePath() (string, bool) { + if j.Service == nil { + return "", false + } + res := "/" + *j.Service + "/" + if j.Method != nil { + res += *j.Method + } + return res, true +} + +// TODO(lyuxuan): delete this struct after cleaning up old service config implementation. +type jsonMC struct { + Name *[]jsonName + WaitForReady *bool + Timeout *string + MaxRequestMessageBytes *int64 + MaxResponseMessageBytes *int64 +} + +// TODO(lyuxuan): delete this struct after cleaning up old service config implementation. +type jsonSC struct { + LoadBalancingPolicy *string + MethodConfig *[]jsonMC +} + +func parseServiceConfig(js string) (ServiceConfig, error) { + var rsc jsonSC + err := json.Unmarshal([]byte(js), &rsc) + if err != nil { + grpclog.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) + return ServiceConfig{}, err + } + sc := ServiceConfig{ + LB: rsc.LoadBalancingPolicy, + Methods: make(map[string]MethodConfig), + } + if rsc.MethodConfig == nil { + return sc, nil + } + + for _, m := range *rsc.MethodConfig { + if m.Name == nil { + continue + } + d, err := parseDuration(m.Timeout) + if err != nil { + grpclog.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) + return ServiceConfig{}, err + } + + mc := MethodConfig{ + WaitForReady: m.WaitForReady, + Timeout: d, + } + if m.MaxRequestMessageBytes != nil { + if *m.MaxRequestMessageBytes > int64(maxInt) { + mc.MaxReqSize = newInt(maxInt) + } else { + mc.MaxReqSize = newInt(int(*m.MaxRequestMessageBytes)) + } + } + if m.MaxResponseMessageBytes != nil { + if *m.MaxResponseMessageBytes > int64(maxInt) { + mc.MaxRespSize = newInt(maxInt) + } else { + mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes)) + } + } + for _, n := range *m.Name { + if path, valid := n.generatePath(); valid { + sc.Methods[path] = mc + } + } + } + + return sc, nil +} + +func min(a, b *int) *int { + if *a < *b { + return a + } + return b +} + +func getMaxSize(mcMax, doptMax *int, defaultVal int) *int { + if mcMax == nil && doptMax == nil { + return &defaultVal + } + if mcMax != nil && doptMax != nil { + return min(mcMax, doptMax) + } + if mcMax != nil { + return mcMax + } + return doptMax +} + +func newInt(b int) *int { + return &b +} diff --git a/vendor/google.golang.org/grpc/stats/stats.go b/vendor/google.golang.org/grpc/stats/stats.go index d5aa2f793bf..3f13190a0ac 100644 --- a/vendor/google.golang.org/grpc/stats/stats.go +++ b/vendor/google.golang.org/grpc/stats/stats.go @@ -169,6 +169,8 @@ func (s *OutTrailer) isRPCStats() {} type End struct { // Client is true if this End is from client side. Client bool + // BeginTime is the time when the RPC began. + BeginTime time.Time // EndTime is the time when the RPC ends. EndTime time.Time // Error is the error the RPC ended with. It is an error generated from diff --git a/vendor/google.golang.org/grpc/status/status.go b/vendor/google.golang.org/grpc/status/status.go index 871dc4b31c7..9c61b094508 100644 --- a/vendor/google.golang.org/grpc/status/status.go +++ b/vendor/google.golang.org/grpc/status/status.go @@ -46,7 +46,7 @@ func (se *statusError) Error() string { return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(p.GetCode()), p.GetMessage()) } -func (se *statusError) status() *Status { +func (se *statusError) GRPCStatus() *Status { return &Status{s: (*spb.Status)(se)} } @@ -120,15 +120,23 @@ func FromProto(s *spb.Status) *Status { } // FromError returns a Status representing err if it was produced from this -// package, otherwise it returns nil, false. +// package or has a method `GRPCStatus() *Status`. Otherwise, ok is false and a +// Status is returned with codes.Unknown and the original error message. func FromError(err error) (s *Status, ok bool) { if err == nil { return &Status{s: &spb.Status{Code: int32(codes.OK)}}, true } - if s, ok := err.(*statusError); ok { - return s.status(), true + if se, ok := err.(interface{ GRPCStatus() *Status }); ok { + return se.GRPCStatus(), true } - return nil, false + return New(codes.Unknown, err.Error()), false +} + +// Convert is a convenience function which removes the need to handle the +// boolean return value from FromError. +func Convert(err error) *Status { + s, _ := FromError(err) + return s } // WithDetails returns a new status with the provided details messages appended to the status. @@ -166,3 +174,16 @@ func (s *Status) Details() []interface{} { } return details } + +// Code returns the Code of the error if it is a Status error, codes.OK if err +// is nil, or codes.Unknown otherwise. +func Code(err error) codes.Code { + // Don't use FromError to avoid allocation of OK status. + if err == nil { + return codes.OK + } + if se, ok := err.(interface{ GRPCStatus() *Status }); ok { + return se.GRPCStatus().Code() + } + return codes.Unknown +} diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index 75eab40b109..a79f385a740 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -19,7 +19,6 @@ package grpc import ( - "bytes" "errors" "io" "sync" @@ -29,15 +28,18 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/balancer" "google.golang.org/grpc/codes" + "google.golang.org/grpc/encoding" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "google.golang.org/grpc/transport" ) // StreamHandler defines the handler called by gRPC server to complete the -// execution of a streaming RPC. +// execution of a streaming RPC. If a StreamHandler returns an error, it +// should be produced by the status package, or else gRPC will use +// codes.Unknown as the status code and err.Error() as the status message +// of the RPC. type StreamHandler func(srv interface{}, stream ServerStream) error // StreamDesc represents a streaming RPC service's method specification. @@ -51,6 +53,8 @@ type StreamDesc struct { } // Stream defines the common interface a client or server stream has to satisfy. +// +// All errors returned from Stream are compatible with the status package. type Stream interface { // Context returns the context for this stream. Context() context.Context @@ -89,43 +93,57 @@ type ClientStream interface { // Stream.SendMsg() may return a non-nil error when something wrong happens sending // the request. The returned error indicates the status of this sending, not the final // status of the RPC. - // Always call Stream.RecvMsg() to get the final status if you care about the status of - // the RPC. + // + // Always call Stream.RecvMsg() to drain the stream and get the final + // status, otherwise there could be leaked resources. Stream } -// NewClientStream creates a new Stream for the client side. This is called -// by generated code. -func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) { +// NewStream creates a new Stream for the client side. This is typically +// called by generated code. +func (cc *ClientConn) NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error) { + // allow interceptor to see all applicable call options, which means those + // configured as defaults from dial option as well as per-call options + opts = combine(cc.dopts.callOptions, opts) + if cc.dopts.streamInt != nil { return cc.dopts.streamInt(ctx, desc, cc, method, newClientStream, opts...) } return newClientStream(ctx, desc, cc, method, opts...) } +// NewClientStream creates a new Stream for the client side. This is typically +// called by generated code. +// +// DEPRECATED: Use ClientConn.NewStream instead. +func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) { + return cc.NewStream(ctx, desc, method, opts...) +} + func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) { - var ( - t transport.ClientTransport - s *transport.Stream - done func(balancer.DoneInfo) - cancel context.CancelFunc - ) c := defaultCallInfo() mc := cc.GetMethodConfig(method) if mc.WaitForReady != nil { c.failFast = !*mc.WaitForReady } - if mc.Timeout != nil { + // Possible context leak: + // The cancel function for the child context we create will only be called + // when RecvMsg returns a non-nil error, if the ClientConn is closed, or if + // an error is generated by SendMsg. + // https://github.com/grpc/grpc-go/issues/1818. + var cancel context.CancelFunc + if mc.Timeout != nil && *mc.Timeout >= 0 { ctx, cancel = context.WithTimeout(ctx, *mc.Timeout) - defer func() { - if err != nil { - cancel() - } - }() + } else { + ctx, cancel = context.WithCancel(ctx) } + defer func() { + if err != nil { + cancel() + } + }() - opts = append(cc.dopts.callOptions, opts...) for _, o := range opts { if err := o.before(c); err != nil { return nil, toRPCErr(err) @@ -133,6 +151,9 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } c.maxSendMessageSize = getMaxSize(mc.MaxReqSize, c.maxSendMessageSize, defaultClientMaxSendMessageSize) c.maxReceiveMessageSize = getMaxSize(mc.MaxRespSize, c.maxReceiveMessageSize, defaultClientMaxReceiveMessageSize) + if err := setCallInfoCodec(c); err != nil { + return nil, err + } callHdr := &transport.CallHdr{ Host: cc.authority, @@ -141,10 +162,27 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth // so we don't flush the header. // If it's client streaming, the user may never send a request or send it any // time soon, so we ask the transport to flush the header. - Flush: desc.ClientStreams, - } - if cc.dopts.cp != nil { + Flush: desc.ClientStreams, + ContentSubtype: c.contentSubtype, + } + + // Set our outgoing compression according to the UseCompressor CallOption, if + // set. In that case, also find the compressor from the encoding package. + // Otherwise, use the compressor configured by the WithCompressor DialOption, + // if set. + var cp Compressor + var comp encoding.Compressor + if ct := c.compressorType; ct != "" { + callHdr.SendCompress = ct + if ct != encoding.Identity { + comp = encoding.GetCompressor(ct) + if comp == nil { + return nil, status.Errorf(codes.Internal, "grpc: Compressor is not installed for requested grpc-encoding %q", ct) + } + } + } else if cc.dopts.cp != nil { callHdr.SendCompress = cc.dopts.cp.Type() + cp = cc.dopts.cp } if c.creds != nil { callHdr.Creds = c.creds @@ -170,11 +208,13 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } ctx = newContextWithRPCInfo(ctx, c.failFast) sh := cc.dopts.copts.StatsHandler + var beginTime time.Time if sh != nil { ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method, FailFast: c.failFast}) + beginTime = time.Now() begin := &stats.Begin{ Client: true, - BeginTime: time.Now(), + BeginTime: beginTime, FailFast: c.failFast, } sh.HandleRPC(ctx, begin) @@ -182,341 +222,369 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth if err != nil { // Only handle end stats if err != nil. end := &stats.End{ - Client: true, - Error: err, + Client: true, + Error: err, + BeginTime: beginTime, + EndTime: time.Now(), } sh.HandleRPC(ctx, end) } }() } + + var ( + t transport.ClientTransport + s *transport.Stream + done func(balancer.DoneInfo) + ) for { + // Check to make sure the context has expired. This will prevent us from + // looping forever if an error occurs for wait-for-ready RPCs where no data + // is sent on the wire. + select { + case <-ctx.Done(): + return nil, toRPCErr(ctx.Err()) + default: + } + t, done, err = cc.getTransport(ctx, c.failFast) if err != nil { - // TODO(zhaoq): Probably revisit the error handling. - if _, ok := status.FromError(err); ok { - return nil, err - } - if err == errConnClosing || err == errConnUnavailable { - if c.failFast { - return nil, Errorf(codes.Unavailable, "%v", err) - } - continue - } - // All the other errors are treated as Internal errors. - return nil, Errorf(codes.Internal, "%v", err) + return nil, err } s, err = t.NewStream(ctx, callHdr) if err != nil { - if _, ok := err.(transport.ConnectionError); ok && done != nil { - // If error is connection error, transport was sending data on wire, - // and we are not sure if anything has been sent on wire. - // If error is not connection error, we are sure nothing has been sent. - updateRPCInfoInContext(ctx, rpcInfo{bytesSent: true, bytesReceived: false}) - } if done != nil { done(balancer.DoneInfo{Err: err}) done = nil } - if _, ok := err.(transport.ConnectionError); (ok || err == transport.ErrStreamDrain) && !c.failFast { + // In the event of any error from NewStream, we never attempted to write + // anything to the wire, so we can retry indefinitely for non-fail-fast + // RPCs. + if !c.failFast { continue } return nil, toRPCErr(err) } break } - // Set callInfo.peer object from stream's context. - if peer, ok := peer.FromContext(s.Context()); ok { - c.peer = peer - } + cs := &clientStream{ opts: opts, c: c, desc: desc, - codec: cc.dopts.codec, - cp: cc.dopts.cp, - dc: cc.dopts.dc, + codec: c.codec, + cp: cp, + comp: comp, cancel: cancel, - - done: done, - t: t, - s: s, - p: &parser{r: s}, - - tracing: EnableTracing, - trInfo: trInfo, - - statsCtx: ctx, - statsHandler: cc.dopts.copts.StatsHandler, + attempt: &csAttempt{ + t: t, + s: s, + p: &parser{r: s}, + done: done, + dc: cc.dopts.dc, + ctx: ctx, + trInfo: trInfo, + statsHandler: sh, + beginTime: beginTime, + }, + } + cs.c.stream = cs + cs.attempt.cs = cs + if desc != unaryStreamDesc { + // Listen on cc and stream contexts to cleanup when the user closes the + // ClientConn or cancels the stream context. In all other cases, an error + // should already be injected into the recv buffer by the transport, which + // the client will eventually receive, and then we will cancel the stream's + // context in clientStream.finish. + go func() { + select { + case <-cc.ctx.Done(): + cs.finish(ErrClientConnClosing) + case <-ctx.Done(): + cs.finish(toRPCErr(ctx.Err())) + } + }() } - // Listen on ctx.Done() to detect cancellation and s.Done() to detect normal termination - // when there is no pending I/O operations on this stream. - go func() { - select { - case <-t.Error(): - // Incur transport error, simply exit. - case <-cc.ctx.Done(): - cs.finish(ErrClientConnClosing) - cs.closeTransportStream(ErrClientConnClosing) - case <-s.Done(): - // TODO: The trace of the RPC is terminated here when there is no pending - // I/O, which is probably not the optimal solution. - cs.finish(s.Status().Err()) - cs.closeTransportStream(nil) - case <-s.GoAway(): - cs.finish(errConnDrain) - cs.closeTransportStream(errConnDrain) - case <-s.Context().Done(): - err := s.Context().Err() - cs.finish(err) - cs.closeTransportStream(transport.ContextErr(err)) - } - }() return cs, nil } // clientStream implements a client side Stream. type clientStream struct { - opts []CallOption - c *callInfo - t transport.ClientTransport - s *transport.Stream - p *parser - desc *StreamDesc - codec Codec - cp Compressor - dc Decompressor - cancel context.CancelFunc + opts []CallOption + c *callInfo + desc *StreamDesc + + codec baseCodec + cp Compressor + comp encoding.Compressor - tracing bool // set to EnableTracing when the clientStream is created. + cancel context.CancelFunc // cancels all attempts - mu sync.Mutex - done func(balancer.DoneInfo) - closed bool - finished bool - // trInfo.tr is set when the clientStream is created (if EnableTracing is true), - // and is set to nil when the clientStream's finish method is called. + sentLast bool // sent an end stream + + mu sync.Mutex // guards finished + finished bool // TODO: replace with atomic cmpxchg or sync.Once? + + attempt *csAttempt // the active client stream attempt + // TODO(hedging): hedging will have multiple attempts simultaneously. +} + +// csAttempt implements a single transport stream attempt within a +// clientStream. +type csAttempt struct { + cs *clientStream + t transport.ClientTransport + s *transport.Stream + p *parser + done func(balancer.DoneInfo) + + dc Decompressor + decomp encoding.Compressor + decompSet bool + + ctx context.Context // the application's context, wrapped by stats/tracing + + mu sync.Mutex // guards trInfo.tr + // trInfo.tr is set when created (if EnableTracing is true), + // and cleared when the finish method is called. trInfo traceInfo - // statsCtx keeps the user context for stats handling. - // All stats collection should use the statsCtx (instead of the stream context) - // so that all the generated stats for a particular RPC can be associated in the processing phase. - statsCtx context.Context statsHandler stats.Handler + beginTime time.Time } func (cs *clientStream) Context() context.Context { - return cs.s.Context() + // TODO(retry): commit the current attempt (the context has peer-aware data). + return cs.attempt.context() } func (cs *clientStream) Header() (metadata.MD, error) { - m, err := cs.s.Header() + m, err := cs.attempt.header() if err != nil { - if _, ok := err.(transport.ConnectionError); !ok { - cs.closeTransportStream(err) - } + // TODO(retry): maybe retry on error or commit attempt on success. + err = toRPCErr(err) + cs.finish(err) } return m, err } func (cs *clientStream) Trailer() metadata.MD { - return cs.s.Trailer() + // TODO(retry): on error, maybe retry (trailers-only). + return cs.attempt.trailer() } func (cs *clientStream) SendMsg(m interface{}) (err error) { - if cs.tracing { - cs.mu.Lock() - if cs.trInfo.tr != nil { - cs.trInfo.tr.LazyLog(&payload{sent: true, msg: m}, true) - } + // TODO(retry): buffer message for replaying if not committed. + return cs.attempt.sendMsg(m) +} + +func (cs *clientStream) RecvMsg(m interface{}) (err error) { + // TODO(retry): maybe retry on error or commit attempt on success. + return cs.attempt.recvMsg(m) +} + +func (cs *clientStream) CloseSend() error { + cs.attempt.closeSend() + return nil +} + +func (cs *clientStream) finish(err error) { + if err == io.EOF { + // Ending a stream with EOF indicates a success. + err = nil + } + cs.mu.Lock() + if cs.finished { cs.mu.Unlock() + return } + cs.finished = true + cs.mu.Unlock() + // TODO(retry): commit current attempt if necessary. + cs.attempt.finish(err) + for _, o := range cs.opts { + o.after(cs.c) + } + cs.cancel() +} + +func (a *csAttempt) context() context.Context { + return a.s.Context() +} + +func (a *csAttempt) header() (metadata.MD, error) { + return a.s.Header() +} + +func (a *csAttempt) trailer() metadata.MD { + return a.s.Trailer() +} + +func (a *csAttempt) sendMsg(m interface{}) (err error) { // TODO Investigate how to signal the stats handling party. // generate error stats if err != nil && err != io.EOF? + cs := a.cs defer func() { - if err != nil { - cs.finish(err) + // For non-client-streaming RPCs, we return nil instead of EOF on success + // because the generated code requires it. finish is not called; RecvMsg() + // will call it with the stream's status independently. + if err == io.EOF && !cs.desc.ClientStreams { + err = nil } - if err == nil { - return - } - if err == io.EOF { - // Specialize the process for server streaming. SendMsg is only called - // once when creating the stream object. io.EOF needs to be skipped when - // the rpc is early finished (before the stream object is created.). - // TODO: It is probably better to move this into the generated code. - if !cs.desc.ClientStreams && cs.desc.ServerStreams { - err = nil - } - return - } - if _, ok := err.(transport.ConnectionError); !ok { - cs.closeTransportStream(err) + if err != nil && err != io.EOF { + // Call finish on the client stream for errors generated by this SendMsg + // call, as these indicate problems created by this client. (Transport + // errors are converted to an io.EOF error below; the real error will be + // returned from RecvMsg eventually in that case, or be retried.) + cs.finish(err) } - err = toRPCErr(err) }() + // TODO: Check cs.sentLast and error if we already ended the stream. + if EnableTracing { + a.mu.Lock() + if a.trInfo.tr != nil { + a.trInfo.tr.LazyLog(&payload{sent: true, msg: m}, true) + } + a.mu.Unlock() + } var outPayload *stats.OutPayload - if cs.statsHandler != nil { + if a.statsHandler != nil { outPayload = &stats.OutPayload{ Client: true, } } - hdr, data, err := encode(cs.codec, m, cs.cp, bytes.NewBuffer([]byte{}), outPayload) + hdr, data, err := encode(cs.codec, m, cs.cp, outPayload, cs.comp) if err != nil { return err } - if cs.c.maxSendMessageSize == nil { - return Errorf(codes.Internal, "callInfo maxSendMessageSize field uninitialized(nil)") - } if len(data) > *cs.c.maxSendMessageSize { - return Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(data), *cs.c.maxSendMessageSize) + return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(data), *cs.c.maxSendMessageSize) } - err = cs.t.Write(cs.s, hdr, data, &transport.Options{Last: false}) - if err == nil && outPayload != nil { - outPayload.SentTime = time.Now() - cs.statsHandler.HandleRPC(cs.statsCtx, outPayload) + if !cs.desc.ClientStreams { + cs.sentLast = true } - return err + err = a.t.Write(a.s, hdr, data, &transport.Options{Last: !cs.desc.ClientStreams}) + if err == nil { + if outPayload != nil { + outPayload.SentTime = time.Now() + a.statsHandler.HandleRPC(a.ctx, outPayload) + } + return nil + } + return io.EOF } -func (cs *clientStream) RecvMsg(m interface{}) (err error) { +func (a *csAttempt) recvMsg(m interface{}) (err error) { + cs := a.cs + defer func() { + if err != nil || !cs.desc.ServerStreams { + // err != nil or non-server-streaming indicates end of stream. + cs.finish(err) + } + }() var inPayload *stats.InPayload - if cs.statsHandler != nil { + if a.statsHandler != nil { inPayload = &stats.InPayload{ Client: true, } } - if cs.c.maxReceiveMessageSize == nil { - return Errorf(codes.Internal, "callInfo maxReceiveMessageSize field uninitialized(nil)") - } - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, *cs.c.maxReceiveMessageSize, inPayload) - defer func() { - // err != nil indicates the termination of the stream. - if err != nil { - cs.finish(err) - } - }() - if err == nil { - if cs.tracing { - cs.mu.Lock() - if cs.trInfo.tr != nil { - cs.trInfo.tr.LazyLog(&payload{sent: false, msg: m}, true) + if !a.decompSet { + // Block until we receive headers containing received message encoding. + if ct := a.s.RecvCompress(); ct != "" && ct != encoding.Identity { + if a.dc == nil || a.dc.Type() != ct { + // No configured decompressor, or it does not match the incoming + // message encoding; attempt to find a registered compressor that does. + a.dc = nil + a.decomp = encoding.GetCompressor(ct) } - cs.mu.Unlock() - } - if inPayload != nil { - cs.statsHandler.HandleRPC(cs.statsCtx, inPayload) - } - if !cs.desc.ClientStreams || cs.desc.ServerStreams { - return - } - // Special handling for client streaming rpc. - // This recv expects EOF or errors, so we don't collect inPayload. - if cs.c.maxReceiveMessageSize == nil { - return Errorf(codes.Internal, "callInfo maxReceiveMessageSize field uninitialized(nil)") - } - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, *cs.c.maxReceiveMessageSize, nil) - cs.closeTransportStream(err) - if err == nil { - return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) + } else { + // No compression is used; disable our decompressor. + a.dc = nil } + // Only initialize this state once per stream. + a.decompSet = true + } + err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.c.maxReceiveMessageSize, inPayload, a.decomp) + if err != nil { if err == io.EOF { - if se := cs.s.Status().Err(); se != nil { - return se + if statusErr := a.s.Status().Err(); statusErr != nil { + return statusErr } - cs.finish(err) - return nil + return io.EOF // indicates successful end of stream. } return toRPCErr(err) } - if _, ok := err.(transport.ConnectionError); !ok { - cs.closeTransportStream(err) - } - if err == io.EOF { - if statusErr := cs.s.Status().Err(); statusErr != nil { - return statusErr + if EnableTracing { + a.mu.Lock() + if a.trInfo.tr != nil { + a.trInfo.tr.LazyLog(&payload{sent: false, msg: m}, true) } - // Returns io.EOF to indicate the end of the stream. - return + a.mu.Unlock() } - return toRPCErr(err) -} - -func (cs *clientStream) CloseSend() (err error) { - err = cs.t.Write(cs.s, nil, nil, &transport.Options{Last: true}) - defer func() { - if err != nil { - cs.finish(err) - } - }() - if err == nil || err == io.EOF { + if inPayload != nil { + a.statsHandler.HandleRPC(a.ctx, inPayload) + } + if cs.desc.ServerStreams { + // Subsequent messages should be received by subsequent RecvMsg calls. return nil } - if _, ok := err.(transport.ConnectionError); !ok { - cs.closeTransportStream(err) + + // Special handling for non-server-stream rpcs. + // This recv expects EOF or errors, so we don't collect inPayload. + err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.c.maxReceiveMessageSize, nil, a.decomp) + if err == nil { + return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) } - err = toRPCErr(err) - return + if err == io.EOF { + return a.s.Status().Err() // non-server streaming Recv returns nil on success + } + return toRPCErr(err) } -func (cs *clientStream) closeTransportStream(err error) { - cs.mu.Lock() - if cs.closed { - cs.mu.Unlock() +func (a *csAttempt) closeSend() { + cs := a.cs + if cs.sentLast { return } - cs.closed = true - cs.mu.Unlock() - cs.t.CloseStream(cs.s, err) + cs.sentLast = true + cs.attempt.t.Write(cs.attempt.s, nil, nil, &transport.Options{Last: true}) + // We ignore errors from Write. Any error it would return would also be + // returned by a subsequent RecvMsg call, and the user is supposed to always + // finish the stream by calling RecvMsg until it returns err != nil. } -func (cs *clientStream) finish(err error) { - cs.mu.Lock() - defer cs.mu.Unlock() - if cs.finished { - return - } - cs.finished = true - defer func() { - if cs.cancel != nil { - cs.cancel() - } - }() - for _, o := range cs.opts { - o.after(cs.c) - } - if cs.done != nil { - updateRPCInfoInContext(cs.s.Context(), rpcInfo{ - bytesSent: cs.s.BytesSent(), - bytesReceived: cs.s.BytesReceived(), +func (a *csAttempt) finish(err error) { + a.mu.Lock() + a.t.CloseStream(a.s, err) + + if a.done != nil { + a.done(balancer.DoneInfo{ + Err: err, + BytesSent: true, + BytesReceived: a.s.BytesReceived(), }) - cs.done(balancer.DoneInfo{Err: err}) - cs.done = nil } - if cs.statsHandler != nil { + if a.statsHandler != nil { end := &stats.End{ - Client: true, - EndTime: time.Now(), - } - if err != io.EOF { - // end.Error is nil if the RPC finished successfully. - end.Error = toRPCErr(err) + Client: true, + BeginTime: a.beginTime, + EndTime: time.Now(), + Error: err, } - cs.statsHandler.HandleRPC(cs.statsCtx, end) + a.statsHandler.HandleRPC(a.ctx, end) } - if !cs.tracing { - return - } - if cs.trInfo.tr != nil { - if err == nil || err == io.EOF { - cs.trInfo.tr.LazyPrintf("RPC: [OK]") + if a.trInfo.tr != nil { + if err == nil { + a.trInfo.tr.LazyPrintf("RPC: [OK]") } else { - cs.trInfo.tr.LazyPrintf("RPC: [%v]", err) - cs.trInfo.tr.SetError() + a.trInfo.tr.LazyPrintf("RPC: [%v]", err) + a.trInfo.tr.SetError() } - cs.trInfo.tr.Finish() - cs.trInfo.tr = nil + a.trInfo.tr.Finish() + a.trInfo.tr = nil } + a.mu.Unlock() } // ServerStream defines the interface a server stream has to satisfy. @@ -540,12 +608,17 @@ type ServerStream interface { // serverStream implements a server side Stream. type serverStream struct { - t transport.ServerTransport - s *transport.Stream - p *parser - codec Codec - cp Compressor - dc Decompressor + ctx context.Context + t transport.ServerTransport + s *transport.Stream + p *parser + codec baseCodec + + cp Compressor + dc Decompressor + comp encoding.Compressor + decomp encoding.Compressor + maxReceiveMessageSize int maxSendMessageSize int trInfo *traceInfo @@ -556,7 +629,7 @@ type serverStream struct { } func (ss *serverStream) Context() context.Context { - return ss.s.Context() + return ss.ctx } func (ss *serverStream) SetHeader(md metadata.MD) error { @@ -601,12 +674,12 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { if ss.statsHandler != nil { outPayload = &stats.OutPayload{} } - hdr, data, err := encode(ss.codec, m, ss.cp, bytes.NewBuffer([]byte{}), outPayload) + hdr, data, err := encode(ss.codec, m, ss.cp, outPayload, ss.comp) if err != nil { return err } if len(data) > ss.maxSendMessageSize { - return Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(data), ss.maxSendMessageSize) + return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", len(data), ss.maxSendMessageSize) } if err := ss.t.Write(ss.s, hdr, data, &transport.Options{Last: false}); err != nil { return toRPCErr(err) @@ -641,12 +714,12 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { if ss.statsHandler != nil { inPayload = &stats.InPayload{} } - if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxReceiveMessageSize, inPayload); err != nil { + if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxReceiveMessageSize, inPayload, ss.decomp); err != nil { if err == io.EOF { return err } if err == io.ErrUnexpectedEOF { - err = Errorf(codes.Internal, io.ErrUnexpectedEOF.Error()) + err = status.Errorf(codes.Internal, io.ErrUnexpectedEOF.Error()) } return toRPCErr(err) } @@ -655,3 +728,13 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { } return nil } + +// MethodFromServerStream returns the method string for the input stream. +// The returned string is in the format of "/service/method". +func MethodFromServerStream(stream ServerStream) (string, bool) { + s := serverTransportStreamFromContext(stream.Context()) + if s == nil { + return "", false + } + return s.Method(), true +} diff --git a/vendor/google.golang.org/grpc/transport/bdp_estimator.go b/vendor/google.golang.org/grpc/transport/bdp_estimator.go index 8dd2ed42792..63cd2627c87 100644 --- a/vendor/google.golang.org/grpc/transport/bdp_estimator.go +++ b/vendor/google.golang.org/grpc/transport/bdp_estimator.go @@ -41,12 +41,9 @@ const ( gamma = 2 ) -var ( - // Adding arbitrary data to ping so that its ack can be - // identified. - // Easter-egg: what does the ping message say? - bdpPing = &ping{data: [8]byte{2, 4, 16, 16, 9, 14, 7, 7}} -) +// Adding arbitrary data to ping so that its ack can be identified. +// Easter-egg: what does the ping message say? +var bdpPing = &ping{data: [8]byte{2, 4, 16, 16, 9, 14, 7, 7}} type bdpEstimator struct { // sentAt is the time when the ping was sent. diff --git a/vendor/google.golang.org/grpc/transport/control.go b/vendor/google.golang.org/grpc/transport/control.go index dd1a8d42e7e..0474b09074b 100644 --- a/vendor/google.golang.org/grpc/transport/control.go +++ b/vendor/google.golang.org/grpc/transport/control.go @@ -20,9 +20,9 @@ package transport import ( "fmt" + "io" "math" "sync" - "sync/atomic" "time" "golang.org/x/net/http2" @@ -49,7 +49,7 @@ const ( // defaultLocalSendQuota sets is default value for number of data // bytes that each stream can schedule before some of it being // flushed out. - defaultLocalSendQuota = 64 * 1024 + defaultLocalSendQuota = 128 * 1024 ) // The following defines various control items which could flow through @@ -89,12 +89,16 @@ type windowUpdate struct { func (*windowUpdate) item() {} type settings struct { - ack bool - ss []http2.Setting + ss []http2.Setting } func (*settings) item() {} +type settingsAck struct { +} + +func (*settingsAck) item() {} + type resetStream struct { streamID uint32 code http2.ErrCode @@ -112,6 +116,7 @@ type goAway struct { func (*goAway) item() {} type flushIO struct { + closeTr bool } func (*flushIO) item() {} @@ -126,9 +131,8 @@ func (*ping) item() {} // quotaPool is a pool which accumulates the quota and sends it to acquire() // when it is available. type quotaPool struct { - c chan int - mu sync.Mutex + c chan struct{} version uint32 quota int } @@ -136,12 +140,8 @@ type quotaPool struct { // newQuotaPool creates a quotaPool which has quota q available to consume. func newQuotaPool(q int) *quotaPool { qb := "aPool{ - c: make(chan int, 1), - } - if q > 0 { - qb.c <- q - } else { - qb.quota = q + quota: q, + c: make(chan struct{}, 1), } return qb } @@ -155,60 +155,83 @@ func (qb *quotaPool) add(v int) { } func (qb *quotaPool) lockedAdd(v int) { - select { - case n := <-qb.c: - qb.quota += n - default: - } - qb.quota += v + var wakeUp bool if qb.quota <= 0 { - return + wakeUp = true // Wake up potential waiters. } - // After the pool has been created, this is the only place that sends on - // the channel. Since mu is held at this point and any quota that was sent - // on the channel has been retrieved, we know that this code will always - // place any positive quota value on the channel. - select { - case qb.c <- qb.quota: - qb.quota = 0 - default: + qb.quota += v + if wakeUp && qb.quota > 0 { + select { + case qb.c <- struct{}{}: + default: + } } } func (qb *quotaPool) addAndUpdate(v int) { qb.mu.Lock() - defer qb.mu.Unlock() qb.lockedAdd(v) - // Update the version only after having added to the quota - // so that if acquireWithVesrion sees the new vesrion it is - // guaranteed to have seen the updated quota. - // Also, still keep this inside of the lock, so that when - // compareAndExecute is processing, this function doesn't - // get executed partially (quota gets updated but the version - // doesn't). - atomic.AddUint32(&(qb.version), 1) + qb.version++ + qb.mu.Unlock() } -func (qb *quotaPool) acquireWithVersion() (<-chan int, uint32) { - return qb.c, atomic.LoadUint32(&(qb.version)) +func (qb *quotaPool) get(v int, wc waiters) (int, uint32, error) { + qb.mu.Lock() + if qb.quota > 0 { + if v > qb.quota { + v = qb.quota + } + qb.quota -= v + ver := qb.version + qb.mu.Unlock() + return v, ver, nil + } + qb.mu.Unlock() + for { + select { + case <-wc.ctx.Done(): + return 0, 0, ContextErr(wc.ctx.Err()) + case <-wc.tctx.Done(): + return 0, 0, ErrConnClosing + case <-wc.done: + return 0, 0, io.EOF + case <-wc.goAway: + return 0, 0, errStreamDrain + case <-qb.c: + qb.mu.Lock() + if qb.quota > 0 { + if v > qb.quota { + v = qb.quota + } + qb.quota -= v + ver := qb.version + if qb.quota > 0 { + select { + case qb.c <- struct{}{}: + default: + } + } + qb.mu.Unlock() + return v, ver, nil + + } + qb.mu.Unlock() + } + } } func (qb *quotaPool) compareAndExecute(version uint32, success, failure func()) bool { qb.mu.Lock() - defer qb.mu.Unlock() - if version == atomic.LoadUint32(&(qb.version)) { + if version == qb.version { success() + qb.mu.Unlock() return true } failure() + qb.mu.Unlock() return false } -// acquire returns the channel on which available quota amounts are sent. -func (qb *quotaPool) acquire() <-chan int { - return qb.c -} - // inFlow deals with inbound flow control type inFlow struct { mu sync.Mutex diff --git a/vendor/google.golang.org/grpc/transport/go16.go b/vendor/google.golang.org/grpc/transport/go16.go new file mode 100644 index 00000000000..5babcf9b877 --- /dev/null +++ b/vendor/google.golang.org/grpc/transport/go16.go @@ -0,0 +1,51 @@ +// +build go1.6,!go1.7 + +/* + * + * Copyright 2016 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 transport + +import ( + "net" + "net/http" + + "google.golang.org/grpc/codes" + + "golang.org/x/net/context" +) + +// dialContext connects to the address on the named network. +func dialContext(ctx context.Context, network, address string) (net.Conn, error) { + return (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address) +} + +// ContextErr converts the error from context package into a StreamError. +func ContextErr(err error) StreamError { + switch err { + case context.DeadlineExceeded: + return streamErrorf(codes.DeadlineExceeded, "%v", err) + case context.Canceled: + return streamErrorf(codes.Canceled, "%v", err) + } + return streamErrorf(codes.Internal, "Unexpected error from context packet: %v", err) +} + +// contextFromRequest returns a background context. +func contextFromRequest(r *http.Request) context.Context { + return context.Background() +} diff --git a/vendor/google.golang.org/grpc/transport/go17.go b/vendor/google.golang.org/grpc/transport/go17.go new file mode 100644 index 00000000000..b7fa6bdb9ca --- /dev/null +++ b/vendor/google.golang.org/grpc/transport/go17.go @@ -0,0 +1,52 @@ +// +build go1.7 + +/* + * + * Copyright 2016 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 transport + +import ( + "context" + "net" + "net/http" + + "google.golang.org/grpc/codes" + + netctx "golang.org/x/net/context" +) + +// dialContext connects to the address on the named network. +func dialContext(ctx context.Context, network, address string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, address) +} + +// ContextErr converts the error from context package into a StreamError. +func ContextErr(err error) StreamError { + switch err { + case context.DeadlineExceeded, netctx.DeadlineExceeded: + return streamErrorf(codes.DeadlineExceeded, "%v", err) + case context.Canceled, netctx.Canceled: + return streamErrorf(codes.Canceled, "%v", err) + } + return streamErrorf(codes.Internal, "Unexpected error from context packet: %v", err) +} + +// contextFromRequest returns a context from the HTTP Request. +func contextFromRequest(r *http.Request) context.Context { + return r.Context() +} diff --git a/vendor/google.golang.org/grpc/transport/handler_server.go b/vendor/google.golang.org/grpc/transport/handler_server.go index 7e0fdb35938..1a5e96c5a17 100644 --- a/vendor/google.golang.org/grpc/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/transport/handler_server.go @@ -40,20 +40,24 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + "google.golang.org/grpc/stats" "google.golang.org/grpc/status" ) // NewServerHandlerTransport returns a ServerTransport handling gRPC // from inside an http.Handler. It requires that the http Server // supports HTTP/2. -func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTransport, error) { +func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats stats.Handler) (ServerTransport, error) { if r.ProtoMajor != 2 { return nil, errors.New("gRPC requires HTTP/2") } if r.Method != "POST" { return nil, errors.New("invalid gRPC request method") } - if !validContentType(r.Header.Get("Content-Type")) { + contentType := r.Header.Get("Content-Type") + // TODO: do we assume contentType is lowercase? we did before + contentSubtype, validContentType := contentSubtype(contentType) + if !validContentType { return nil, errors.New("invalid gRPC request content-type") } if _, ok := w.(http.Flusher); !ok { @@ -64,10 +68,13 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTr } st := &serverHandlerTransport{ - rw: w, - req: r, - closedCh: make(chan struct{}), - writes: make(chan func()), + rw: w, + req: r, + closedCh: make(chan struct{}), + writes: make(chan func()), + contentType: contentType, + contentSubtype: contentSubtype, + stats: stats, } if v := r.Header.Get("grpc-timeout"); v != "" { @@ -79,7 +86,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTr st.timeout = to } - var metakv []string + metakv := []string{"content-type", contentType} if r.Host != "" { metakv = append(metakv, ":authority", r.Host) } @@ -91,7 +98,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTr for _, v := range vv { v, err := decodeMetadataHeader(k, v) if err != nil { - return nil, streamErrorf(codes.InvalidArgument, "malformed binary metadata: %v", err) + return nil, streamErrorf(codes.Internal, "malformed binary metadata: %v", err) } metakv = append(metakv, k, v) } @@ -126,6 +133,14 @@ type serverHandlerTransport struct { // block concurrent WriteStatus calls // e.g. grpc/(*serverStream).SendMsg/RecvMsg writeStatusMu sync.Mutex + + // we just mirror the request content-type + contentType string + // we store both contentType and contentSubtype so we don't keep recreating them + // TODO make sure this is consistent across handler_server and http2_server + contentSubtype string + + stats stats.Handler } func (ht *serverHandlerTransport) Close() error { @@ -219,6 +234,9 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro }) if err == nil { // transport has not been closed + if ht.stats != nil { + ht.stats.HandleRPC(s.Context(), &stats.OutTrailer{}) + } ht.Close() close(ht.writes) } @@ -235,7 +253,7 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) { h := ht.rw.Header() h["Date"] = nil // suppress Date to make tests happy; TODO: restore - h.Set("Content-Type", "application/grpc") + h.Set("Content-Type", ht.contentType) // Predeclare trailers we'll set later in WriteStatus (after the body). // This is a SHOULD in the HTTP RFC, and the way you add (known) @@ -263,7 +281,7 @@ func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts } func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { - return ht.do(func() { + err := ht.do(func() { ht.writeCommonHeaders(s) h := ht.rw.Header() for k, vv := range md { @@ -279,17 +297,24 @@ func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { ht.rw.WriteHeader(200) ht.rw.(http.Flusher).Flush() }) + + if err == nil { + if ht.stats != nil { + ht.stats.HandleRPC(s.Context(), &stats.OutHeader{}) + } + } + return err } func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), traceCtx func(context.Context, string) context.Context) { // With this transport type there will be exactly 1 stream: this HTTP request. - var ctx context.Context + ctx := contextFromRequest(ht.req) var cancel context.CancelFunc if ht.timeoutSet { - ctx, cancel = context.WithTimeout(context.Background(), ht.timeout) + ctx, cancel = context.WithTimeout(ctx, ht.timeout) } else { - ctx, cancel = context.WithCancel(context.Background()) + ctx, cancel = context.WithCancel(ctx) } // requestOver is closed when either the request's context is done @@ -313,13 +338,14 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace req := ht.req s := &Stream{ - id: 0, // irrelevant - requestRead: func(int) {}, - cancel: cancel, - buf: newRecvBuffer(), - st: ht, - method: req.URL.Path, - recvCompress: req.Header.Get("grpc-encoding"), + id: 0, // irrelevant + requestRead: func(int) {}, + cancel: cancel, + buf: newRecvBuffer(), + st: ht, + method: req.URL.Path, + recvCompress: req.Header.Get("grpc-encoding"), + contentSubtype: ht.contentSubtype, } pr := &peer.Peer{ Addr: ht.RemoteAddr(), @@ -328,8 +354,16 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace pr.AuthInfo = credentials.TLSInfo{State: *req.TLS} } ctx = metadata.NewIncomingContext(ctx, ht.headerMD) - ctx = peer.NewContext(ctx, pr) - s.ctx = newContextWithStream(ctx, s) + s.ctx = peer.NewContext(ctx, pr) + if ht.stats != nil { + s.ctx = ht.stats.TagRPC(s.ctx, &stats.RPCTagInfo{FullMethodName: s.method}) + inHeader := &stats.InHeader{ + FullMethod: s.method, + RemoteAddr: ht.RemoteAddr(), + Compression: s.recvCompress, + } + ht.stats.HandleRPC(s.ctx, inHeader) + } s.trReader = &transportReader{ reader: &recvBufferReader{ctx: s.ctx, recv: s.buf}, windowHandler: func(int) {}, diff --git a/vendor/google.golang.org/grpc/transport/http2_client.go b/vendor/google.golang.org/grpc/transport/http2_client.go index 1abb62e6df4..8b5be0d6d51 100644 --- a/vendor/google.golang.org/grpc/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/transport/http2_client.go @@ -20,6 +20,7 @@ package transport import ( "bytes" + "fmt" "io" "math" "net" @@ -44,7 +45,6 @@ import ( type http2Client struct { ctx context.Context cancel context.CancelFunc - target string // server name/addr userAgent string md interface{} conn net.Conn // underlying communication channel @@ -69,6 +69,9 @@ type http2Client struct { fc *inFlow // sendQuotaPool provides flow control to outbound message. sendQuotaPool *quotaPool + // localSendQuota limits the amount of data that can be scheduled + // for writing before it is actually written out. + localSendQuota *quotaPool // streamsQuota limits the max number of concurrent streams. streamsQuota *quotaPool @@ -91,6 +94,11 @@ type http2Client struct { bdpEst *bdpEstimator outQuotaVersion uint32 + // onSuccess is a callback that client transport calls upon + // receiving server preface to signal that a succefull HTTP2 + // connection was established. + onSuccess func() + mu sync.Mutex // guard the following variables state transportState // the state of underlying connection activeStreams map[uint32]*Stream @@ -109,22 +117,10 @@ func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error if fn != nil { return fn(ctx, addr) } - return (&net.Dialer{}).DialContext(ctx, "tcp", addr) + return dialContext(ctx, "tcp", addr) } func isTemporary(err error) bool { - switch err { - case io.EOF: - // Connection closures may be resolved upon retry, and are thus - // treated as temporary. - return true - case context.DeadlineExceeded: - // In Go 1.7, context.DeadlineExceeded implements Timeout(), and this - // special case is not needed. Until then, we need to keep this - // clause. - return true - } - switch err := err.(type) { case interface { Temporary() bool @@ -137,18 +133,16 @@ func isTemporary(err error) bool { // temporary. return err.Timeout() } - return false + return true } // newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // and starts to receive messages on it. Non-nil error returns if construction // fails. -func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, timeout time.Duration) (_ ClientTransport, err error) { +func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts ConnectOptions, onSuccess func()) (_ ClientTransport, err error) { scheme := "http" ctx, cancel := context.WithCancel(ctx) - connectCtx, connectCancel := context.WithTimeout(ctx, timeout) defer func() { - connectCancel() if err != nil { cancel() } @@ -173,12 +167,9 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, t ) if creds := opts.TransportCredentials; creds != nil { scheme = "https" - conn, authInfo, err = creds.ClientHandshake(connectCtx, addr.Addr, conn) + conn, authInfo, err = creds.ClientHandshake(connectCtx, addr.Authority, conn) if err != nil { - // Credentials handshake errors are typically considered permanent - // to avoid retrying on e.g. bad certificates. - temp := isTemporary(err) - return nil, connectionErrorf(temp, err, "transport: authentication handshake failed: %v", err) + return nil, connectionErrorf(isTemporary(err), err, "transport: authentication handshake failed: %v", err) } isSecure = true } @@ -208,7 +199,6 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, t t := &http2Client{ ctx: ctx, cancel: cancel, - target: addr.Addr, userAgent: opts.UserAgent, md: addr.Metadata, conn: conn, @@ -225,6 +215,7 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, t controlBuf: newControlBuffer(), fc: &inFlow{limit: uint32(icwz)}, sendQuotaPool: newQuotaPool(defaultWindowSize), + localSendQuota: newQuotaPool(defaultLocalSendQuota), scheme: scheme, state: reachable, activeStreams: make(map[uint32]*Stream), @@ -236,6 +227,7 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, t kp: kp, statsHandler: opts.StatsHandler, initialWindowSize: initialWindowSize, + onSuccess: onSuccess, } if opts.InitialWindowSize >= defaultWindowSize { t.initialWindowSize = opts.InitialWindowSize @@ -296,7 +288,7 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions, t t.framer.writer.Flush() go func() { loopyWriter(t.ctx, t.controlBuf, t.itemHandler) - t.Close() + t.conn.Close() }() if t.kp.Time != infinity { go t.keepalive() @@ -315,8 +307,8 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { buf: newRecvBuffer(), fc: &inFlow{limit: uint32(t.initialWindowSize)}, sendQuotaPool: newQuotaPool(int(t.streamSendQuota)), - localSendQuota: newQuotaPool(defaultLocalSendQuota), headerChan: make(chan struct{}), + contentSubtype: callHdr.ContentSubtype, } t.nextID += 2 s.requestRead = func(n int) { @@ -336,7 +328,12 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { t.updateWindow(s, uint32(n)) }, } - + s.waiters = waiters{ + ctx: s.ctx, + tctx: t.ctx, + done: s.done, + goAway: s.goAway, + } return s } @@ -369,7 +366,11 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea for _, c := range t.creds { data, err := c.GetRequestMetadata(ctx, audience) if err != nil { - return nil, streamErrorf(codes.Internal, "transport: %v", err) + if _, ok := status.FromError(err); ok { + return nil, err + } + + return nil, streamErrorf(codes.Unauthenticated, "transport: %v", err) } for k, v := range data { // Capital header names are illegal in HTTP/2. @@ -402,22 +403,18 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea } if t.state == draining { t.mu.Unlock() - return nil, ErrStreamDrain + return nil, errStreamDrain } if t.state != reachable { t.mu.Unlock() return nil, ErrConnClosing } t.mu.Unlock() - sq, err := wait(ctx, t.ctx, nil, nil, t.streamsQuota.acquire()) - if err != nil { + // Get a quota of 1 from streamsQuota. + if _, _, err := t.streamsQuota.get(1, waiters{ctx: ctx, tctx: t.ctx}); err != nil { return nil, err } - // Returns the quota balance back. - if sq > 1 { - t.streamsQuota.add(sq - 1) - } - // TODO(mmukhi): Benchmark if the perfomance gets better if count the metadata and other header fields + // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields // first and create a slice of that exact size. // Make the slice of certain predictable size to reduce allocations made by append. hfLen := 7 // :method, :scheme, :path, :authority, content-type, user-agent, te @@ -427,7 +424,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea headerFields = append(headerFields, hpack.HeaderField{Name: ":scheme", Value: t.scheme}) headerFields = append(headerFields, hpack.HeaderField{Name: ":path", Value: callHdr.Method}) headerFields = append(headerFields, hpack.HeaderField{Name: ":authority", Value: callHdr.Host}) - headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: "application/grpc"}) + headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(callHdr.ContentSubtype)}) headerFields = append(headerFields, hpack.HeaderField{Name: "user-agent", Value: t.userAgent}) headerFields = append(headerFields, hpack.HeaderField{Name: "te", Value: "trailers"}) @@ -452,7 +449,22 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea if b := stats.OutgoingTrace(ctx); b != nil { headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-trace-bin", Value: encodeBinHeader(b)}) } - if md, ok := metadata.FromOutgoingContext(ctx); ok { + + if md, added, ok := metadata.FromOutgoingContextRaw(ctx); ok { + var k string + for _, vv := range added { + for i, v := range vv { + if i%2 == 0 { + k = v + continue + } + // HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set. + if isReservedHeader(k) { + continue + } + headerFields = append(headerFields, hpack.HeaderField{Name: strings.ToLower(k), Value: encodeMetadataHeader(k, v)}) + } + } for k, vv := range md { // HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set. if isReservedHeader(k) { @@ -477,7 +489,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea if t.state == draining { t.mu.Unlock() t.streamsQuota.add(1) - return nil, ErrStreamDrain + return nil, errStreamDrain } if t.state != reachable { t.mu.Unlock() @@ -505,10 +517,6 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea }) t.mu.Unlock() - s.mu.Lock() - s.bytesSent = true - s.mu.Unlock() - if t.statsHandler != nil { outHeader := &stats.OutHeader{ Client: true, @@ -573,7 +581,7 @@ func (t *http2Client) CloseStream(s *Stream, err error) { } s.state = streamDone s.mu.Unlock() - if _, ok := err.(StreamError); ok { + if err != nil && !rstStream { rstStream = true rstError = http2.ErrCodeCancel } @@ -582,16 +590,16 @@ func (t *http2Client) CloseStream(s *Stream, err error) { // Close kicks off the shutdown process of the transport. This should be called // only once on a transport. Once it is called, the transport should not be // accessed any more. -func (t *http2Client) Close() (err error) { +func (t *http2Client) Close() error { t.mu.Lock() if t.state == closing { t.mu.Unlock() - return + return nil } t.state = closing t.mu.Unlock() t.cancel() - err = t.conn.Close() + err := t.conn.Close() t.mu.Lock() streams := t.activeStreams t.activeStreams = nil @@ -642,6 +650,8 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) e select { case <-s.ctx.Done(): return ContextErr(s.ctx.Err()) + case <-s.done: + return io.EOF case <-t.ctx.Done(): return ErrConnClosing default: @@ -659,44 +669,46 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) e } hdr = append(hdr, data[:emptyLen]...) data = data[emptyLen:] + var ( + streamQuota int + streamQuotaVer uint32 + err error + ) for idx, r := range [][]byte{hdr, data} { for len(r) > 0 { size := http2MaxFrameLen - // Wait until the stream has some quota to send the data. - quotaChan, quotaVer := s.sendQuotaPool.acquireWithVersion() - sq, err := wait(s.ctx, t.ctx, s.done, s.goAway, quotaChan) - if err != nil { - return err + if size > len(r) { + size = len(r) + } + if streamQuota == 0 { // Used up all the locally cached stream quota. + // Get all the stream quota there is. + streamQuota, streamQuotaVer, err = s.sendQuotaPool.get(math.MaxInt32, s.waiters) + if err != nil { + return err + } + } + if size > streamQuota { + size = streamQuota } - // Wait until the transport has some quota to send the data. - tq, err := wait(s.ctx, t.ctx, s.done, s.goAway, t.sendQuotaPool.acquire()) + + // Get size worth quota from transport. + tq, _, err := t.sendQuotaPool.get(size, s.waiters) if err != nil { return err } - if sq < size { - size = sq - } if tq < size { size = tq } - if size > len(r) { - size = len(r) - } - p := r[:size] - ps := len(p) - if ps < tq { - // Overbooked transport quota. Return it back. - t.sendQuotaPool.add(tq - ps) - } - // Acquire local send quota to be able to write to the controlBuf. - ltq, err := wait(s.ctx, t.ctx, s.done, s.goAway, s.localSendQuota.acquire()) + ltq, _, err := t.localSendQuota.get(size, s.waiters) if err != nil { - if _, ok := err.(ConnectionError); !ok { - t.sendQuotaPool.add(ps) - } + // Add the acquired quota back to transport. + t.sendQuotaPool.add(tq) return err } - s.localSendQuota.add(ltq - ps) // It's ok if we make it negative. + // even if ltq is smaller than size we don't adjust size since + // ltq is only a soft limit. + streamQuota -= size + p := r[:size] var endStream bool // See if this is the last frame to be written. if opts.Last { @@ -711,21 +723,25 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) e } } success := func() { - t.controlBuf.put(&dataFrame{streamID: s.id, endStream: endStream, d: p, f: func() { s.localSendQuota.add(ps) }}) - if ps < sq { - s.sendQuotaPool.lockedAdd(sq - ps) - } - r = r[ps:] + ltq := ltq + t.controlBuf.put(&dataFrame{streamID: s.id, endStream: endStream, d: p, f: func() { t.localSendQuota.add(ltq) }}) + r = r[size:] } - failure := func() { - s.sendQuotaPool.lockedAdd(sq) + failure := func() { // The stream quota version must have changed. + // Our streamQuota cache is invalidated now, so give it back. + s.sendQuotaPool.lockedAdd(streamQuota + size) } - if !s.sendQuotaPool.compareAndExecute(quotaVer, success, failure) { - t.sendQuotaPool.add(ps) - s.localSendQuota.add(ps) + if !s.sendQuotaPool.compareAndExecute(streamQuotaVer, success, failure) { + // Couldn't send this chunk out. + t.sendQuotaPool.add(size) + t.localSendQuota.add(ltq) + streamQuota = 0 } } } + if streamQuota > 0 { // Add the left over quota back to stream. + s.sendQuotaPool.add(streamQuota) + } if !opts.Last { return nil } @@ -791,7 +807,6 @@ func (t *http2Client) updateFlowControl(n uint32) { t.mu.Unlock() t.controlBuf.put(&windowUpdate{0, t.fc.newLimit(n)}) t.controlBuf.put(&settings{ - ack: false, ss: []http2.Setting{ { ID: http2.SettingInitialWindowSize, @@ -894,7 +909,13 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) { close(s.headerChan) s.headerDone = true } - statusCode, ok := http2ErrConvTab[http2.ErrCode(f.ErrCode)] + + code := http2.ErrCode(f.ErrCode) + if code == http2.ErrCodeRefusedStream { + // The stream was unprocessed by the server. + s.unprocessed = true + } + statusCode, ok := http2ErrConvTab[code] if !ok { warningf("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error %v", f.ErrCode) statusCode = codes.Unknown @@ -904,17 +925,48 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) { s.write(recvMsg{err: io.EOF}) } -func (t *http2Client) handleSettings(f *http2.SettingsFrame) { +func (t *http2Client) handleSettings(f *http2.SettingsFrame, isFirst bool) { if f.IsAck() { return } - var ss []http2.Setting + var rs []http2.Setting + var ps []http2.Setting + isMaxConcurrentStreamsMissing := true f.ForeachSetting(func(s http2.Setting) error { - ss = append(ss, s) + if s.ID == http2.SettingMaxConcurrentStreams { + isMaxConcurrentStreamsMissing = false + } + if t.isRestrictive(s) { + rs = append(rs, s) + } else { + ps = append(ps, s) + } return nil }) - // The settings will be applied once the ack is sent. - t.controlBuf.put(&settings{ack: true, ss: ss}) + if isFirst && isMaxConcurrentStreamsMissing { + // This means server is imposing no limits on + // maximum number of concurrent streams initiated by client. + // So we must remove our self-imposed limit. + ps = append(ps, http2.Setting{ + ID: http2.SettingMaxConcurrentStreams, + Val: math.MaxUint32, + }) + } + t.applySettings(rs) + t.controlBuf.put(&settingsAck{}) + t.applySettings(ps) +} + +func (t *http2Client) isRestrictive(s http2.Setting) bool { + switch s.ID { + case http2.SettingMaxConcurrentStreams: + return int(s.Val) < t.maxStreams + case http2.SettingInitialWindowSize: + // Note: we don't acquire a lock here to read streamSendQuota + // because the same goroutine updates it later. + return s.Val < t.streamSendQuota + } + return false } func (t *http2Client) handlePing(f *http2.PingFrame) { @@ -945,12 +997,16 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { t.Close() return } - // A client can receive multiple GoAways from server (look at https://github.com/grpc/grpc-go/issues/1387). - // The idea is that the first GoAway will be sent with an ID of MaxInt32 and the second GoAway will be sent after an RTT delay - // with the ID of the last stream the server will process. - // Therefore, when we get the first GoAway we don't really close any streams. While in case of second GoAway we - // close all streams created after the second GoAwayId. This way streams that were in-flight while the GoAway from server - // was being sent don't get killed. + // A client can receive multiple GoAways from the server (see + // https://github.com/grpc/grpc-go/issues/1387). The idea is that the first + // GoAway will be sent with an ID of MaxInt32 and the second GoAway will be + // sent after an RTT delay with the ID of the last stream the server will + // process. + // + // Therefore, when we get the first GoAway we don't necessarily close any + // streams. While in case of second GoAway we close all streams created after + // the GoAwayId. This way streams that were in-flight while the GoAway from + // server was being sent don't get killed. select { case <-t.goAway: // t.goAway has been closed (i.e.,multiple GoAways). // If there are multiple GoAways the first one should always have an ID greater than the following ones. @@ -972,6 +1028,11 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { } for streamID, stream := range t.activeStreams { if streamID > id && streamID <= upperLimit { + // The stream was unprocessed by the server. + stream.mu.Lock() + stream.unprocessed = true + stream.finish(statusGoAway) + stream.mu.Unlock() close(stream.goAway) } } @@ -988,11 +1049,11 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { // It expects a lock on transport's mutext to be held by // the caller. func (t *http2Client) setGoAwayReason(f *http2.GoAwayFrame) { - t.goAwayReason = NoReason + t.goAwayReason = GoAwayNoReason switch f.ErrCode { case http2.ErrCodeEnhanceYourCalm: if string(f.DebugData()) == "too_many_pings" { - t.goAwayReason = TooManyPings + t.goAwayReason = GoAwayTooManyPings } } } @@ -1058,22 +1119,22 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { }() s.mu.Lock() - if !endStream { - s.recvCompress = state.encoding - } if !s.headerDone { - if !endStream && len(state.mdata) > 0 { - s.header = state.mdata + if !endStream { + // Headers frame is not actually a trailers-only frame. + isHeader = true + s.recvCompress = state.encoding + if len(state.mdata) > 0 { + s.header = state.mdata + } } close(s.headerChan) s.headerDone = true - isHeader = true } if !endStream || s.state == streamDone { s.mu.Unlock() return } - if len(state.mdata) > 0 { s.trailer = state.mdata } @@ -1111,7 +1172,8 @@ func (t *http2Client) reader() { t.Close() return } - t.handleSettings(sf) + t.onSuccess() + t.handleSettings(sf, true) // loop to keep reading incoming messages on this transport. for { @@ -1144,7 +1206,7 @@ func (t *http2Client) reader() { case *http2.RSTStreamFrame: t.handleRSTStream(frame) case *http2.SettingsFrame: - t.handleSettings(frame) + t.handleSettings(frame, false) case *http2.PingFrame: t.handlePing(frame) case *http2.GoAwayFrame: @@ -1167,10 +1229,8 @@ func (t *http2Client) applySettings(ss []http2.Setting) { if s.Val > math.MaxInt32 { s.Val = math.MaxInt32 } - t.mu.Lock() ms := t.maxStreams t.maxStreams = int(s.Val) - t.mu.Unlock() t.streamsQuota.add(int(s.Val) - ms) case http2.SettingInitialWindowSize: t.mu.Lock() @@ -1187,14 +1247,19 @@ func (t *http2Client) applySettings(ss []http2.Setting) { // TODO(mmukhi): A lot of this code(and code in other places in the tranpsort layer) // is duplicated between the client and the server. // The transport layer needs to be refactored to take care of this. -func (t *http2Client) itemHandler(i item) error { - var err error +func (t *http2Client) itemHandler(i item) (err error) { + defer func() { + if err != nil { + errorf(" error in itemHandler: %v", err) + } + }() switch i := i.(type) { case *dataFrame: - err = t.framer.fr.WriteData(i.streamID, i.endStream, i.d) - if err == nil { - i.f() + if err := t.framer.fr.WriteData(i.streamID, i.endStream, i.d); err != nil { + return err } + i.f() + return nil case *headerFrame: t.hBuf.Reset() for _, f := range i.hf { @@ -1228,34 +1293,33 @@ func (t *http2Client) itemHandler(i item) error { return err } } + return nil case *windowUpdate: - err = t.framer.fr.WriteWindowUpdate(i.streamID, i.increment) + return t.framer.fr.WriteWindowUpdate(i.streamID, i.increment) case *settings: - if i.ack { - t.applySettings(i.ss) - err = t.framer.fr.WriteSettingsAck() - } else { - err = t.framer.fr.WriteSettings(i.ss...) - } + return t.framer.fr.WriteSettings(i.ss...) + case *settingsAck: + return t.framer.fr.WriteSettingsAck() case *resetStream: // If the server needs to be to intimated about stream closing, // then we need to make sure the RST_STREAM frame is written to // the wire before the headers of the next stream waiting on // streamQuota. We ensure this by adding to the streamsQuota pool // only after having acquired the writableChan to send RST_STREAM. - err = t.framer.fr.WriteRSTStream(i.streamID, i.code) + err := t.framer.fr.WriteRSTStream(i.streamID, i.code) t.streamsQuota.add(1) + return err case *flushIO: - err = t.framer.writer.Flush() + return t.framer.writer.Flush() case *ping: if !i.ack { t.bdpEst.timesnap(i.data) } - err = t.framer.fr.WritePing(i.ack, i.data) + return t.framer.fr.WritePing(i.ack, i.data) default: - errorf("transport: http2Client.controller got unexpected item type %v\n", i) + errorf("transport: http2Client.controller got unexpected item type %v", i) + return fmt.Errorf("transport: http2Client.controller got unexpected item type %v", i) } - return err } // keepalive running in a separate goroutune makes sure the connection is alive by sending pings. diff --git a/vendor/google.golang.org/grpc/transport/http2_server.go b/vendor/google.golang.org/grpc/transport/http2_server.go index 00df8eed0fd..97b214c640e 100644 --- a/vendor/google.golang.org/grpc/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/transport/http2_server.go @@ -70,7 +70,10 @@ type http2Server struct { fc *inFlow // sendQuotaPool provides flow control to outbound message. sendQuotaPool *quotaPool - stats stats.Handler + // localSendQuota limits the amount of data that can be scheduled + // for writing before it is actually written out. + localSendQuota *quotaPool + stats stats.Handler // Flag to keep track of reading activity on transport. // 1 is true and 0 is false. activity uint32 // Accessed atomically. @@ -199,6 +202,7 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err controlBuf: newControlBuffer(), fc: &inFlow{limit: uint32(icwz)}, sendQuotaPool: newQuotaPool(defaultWindowSize), + localSendQuota: newQuotaPool(defaultLocalSendQuota), state: reachable, activeStreams: make(map[uint32]*Stream), streamSendQuota: defaultWindowSize, @@ -224,6 +228,12 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err } t.framer.writer.Flush() + defer func() { + if err != nil { + t.Close() + } + }() + // Check the validity of client preface. preface := make([]byte, len(clientPreface)) if _, err := io.ReadFull(t.conn, preface); err != nil { @@ -235,8 +245,7 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err frame, err := t.framer.fr.ReadFrame() if err == io.EOF || err == io.ErrUnexpectedEOF { - t.Close() - return + return nil, err } if err != nil { return nil, connectionErrorf(false, err, "transport: http2Server.HandleStreams failed to read initial settings frame: %v", err) @@ -250,7 +259,7 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err go func() { loopyWriter(t.ctx, t.controlBuf, t.itemHandler) - t.Close() + t.conn.Close() }() go t.keepalive() return t, nil @@ -272,12 +281,13 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( buf := newRecvBuffer() s := &Stream{ - id: streamID, - st: t, - buf: buf, - fc: &inFlow{limit: uint32(t.initialWindowSize)}, - recvCompress: state.encoding, - method: state.method, + id: streamID, + st: t, + buf: buf, + fc: &inFlow{limit: uint32(t.initialWindowSize)}, + recvCompress: state.encoding, + method: state.method, + contentSubtype: state.contentSubtype, } if frame.StreamEnded() { @@ -297,10 +307,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( pr.AuthInfo = t.authInfo } s.ctx = peer.NewContext(s.ctx, pr) - // Cache the current stream to the context so that the server application - // can find out. Required when the server wants to send some metadata - // back to the client (unary call only). - s.ctx = newContextWithStream(s.ctx, s) // Attach the received metadata to the context. if len(state.mdata) > 0 { s.ctx = metadata.NewIncomingContext(s.ctx, state.mdata) @@ -341,7 +347,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } t.maxStreamID = streamID s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota)) - s.localSendQuota = newQuotaPool(defaultLocalSendQuota) t.activeStreams[streamID] = s if len(t.activeStreams) == 1 { t.idle = time.Time{} @@ -371,6 +376,10 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( t.updateWindow(s, uint32(n)) }, } + s.waiters = waiters{ + ctx: s.ctx, + tctx: t.ctx, + } handle(s) return } @@ -486,7 +495,6 @@ func (t *http2Server) updateFlowControl(n uint32) { t.mu.Unlock() t.controlBuf.put(&windowUpdate{0, t.fc.newLimit(n)}) t.controlBuf.put(&settings{ - ack: false, ss: []http2.Setting{ { ID: http2.SettingInitialWindowSize, @@ -584,12 +592,29 @@ func (t *http2Server) handleSettings(f *http2.SettingsFrame) { if f.IsAck() { return } - var ss []http2.Setting + var rs []http2.Setting + var ps []http2.Setting f.ForeachSetting(func(s http2.Setting) error { - ss = append(ss, s) + if t.isRestrictive(s) { + rs = append(rs, s) + } else { + ps = append(ps, s) + } return nil }) - t.controlBuf.put(&settings{ack: true, ss: ss}) + t.applySettings(rs) + t.controlBuf.put(&settingsAck{}) + t.applySettings(ps) +} + +func (t *http2Server) isRestrictive(s http2.Setting) bool { + switch s.ID { + case http2.SettingInitialWindowSize: + // Note: we don't acquire a lock here to read streamSendQuota + // because the same goroutine updates it later. + return s.Val < t.streamSendQuota + } + return false } func (t *http2Server) applySettings(ss []http2.Setting) { @@ -656,7 +681,7 @@ func (t *http2Server) handlePing(f *http2.PingFrame) { if t.pingStrikes > maxPingStrikes { // Send goaway and close the connection. - errorf("transport: Got to too many pings from the client, closing the connection.") + errorf("transport: Got too many pings from the client, closing the connection.") t.controlBuf.put(&goAway{code: http2.ErrCodeEnhanceYourCalm, debugData: []byte("too_many_pings"), closeConn: true}) } } @@ -698,11 +723,11 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { } md = s.header s.mu.Unlock() - // TODO(mmukhi): Benchmark if the perfomance gets better if count the metadata and other header fields + // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields // first and create a slice of that exact size. headerFields := make([]hpack.HeaderField, 0, 2) // at least :status, content-type will be there if none else. headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) - headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: "application/grpc"}) + headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) if s.sendCompress != "" { headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress}) } @@ -721,9 +746,9 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { endStream: false, }) if t.stats != nil { - outHeader := &stats.OutHeader{ - //WireLength: // TODO(mmukhi): Revisit this later, if needed. - } + // Note: WireLength is not set in outHeader. + // TODO(mmukhi): Revisit this later, if needed. + outHeader := &stats.OutHeader{} t.stats.HandleRPC(s.Context(), outHeader) } return nil @@ -759,12 +784,12 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { headersSent = true } - // TODO(mmukhi): Benchmark if the perfomance gets better if count the metadata and other header fields + // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields // first and create a slice of that exact size. headerFields := make([]hpack.HeaderField, 0, 2) // grpc-status and grpc-message will be there if none else. if !headersSent { headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) - headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: "application/grpc"}) + headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) } headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())}) @@ -803,7 +828,7 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { // Write converts the data into HTTP2 data frame and sends it out. Non-nil error // is returns if it fails (e.g., framing error, transport error). -func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) (err error) { +func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { select { case <-s.ctx.Done(): return ContextErr(s.ctx.Err()) @@ -814,10 +839,6 @@ func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) ( var writeHeaderFrame bool s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return streamErrorf(codes.Unknown, "the stream has been done") - } if !s.headerOk { writeHeaderFrame = true } @@ -832,66 +853,68 @@ func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) ( } hdr = append(hdr, data[:emptyLen]...) data = data[emptyLen:] + var ( + streamQuota int + streamQuotaVer uint32 + err error + ) for _, r := range [][]byte{hdr, data} { for len(r) > 0 { size := http2MaxFrameLen - // Wait until the stream has some quota to send the data. - quotaChan, quotaVer := s.sendQuotaPool.acquireWithVersion() - sq, err := wait(s.ctx, t.ctx, nil, nil, quotaChan) - if err != nil { - return err + if size > len(r) { + size = len(r) } - // Wait until the transport has some quota to send the data. - tq, err := wait(s.ctx, t.ctx, nil, nil, t.sendQuotaPool.acquire()) + if streamQuota == 0 { // Used up all the locally cached stream quota. + // Get all the stream quota there is. + streamQuota, streamQuotaVer, err = s.sendQuotaPool.get(math.MaxInt32, s.waiters) + if err != nil { + return err + } + } + if size > streamQuota { + size = streamQuota + } + // Get size worth quota from transport. + tq, _, err := t.sendQuotaPool.get(size, s.waiters) if err != nil { return err } - if sq < size { - size = sq - } if tq < size { size = tq } - if size > len(r) { - size = len(r) - } - p := r[:size] - ps := len(p) - if ps < tq { - // Overbooked transport quota. Return it back. - t.sendQuotaPool.add(tq - ps) - } - // Acquire local send quota to be able to write to the controlBuf. - ltq, err := wait(s.ctx, t.ctx, nil, nil, s.localSendQuota.acquire()) + ltq, _, err := t.localSendQuota.get(size, s.waiters) if err != nil { - if _, ok := err.(ConnectionError); !ok { - t.sendQuotaPool.add(ps) - } + // Add the acquired quota back to transport. + t.sendQuotaPool.add(tq) return err } - s.localSendQuota.add(ltq - ps) // It's ok we make this negative. - // Reset ping strikes when sending data since this might cause - // the peer to send ping. - atomic.StoreUint32(&t.resetPingStrikes, 1) + // even if ltq is smaller than size we don't adjust size since, + // ltq is only a soft limit. + streamQuota -= size + p := r[:size] success := func() { + ltq := ltq t.controlBuf.put(&dataFrame{streamID: s.id, endStream: false, d: p, f: func() { - s.localSendQuota.add(ps) + t.localSendQuota.add(ltq) }}) - if ps < sq { - // Overbooked stream quota. Return it back. - s.sendQuotaPool.lockedAdd(sq - ps) - } - r = r[ps:] + r = r[size:] } - failure := func() { - s.sendQuotaPool.lockedAdd(sq) + failure := func() { // The stream quota version must have changed. + // Our streamQuota cache is invalidated now, so give it back. + s.sendQuotaPool.lockedAdd(streamQuota + size) } - if !s.sendQuotaPool.compareAndExecute(quotaVer, success, failure) { - t.sendQuotaPool.add(ps) - s.localSendQuota.add(ps) + if !s.sendQuotaPool.compareAndExecute(streamQuotaVer, success, failure) { + // Couldn't send this chunk out. + t.sendQuotaPool.add(size) + t.localSendQuota.add(ltq) + streamQuota = 0 } } } + if streamQuota > 0 { + // ADd the left over quota back to stream. + s.sendQuotaPool.add(streamQuota) + } return nil } @@ -983,6 +1006,9 @@ var goAwayPing = &ping{data: [8]byte{1, 6, 1, 8, 0, 3, 3, 9}} func (t *http2Server) itemHandler(i item) error { switch i := i.(type) { case *dataFrame: + // Reset ping strikes when sending data since this might cause + // the peer to send ping. + atomic.StoreUint32(&t.resetPingStrikes, 1) if err := t.framer.fr.WriteData(i.streamID, i.endStream, i.d); err != nil { return err } @@ -1027,11 +1053,9 @@ func (t *http2Server) itemHandler(i item) error { case *windowUpdate: return t.framer.fr.WriteWindowUpdate(i.streamID, i.increment) case *settings: - if i.ack { - t.applySettings(i.ss) - return t.framer.fr.WriteSettingsAck() - } return t.framer.fr.WriteSettings(i.ss...) + case *settingsAck: + return t.framer.fr.WriteSettingsAck() case *resetStream: return t.framer.fr.WriteRSTStream(i.streamID, i.code) case *goAway: @@ -1045,6 +1069,9 @@ func (t *http2Server) itemHandler(i item) error { if !i.headsUp { // Stop accepting more streams now. t.state = draining + if len(t.activeStreams) == 0 { + i.closeConn = true + } t.mu.Unlock() if err := t.framer.fr.WriteGoAway(sid, i.code, i.debugData); err != nil { return err @@ -1052,8 +1079,7 @@ func (t *http2Server) itemHandler(i item) error { if i.closeConn { // Abruptly close the connection following the GoAway (via // loopywriter). But flush out what's inside the buffer first. - t.framer.writer.Flush() - return fmt.Errorf("transport: Connection closing") + t.controlBuf.put(&flushIO{closeTr: true}) } return nil } @@ -1083,7 +1109,13 @@ func (t *http2Server) itemHandler(i item) error { }() return nil case *flushIO: - return t.framer.writer.Flush() + if err := t.framer.writer.Flush(); err != nil { + return err + } + if i.closeTr { + return ErrConnClosing + } + return nil case *ping: if !i.ack { t.bdpEst.timesnap(i.data) @@ -1131,7 +1163,7 @@ func (t *http2Server) closeStream(s *Stream) { t.idle = time.Now() } if t.state == draining && len(t.activeStreams) == 0 { - defer t.Close() + defer t.controlBuf.put(&flushIO{closeTr: true}) } t.mu.Unlock() // In case stream sending and receiving are invoked in separate diff --git a/vendor/google.golang.org/grpc/transport/http_util.go b/vendor/google.golang.org/grpc/transport/http_util.go index 39f878cfd5b..de37e38ec9f 100644 --- a/vendor/google.golang.org/grpc/transport/http_util.go +++ b/vendor/google.golang.org/grpc/transport/http_util.go @@ -46,6 +46,12 @@ const ( // http2IOBufSize specifies the buffer size for sending frames. defaultWriteBufSize = 32 * 1024 defaultReadBufSize = 32 * 1024 + // baseContentType is the base content-type for gRPC. This is a valid + // content-type on it's own, but can also include a content-subtype such as + // "proto" as a suffix after "+" or ";". See + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests + // for more details. + baseContentType = "application/grpc" ) var ( @@ -64,7 +70,7 @@ var ( http2.ErrCodeConnect: codes.Internal, http2.ErrCodeEnhanceYourCalm: codes.ResourceExhausted, http2.ErrCodeInadequateSecurity: codes.PermissionDenied, - http2.ErrCodeHTTP11Required: codes.FailedPrecondition, + http2.ErrCodeHTTP11Required: codes.Internal, } statusCodeConvTab = map[codes.Code]http2.ErrCode{ codes.Internal: http2.ErrCodeInternal, @@ -111,9 +117,10 @@ type decodeState struct { timeout time.Duration method string // key-value metadata map from the peer. - mdata map[string][]string - statsTags []byte - statsTrace []byte + mdata map[string][]string + statsTags []byte + statsTrace []byte + contentSubtype string } // isReservedHeader checks whether hdr belongs to HTTP2 headers @@ -149,17 +156,44 @@ func isWhitelistedPseudoHeader(hdr string) bool { } } -func validContentType(t string) bool { - e := "application/grpc" - if !strings.HasPrefix(t, e) { - return false +// contentSubtype returns the content-subtype for the given content-type. The +// given content-type must be a valid content-type that starts with +// "application/grpc". A content-subtype will follow "application/grpc" after a +// "+" or ";". See +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. +// +// If contentType is not a valid content-type for gRPC, the boolean +// will be false, otherwise true. If content-type == "application/grpc", +// "application/grpc+", or "application/grpc;", the boolean will be true, +// but no content-subtype will be returned. +// +// contentType is assumed to be lowercase already. +func contentSubtype(contentType string) (string, bool) { + if contentType == baseContentType { + return "", true + } + if !strings.HasPrefix(contentType, baseContentType) { + return "", false + } + // guaranteed since != baseContentType and has baseContentType prefix + switch contentType[len(baseContentType)] { + case '+', ';': + // this will return true for "application/grpc+" or "application/grpc;" + // which the previous validContentType function tested to be valid, so we + // just say that no content-subtype is specified in this case + return contentType[len(baseContentType)+1:], true + default: + return "", false } - // Support variations on the content-type - // (e.g. "application/grpc+blah", "application/grpc;blah"). - if len(t) > len(e) && t[len(e)] != '+' && t[len(e)] != ';' { - return false +} + +// contentSubtype is assumed to be lowercase +func contentType(contentSubtype string) string { + if contentSubtype == "" { + return baseContentType } - return true + return baseContentType + "+" + contentSubtype } func (d *decodeState) status() *status.Status { @@ -247,9 +281,16 @@ func (d *decodeState) addMetadata(k, v string) { func (d *decodeState) processHeaderField(f hpack.HeaderField) error { switch f.Name { case "content-type": - if !validContentType(f.Value) { - return streamErrorf(codes.FailedPrecondition, "transport: received the unexpected content-type %q", f.Value) + contentSubtype, validContentType := contentSubtype(f.Value) + if !validContentType { + return streamErrorf(codes.Internal, "transport: received the unexpected content-type %q", f.Value) } + d.contentSubtype = contentSubtype + // TODO: do we want to propagate the whole content-type in the metadata, + // or come up with a way to just propagate the content-subtype if it was set? + // ie {"content-type": "application/grpc+proto"} or {"content-subtype": "proto"} + // in the metadata? + d.addMetadata(f.Name, f.Value) case "grpc-encoding": d.encoding = f.Value case "grpc-status": diff --git a/vendor/google.golang.org/grpc/transport/transport.go b/vendor/google.golang.org/grpc/transport/transport.go index bde8fa5c3a2..e0c1e343e7a 100644 --- a/vendor/google.golang.org/grpc/transport/transport.go +++ b/vendor/google.golang.org/grpc/transport/transport.go @@ -17,16 +17,15 @@ */ // Package transport defines and implements message oriented communication -// channel to complete various transactions (e.g., an RPC). +// channel to complete various transactions (e.g., an RPC). It is meant for +// grpc-internal usage and is not intended to be imported directly by users. package transport // import "google.golang.org/grpc/transport" import ( - stdctx "context" "fmt" "io" "net" "sync" - "time" "golang.org/x/net/context" "golang.org/x/net/http2" @@ -134,7 +133,7 @@ func (r *recvBufferReader) read(p []byte) (n int, err error) { case <-r.ctx.Done(): return 0, ContextErr(r.ctx.Err()) case <-r.goAway: - return 0, ErrStreamDrain + return 0, errStreamDrain case m := <-r.recv.get(): r.recv.load() if m.err != nil { @@ -211,66 +210,71 @@ const ( // Stream represents an RPC in the transport layer. type Stream struct { - id uint32 - // nil for client side Stream. - st ServerTransport - // ctx is the associated context of the stream. - ctx context.Context - // cancel is always nil for client side Stream. - cancel context.CancelFunc - // done is closed when the final status arrives. - done chan struct{} - // goAway is closed when the server sent GoAways signal before this stream was initiated. - goAway chan struct{} - // method records the associated RPC method of the stream. - method string + id uint32 + st ServerTransport // nil for client side Stream + ctx context.Context // the associated context of the stream + cancel context.CancelFunc // always nil for client side Stream + done chan struct{} // closed when the final status arrives + goAway chan struct{} // closed when a GOAWAY control message is received + method string // the associated RPC method of the stream recvCompress string sendCompress string buf *recvBuffer trReader io.Reader fc *inFlow recvQuota uint32 - - // TODO: Remote this unused variable. - // The accumulated inbound quota pending for window update. - updateQuota uint32 + waiters waiters // Callback to state application's intentions to read data. This - // is used to adjust flow control, if need be. + // is used to adjust flow control, if needed. requestRead func(int) - sendQuotaPool *quotaPool - localSendQuota *quotaPool - // Close headerChan to indicate the end of reception of header metadata. - headerChan chan struct{} - // header caches the received header metadata. - header metadata.MD - // The key-value map of trailer metadata. - trailer metadata.MD - - mu sync.RWMutex // guard the following - // headerOK becomes true from the first header is about to send. - headerOk bool + sendQuotaPool *quotaPool + headerChan chan struct{} // closed to indicate the end of header metadata. + headerDone bool // set when headerChan is closed. Used to avoid closing headerChan multiple times. + header metadata.MD // the received header metadata. + trailer metadata.MD // the key-value map of trailer metadata. + + mu sync.RWMutex // guard the following + headerOk bool // becomes true from the first header is about to send state streamState - // true iff headerChan is closed. Used to avoid closing headerChan - // multiple times. - headerDone bool - // the status error received from the server. - status *status.Status - // rstStream indicates whether a RST_STREAM frame needs to be sent - // to the server to signify that this stream is closing. - rstStream bool - // rstError is the error that needs to be sent along with the RST_STREAM frame. - rstError http2.ErrCode - // bytesSent and bytesReceived indicates whether any bytes have been sent or - // received on this stream. - bytesSent bool - bytesReceived bool + + status *status.Status // the status error received from the server + + rstStream bool // indicates whether a RST_STREAM frame needs to be sent + rstError http2.ErrCode // the error that needs to be sent along with the RST_STREAM frame + + bytesReceived bool // indicates whether any bytes have been received on this stream + unprocessed bool // set if the server sends a refused stream or GOAWAY including this stream + + // contentSubtype is the content-subtype for requests. + // this must be lowercase or the behavior is undefined. + contentSubtype string +} + +func (s *Stream) waitOnHeader() error { + if s.headerChan == nil { + // On the server headerChan is always nil since a stream originates + // only after having received headers. + return nil + } + wc := s.waiters + select { + case <-wc.ctx.Done(): + return ContextErr(wc.ctx.Err()) + case <-wc.goAway: + return errStreamDrain + case <-s.headerChan: + return nil + } } // RecvCompress returns the compression algorithm applied to the inbound // message. It is empty string if there is no compression applied. func (s *Stream) RecvCompress() string { + if err := s.waitOnHeader(); err != nil { + return "" + } return s.recvCompress } @@ -295,15 +299,7 @@ func (s *Stream) GoAway() <-chan struct{} { // is available. It blocks until i) the metadata is ready or ii) there is no // header metadata or iii) the stream is canceled/expired. func (s *Stream) Header() (metadata.MD, error) { - var err error - select { - case <-s.ctx.Done(): - err = ContextErr(s.ctx.Err()) - case <-s.goAway: - err = ErrStreamDrain - case <-s.headerChan: - return s.header.Copy(), nil - } + err := s.waitOnHeader() // Even if the stream is closed, header is returned if available. select { case <-s.headerChan: @@ -329,6 +325,15 @@ func (s *Stream) ServerTransport() ServerTransport { return s.st } +// ContentSubtype returns the content-subtype for a request. For example, a +// content-subtype of "proto" will result in a content-type of +// "application/grpc+proto". This will always be lowercase. See +// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for +// more details. +func (s *Stream) ContentSubtype() string { + return s.contentSubtype +} + // Context returns the context of the stream. func (s *Stream) Context() context.Context { return s.ctx @@ -361,6 +366,14 @@ func (s *Stream) SetHeader(md metadata.MD) error { return nil } +// SendHeader sends the given header metadata. The given metadata is +// combined with any metadata set by previous calls to SetHeader and +// then written to the transport stream. +func (s *Stream) SendHeader(md metadata.MD) error { + t := s.ServerTransport() + return t.WriteHeader(s, md) +} + // SetTrailer sets the trailer metadata which will be sent with the RPC status // by the server. This can be called multiple times. Server side only. func (s *Stream) SetTrailer(md metadata.MD) error { @@ -417,18 +430,19 @@ func (s *Stream) finish(st *status.Status) { close(s.done) } -// BytesSent indicates whether any bytes have been sent on this stream. -func (s *Stream) BytesSent() bool { +// BytesReceived indicates whether any bytes have been received on this stream. +func (s *Stream) BytesReceived() bool { s.mu.Lock() - bs := s.bytesSent + br := s.bytesReceived s.mu.Unlock() - return bs + return br } -// BytesReceived indicates whether any bytes have been received on this stream. -func (s *Stream) BytesReceived() bool { +// Unprocessed indicates whether the server did not process this stream -- +// i.e. it sent a refused stream or GOAWAY including this stream ID. +func (s *Stream) Unprocessed() bool { s.mu.Lock() - br := s.bytesReceived + br := s.unprocessed s.mu.Unlock() return br } @@ -439,21 +453,6 @@ func (s *Stream) GoString() string { return fmt.Sprintf("", s, s.method) } -// The key to save transport.Stream in the context. -type streamKey struct{} - -// newContextWithStream creates a new context from ctx and attaches stream -// to it. -func newContextWithStream(ctx context.Context, stream *Stream) context.Context { - return context.WithValue(ctx, streamKey{}, stream) -} - -// StreamFromContext returns the stream saved in ctx. -func StreamFromContext(ctx context.Context) (s *Stream, ok bool) { - s, ok = ctx.Value(streamKey{}).(*Stream) - return -} - // state of transport type transportState int @@ -514,14 +513,15 @@ type ConnectOptions struct { // TargetInfo contains the information of the target such as network address and metadata. type TargetInfo struct { - Addr string - Metadata interface{} + Addr string + Metadata interface{} + Authority string } // NewClientTransport establishes the transport with the required ConnectOptions // and returns it to the caller. -func NewClientTransport(ctx context.Context, target TargetInfo, opts ConnectOptions, timeout time.Duration) (ClientTransport, error) { - return newHTTP2Client(ctx, target, opts, timeout) +func NewClientTransport(connectCtx, ctx context.Context, target TargetInfo, opts ConnectOptions, onSuccess func()) (ClientTransport, error) { + return newHTTP2Client(connectCtx, ctx, target, opts, onSuccess) } // Options provides additional hints and information for message @@ -545,10 +545,6 @@ type CallHdr struct { // Method specifies the operation to perform. Method string - // RecvCompress specifies the compression algorithm applied on - // inbound messages. - RecvCompress string - // SendCompress specifies the compression algorithm applied on // outbound message. SendCompress string @@ -563,6 +559,14 @@ type CallHdr struct { // for performance purposes. // If it's false, new stream will never be flushed. Flush bool + + // ContentSubtype specifies the content-subtype for a request. For example, a + // content-subtype of "proto" will result in a content-type of + // "application/grpc+proto". The value of ContentSubtype must be all + // lowercase, otherwise the behavior is undefined. See + // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests + // for more details. + ContentSubtype string } // ClientTransport is the common interface for all gRPC client-side transport @@ -686,9 +690,13 @@ func (e ConnectionError) Origin() error { var ( // ErrConnClosing indicates that the transport is closing. ErrConnClosing = connectionErrorf(true, nil, "transport is closing") - // ErrStreamDrain indicates that the stream is rejected by the server because - // the server stops accepting new RPCs. - ErrStreamDrain = streamErrorf(codes.Unavailable, "the server stops accepting new RPCs") + // errStreamDrain indicates that the stream is rejected because the + // connection is draining. This could be caused by goaway or balancer + // removing the address. + errStreamDrain = streamErrorf(codes.Unavailable, "the connection is draining") + // StatusGoAway indicates that the server sent a GOAWAY that included this + // stream's ID in unprocessed RPCs. + statusGoAway = status.New(codes.Unavailable, "the stream is rejected because server is draining the connection") ) // TODO: See if we can replace StreamError with status package errors. @@ -703,44 +711,27 @@ func (e StreamError) Error() string { return fmt.Sprintf("stream error: code = %s desc = %q", e.Code, e.Desc) } -// wait blocks until it can receive from one of the provided contexts or channels -func wait(ctx, tctx context.Context, done, goAway <-chan struct{}, proceed <-chan int) (int, error) { - select { - case <-ctx.Done(): - return 0, ContextErr(ctx.Err()) - case <-done: - return 0, io.EOF - case <-goAway: - return 0, ErrStreamDrain - case <-tctx.Done(): - return 0, ErrConnClosing - case i := <-proceed: - return i, nil - } -} - -// ContextErr converts the error from context package into a StreamError. -func ContextErr(err error) StreamError { - switch err { - case context.DeadlineExceeded, stdctx.DeadlineExceeded: - return streamErrorf(codes.DeadlineExceeded, "%v", err) - case context.Canceled, stdctx.Canceled: - return streamErrorf(codes.Canceled, "%v", err) - } - return streamErrorf(codes.Internal, "Unexpected error from context packet: %v", err) +// waiters are passed to quotaPool get methods to +// wait on in addition to waiting on quota. +type waiters struct { + ctx context.Context + tctx context.Context + done chan struct{} + goAway chan struct{} } // GoAwayReason contains the reason for the GoAway frame received. type GoAwayReason uint8 const ( - // Invalid indicates that no GoAway frame is received. - Invalid GoAwayReason = 0 - // NoReason is the default value when GoAway frame is received. - NoReason GoAwayReason = 1 - // TooManyPings indicates that a GoAway frame with ErrCodeEnhanceYourCalm - // was received and that the debug data said "too_many_pings". - TooManyPings GoAwayReason = 2 + // GoAwayInvalid indicates that no GoAway frame is received. + GoAwayInvalid GoAwayReason = 0 + // GoAwayNoReason is the default value when GoAway frame is received. + GoAwayNoReason GoAwayReason = 1 + // GoAwayTooManyPings indicates that a GoAway frame with + // ErrCodeEnhanceYourCalm was received and that the debug data said + // "too_many_pings". + GoAwayTooManyPings GoAwayReason = 2 ) // loopyWriter is run in a separate go routine. It is the single code path that will @@ -751,6 +742,7 @@ func loopyWriter(ctx context.Context, cbuf *controlBuffer, handler func(item) er case i := <-cbuf.get(): cbuf.load() if err := handler(i); err != nil { + errorf("transport: Error while handling item. Err: %v", err) return } case <-ctx.Done(): @@ -762,12 +754,14 @@ func loopyWriter(ctx context.Context, cbuf *controlBuffer, handler func(item) er case i := <-cbuf.get(): cbuf.load() if err := handler(i); err != nil { + errorf("transport: Error while handling item. Err: %v", err) return } case <-ctx.Done(): return default: if err := handler(&flushIO{}); err != nil { + errorf("transport: Error while flushing. Err: %v", err) return } break hasData From 7fe4a08fdcf4964324c57d520d697c7e83b9aaa8 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 19 Mar 2018 11:17:00 -0700 Subject: [PATCH 02/39] clientv3/balancer: initial commit Signed-off-by: Gyuho Lee --- clientv3/balancer/balancer.go | 275 ++++++++++++++++++ clientv3/balancer/config.go | 39 +++ clientv3/balancer/connectivity.go | 58 ++++ clientv3/balancer/grpc1.7-health_test.go | 10 +- clientv3/balancer/picker/doc.go | 16 + clientv3/balancer/picker/err.go | 39 +++ clientv3/balancer/picker/picker.go | 31 ++ clientv3/balancer/picker/picker_policy.go | 49 ++++ .../balancer/picker/roundrobin_balanced.go | 105 +++++++ clientv3/balancer/utils.go | 45 +++ clientv3/balancer/utils_test.go | 20 ++ 11 files changed, 682 insertions(+), 5 deletions(-) create mode 100644 clientv3/balancer/balancer.go create mode 100644 clientv3/balancer/config.go create mode 100644 clientv3/balancer/connectivity.go create mode 100644 clientv3/balancer/picker/doc.go create mode 100644 clientv3/balancer/picker/err.go create mode 100644 clientv3/balancer/picker/picker.go create mode 100644 clientv3/balancer/picker/picker_policy.go create mode 100644 clientv3/balancer/picker/roundrobin_balanced.go create mode 100644 clientv3/balancer/utils.go create mode 100644 clientv3/balancer/utils_test.go diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go new file mode 100644 index 00000000000..1bb32402896 --- /dev/null +++ b/clientv3/balancer/balancer.go @@ -0,0 +1,275 @@ +// Copyright 2018 The etcd 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 balancer + +import ( + "fmt" + "sync" + + "github.com/coreos/etcd/clientv3/balancer/picker" + + "go.uber.org/zap" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/resolver" + _ "google.golang.org/grpc/resolver/dns" // register DNS resolver + _ "google.golang.org/grpc/resolver/passthrough" // register passthrough resolver +) + +// Balancer defines client balancer interface. +type Balancer interface { + // Builder is called at the beginning to initialize sub-connection states and picker. + balancer.Builder + + // Balancer is called on specified client connection. Client initiates gRPC + // connection with "grpc.Dial(addr, grpc.WithBalancerName)", and then those resolved + // addresses are passed to "grpc/balancer.Balancer.HandleResolvedAddrs". + // For each resolved address, balancer calls "balancer.ClientConn.NewSubConn". + // "grpc/balancer.Balancer.HandleSubConnStateChange" is called when connectivity state + // changes, thus requires failover logic in this method. + balancer.Balancer + + // Picker calls "Pick" for every client request. + picker.Picker + + // SetEndpoints updates client's endpoints. + SetEndpoints(eps ...string) +} + +type baseBalancer struct { + policy picker.Policy + name string + lg *zap.Logger + + mu sync.RWMutex + + eps []string + + addrToSc map[resolver.Address]balancer.SubConn + scToAddr map[balancer.SubConn]resolver.Address + scToSt map[balancer.SubConn]connectivity.State + + currrentConn balancer.ClientConn + currentState connectivity.State + csEvltr *connectivityStateEvaluator + + picker.Picker +} + +// New returns a new balancer from specified picker policy. +func New(cfg Config) Balancer { + bb := &baseBalancer{ + policy: cfg.Policy, + name: cfg.Policy.String(), + lg: cfg.Logger, + + eps: cfg.Endpoints, + + addrToSc: make(map[resolver.Address]balancer.SubConn), + scToAddr: make(map[balancer.SubConn]resolver.Address), + scToSt: make(map[balancer.SubConn]connectivity.State), + + currrentConn: nil, + csEvltr: &connectivityStateEvaluator{}, + + // initialize picker always returns "ErrNoSubConnAvailable" + Picker: picker.NewErr(balancer.ErrNoSubConnAvailable), + } + if cfg.Name != "" { + bb.name = cfg.Name + } + if bb.lg == nil { + bb.lg = zap.NewNop() + } + + balancer.Register(bb) + bb.lg.Info( + "registered balancer", + zap.String("policy", bb.policy.String()), + zap.String("name", bb.name), + ) + return bb +} + +// Name implements "grpc/balancer.Builder" interface. +func (bb *baseBalancer) Name() string { return bb.name } + +// Build implements "grpc/balancer.Builder" interface. +// Build is called initially when creating "ccBalancerWrapper". +// "grpc.Dial" is called to this client connection. +// Then, resolved addreses will be handled via "HandleResolvedAddrs". +func (bb *baseBalancer) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { + // TODO: support multiple connections + bb.mu.Lock() + bb.currrentConn = cc + bb.mu.Unlock() + + bb.lg.Info( + "built balancer", + zap.String("policy", bb.policy.String()), + zap.String("resolver-target", cc.Target()), + ) + return bb +} + +// HandleResolvedAddrs implements "grpc/balancer.Balancer" interface. +// gRPC sends initial or updated resolved addresses from "Build". +func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) { + if err != nil { + bb.lg.Warn("HandleResolvedAddrs called with error", zap.Error(err)) + return + } + bb.lg.Info("resolved", zap.Strings("addresses", addrsToStrings(addrs))) + + bb.mu.Lock() + defer bb.mu.Unlock() + + resolved := make(map[resolver.Address]struct{}) + for _, addr := range addrs { + resolved[addr] = struct{}{} + if _, ok := bb.addrToSc[addr]; !ok { + sc, err := bb.currrentConn.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{}) + if err != nil { + bb.lg.Warn("NewSubConn failed", zap.Error(err), zap.String("address", addr.Addr)) + continue + } + bb.addrToSc[addr] = sc + bb.scToAddr[sc] = addr + bb.scToSt[sc] = connectivity.Idle + sc.Connect() + } + } + + for addr, sc := range bb.addrToSc { + if _, ok := resolved[addr]; !ok { + // was removed by resolver or failed to create subconn + bb.currrentConn.RemoveSubConn(sc) + delete(bb.addrToSc, addr) + + bb.lg.Info( + "removed subconn", + zap.String("address", addr.Addr), + zap.String("subconn", scToString(sc)), + ) + + // Keep the state of this sc in bb.scToSt until sc's state becomes Shutdown. + // The entry will be deleted in HandleSubConnStateChange. + // (DO NOT) delete(bb.scToAddr, sc) + // (DO NOT) delete(bb.scToSt, sc) + } + } +} + +// HandleSubConnStateChange implements "grpc/balancer.Balancer" interface. +func (bb *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { + bb.mu.Lock() + defer bb.mu.Unlock() + + old, ok := bb.scToSt[sc] + if !ok { + bb.lg.Warn( + "state change for an unknown subconn", + zap.String("subconn", scToString(sc)), + zap.String("state", s.String()), + ) + return + } + + bb.lg.Info( + "state changed", + zap.Bool("connected", s == connectivity.Ready), + zap.String("subconn", scToString(sc)), + zap.String("address", bb.scToAddr[sc].Addr), + zap.String("old-state", old.String()), + zap.String("new-state", s.String()), + ) + + bb.scToSt[sc] = s + switch s { + case connectivity.Idle: + sc.Connect() + case connectivity.Shutdown: + // When an address was removed by resolver, b called RemoveSubConn but + // kept the sc's state in scToSt. Remove state for this sc here. + delete(bb.scToAddr, sc) + delete(bb.scToSt, sc) + } + + oldAggrState := bb.currentState + bb.currentState = bb.csEvltr.recordTransition(old, s) + + // Regenerate picker when one of the following happens: + // - this sc became ready from not-ready + // - this sc became not-ready from ready + // - the aggregated state of balancer became TransientFailure from non-TransientFailure + // - the aggregated state of balancer became non-TransientFailure from TransientFailure + if (s == connectivity.Ready) != (old == connectivity.Ready) || + (bb.currentState == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) { + bb.regeneratePicker() + } + + bb.currrentConn.UpdateBalancerState(bb.currentState, bb.Picker) + return +} + +func (bb *baseBalancer) regeneratePicker() { + if bb.currentState == connectivity.TransientFailure { + bb.Picker = picker.NewErr(balancer.ErrTransientFailure) + return + } + + // only pass ready subconns to picker + scs := make([]balancer.SubConn, 0) + addrToSc := make(map[resolver.Address]balancer.SubConn) + scToAddr := make(map[balancer.SubConn]resolver.Address) + for addr, sc := range bb.addrToSc { + if st, ok := bb.scToSt[sc]; ok && st == connectivity.Ready { + scs = append(scs, sc) + addrToSc[addr] = sc + scToAddr[sc] = addr + } + } + + switch bb.policy { + case picker.RoundrobinBalanced: + bb.Picker = picker.NewRoundrobinBalanced(bb.lg, scs, addrToSc, scToAddr) + + default: + panic(fmt.Errorf("invalid balancer picker policy (%d)", bb.policy)) + } + + bb.lg.Info( + "generated picker", + zap.String("policy", bb.policy.String()), + zap.Strings("subconn-ready", scsToStrings(addrToSc)), + zap.Int("subconn-size", len(addrToSc)), + ) +} + +// SetEndpoints updates client's endpoints. +// TODO: implement this +func (bb *baseBalancer) SetEndpoints(eps ...string) { + addrs := epsToAddrs(eps...) + bb.mu.Lock() + bb.Picker.UpdateAddrs(addrs) + bb.mu.Unlock() +} + +// Close implements "grpc/balancer.Balancer" interface. +// Close is a nop because base balancer doesn't have internal state to clean up, +// and it doesn't need to call RemoveSubConn for the SubConns. +func (bb *baseBalancer) Close() { + // TODO +} diff --git a/clientv3/balancer/config.go b/clientv3/balancer/config.go new file mode 100644 index 00000000000..5c649e220fc --- /dev/null +++ b/clientv3/balancer/config.go @@ -0,0 +1,39 @@ +// Copyright 2018 The etcd 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 balancer + +import ( + "github.com/coreos/etcd/clientv3/balancer/picker" + + "go.uber.org/zap" +) + +// Config defines balancer configurations. +type Config struct { + // Policy configures balancer policy. + Policy picker.Policy + + // Name defines an additional name for balancer. + // Useful for balancer testing to avoid register conflicts. + // If empty, defaults to policy name. + Name string + + // Logger configures balancer logging. + // If nil, logs are discarded. + Logger *zap.Logger + + // Endpoints is a list of server endpoints. + Endpoints []string +} diff --git a/clientv3/balancer/connectivity.go b/clientv3/balancer/connectivity.go new file mode 100644 index 00000000000..6cdeb3fa3a4 --- /dev/null +++ b/clientv3/balancer/connectivity.go @@ -0,0 +1,58 @@ +// Copyright 2018 The etcd 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 balancer + +import "google.golang.org/grpc/connectivity" + +// connectivityStateEvaluator gets updated by addrConns when their +// states transition, based on which it evaluates the state of +// ClientConn. +type connectivityStateEvaluator struct { + numReady uint64 // Number of addrConns in ready state. + numConnecting uint64 // Number of addrConns in connecting state. + numTransientFailure uint64 // Number of addrConns in transientFailure. +} + +// recordTransition records state change happening in every subConn and based on +// that it evaluates what aggregated state should be. +// It can only transition between Ready, Connecting and TransientFailure. Other states, +// Idle and Shutdown are transitioned into by ClientConn; in the beginning of the connection +// before any subConn is created ClientConn is in idle state. In the end when ClientConn +// closes it is in Shutdown state. +// +// recordTransition should only be called synchronously from the same goroutine. +func (cse *connectivityStateEvaluator) recordTransition(oldState, newState connectivity.State) connectivity.State { + // Update counters. + for idx, state := range []connectivity.State{oldState, newState} { + updateVal := 2*uint64(idx) - 1 // -1 for oldState and +1 for new. + switch state { + case connectivity.Ready: + cse.numReady += updateVal + case connectivity.Connecting: + cse.numConnecting += updateVal + case connectivity.TransientFailure: + cse.numTransientFailure += updateVal + } + } + + // Evaluate. + if cse.numReady > 0 { + return connectivity.Ready + } + if cse.numConnecting > 0 { + return connectivity.Connecting + } + return connectivity.TransientFailure +} diff --git a/clientv3/balancer/grpc1.7-health_test.go b/clientv3/balancer/grpc1.7-health_test.go index bf139a5b14b..1671f53386a 100644 --- a/clientv3/balancer/grpc1.7-health_test.go +++ b/clientv3/balancer/grpc1.7-health_test.go @@ -30,7 +30,7 @@ import ( var endpoints = []string{"localhost:2379", "localhost:22379", "localhost:32379"} -func TestBalancerGetUnblocking(t *testing.T) { +func TestOldHealthBalancerGetUnblocking(t *testing.T) { hb := NewGRPC17Health(endpoints, minHealthRetryDuration, func(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { return nil, nil }) defer hb.Close() if addrs := <-hb.Notify(); len(addrs) != len(endpoints) { @@ -74,7 +74,7 @@ func TestBalancerGetUnblocking(t *testing.T) { } } -func TestBalancerGetBlocking(t *testing.T) { +func TestOldHealthBalancerGetBlocking(t *testing.T) { hb := NewGRPC17Health(endpoints, minHealthRetryDuration, func(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { return nil, nil }) defer hb.Close() if addrs := <-hb.Notify(); len(addrs) != len(endpoints) { @@ -131,9 +131,9 @@ func TestBalancerGetBlocking(t *testing.T) { } } -// TestHealthBalancerGraylist checks one endpoint is tried after the other +// TestOldHealthBalancerGraylist checks one endpoint is tried after the other // due to gray listing. -func TestHealthBalancerGraylist(t *testing.T) { +func TestOldHealthBalancerGraylist(t *testing.T) { var wg sync.WaitGroup // Use 3 endpoints so gray list doesn't fallback to all connections // after failing on 2 endpoints. @@ -192,7 +192,7 @@ func TestHealthBalancerGraylist(t *testing.T) { // TestBalancerDoNotBlockOnClose ensures that balancer and grpc don't deadlock each other // due to rapid open/close conn. The deadlock causes balancer.Close() to block forever. // See issue: https://github.com/coreos/etcd/issues/7283 for more detail. -func TestBalancerDoNotBlockOnClose(t *testing.T) { +func TestOldHealthBalancerDoNotBlockOnClose(t *testing.T) { defer testutil.AfterTest(t) kcl := newKillConnListener(t, 3) diff --git a/clientv3/balancer/picker/doc.go b/clientv3/balancer/picker/doc.go new file mode 100644 index 00000000000..35dabf5532f --- /dev/null +++ b/clientv3/balancer/picker/doc.go @@ -0,0 +1,16 @@ +// Copyright 2018 The etcd 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 picker defines/implements client balancer picker policy. +package picker diff --git a/clientv3/balancer/picker/err.go b/clientv3/balancer/picker/err.go new file mode 100644 index 00000000000..281f453fc1a --- /dev/null +++ b/clientv3/balancer/picker/err.go @@ -0,0 +1,39 @@ +// Copyright 2018 The etcd 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 picker + +import ( + "context" + + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +// NewErr returns a picker that always returns err on "Pick". +func NewErr(err error) Picker { + return &errPicker{err: err} +} + +type errPicker struct { + err error +} + +func (p *errPicker) Pick(context.Context, balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + return nil, nil, p.err +} + +func (p *errPicker) UpdateAddrs(addrs []resolver.Address) { + return +} diff --git a/clientv3/balancer/picker/picker.go b/clientv3/balancer/picker/picker.go new file mode 100644 index 00000000000..93412e2afec --- /dev/null +++ b/clientv3/balancer/picker/picker.go @@ -0,0 +1,31 @@ +// Copyright 2018 The etcd 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 picker + +import ( + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +// Picker defines balancer Picker methods. +type Picker interface { + balancer.Picker + + // UpdateAddrs updates current endpoints in picker. + // Used when endpoints are updated manually. + // TODO: handle resolver target change + // TODO: handle resolved addresses change + UpdateAddrs(addrs []resolver.Address) +} diff --git a/clientv3/balancer/picker/picker_policy.go b/clientv3/balancer/picker/picker_policy.go new file mode 100644 index 00000000000..463ddc2a5c1 --- /dev/null +++ b/clientv3/balancer/picker/picker_policy.go @@ -0,0 +1,49 @@ +// Copyright 2018 The etcd 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 picker + +import "fmt" + +// Policy defines balancer picker policy. +type Policy uint8 + +const ( + // TODO: custom picker is not supported yet. + // custom defines custom balancer picker. + custom Policy = iota + + // RoundrobinBalanced balance loads over multiple endpoints + // and implements failover in roundrobin fashion. + RoundrobinBalanced Policy = iota + + // TODO: only send loads to pinned address "RoundrobinFailover" + // just like how 3.3 client works + // + // TODO: priotize leader + // TODO: health-check + // TODO: weighted roundrobin + // TODO: power of two random choice +) + +func (p Policy) String() string { + switch p { + case custom: + panic("'custom' picker policy is not supported yet") + case RoundrobinBalanced: + return "etcd-client-roundrobin-balanced" + default: + panic(fmt.Errorf("invalid balancer picker policy (%d)", p)) + } +} diff --git a/clientv3/balancer/picker/roundrobin_balanced.go b/clientv3/balancer/picker/roundrobin_balanced.go new file mode 100644 index 00000000000..9175562a27c --- /dev/null +++ b/clientv3/balancer/picker/roundrobin_balanced.go @@ -0,0 +1,105 @@ +// Copyright 2018 The etcd 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 picker + +import ( + "context" + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +// NewRoundrobinBalanced returns a new roundrobin balanced picker. +func NewRoundrobinBalanced( + lg *zap.Logger, + scs []balancer.SubConn, + addrToSc map[resolver.Address]balancer.SubConn, + scToAddr map[balancer.SubConn]resolver.Address, +) Picker { + return &rrBalanced{ + lg: lg, + scs: scs, + addrToSc: addrToSc, + scToAddr: scToAddr, + } +} + +type rrBalanced struct { + lg *zap.Logger + + mu sync.RWMutex + next int + scs []balancer.SubConn + + addrToSc map[resolver.Address]balancer.SubConn + scToAddr map[balancer.SubConn]resolver.Address + + updateAddrs func(addrs []resolver.Address) +} + +// Pick is called for every client request. +func (rb *rrBalanced) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { + rb.mu.RLock() + n := len(rb.scs) + rb.mu.RUnlock() + if n == 0 { + return nil, nil, balancer.ErrNoSubConnAvailable + } + + rb.mu.Lock() + cur := rb.next + sc := rb.scs[cur] + picked := rb.scToAddr[sc].Addr + rb.next = (rb.next + 1) % len(rb.scs) + rb.mu.Unlock() + + rb.lg.Debug( + "picked", + zap.String("address", picked), + zap.Int("subconn-index", cur), + zap.Int("subconn-size", n), + ) + + doneFunc := func(info balancer.DoneInfo) { + // TODO: error handling? + fss := []zapcore.Field{ + zap.Error(info.Err), + zap.String("address", picked), + zap.Bool("success", info.Err == nil), + zap.Bool("bytes-sent", info.BytesSent), + zap.Bool("bytes-received", info.BytesReceived), + } + if info.Err == nil { + rb.lg.Debug("balancer done", fss...) + } else { + rb.lg.Warn("balancer failed", fss...) + } + } + return sc, doneFunc, nil +} + +// UpdateAddrs +// TODO: implement this +func (rb *rrBalanced) UpdateAddrs(addrs []resolver.Address) { + rb.mu.Lock() + // close all resolved sub-connections first + for _, sc := range rb.scs { + sc.UpdateAddresses([]resolver.Address{}) + } + rb.mu.Unlock() +} diff --git a/clientv3/balancer/utils.go b/clientv3/balancer/utils.go new file mode 100644 index 00000000000..e54d203c1b7 --- /dev/null +++ b/clientv3/balancer/utils.go @@ -0,0 +1,45 @@ +package balancer + +import ( + "fmt" + "net/url" + "sort" + + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/resolver" +) + +func scToString(sc balancer.SubConn) string { + return fmt.Sprintf("%p", sc) +} + +func scsToStrings(scs map[resolver.Address]balancer.SubConn) (ss []string) { + ss = make([]string, 0, len(scs)) + for a, sc := range scs { + ss = append(ss, fmt.Sprintf("%s (%s)", a.Addr, scToString(sc))) + } + sort.Strings(ss) + return ss +} + +func addrsToStrings(addrs []resolver.Address) (ss []string) { + ss = make([]string, len(addrs)) + for i := range addrs { + ss[i] = addrs[i].Addr + } + sort.Strings(ss) + return ss +} + +func epsToAddrs(eps ...string) (addrs []resolver.Address) { + addrs = make([]resolver.Address, 0, len(eps)) + for _, ep := range eps { + u, err := url.Parse(ep) + if err != nil { + addrs = append(addrs, resolver.Address{Addr: ep, Type: resolver.Backend}) + continue + } + addrs = append(addrs, resolver.Address{Addr: u.Host, Type: resolver.Backend}) + } + return addrs +} diff --git a/clientv3/balancer/utils_test.go b/clientv3/balancer/utils_test.go new file mode 100644 index 00000000000..0ef69c86460 --- /dev/null +++ b/clientv3/balancer/utils_test.go @@ -0,0 +1,20 @@ +package balancer + +import ( + "reflect" + "testing" + + "google.golang.org/grpc/resolver" +) + +func Test_epsToAddrs(t *testing.T) { + eps := []string{"https://example.com:2379", "127.0.0.1:2379"} + exp := []resolver.Address{ + {Addr: "example.com:2379", Type: resolver.Backend}, + {Addr: "127.0.0.1:2379", Type: resolver.Backend}, + } + rs := epsToAddrs(eps...) + if !reflect.DeepEqual(rs, exp) { + t.Fatalf("expected %v, got %v", exp, rs) + } +} From 657c2e15cc187b3cc8d47aa425291e3ecd45ab67 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Wed, 28 Mar 2018 15:51:33 -0700 Subject: [PATCH 03/39] *: introduce mock server for testing load balancing and add a simple happy-path load balancer test Author: Joe Betz Date: Wed Mar 28 15:51:33 2018 -0700 --- Gopkg.lock | 1 + clientv3/balancer/balancer_test.go | 105 ++++++++++++++++++ clientv3/balancer/utils.go | 9 ++ pkg/mock/mockserver/doc.go | 16 +++ pkg/mock/mockserver/mockserver.go | 90 +++++++++++++++ .../grpc/resolver/manual/manual.go | 91 +++++++++++++++ 6 files changed, 312 insertions(+) create mode 100644 clientv3/balancer/balancer_test.go create mode 100644 pkg/mock/mockserver/doc.go create mode 100644 pkg/mock/mockserver/mockserver.go create mode 100644 vendor/google.golang.org/grpc/resolver/manual/manual.go diff --git a/Gopkg.lock b/Gopkg.lock index 9ca9a6596d5..657ab9d1a1d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -353,6 +353,7 @@ "peer", "resolver", "resolver/dns", + "resolver/manual", "resolver/passthrough", "stats", "status", diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go new file mode 100644 index 00000000000..d1c399305b6 --- /dev/null +++ b/clientv3/balancer/balancer_test.go @@ -0,0 +1,105 @@ +// Copyright 2018 The etcd 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 balancer + +import ( + "context" + "fmt" + "testing" + + "github.com/coreos/etcd/clientv3/balancer/picker" + pb "github.com/coreos/etcd/etcdserver/etcdserverpb" + "github.com/coreos/etcd/pkg/mock/mockserver" + + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/resolver/manual" +) + +// TestRoundRobinBalancedResolvableNoFailover ensures that +// requests to a resolvable endpoint can be balanced between +// multiple, if any, nodes. And there needs be no failover. +func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { + testCases := []struct { + name string + serverCount int + reqN int + }{ + {name: "rrBalanced_1", serverCount: 1, reqN: 5}, + {name: "rrBalanced_3", serverCount: 3, reqN: 7}, + {name: "rrBalanced_5", serverCount: 5, reqN: 10}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ms, err := mockserver.StartMockServers(tc.serverCount) + if err != nil { + t.Fatalf("failed to start mock servers: %v", err) + } + defer ms.Stop() + var resolvedAddrs []resolver.Address + for _, svr := range ms { + resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + } + + rsv, closeResolver := manual.GenerateAndRegisterManualResolver() + defer closeResolver() + cfg := Config{ + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), + Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + } + rrb := New(cfg) + conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + if err != nil { + t.Fatalf("failed to dial mock server: %s", err) + } + defer conn.Close() + rsv.NewAddress(resolvedAddrs) + cli := pb.NewKVClient(conn) + + reqFunc := func(ctx context.Context) (picked string, err error) { + var p peer.Peer + _, err = cli.Range(ctx, &pb.RangeRequest{Key: []byte("/x")}, grpc.Peer(&p)) + if p.Addr != nil { + picked = p.Addr.String() + } + return picked, err + } + + prev, switches := "", 0 + for i := 0; i < tc.reqN; i++ { + picked, err := reqFunc(context.Background()) + if err != nil { + t.Fatalf("#%d: unexpected failure %v", i, err) + } + if prev == "" { + prev = picked + continue + } + if prev != picked { + switches++ + } + prev = picked + } + if tc.serverCount > 1 && switches < tc.reqN-3 { // -3 for initial resolutions + t.Fatalf("expected balanced loads for %d requests, got switches %d", tc.reqN, switches) + } + }) + } +} diff --git a/clientv3/balancer/utils.go b/clientv3/balancer/utils.go index e54d203c1b7..5cf9bf6c473 100644 --- a/clientv3/balancer/utils.go +++ b/clientv3/balancer/utils.go @@ -4,6 +4,8 @@ import ( "fmt" "net/url" "sort" + "sync/atomic" + "time" "google.golang.org/grpc/balancer" "google.golang.org/grpc/resolver" @@ -43,3 +45,10 @@ func epsToAddrs(eps ...string) (addrs []resolver.Address) { } return addrs } + +var genN = new(uint32) + +func genName() string { + now := time.Now().UnixNano() + return fmt.Sprintf("%X%X", now, atomic.AddUint32(genN, 1)) +} diff --git a/pkg/mock/mockserver/doc.go b/pkg/mock/mockserver/doc.go new file mode 100644 index 00000000000..030b3b2ffb7 --- /dev/null +++ b/pkg/mock/mockserver/doc.go @@ -0,0 +1,16 @@ +// Copyright 2018 The etcd 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 mockserver provides mock implementations for etcdserver's server interface. +package mockserver diff --git a/pkg/mock/mockserver/mockserver.go b/pkg/mock/mockserver/mockserver.go new file mode 100644 index 00000000000..4ff6d303c58 --- /dev/null +++ b/pkg/mock/mockserver/mockserver.go @@ -0,0 +1,90 @@ +// Copyright 2018 The etcd 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 mockserver + +import ( + "context" + "fmt" + "net" + + pb "github.com/coreos/etcd/etcdserver/etcdserverpb" + + "google.golang.org/grpc" +) + +// MockServer provides a mocked out grpc server of the etcdserver interface. +type MockServer struct { + GrpcServer *grpc.Server + Address string +} + +// MockServers provides a cluster of mocket out gprc servers of the etcdserver interface. +type MockServers []*MockServer + +// StartMockServers creates the desired count of mock servers +// and starts them. +func StartMockServers(count int) (svrs MockServers, err error) { + svrs = make(MockServers, count) + defer func() { + if err != nil { + svrs.Stop() + } + }() + + for i := 0; i < count; i++ { + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + return nil, fmt.Errorf("failed to listen %v", err) + } + + svr := grpc.NewServer() + pb.RegisterKVServer(svr, &mockKVServer{}) + svrs[i] = &MockServer{GrpcServer: svr, Address: listener.Addr().String()} + go func(svr *grpc.Server, l net.Listener) { + svr.Serve(l) + }(svr, listener) + } + + return svrs, nil +} + +// Stop stops the mock server, immediately closing all open connections and listeners. +func (svrs MockServers) Stop() { + for _, svr := range svrs { + svr.GrpcServer.Stop() + } +} + +type mockKVServer struct{} + +func (m *mockKVServer) Range(context.Context, *pb.RangeRequest) (*pb.RangeResponse, error) { + return &pb.RangeResponse{}, nil +} + +func (m *mockKVServer) Put(context.Context, *pb.PutRequest) (*pb.PutResponse, error) { + return &pb.PutResponse{}, nil +} + +func (m *mockKVServer) DeleteRange(context.Context, *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) { + return &pb.DeleteRangeResponse{}, nil +} + +func (m *mockKVServer) Txn(context.Context, *pb.TxnRequest) (*pb.TxnResponse, error) { + return &pb.TxnResponse{}, nil +} + +func (m *mockKVServer) Compact(context.Context, *pb.CompactionRequest) (*pb.CompactionResponse, error) { + return &pb.CompactionResponse{}, nil +} diff --git a/vendor/google.golang.org/grpc/resolver/manual/manual.go b/vendor/google.golang.org/grpc/resolver/manual/manual.go new file mode 100644 index 00000000000..50ed762a83d --- /dev/null +++ b/vendor/google.golang.org/grpc/resolver/manual/manual.go @@ -0,0 +1,91 @@ +/* + * + * Copyright 2017 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 manual defines a resolver that can be used to manually send resolved +// addresses to ClientConn. +package manual + +import ( + "strconv" + "time" + + "google.golang.org/grpc/resolver" +) + +// NewBuilderWithScheme creates a new test resolver builder with the given scheme. +func NewBuilderWithScheme(scheme string) *Resolver { + return &Resolver{ + scheme: scheme, + } +} + +// Resolver is also a resolver builder. +// It's build() function always returns itself. +type Resolver struct { + scheme string + + // Fields actually belong to the resolver. + cc resolver.ClientConn + bootstrapAddrs []resolver.Address +} + +// InitialAddrs adds resolved addresses to the resolver so that +// NewAddress doesn't need to be explicitly called after Dial. +func (r *Resolver) InitialAddrs(addrs []resolver.Address) { + r.bootstrapAddrs = addrs +} + +// Build returns itself for Resolver, because it's both a builder and a resolver. +func (r *Resolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + r.cc = cc + if r.bootstrapAddrs != nil { + r.NewAddress(r.bootstrapAddrs) + } + return r, nil +} + +// Scheme returns the test scheme. +func (r *Resolver) Scheme() string { + return r.scheme +} + +// ResolveNow is a noop for Resolver. +func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} + +// Close is a noop for Resolver. +func (*Resolver) Close() {} + +// NewAddress calls cc.NewAddress. +func (r *Resolver) NewAddress(addrs []resolver.Address) { + r.cc.NewAddress(addrs) +} + +// NewServiceConfig calls cc.NewServiceConfig. +func (r *Resolver) NewServiceConfig(sc string) { + r.cc.NewServiceConfig(sc) +} + +// GenerateAndRegisterManualResolver generates a random scheme and a Resolver +// with it. It also regieter this Resolver. +// It returns the Resolver and a cleanup function to unregister it. +func GenerateAndRegisterManualResolver() (*Resolver, func()) { + scheme := strconv.FormatInt(time.Now().UnixNano(), 36) + r := NewBuilderWithScheme(scheme) + resolver.Register(r) + return r, func() { resolver.UnregisterForTesting(scheme) } +} From f1aa428a38ae0d58a301dcfdb8ced77865be276f Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Sun, 1 Apr 2018 17:52:26 -0700 Subject: [PATCH 04/39] pkg/mock/mockserver: support restart Signed-off-by: Gyuho Lee --- pkg/mock/mockserver/mockserver.go | 78 ++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/pkg/mock/mockserver/mockserver.go b/pkg/mock/mockserver/mockserver.go index 4ff6d303c58..08d6bbe8054 100644 --- a/pkg/mock/mockserver/mockserver.go +++ b/pkg/mock/mockserver/mockserver.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "net" + "sync" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" @@ -26,45 +27,86 @@ import ( // MockServer provides a mocked out grpc server of the etcdserver interface. type MockServer struct { - GrpcServer *grpc.Server + ln net.Listener Address string + GrpcServer *grpc.Server } // MockServers provides a cluster of mocket out gprc servers of the etcdserver interface. -type MockServers []*MockServer +type MockServers struct { + mu sync.RWMutex + Servers []*MockServer + wg sync.WaitGroup +} // StartMockServers creates the desired count of mock servers // and starts them. -func StartMockServers(count int) (svrs MockServers, err error) { - svrs = make(MockServers, count) +func StartMockServers(count int) (ms *MockServers, err error) { + ms = &MockServers{ + Servers: make([]*MockServer, count), + wg: sync.WaitGroup{}, + } defer func() { if err != nil { - svrs.Stop() + ms.Stop() } }() - - for i := 0; i < count; i++ { - listener, err := net.Listen("tcp", "localhost:0") + for idx := 0; idx < count; idx++ { + ln, err := net.Listen("tcp", "localhost:0") if err != nil { return nil, fmt.Errorf("failed to listen %v", err) } + ms.Servers[idx] = &MockServer{ln: ln, Address: ln.Addr().String()} + ms.StartAt(idx) + } + return ms, nil +} + +// StartAt restarts mock server at given index. +func (ms *MockServers) StartAt(idx int) (err error) { + ms.mu.Lock() + defer ms.mu.Unlock() + + if ms.Servers[idx].ln == nil { + ms.Servers[idx].ln, err = net.Listen("tcp", ms.Servers[idx].Address) + if err != nil { + return fmt.Errorf("failed to listen %v", err) + } + } + + svr := grpc.NewServer() + pb.RegisterKVServer(svr, &mockKVServer{}) + ms.Servers[idx].GrpcServer = svr + + go func(svr *grpc.Server, l net.Listener) { + ms.wg.Add(1) + svr.Serve(l) + }(ms.Servers[idx].GrpcServer, ms.Servers[idx].ln) + + return nil +} + +// StopAt stops mock server at given index. +func (ms *MockServers) StopAt(idx int) { + ms.mu.Lock() + defer ms.mu.Unlock() - svr := grpc.NewServer() - pb.RegisterKVServer(svr, &mockKVServer{}) - svrs[i] = &MockServer{GrpcServer: svr, Address: listener.Addr().String()} - go func(svr *grpc.Server, l net.Listener) { - svr.Serve(l) - }(svr, listener) + if ms.Servers[idx].ln == nil { + return } - return svrs, nil + ms.Servers[idx].GrpcServer.Stop() + ms.Servers[idx].GrpcServer = nil + ms.Servers[idx].ln = nil + ms.wg.Done() } // Stop stops the mock server, immediately closing all open connections and listeners. -func (svrs MockServers) Stop() { - for _, svr := range svrs { - svr.GrpcServer.Stop() +func (ms *MockServers) Stop() { + for idx := range ms.Servers { + ms.StopAt(idx) } + ms.wg.Wait() } type mockKVServer struct{} From 7c92185fe34188ee16b75e5ade83c8abc1ea95a5 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 2 Apr 2018 01:52:24 -0700 Subject: [PATCH 05/39] clientv3/balancer: use new mock server in tests Signed-off-by: Gyuho Lee --- clientv3/balancer/balancer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index d1c399305b6..5ae0e660bae 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -52,7 +52,7 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { } defer ms.Stop() var resolvedAddrs []resolver.Address - for _, svr := range ms { + for _, svr := range ms.Servers { resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) } From 370761de82de16509bbc7a8833c7711987265c1d Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 2 Apr 2018 01:45:24 -0700 Subject: [PATCH 06/39] clientv3/balancer: add more failover tests with resolver Signed-off-by: Gyuho Lee --- clientv3/balancer/balancer_test.go | 190 +++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index 5ae0e660bae..bf9aad93a03 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -17,7 +17,9 @@ package balancer import ( "context" "fmt" + "strings" "testing" + "time" "github.com/coreos/etcd/clientv3/balancer/picker" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" @@ -25,9 +27,11 @@ import ( "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver/manual" + "google.golang.org/grpc/status" ) // TestRoundRobinBalancedResolvableNoFailover ensures that @@ -103,3 +107,189 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { }) } } + +// TestRoundRobinBalancedResolvableFailoverFromServerFail ensures that +// loads be rebalanced while one server goes down and comes back. +func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { + serverCount := 5 + ms, err := mockserver.StartMockServers(serverCount) + if err != nil { + t.Fatalf("failed to start mock servers: %s", err) + } + defer ms.Stop() + var resolvedAddrs []resolver.Address + for _, svr := range ms.Servers { + resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + } + + rsv, closeResolver := manual.GenerateAndRegisterManualResolver() + defer closeResolver() + cfg := Config{ + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), + Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + } + rrb := New(cfg) + conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + if err != nil { + t.Fatalf("failed to dial mock server: %s", err) + } + defer conn.Close() + rsv.NewAddress(resolvedAddrs) + cli := pb.NewKVClient(conn) + + reqFunc := func(ctx context.Context) (picked string, err error) { + var p peer.Peer + _, err = cli.Range(ctx, &pb.RangeRequest{Key: []byte("/x")}, grpc.Peer(&p)) + if p.Addr != nil { + picked = p.Addr.String() + } + return picked, err + } + + // stop first server, loads should be redistributed + // stopped server should never be picked + ms.StopAt(0) + available := make(map[string]struct{}) + for i := 1; i < serverCount; i++ { + available[resolvedAddrs[i].Addr] = struct{}{} + } + + reqN := 10 + prev, switches := "", 0 + for i := 0; i < reqN; i++ { + picked, err := reqFunc(context.Background()) + if err != nil && strings.Contains(err.Error(), "transport is closing") { + continue + } + if prev == "" { // first failover + if resolvedAddrs[0].Addr == picked { + t.Fatalf("expected failover from %q, picked %q", resolvedAddrs[0].Addr, picked) + } + prev = picked + continue + } + if _, ok := available[picked]; !ok { + t.Fatalf("picked unavailable address %q (available %v)", picked, available) + } + if prev != picked { + switches++ + } + prev = picked + } + if switches < reqN-3 { // -3 for initial resolutions + failover + t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches) + } + + // now failed server comes back + ms.StartAt(0) + + // enough time for reconnecting to recovered server + time.Sleep(time.Second) + + prev, switches = "", 0 + recoveredAddr, recovered := resolvedAddrs[0].Addr, 0 + available[recoveredAddr] = struct{}{} + + for i := 0; i < 2*reqN; i++ { + picked, err := reqFunc(context.Background()) + if err != nil { + t.Fatalf("#%d: unexpected failure %v", i, err) + } + if prev == "" { + prev = picked + continue + } + if _, ok := available[picked]; !ok { + t.Fatalf("#%d: picked unavailable address %q (available %v)", i, picked, available) + } + if prev != picked { + switches++ + } + if picked == recoveredAddr { + recovered++ + } + prev = picked + } + if switches < reqN-3 { // -3 for initial resolutions + t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches) + } + if recovered < reqN/serverCount { + t.Fatalf("recovered server %q got only %d requests", recoveredAddr, recovered) + } +} + +// TestRoundRobinBalancedResolvableFailoverFromRequestFail ensures that +// loads be rebalanced while some requests are failed. +func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { + serverCount := 5 + ms, err := mockserver.StartMockServers(serverCount) + if err != nil { + t.Fatalf("failed to start mock servers: %s", err) + } + defer ms.Stop() + var resolvedAddrs []resolver.Address + available := make(map[string]struct{}) + for _, svr := range ms.Servers { + resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + available[svr.Address] = struct{}{} + } + + rsv, closeResolver := manual.GenerateAndRegisterManualResolver() + defer closeResolver() + cfg := Config{ + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), + Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + } + rrb := New(cfg) + conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + if err != nil { + t.Fatalf("failed to dial mock server: %s", err) + } + defer conn.Close() + rsv.NewAddress(resolvedAddrs) + cli := pb.NewKVClient(conn) + + reqFunc := func(ctx context.Context) (picked string, err error) { + var p peer.Peer + _, err = cli.Range(ctx, &pb.RangeRequest{Key: []byte("/x")}, grpc.Peer(&p)) + if p.Addr != nil { + picked = p.Addr.String() + } + return picked, err + } + + reqN := 20 + prev, switches := "", 0 + for i := 0; i < reqN; i++ { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if i%2 == 0 { + cancel() + } + picked, err := reqFunc(ctx) + if i%2 == 0 { + if s, ok := status.FromError(err); ok && s.Code() != codes.Canceled || picked != "" { + t.Fatalf("#%d: expected %v, got %v", i, context.Canceled, err) + } + continue + } + if prev == "" && picked != "" { + prev = picked + continue + } + if _, ok := available[picked]; !ok { + t.Fatalf("#%d: picked unavailable address %q (available %v)", i, picked, available) + } + if prev != picked { + switches++ + } + prev = picked + } + if switches < reqN/2-3 { // -3 for initial resolutions + failover + t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches) + } +} From 9867210a540f806f142a5f7f687ec77c7c1653f0 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 2 Apr 2018 01:54:18 -0700 Subject: [PATCH 07/39] clientv3/balancer: add "TestRoundRobinBalancedPassthrough" (WIP) Signed-off-by: Gyuho Lee --- clientv3/balancer/balancer_test.go | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index bf9aad93a03..65f71625a2c 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -293,3 +293,62 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches) } } + +// TestRoundRobinBalancedPassthrough ensures that requests with +// passthrough resolver be balanced with roundrobin picker. +// TODO: this is not working right now... +// Maintain multiple client connections? +func TestRoundRobinBalancedPassthrough(t *testing.T) { + ms, err := mockserver.StartMockServers(3) + if err != nil { + t.Fatalf("failed to start mock servers: %s", err) + } + defer ms.Stop() + eps := make([]string, len(ms.Servers)) + for i, svr := range ms.Servers { + eps[i] = fmt.Sprintf("passthrough:///%s", svr.Address) + } + + cfg := Config{ + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), + Endpoints: eps, + } + rrb := New(cfg) + conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + if err != nil { + t.Fatalf("Failed to dial mock server: %s", err) + } + defer conn.Close() + cli := pb.NewKVClient(conn) + + reqFunc := func(ctx context.Context) (picked string, err error) { + var p peer.Peer + _, err = cli.Range(ctx, &pb.RangeRequest{Key: []byte("/x")}, grpc.Peer(&p)) + if p.Addr != nil { + picked = p.Addr.String() + } + return picked, err + } + + reqN := 20 + prev, switches := "", 0 + for i := 0; i < reqN; i++ { + picked, err := reqFunc(context.Background()) + if err != nil { + t.Fatal(err) + } + if prev == "" && picked != "" { + prev = picked + continue + } + if prev != picked { + switches++ + } + prev = picked + } + if switches < reqN/2-3 { // -3 for initial resolutions + failover + t.Logf("expected balanced loads for %d requests, got switches %d", reqN, switches) + } +} From 4d2a25b05694f656c0394e67554fb9e13d5b77bd Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Tue, 3 Apr 2018 16:03:53 -0700 Subject: [PATCH 08/39] clientv3/balancer: add endpoints resolver --- clientv3/balancer/balancer.go | 11 +- clientv3/balancer/balancer_test.go | 104 +++++---------- .../balancer/resolver/endpoint/endpoint.go | 119 ++++++++++++++++++ 3 files changed, 156 insertions(+), 78 deletions(-) create mode 100644 clientv3/balancer/resolver/endpoint/endpoint.go diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index 1bb32402896..ae91ca4ec24 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -16,6 +16,7 @@ package balancer import ( "fmt" + "strings" "sync" "github.com/coreos/etcd/clientv3/balancer/picker" @@ -69,7 +70,13 @@ type baseBalancer struct { } // New returns a new balancer from specified picker policy. -func New(cfg Config) Balancer { +func New(cfg Config) (Balancer, error) { + for _, ep := range cfg.Endpoints { + if !strings.HasPrefix(ep, "etcd://") { + return nil, fmt.Errorf("'etcd' target schema required for etcd load balancer endpoints but got '%s'", ep) + } + } + bb := &baseBalancer{ policy: cfg.Policy, name: cfg.Policy.String(), @@ -100,7 +107,7 @@ func New(cfg Config) Balancer { zap.String("policy", bb.policy.String()), zap.String("name", bb.name), ) - return bb + return bb, nil } // Name implements "grpc/balancer.Builder" interface. diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index 65f71625a2c..f10c5f60224 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/coreos/etcd/clientv3/balancer/picker" + "github.com/coreos/etcd/clientv3/balancer/resolver/endpoint" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/pkg/mock/mockserver" @@ -30,7 +31,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/resolver" - "google.golang.org/grpc/resolver/manual" "google.golang.org/grpc/status" ) @@ -60,21 +60,25 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) } - rsv, closeResolver := manual.GenerateAndRegisterManualResolver() - defer closeResolver() + rsv := endpoint.EndpointResolver("nofailover") + defer rsv.Close() + rsv.InitialAddrs(resolvedAddrs) + cfg := Config{ Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + Endpoints: []string{fmt.Sprintf("etcd://nofailover/mock.server")}, + } + rrb, err := New(cfg) + if err != nil { + t.Fatalf("failed to create builder: %v", err) } - rrb := New(cfg) conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { - t.Fatalf("failed to dial mock server: %s", err) + t.Fatalf("failed to dial mock server: %v", err) } defer conn.Close() - rsv.NewAddress(resolvedAddrs) cli := pb.NewKVClient(conn) reqFunc := func(ctx context.Context) (picked string, err error) { @@ -122,21 +126,25 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) } - rsv, closeResolver := manual.GenerateAndRegisterManualResolver() - defer closeResolver() + rsv := endpoint.EndpointResolver("serverfail") + defer rsv.Close() + rsv.InitialAddrs(resolvedAddrs) + cfg := Config{ Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + Endpoints: []string{fmt.Sprintf("etcd://serverfail/mock.server")}, + } + rrb, err := New(cfg) + if err != nil { + t.Fatalf("failed to create builder: %v", err) } - rrb := New(cfg) conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } defer conn.Close() - rsv.NewAddress(resolvedAddrs) cli := pb.NewKVClient(conn) reqFunc := func(ctx context.Context) (picked string, err error) { @@ -235,22 +243,25 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) available[svr.Address] = struct{}{} } + rsv := endpoint.EndpointResolver("requestfail") + defer rsv.Close() + rsv.InitialAddrs(resolvedAddrs) - rsv, closeResolver := manual.GenerateAndRegisterManualResolver() - defer closeResolver() cfg := Config{ Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("%s:///mock.server", rsv.Scheme())}, + Endpoints: []string{fmt.Sprintf("etcd://requestfail/mock.server")}, + } + rrb, err := New(cfg) + if err != nil { + t.Fatalf("failed to create builder: %v", err) } - rrb := New(cfg) conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } defer conn.Close() - rsv.NewAddress(resolvedAddrs) cli := pb.NewKVClient(conn) reqFunc := func(ctx context.Context) (picked string, err error) { @@ -293,62 +304,3 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches) } } - -// TestRoundRobinBalancedPassthrough ensures that requests with -// passthrough resolver be balanced with roundrobin picker. -// TODO: this is not working right now... -// Maintain multiple client connections? -func TestRoundRobinBalancedPassthrough(t *testing.T) { - ms, err := mockserver.StartMockServers(3) - if err != nil { - t.Fatalf("failed to start mock servers: %s", err) - } - defer ms.Stop() - eps := make([]string, len(ms.Servers)) - for i, svr := range ms.Servers { - eps[i] = fmt.Sprintf("passthrough:///%s", svr.Address) - } - - cfg := Config{ - Policy: picker.RoundrobinBalanced, - Name: genName(), - Logger: zap.NewExample(), - Endpoints: eps, - } - rrb := New(cfg) - conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) - if err != nil { - t.Fatalf("Failed to dial mock server: %s", err) - } - defer conn.Close() - cli := pb.NewKVClient(conn) - - reqFunc := func(ctx context.Context) (picked string, err error) { - var p peer.Peer - _, err = cli.Range(ctx, &pb.RangeRequest{Key: []byte("/x")}, grpc.Peer(&p)) - if p.Addr != nil { - picked = p.Addr.String() - } - return picked, err - } - - reqN := 20 - prev, switches := "", 0 - for i := 0; i < reqN; i++ { - picked, err := reqFunc(context.Background()) - if err != nil { - t.Fatal(err) - } - if prev == "" && picked != "" { - prev = picked - continue - } - if prev != picked { - switches++ - } - prev = picked - } - if switches < reqN/2-3 { // -3 for initial resolutions + failover - t.Logf("expected balanced loads for %d requests, got switches %d", reqN, switches) - } -} diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go new file mode 100644 index 00000000000..d3cabec3006 --- /dev/null +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -0,0 +1,119 @@ +// Copyright 2018 The etcd 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. + +// resolves to etcd entpoints for grpc targets of the form 'etcd:///'. +package endpoint + +import ( + "fmt" + "sync" + + "google.golang.org/grpc/resolver" +) + +const ( + scheme = "etcd" +) + +var ( + bldr *builder +) + +func init() { + bldr = &builder{ + clusterResolvers: make(map[string]*Resolver), + } + resolver.Register(bldr) +} + +type builder struct { + clusterResolvers map[string]*Resolver + sync.RWMutex +} + +// Build creates or reuses an etcd resolver for the etcd cluster name identified by the authority part of the target. +func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { + if len(target.Authority) < 1 { + return nil, fmt.Errorf("'etcd' target scheme requires non-empty authority identifying etcd cluster being routed to") + } + r := b.getResolver(target.Authority) + r.cc = cc + if r.bootstrapAddrs != nil { + r.NewAddress(r.bootstrapAddrs) + } + return r, nil +} + +func (b *builder) getResolver(clusterName string) *Resolver { + b.RLock() + r, ok := b.clusterResolvers[clusterName] + b.RUnlock() + if !ok { + r = &Resolver{ + clusterName: clusterName, + } + b.Lock() + b.clusterResolvers[clusterName] = r + b.Unlock() + } + return r +} + +func (b *builder) addResolver(r *Resolver) { + bldr.Lock() + bldr.clusterResolvers[r.clusterName] = r + bldr.Unlock() +} + +func (b *builder) removeResolver(r *Resolver) { + bldr.Lock() + delete(bldr.clusterResolvers, r.clusterName) + bldr.Unlock() +} + +func (r *builder) Scheme() string { + return scheme +} + +// EndpointResolver gets the resolver for given etcd cluster name. +func EndpointResolver(clusterName string) *Resolver { + return bldr.getResolver(clusterName) +} + +// Resolver provides a resolver for a single etcd cluster, identified by name. +type Resolver struct { + clusterName string + cc resolver.ClientConn + bootstrapAddrs []resolver.Address +} + +// InitialAddrs sets the initial endpoint addresses for the resolver. +func (r *Resolver) InitialAddrs(addrs []resolver.Address) { + r.bootstrapAddrs = addrs +} + +// NewAddress updates the addresses of the resolver. +func (r *Resolver) NewAddress(addrs []resolver.Address) error { + if r.cc == nil { + return fmt.Errorf("resolver not yet built, use InitialAddrs to provide initialization endpoints") + } + r.cc.NewAddress(addrs) + return nil +} + +func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} + +func (r *Resolver) Close() { + bldr.removeResolver(r) +} From ed6bc2b554733318281c5646d8c6b8c653a9408f Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Wed, 4 Apr 2018 16:35:15 -0700 Subject: [PATCH 09/39] clientv3: add load balancer unix socket test --- clientv3/balancer/balancer_test.go | 15 +++-- .../balancer/resolver/endpoint/endpoint.go | 13 ++++ clientv3/balancer/utils.go | 14 ++++ clientv3/balancer/utils_test.go | 14 ++++ pkg/mock/mockserver/mockserver.go | 67 +++++++++++++++++-- vendor/google.golang.org/grpc/clientconn.go | 13 +++- 6 files changed, 124 insertions(+), 12 deletions(-) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index f10c5f60224..8a3e9359813 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -42,22 +42,25 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { name string serverCount int reqN int + network string }{ - {name: "rrBalanced_1", serverCount: 1, reqN: 5}, - {name: "rrBalanced_3", serverCount: 3, reqN: 7}, - {name: "rrBalanced_5", serverCount: 5, reqN: 10}, + {name: "rrBalanced_1", serverCount: 1, reqN: 5, network: "tcp"}, + {name: "rrBalanced_1_unix_sockets", serverCount: 1, reqN: 5, network: "unix"}, + {name: "rrBalanced_3", serverCount: 3, reqN: 7, network: "tcp"}, + {name: "rrBalanced_5", serverCount: 5, reqN: 10, network: "tcp"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ms, err := mockserver.StartMockServers(tc.serverCount) + ms, err := mockserver.StartMockServersOnNetwork(tc.serverCount, tc.network) if err != nil { t.Fatalf("failed to start mock servers: %v", err) } defer ms.Stop() + var resolvedAddrs []resolver.Address for _, svr := range ms.Servers { - resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + resolvedAddrs = append(resolvedAddrs, svr.ResolverAddress()) } rsv := endpoint.EndpointResolver("nofailover") @@ -68,7 +71,7 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("etcd://nofailover/mock.server")}, + Endpoints: []string{fmt.Sprintf("etcd://nofailover/*")}, } rrb, err := New(cfg) if err != nil { diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index d3cabec3006..4679f5ffd8a 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -103,6 +103,19 @@ func (r *Resolver) InitialAddrs(addrs []resolver.Address) { r.bootstrapAddrs = addrs } +func (r *Resolver) InitialEndpoints(eps []string) { + r.InitialAddrs(epsToAddrs(eps...)) +} + +// TODO: use balancer.epsToAddrs +func epsToAddrs(eps ...string) (addrs []resolver.Address) { + addrs = make([]resolver.Address, 0, len(eps)) + for _, ep := range eps { + addrs = append(addrs, resolver.Address{Addr: ep}) + } + return addrs +} + // NewAddress updates the addresses of the resolver. func (r *Resolver) NewAddress(addrs []resolver.Address) error { if r.cc == nil { diff --git a/clientv3/balancer/utils.go b/clientv3/balancer/utils.go index 5cf9bf6c473..a11faeb7e6c 100644 --- a/clientv3/balancer/utils.go +++ b/clientv3/balancer/utils.go @@ -1,3 +1,17 @@ +// Copyright 2018 The etcd 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 balancer import ( diff --git a/clientv3/balancer/utils_test.go b/clientv3/balancer/utils_test.go index 0ef69c86460..e58cd349576 100644 --- a/clientv3/balancer/utils_test.go +++ b/clientv3/balancer/utils_test.go @@ -1,3 +1,17 @@ +// Copyright 2018 The etcd 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 balancer import ( diff --git a/pkg/mock/mockserver/mockserver.go b/pkg/mock/mockserver/mockserver.go index 08d6bbe8054..e1ed10559e0 100644 --- a/pkg/mock/mockserver/mockserver.go +++ b/pkg/mock/mockserver/mockserver.go @@ -17,21 +17,36 @@ package mockserver import ( "context" "fmt" + "io/ioutil" "net" + "os" "sync" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "google.golang.org/grpc" + "google.golang.org/grpc/resolver" ) // MockServer provides a mocked out grpc server of the etcdserver interface. type MockServer struct { ln net.Listener + Network string Address string GrpcServer *grpc.Server } +func (ms *MockServer) ResolverAddress() resolver.Address { + switch ms.Network { + case "unix": + return resolver.Address{Addr: fmt.Sprintf("unix://%s", ms.Address)} + case "tcp": + return resolver.Address{Addr: ms.Address} + default: + panic("illegal network type: " + ms.Network) + } +} + // MockServers provides a cluster of mocket out gprc servers of the etcdserver interface. type MockServers struct { mu sync.RWMutex @@ -42,8 +57,50 @@ type MockServers struct { // StartMockServers creates the desired count of mock servers // and starts them. func StartMockServers(count int) (ms *MockServers, err error) { + return StartMockServersOnNetwork(count, "tcp") +} + +// StartMockServersOnNetwork creates mock servers on either 'tcp' or 'unix' sockets. +func StartMockServersOnNetwork(count int, network string) (ms *MockServers, err error) { + switch network { + case "tcp": + return startMockServersTcp(count) + case "unix": + return startMockServersUnix(count) + default: + return nil, fmt.Errorf("unsupported network type: %s", network) + } +} + +func startMockServersTcp(count int) (ms *MockServers, err error) { + addrs := make([]string, 0, count) + for i := 0; i < count; i++ { + addrs = append(addrs, "localhost:0") + } + return startMockServers("tcp", addrs) +} + +func startMockServersUnix(count int) (ms *MockServers, err error) { + dir := os.TempDir() + addrs := make([]string, 0, count) + for i := 0; i < count; i++ { + f, err := ioutil.TempFile(dir, "etcd-unix-so-") + if err != nil { + return nil, fmt.Errorf("failed to allocate temp file for unix socket: %v", err) + } + fn := f.Name() + err = os.Remove(fn) + if err != nil { + return nil, fmt.Errorf("failed to remove temp file before creating unix socket: %v", err) + } + addrs = append(addrs, fn) + } + return startMockServers("unix", addrs) +} + +func startMockServers(network string, addrs []string) (ms *MockServers, err error) { ms = &MockServers{ - Servers: make([]*MockServer, count), + Servers: make([]*MockServer, len(addrs)), wg: sync.WaitGroup{}, } defer func() { @@ -51,12 +108,12 @@ func StartMockServers(count int) (ms *MockServers, err error) { ms.Stop() } }() - for idx := 0; idx < count; idx++ { - ln, err := net.Listen("tcp", "localhost:0") + for idx, addr := range addrs { + ln, err := net.Listen(network, addr) if err != nil { return nil, fmt.Errorf("failed to listen %v", err) } - ms.Servers[idx] = &MockServer{ln: ln, Address: ln.Addr().String()} + ms.Servers[idx] = &MockServer{ln: ln, Network: network, Address: ln.Addr().String()} ms.StartAt(idx) } return ms, nil @@ -68,7 +125,7 @@ func (ms *MockServers) StartAt(idx int) (err error) { defer ms.mu.Unlock() if ms.Servers[idx].ln == nil { - ms.Servers[idx].ln, err = net.Listen("tcp", ms.Servers[idx].Address) + ms.Servers[idx].ln, err = net.Listen(ms.Servers[idx].Network, ms.Servers[idx].Address) if err != nil { return fmt.Errorf("failed to listen %v", err) } diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 64a7982fad1..d1426a4bb6b 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -24,6 +24,7 @@ import ( "math" "net" "reflect" + "regexp" "strings" "sync" "time" @@ -443,7 +444,17 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if cc.dopts.copts.Dialer == nil { cc.dopts.copts.Dialer = newProxyDialer( func(ctx context.Context, addr string) (net.Conn, error) { - return dialContext(ctx, "tcp", addr) + network := "tcp" + p := regexp.MustCompile(`[a-z]+://`) + if p.MatchString(addr) { + parts := strings.Split(addr, "://") + scheme := parts[0] + if scheme == "unix" && len(parts) > 1 && len(parts[1]) > 0 { + network = "unix" + addr = parts[1] + } + } + return dialContext(ctx, network, addr) }, ) } From 6080fa1270b925a51e38be2aacd050911a0205e8 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Thu, 5 Apr 2018 17:33:07 -0700 Subject: [PATCH 10/39] clientv3: Integrate new grpc load balancer interface with etcd client --- clientv3/balancer/balancer.go | 18 +-- clientv3/balancer/balancer_test.go | 6 +- .../balancer/resolver/endpoint/endpoint.go | 88 ++++++++++-- clientv3/client.go | 130 +++++++++--------- clientv3/ordering/util_test.go | 11 +- clientv3/retry.go | 37 +++-- integration/embed_test.go | 5 +- vendor/google.golang.org/grpc/clientconn.go | 2 +- 8 files changed, 196 insertions(+), 101 deletions(-) diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index ae91ca4ec24..2bce6c8de6d 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -62,7 +62,7 @@ type baseBalancer struct { scToAddr map[balancer.SubConn]resolver.Address scToSt map[balancer.SubConn]connectivity.State - currrentConn balancer.ClientConn + currentConn balancer.ClientConn currentState connectivity.State csEvltr *connectivityStateEvaluator @@ -72,8 +72,8 @@ type baseBalancer struct { // New returns a new balancer from specified picker policy. func New(cfg Config) (Balancer, error) { for _, ep := range cfg.Endpoints { - if !strings.HasPrefix(ep, "etcd://") { - return nil, fmt.Errorf("'etcd' target schema required for etcd load balancer endpoints but got '%s'", ep) + if !strings.HasPrefix(ep, "endpoint://") { + return nil, fmt.Errorf("'endpoint' target schema required for etcd load balancer endpoints but got '%s'", ep) } } @@ -88,8 +88,8 @@ func New(cfg Config) (Balancer, error) { scToAddr: make(map[balancer.SubConn]resolver.Address), scToSt: make(map[balancer.SubConn]connectivity.State), - currrentConn: nil, - csEvltr: &connectivityStateEvaluator{}, + currentConn: nil, + csEvltr: &connectivityStateEvaluator{}, // initialize picker always returns "ErrNoSubConnAvailable" Picker: picker.NewErr(balancer.ErrNoSubConnAvailable), @@ -120,7 +120,7 @@ func (bb *baseBalancer) Name() string { return bb.name } func (bb *baseBalancer) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { // TODO: support multiple connections bb.mu.Lock() - bb.currrentConn = cc + bb.currentConn = cc bb.mu.Unlock() bb.lg.Info( @@ -147,7 +147,7 @@ func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) for _, addr := range addrs { resolved[addr] = struct{}{} if _, ok := bb.addrToSc[addr]; !ok { - sc, err := bb.currrentConn.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{}) + sc, err := bb.currentConn.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{}) if err != nil { bb.lg.Warn("NewSubConn failed", zap.Error(err), zap.String("address", addr.Addr)) continue @@ -162,7 +162,7 @@ func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) for addr, sc := range bb.addrToSc { if _, ok := resolved[addr]; !ok { // was removed by resolver or failed to create subconn - bb.currrentConn.RemoveSubConn(sc) + bb.currentConn.RemoveSubConn(sc) delete(bb.addrToSc, addr) bb.lg.Info( @@ -227,7 +227,7 @@ func (bb *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connecti bb.regeneratePicker() } - bb.currrentConn.UpdateBalancerState(bb.currentState, bb.Picker) + bb.currentConn.UpdateBalancerState(bb.currentState, bb.Picker) return } diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index 8a3e9359813..80243d9630e 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -71,7 +71,7 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("etcd://nofailover/*")}, + Endpoints: []string{fmt.Sprintf("endpoint://nofailover/*")}, } rrb, err := New(cfg) if err != nil { @@ -137,7 +137,7 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("etcd://serverfail/mock.server")}, + Endpoints: []string{fmt.Sprintf("endpoint://serverfail/mock.server")}, } rrb, err := New(cfg) if err != nil { @@ -254,7 +254,7 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { Policy: picker.RoundrobinBalanced, Name: genName(), Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("etcd://requestfail/mock.server")}, + Endpoints: []string{fmt.Sprintf("endpoint://requestfail/mock.server")}, } rrb, err := New(cfg) if err != nil { diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 4679f5ffd8a..617f11f234b 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -12,21 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -// resolves to etcd entpoints for grpc targets of the form 'etcd:///'. +// resolves to etcd entpoints for grpc targets of the form 'endpoint:///'. package endpoint import ( "fmt" + "net/url" + "strings" "sync" "google.golang.org/grpc/resolver" ) const ( - scheme = "etcd" + scheme = "endpoint" ) var ( + targetPrefix = fmt.Sprintf("%s://", scheme) + bldr *builder ) @@ -49,8 +53,8 @@ func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts res } r := b.getResolver(target.Authority) r.cc = cc - if r.bootstrapAddrs != nil { - r.NewAddress(r.bootstrapAddrs) + if r.addrs != nil { + r.NewAddress(r.addrs) } return r, nil } @@ -93,14 +97,19 @@ func EndpointResolver(clusterName string) *Resolver { // Resolver provides a resolver for a single etcd cluster, identified by name. type Resolver struct { - clusterName string - cc resolver.ClientConn - bootstrapAddrs []resolver.Address + clusterName string + cc resolver.ClientConn + addrs []resolver.Address + hostToAddr map[string]resolver.Address + sync.RWMutex } // InitialAddrs sets the initial endpoint addresses for the resolver. func (r *Resolver) InitialAddrs(addrs []resolver.Address) { - r.bootstrapAddrs = addrs + r.Lock() + r.addrs = addrs + r.hostToAddr = keyAddrsByHost(addrs) + r.Unlock() } func (r *Resolver) InitialEndpoints(eps []string) { @@ -121,12 +130,75 @@ func (r *Resolver) NewAddress(addrs []resolver.Address) error { if r.cc == nil { return fmt.Errorf("resolver not yet built, use InitialAddrs to provide initialization endpoints") } + r.Lock() + r.addrs = addrs + r.hostToAddr = keyAddrsByHost(addrs) + r.Unlock() r.cc.NewAddress(addrs) return nil } +func keyAddrsByHost(addrs []resolver.Address) map[string]resolver.Address { + // TODO: etcd may be is running on multiple ports on the same host, what to do? Keep a list of addresses? + byHost := make(map[string]resolver.Address, len(addrs)) + for _, addr := range addrs { + _, host, _ := ParseEndpoint(addr.Addr) + byHost[host] = addr + } + return byHost +} + +// Endpoint get the resolver address for the host, if any. +func (r *Resolver) Endpoint(host string) string { + var addr string + r.RLock() + if a, ok := r.hostToAddr[host]; ok { + addr = a.Addr + } + r.RUnlock() + return addr +} + func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} func (r *Resolver) Close() { bldr.removeResolver(r) } + +// Parse endpoint parses a endpoint of the form (http|https)://*|(unix|unixs)://) and returns a +// protocol ('tcp' or 'unix'), host (or filepath if a unix socket) and scheme (http, https, unix, unixs). +func ParseEndpoint(endpoint string) (proto string, host string, scheme string) { + proto = "tcp" + host = endpoint + url, uerr := url.Parse(endpoint) + if uerr != nil || !strings.Contains(endpoint, "://") { + return proto, host, scheme + } + scheme = url.Scheme + + // strip scheme:// prefix since grpc dials by host + host = url.Host + switch url.Scheme { + case "http", "https": + case "unix", "unixs": + proto = "unix" + host = url.Host + url.Path + default: + proto, host = "", "" + } + return proto, host, scheme +} + +// ParseTarget parses a endpoint:/// string and returns the parsed clusterName and endpoint. +// If the target is malformed, an error is returned. +func ParseTarget(target string) (string, string, error) { + noPrefix := strings.TrimPrefix(target, targetPrefix) + if noPrefix == target { + return "", "", fmt.Errorf("malformed target, %s prefix is required: %s", targetPrefix, target) + } + parts := strings.SplitN(noPrefix, "/", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("malformed target, expected %s:///, but got %s", scheme, target) + } + return parts[0], parts[1], nil +} diff --git a/clientv3/client.go b/clientv3/client.go index 00d621ffe8b..794d1592882 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -27,13 +27,17 @@ import ( "time" "github.com/coreos/etcd/clientv3/balancer" + "github.com/coreos/etcd/clientv3/balancer/picker" + "github.com/coreos/etcd/clientv3/balancer/resolver/endpoint" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/resolver" "google.golang.org/grpc/status" ) @@ -56,7 +60,8 @@ type Client struct { cfg Config creds *credentials.TransportCredentials - balancer *balancer.GRPC17Health + balancer balancer.Balancer + resolver *endpoint.Resolver mu *sync.Mutex ctx context.Context @@ -128,14 +133,21 @@ func (c *Client) SetEndpoints(eps ...string) { c.mu.Lock() c.cfg.Endpoints = eps c.mu.Unlock() - c.balancer.UpdateAddrs(eps...) - if c.balancer.NeedUpdate() { + var addrs []resolver.Address + for _, ep := range eps { + addrs = append(addrs, resolver.Address{Addr: ep}) + } + + c.resolver.NewAddress(addrs) + + // TODO: Does the new grpc balancer provide a way to block until the endpoint changes are propagated? + /*if c.balancer.NeedUpdate() { select { case c.balancer.UpdateAddrsC() <- balancer.NotifyNext: case <-c.balancer.StopC(): } - } + }*/ } // Sync synchronizes client's endpoints with the known endpoints from the etcd membership. @@ -189,28 +201,6 @@ func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...str }, nil } -func parseEndpoint(endpoint string) (proto string, host string, scheme string) { - proto = "tcp" - host = endpoint - url, uerr := url.Parse(endpoint) - if uerr != nil || !strings.Contains(endpoint, "://") { - return proto, host, scheme - } - scheme = url.Scheme - - // strip scheme:// prefix since grpc dials by host - host = url.Host - switch url.Scheme { - case "http", "https": - case "unix", "unixs": - proto = "unix" - host = url.Host + url.Path - default: - proto, host = "", "" - } - return proto, host, scheme -} - func (c *Client) processCreds(scheme string) (creds *credentials.TransportCredentials) { creds = c.creds switch scheme { @@ -231,7 +221,12 @@ func (c *Client) processCreds(scheme string) (creds *credentials.TransportCreden } // dialSetupOpts gives the dial opts prior to any authentication -func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts []grpc.DialOption) { +func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts []grpc.DialOption, err error) { + _, ep, err := endpoint.ParseTarget(target) + if err != nil { + return nil, fmt.Errorf("unable to parse target: %v", err) + } + if c.cfg.DialTimeout > 0 { opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)} } @@ -245,11 +240,12 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts opts = append(opts, dopts...) f := func(host string, t time.Duration) (net.Conn, error) { - proto, host, _ := parseEndpoint(c.balancer.Endpoint(host)) - if host == "" && endpoint != "" { + // TODO: eliminate this ParseEndpoint call, the endpoint is already parsed by the resolver. + proto, host, _ := endpoint.ParseEndpoint(c.resolver.Endpoint(host)) + if host == "" && ep != "" { // dialing an endpoint not in the balancer; use // endpoint passed into dial - proto, host, _ = parseEndpoint(endpoint) + proto, host, _ = endpoint.ParseEndpoint(ep) } if proto == "" { return nil, fmt.Errorf("unknown scheme for %q", host) @@ -272,7 +268,7 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts opts = append(opts, grpc.WithDialer(f)) creds := c.creds - if _, _, scheme := parseEndpoint(endpoint); len(scheme) != 0 { + if _, _, scheme := endpoint.ParseEndpoint(ep); len(scheme) != 0 { creds = c.processCreds(scheme) } if creds != nil { @@ -281,12 +277,12 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts opts = append(opts, grpc.WithInsecure()) } - return opts + return opts, nil } // Dial connects to a single endpoint using the client's config. -func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) { - return c.dial(endpoint) +func (c *Client) Dial(ep string) (*grpc.ClientConn, error) { + return c.dial(ep) } func (c *Client) getToken(ctx context.Context) error { @@ -297,7 +293,12 @@ func (c *Client) getToken(ctx context.Context) error { endpoint := c.cfg.Endpoints[i] host := getHost(endpoint) // use dial options without dopts to avoid reusing the client balancer - auth, err = newAuthenticator(host, c.dialSetupOpts(endpoint), c) + var dOpts []grpc.DialOption + dOpts, err = c.dialSetupOpts(endpoint) + if err != nil { + continue + } + auth, err = newAuthenticator(host, dOpts, c) if err != nil { continue } @@ -320,8 +321,11 @@ func (c *Client) getToken(ctx context.Context) error { } func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { - opts := c.dialSetupOpts(endpoint, dopts...) - host := getHost(endpoint) + opts, err := c.dialSetupOpts(endpoint, dopts...) + if err != nil { + return nil, err + } + if c.Username != "" && c.Password != "" { c.tokenCred = &authTokenCredential{ tokenMu: &sync.RWMutex{}, @@ -334,7 +338,7 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo ctx = cctx } - err := c.getToken(ctx) + err = c.getToken(ctx) if err != nil { if toErr(ctx, err) != rpctypes.ErrAuthNotEnabled { if err == ctx.Err() && ctx.Err() != c.ctx.Err() { @@ -349,7 +353,11 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo opts = append(opts, c.cfg.DialOptions...) - conn, err := grpc.DialContext(c.ctx, host, opts...) + // TODO: The hosts check doesn't really make sense for a load balanced endpoint url for the new grpc load balancer interface. + // Is it safe/sane to use the provided endpoint here? + //host := getHost(endpoint) + //conn, err := grpc.DialContext(c.ctx, host, opts...) + conn, err := grpc.DialContext(c.ctx, endpoint, opts...) if err != nil { return nil, err } @@ -412,41 +420,35 @@ func newClient(cfg *Config) (*Client, error) { client.callOpts = callOpts } - client.balancer = balancer.NewGRPC17Health(cfg.Endpoints, cfg.DialTimeout, client.dial) + rsv := endpoint.EndpointResolver("default") + rsv.InitialEndpoints(cfg.Endpoints) + bCfg := balancer.Config{ + Policy: picker.RoundrobinBalanced, + Name: "rrbalancer", + Logger: zap.NewExample(), // zap.NewNop(), + // TODO: these are really "targets", not "endpoints" + Endpoints: []string{fmt.Sprintf("endpoint://default/%s", cfg.Endpoints[0])}, + } + rrb, err := balancer.New(bCfg) + if err != nil { + return nil, err + } + client.resolver = rsv + client.balancer = rrb // use Endpoints[0] so that for https:// without any tls config given, then // grpc will assume the certificate server name is the endpoint host. - conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer)) + conn, err := client.dial(bCfg.Endpoints[0], grpc.WithBalancerName(rrb.Name())) if err != nil { client.cancel() client.balancer.Close() + rsv.Close() return nil, err } + // TODO: With the old grpc balancer interface, we waited until the dial timeout + // for the balancer to be ready. Is there an equivalent wait we should do with the new grpc balancer interface? client.conn = conn - // wait for a connection - if cfg.DialTimeout > 0 { - hasConn := false - waitc := time.After(cfg.DialTimeout) - select { - case <-client.balancer.Ready(): - hasConn = true - case <-ctx.Done(): - case <-waitc: - } - if !hasConn { - err := context.DeadlineExceeded - select { - case err = <-client.dialerrc: - default: - } - client.cancel() - client.balancer.Close() - conn.Close() - return nil, err - } - } - client.Cluster = NewCluster(client) client.KV = NewKV(client) client.Lease = NewLease(client) diff --git a/clientv3/ordering/util_test.go b/clientv3/ordering/util_test.go index 142c67072c2..9282f985f84 100644 --- a/clientv3/ordering/util_test.go +++ b/clientv3/ordering/util_test.go @@ -122,6 +122,7 @@ func TestUnresolvableOrderViolation(t *testing.T) { // NewOrderViolationSwitchEndpointClosure will be able to // access the full list of endpoints. cli.SetEndpoints(eps...) + time.Sleep(1 * time.Second) // give enough time for operation OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(*cli)) // set prevRev to the first member's revision of "foo" such that // the revision is higher than the fourth and fifth members' revision of "foo" @@ -133,8 +134,14 @@ func TestUnresolvableOrderViolation(t *testing.T) { clus.Members[0].Stop(t) clus.Members[1].Stop(t) clus.Members[2].Stop(t) - clus.Members[3].Restart(t) - clus.Members[4].Restart(t) + err = clus.Members[3].Restart(t) + if err != nil { + t.Fatal(err) + } + err = clus.Members[4].Restart(t) + if err != nil { + t.Fatal(err) + } cli.SetEndpoints(clus.Members[3].GRPCAddr()) time.Sleep(1 * time.Second) // give enough time for operation diff --git a/clientv3/retry.go b/clientv3/retry.go index 6226d787e95..6c7fcfcf6b6 100644 --- a/clientv3/retry.go +++ b/clientv3/retry.go @@ -78,6 +78,8 @@ func isNonRepeatableStopError(err error) bool { return desc != "there is no address available" && desc != "there is no connection available" } +// TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? +/* func (c *Client) newRetryWrapper() retryRPCFunc { return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { var isStop retryStopErrFunc @@ -110,8 +112,9 @@ func (c *Client) newRetryWrapper() retryRPCFunc { } } } -} +}*/ +/* func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc { return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { for { @@ -133,7 +136,7 @@ func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc { return err } } -} +}*/ type retryKVClient struct { kc pb.KVClient @@ -142,10 +145,12 @@ type retryKVClient struct { // RetryKVClient implements a KVClient. func RetryKVClient(c *Client) pb.KVClient { - return &retryKVClient{ + return pb.NewKVClient(c.conn) + // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? + /*return &retryKVClient{ kc: pb.NewKVClient(c.conn), retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), - } + }*/ } func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) { err = rkv.retryf(ctx, func(rctx context.Context) error { @@ -195,10 +200,12 @@ type retryLeaseClient struct { // RetryLeaseClient implements a LeaseClient. func RetryLeaseClient(c *Client) pb.LeaseClient { - return &retryLeaseClient{ + return pb.NewLeaseClient(c.conn) + // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? + /*return &retryLeaseClient{ lc: pb.NewLeaseClient(c.conn), retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), - } + }*/ } func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTimeToLiveRequest, opts ...grpc.CallOption) (resp *pb.LeaseTimeToLiveResponse, err error) { @@ -249,10 +256,12 @@ type retryClusterClient struct { // RetryClusterClient implements a ClusterClient. func RetryClusterClient(c *Client) pb.ClusterClient { - return &retryClusterClient{ + return pb.NewClusterClient(c.conn) + // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? + /*return &retryClusterClient{ cc: pb.NewClusterClient(c.conn), retryf: c.newRetryWrapper(), - } + }*/ } func (rcc *retryClusterClient) MemberList(ctx context.Context, in *pb.MemberListRequest, opts ...grpc.CallOption) (resp *pb.MemberListResponse, err error) { @@ -294,10 +303,12 @@ type retryMaintenanceClient struct { // RetryMaintenanceClient implements a Maintenance. func RetryMaintenanceClient(c *Client, conn *grpc.ClientConn) pb.MaintenanceClient { - return &retryMaintenanceClient{ + return pb.NewMaintenanceClient(conn) + // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? + /*return &retryMaintenanceClient{ mc: pb.NewMaintenanceClient(conn), retryf: c.newRetryWrapper(), - } + }*/ } func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmRequest, opts ...grpc.CallOption) (resp *pb.AlarmResponse, err error) { @@ -363,10 +374,12 @@ type retryAuthClient struct { // RetryAuthClient implements a AuthClient. func RetryAuthClient(c *Client) pb.AuthClient { - return &retryAuthClient{ + return pb.NewAuthClient(c.conn) + // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? + /*return &retryAuthClient{ ac: pb.NewAuthClient(c.conn), retryf: c.newRetryWrapper(), - } + }*/ } func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListRequest, opts ...grpc.CallOption) (resp *pb.AuthUserListResponse, err error) { diff --git a/integration/embed_test.go b/integration/embed_test.go index 6af58ea1bd8..32e614ff742 100644 --- a/integration/embed_test.go +++ b/integration/embed_test.go @@ -108,8 +108,9 @@ func TestEmbedEtcd(t *testing.T) { } } -func TestEmbedEtcdGracefulStopSecure(t *testing.T) { testEmbedEtcdGracefulStop(t, true) } -func TestEmbedEtcdGracefulStopInsecure(t *testing.T) { testEmbedEtcdGracefulStop(t, false) } +// TODO: reenable +//func TestEmbedEtcdGracefulStopSecure(t *testing.T) { testEmbedEtcdGracefulStop(t, true) } +//func TestEmbedEtcdGracefulStopInsecure(t *testing.T) { testEmbedEtcdGracefulStop(t, false) } // testEmbedEtcdGracefulStop ensures embedded server stops // cutting existing transports. diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index d1426a4bb6b..8dcc630ba40 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -449,7 +449,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if p.MatchString(addr) { parts := strings.Split(addr, "://") scheme := parts[0] - if scheme == "unix" && len(parts) > 1 && len(parts[1]) > 0 { + if (scheme == "unix" || scheme == "unixs") && len(parts) > 1 && len(parts[1]) > 0 { network = "unix" addr = parts[1] } From f20a1173d89d1d2f5f7a282e43ea23177f7b7c66 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 6 Apr 2018 14:04:51 -0700 Subject: [PATCH 11/39] clientv3: Fix new load balancer integration issues --- clientv3/balancer/balancer.go | 27 +----------- clientv3/balancer/balancer_test.go | 42 +++++++------------ clientv3/balancer/config.go | 3 -- clientv3/balancer/picker/err.go | 5 --- clientv3/balancer/picker/picker.go | 7 ---- .../balancer/picker/roundrobin_balanced.go | 13 ------ .../balancer/resolver/endpoint/endpoint.go | 4 ++ clientv3/client.go | 42 +++++++++++-------- 8 files changed, 46 insertions(+), 97 deletions(-) diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index 2bce6c8de6d..c2533e8701c 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -16,7 +16,6 @@ package balancer import ( "fmt" - "strings" "sync" "github.com/coreos/etcd/clientv3/balancer/picker" @@ -44,9 +43,6 @@ type Balancer interface { // Picker calls "Pick" for every client request. picker.Picker - - // SetEndpoints updates client's endpoints. - SetEndpoints(eps ...string) } type baseBalancer struct { @@ -56,8 +52,6 @@ type baseBalancer struct { mu sync.RWMutex - eps []string - addrToSc map[resolver.Address]balancer.SubConn scToAddr map[balancer.SubConn]resolver.Address scToSt map[balancer.SubConn]connectivity.State @@ -70,20 +64,12 @@ type baseBalancer struct { } // New returns a new balancer from specified picker policy. -func New(cfg Config) (Balancer, error) { - for _, ep := range cfg.Endpoints { - if !strings.HasPrefix(ep, "endpoint://") { - return nil, fmt.Errorf("'endpoint' target schema required for etcd load balancer endpoints but got '%s'", ep) - } - } - +func New(cfg Config) Balancer { bb := &baseBalancer{ policy: cfg.Policy, name: cfg.Policy.String(), lg: cfg.Logger, - eps: cfg.Endpoints, - addrToSc: make(map[resolver.Address]balancer.SubConn), scToAddr: make(map[balancer.SubConn]resolver.Address), scToSt: make(map[balancer.SubConn]connectivity.State), @@ -107,7 +93,7 @@ func New(cfg Config) (Balancer, error) { zap.String("policy", bb.policy.String()), zap.String("name", bb.name), ) - return bb, nil + return bb } // Name implements "grpc/balancer.Builder" interface. @@ -265,15 +251,6 @@ func (bb *baseBalancer) regeneratePicker() { ) } -// SetEndpoints updates client's endpoints. -// TODO: implement this -func (bb *baseBalancer) SetEndpoints(eps ...string) { - addrs := epsToAddrs(eps...) - bb.mu.Lock() - bb.Picker.UpdateAddrs(addrs) - bb.mu.Unlock() -} - // Close implements "grpc/balancer.Balancer" interface. // Close is a nop because base balancer doesn't have internal state to clean up, // and it doesn't need to call RemoveSubConn for the SubConns. diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index 80243d9630e..5de5b59302e 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -68,16 +68,12 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { rsv.InitialAddrs(resolvedAddrs) cfg := Config{ - Policy: picker.RoundrobinBalanced, - Name: genName(), - Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("endpoint://nofailover/*")}, + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), } - rrb, err := New(cfg) - if err != nil { - t.Fatalf("failed to create builder: %v", err) - } - conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + rrb := New(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://nofailover/*"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { t.Fatalf("failed to dial mock server: %v", err) } @@ -134,16 +130,12 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { rsv.InitialAddrs(resolvedAddrs) cfg := Config{ - Policy: picker.RoundrobinBalanced, - Name: genName(), - Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("endpoint://serverfail/mock.server")}, - } - rrb, err := New(cfg) - if err != nil { - t.Fatalf("failed to create builder: %v", err) + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), } - conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + rrb := New(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://serverfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } @@ -251,16 +243,12 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { rsv.InitialAddrs(resolvedAddrs) cfg := Config{ - Policy: picker.RoundrobinBalanced, - Name: genName(), - Logger: zap.NewExample(), - Endpoints: []string{fmt.Sprintf("endpoint://requestfail/mock.server")}, - } - rrb, err := New(cfg) - if err != nil { - t.Fatalf("failed to create builder: %v", err) + Policy: picker.RoundrobinBalanced, + Name: genName(), + Logger: zap.NewExample(), } - conn, err := grpc.Dial(cfg.Endpoints[0], grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + rrb := New(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://requestfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } diff --git a/clientv3/balancer/config.go b/clientv3/balancer/config.go index 5c649e220fc..2156984df5b 100644 --- a/clientv3/balancer/config.go +++ b/clientv3/balancer/config.go @@ -33,7 +33,4 @@ type Config struct { // Logger configures balancer logging. // If nil, logs are discarded. Logger *zap.Logger - - // Endpoints is a list of server endpoints. - Endpoints []string } diff --git a/clientv3/balancer/picker/err.go b/clientv3/balancer/picker/err.go index 281f453fc1a..c70ce158b68 100644 --- a/clientv3/balancer/picker/err.go +++ b/clientv3/balancer/picker/err.go @@ -18,7 +18,6 @@ import ( "context" "google.golang.org/grpc/balancer" - "google.golang.org/grpc/resolver" ) // NewErr returns a picker that always returns err on "Pick". @@ -33,7 +32,3 @@ type errPicker struct { func (p *errPicker) Pick(context.Context, balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) { return nil, nil, p.err } - -func (p *errPicker) UpdateAddrs(addrs []resolver.Address) { - return -} diff --git a/clientv3/balancer/picker/picker.go b/clientv3/balancer/picker/picker.go index 93412e2afec..7ea761bdb57 100644 --- a/clientv3/balancer/picker/picker.go +++ b/clientv3/balancer/picker/picker.go @@ -16,16 +16,9 @@ package picker import ( "google.golang.org/grpc/balancer" - "google.golang.org/grpc/resolver" ) // Picker defines balancer Picker methods. type Picker interface { balancer.Picker - - // UpdateAddrs updates current endpoints in picker. - // Used when endpoints are updated manually. - // TODO: handle resolver target change - // TODO: handle resolved addresses change - UpdateAddrs(addrs []resolver.Address) } diff --git a/clientv3/balancer/picker/roundrobin_balanced.go b/clientv3/balancer/picker/roundrobin_balanced.go index 9175562a27c..b043d572dd7 100644 --- a/clientv3/balancer/picker/roundrobin_balanced.go +++ b/clientv3/balancer/picker/roundrobin_balanced.go @@ -48,8 +48,6 @@ type rrBalanced struct { addrToSc map[resolver.Address]balancer.SubConn scToAddr map[balancer.SubConn]resolver.Address - - updateAddrs func(addrs []resolver.Address) } // Pick is called for every client request. @@ -92,14 +90,3 @@ func (rb *rrBalanced) Pick(ctx context.Context, opts balancer.PickOptions) (bala } return sc, doneFunc, nil } - -// UpdateAddrs -// TODO: implement this -func (rb *rrBalanced) UpdateAddrs(addrs []resolver.Address) { - rb.mu.Lock() - // close all resolved sub-connections first - for _, sc := range rb.scs { - sc.UpdateAddresses([]resolver.Address{}) - } - rb.mu.Unlock() -} diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 617f11f234b..78950e5dac8 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -165,6 +165,10 @@ func (r *Resolver) Close() { bldr.removeResolver(r) } +func (r *Resolver) Target(endpoint string) string { + return fmt.Sprintf("%s://%s/%s", scheme, r.clusterName, endpoint) +} + // Parse endpoint parses a endpoint of the form (http|https)://*|(unix|unixs)://) and returns a // protocol ('tcp' or 'unix'), host (or filepath if a unix socket) and scheme (http, https, unix, unixs). func ParseEndpoint(endpoint string) (proto string, host string, scheme string) { diff --git a/clientv3/client.go b/clientv3/client.go index 794d1592882..4075a6c7f4e 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -44,8 +44,18 @@ import ( var ( ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints") ErrOldCluster = errors.New("etcdclient: old cluster version") + + defaultBalancer balancer.Balancer ) +func init() { + defaultBalancer = balancer.New(balancer.Config{ + Policy: picker.RoundrobinBalanced, + Name: fmt.Sprintf("etcd-%s", picker.RoundrobinBalanced.String()), + Logger: zap.NewNop(), // zap.NewExample(), + }) +} + // Client provides and manages an etcd v3 client session. type Client struct { Cluster @@ -112,6 +122,9 @@ func (c *Client) Close() error { if c.conn != nil { return toErr(c.ctx, c.conn.Close()) } + if c.resolver != nil { + c.resolver.Close() + } return c.ctx.Err() } @@ -281,8 +294,8 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] } // Dial connects to a single endpoint using the client's config. -func (c *Client) Dial(ep string) (*grpc.ClientConn, error) { - return c.dial(ep) +func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) { + return c.dial(endpoint) } func (c *Client) getToken(ctx context.Context) error { @@ -294,7 +307,7 @@ func (c *Client) getToken(ctx context.Context) error { host := getHost(endpoint) // use dial options without dopts to avoid reusing the client balancer var dOpts []grpc.DialOption - dOpts, err = c.dialSetupOpts(endpoint) + dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint)) if err != nil { continue } @@ -420,28 +433,23 @@ func newClient(cfg *Config) (*Client, error) { client.callOpts = callOpts } - rsv := endpoint.EndpointResolver("default") + clientId := fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36)) + rsv := endpoint.EndpointResolver(clientId) rsv.InitialEndpoints(cfg.Endpoints) - bCfg := balancer.Config{ - Policy: picker.RoundrobinBalanced, - Name: "rrbalancer", - Logger: zap.NewExample(), // zap.NewNop(), - // TODO: these are really "targets", not "endpoints" - Endpoints: []string{fmt.Sprintf("endpoint://default/%s", cfg.Endpoints[0])}, - } - rrb, err := balancer.New(bCfg) - if err != nil { - return nil, err + + targets := []string{} + for _, ep := range cfg.Endpoints { + targets = append(targets, fmt.Sprintf("endpoint://%s/%s", clientId, ep)) } + client.resolver = rsv - client.balancer = rrb + client.balancer = defaultBalancer // TODO: allow alternate balancers to be passed in via config? // use Endpoints[0] so that for https:// without any tls config given, then // grpc will assume the certificate server name is the endpoint host. - conn, err := client.dial(bCfg.Endpoints[0], grpc.WithBalancerName(rrb.Name())) + conn, err := client.dial(targets[0], grpc.WithBalancerName(client.balancer.Name())) if err != nil { client.cancel() - client.balancer.Close() rsv.Close() return nil, err } From 12acfc057a0285cea1a9f5d05b0aef289eeb5d38 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Mon, 9 Apr 2018 14:15:22 -0700 Subject: [PATCH 12/39] vendor: upgrade grpc/grpc-go to v1.11.3 --- Gopkg.lock | 6 +- test | 2 +- vendor/google.golang.org/genproto/regen.go | 123 ------------------ vendor/google.golang.org/grpc/clientconn.go | 12 +- .../grpc/resolver/manual/manual.go | 91 ------------- vendor/google.golang.org/grpc/rpc_util.go | 37 +++++- vendor/google.golang.org/grpc/server.go | 10 ++ vendor/google.golang.org/grpc/stream.go | 6 +- 8 files changed, 52 insertions(+), 235 deletions(-) delete mode 100644 vendor/google.golang.org/genproto/regen.go delete mode 100644 vendor/google.golang.org/grpc/resolver/manual/manual.go diff --git a/Gopkg.lock b/Gopkg.lock index 657ab9d1a1d..4b576cc6b30 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -360,8 +360,8 @@ "tap", "transport" ] - revision = "1e2570b1b19ade82d8dbb31bba4e65e9f9ef5b34" - version = "v1.11.1" + revision = "d11072e7ca9811b1100b80ca0269ac831f06d024" + version = "v1.11.3" [[projects]] name = "gopkg.in/cheggaaa/pb.v1" @@ -378,6 +378,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b747b3fd3120687183829e5d2d5b2d10bba1719402c9bcc7c955d27ab5f884a0" + inputs-digest = "9b6e97c6524385aecaa477af059f4f6555b3609d4a283ecf27ac2ad4fbe08f06" solver-name = "gps-cdcl" solver-version = 1 diff --git a/test b/test index f797c872dcc..61f8fda6553 100755 --- a/test +++ b/test @@ -380,7 +380,7 @@ function shellcheck_pass { function markdown_you_pass { # eschew you - yous=$(find . -name \*.md ! -path './vendor/*' ! -path './gopath.proto/*' -exec grep -E --color "[Yy]ou[r]?[ '.,;]" {} + | grep -v /v2/ || true) + yous=$(find . -name \*.md ! -path './vendor/*' ! -path './Documentation/v2/*' ! -path './gopath.proto/*' -exec grep -E --color "[Yy]ou[r]?[ '.,;]" {} + || true) if [ ! -z "$yous" ]; then echo -e "found 'you' in documentation:\\n${yous}" exit 255 diff --git a/vendor/google.golang.org/genproto/regen.go b/vendor/google.golang.org/genproto/regen.go deleted file mode 100644 index 9c906f2095b..00000000000 --- a/vendor/google.golang.org/genproto/regen.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2016 Google Inc. -// -// 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. - -// +build ignore - -// Regen.go regenerates the genproto repository. -// -// Regen.go recursively walks through each directory named by given arguments, -// looking for all .proto files. (Symlinks are not followed.) -// If the pkg_prefix flag is not an empty string, -// any proto file without `go_package` option -// or whose option does not begin with the prefix is ignored. -// Protoc is executed on remaining files, -// one invocation per set of files declaring the same Go package. -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "strconv" - "strings" -) - -var goPkgOptRe = regexp.MustCompile(`(?m)^option go_package = (.*);`) - -func usage() { - fmt.Fprintln(os.Stderr, `usage: go run regen.go -go_out=path/to/output [-pkg_prefix=pkg/prefix] roots... - -Most users will not need to run this file directly. -To regenerate this repository, run regen.sh instead.`) - flag.PrintDefaults() -} - -func main() { - goOutDir := flag.String("go_out", "", "go_out argument to pass to protoc-gen-go") - pkgPrefix := flag.String("pkg_prefix", "", "only include proto files with go_package starting with this prefix") - flag.Usage = usage - flag.Parse() - - if *goOutDir == "" { - log.Fatal("need go_out flag") - } - - pkgFiles := make(map[string][]string) - walkFn := func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.Mode().IsRegular() || !strings.HasSuffix(path, ".proto") { - return nil - } - pkg, err := goPkg(path) - if err != nil { - return err - } - pkgFiles[pkg] = append(pkgFiles[pkg], path) - return nil - } - for _, root := range flag.Args() { - if err := filepath.Walk(root, walkFn); err != nil { - log.Fatal(err) - } - } - for pkg, fnames := range pkgFiles { - if !strings.HasPrefix(pkg, *pkgPrefix) { - continue - } - if out, err := protoc(*goOutDir, flag.Args(), fnames); err != nil { - log.Fatalf("error executing protoc: %s\n%s", err, out) - } - } -} - -// goPkg reports the import path declared in the given file's -// `go_package` option. If the option is missing, goPkg returns empty string. -func goPkg(fname string) (string, error) { - content, err := ioutil.ReadFile(fname) - if err != nil { - return "", err - } - - var pkgName string - if match := goPkgOptRe.FindSubmatch(content); len(match) > 0 { - pn, err := strconv.Unquote(string(match[1])) - if err != nil { - return "", err - } - pkgName = pn - } - if p := strings.IndexRune(pkgName, ';'); p > 0 { - pkgName = pkgName[:p] - } - return pkgName, nil -} - -// protoc executes the "protoc" command on files named in fnames, -// passing go_out and include flags specified in goOut and includes respectively. -// protoc returns combined output from stdout and stderr. -func protoc(goOut string, includes, fnames []string) ([]byte, error) { - args := []string{"--go_out=plugins=grpc:" + goOut} - for _, inc := range includes { - args = append(args, "-I", inc) - } - args = append(args, fnames...) - return exec.Command("protoc", args...).CombinedOutput() -} diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 8dcc630ba40..6385407292a 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -24,7 +24,6 @@ import ( "math" "net" "reflect" - "regexp" "strings" "sync" "time" @@ -444,16 +443,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if cc.dopts.copts.Dialer == nil { cc.dopts.copts.Dialer = newProxyDialer( func(ctx context.Context, addr string) (net.Conn, error) { - network := "tcp" - p := regexp.MustCompile(`[a-z]+://`) - if p.MatchString(addr) { - parts := strings.Split(addr, "://") - scheme := parts[0] - if (scheme == "unix" || scheme == "unixs") && len(parts) > 1 && len(parts[1]) > 0 { - network = "unix" - addr = parts[1] - } - } + network, addr := parseDialTarget(addr) return dialContext(ctx, network, addr) }, ) diff --git a/vendor/google.golang.org/grpc/resolver/manual/manual.go b/vendor/google.golang.org/grpc/resolver/manual/manual.go deleted file mode 100644 index 50ed762a83d..00000000000 --- a/vendor/google.golang.org/grpc/resolver/manual/manual.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - * - * Copyright 2017 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 manual defines a resolver that can be used to manually send resolved -// addresses to ClientConn. -package manual - -import ( - "strconv" - "time" - - "google.golang.org/grpc/resolver" -) - -// NewBuilderWithScheme creates a new test resolver builder with the given scheme. -func NewBuilderWithScheme(scheme string) *Resolver { - return &Resolver{ - scheme: scheme, - } -} - -// Resolver is also a resolver builder. -// It's build() function always returns itself. -type Resolver struct { - scheme string - - // Fields actually belong to the resolver. - cc resolver.ClientConn - bootstrapAddrs []resolver.Address -} - -// InitialAddrs adds resolved addresses to the resolver so that -// NewAddress doesn't need to be explicitly called after Dial. -func (r *Resolver) InitialAddrs(addrs []resolver.Address) { - r.bootstrapAddrs = addrs -} - -// Build returns itself for Resolver, because it's both a builder and a resolver. -func (r *Resolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { - r.cc = cc - if r.bootstrapAddrs != nil { - r.NewAddress(r.bootstrapAddrs) - } - return r, nil -} - -// Scheme returns the test scheme. -func (r *Resolver) Scheme() string { - return r.scheme -} - -// ResolveNow is a noop for Resolver. -func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} - -// Close is a noop for Resolver. -func (*Resolver) Close() {} - -// NewAddress calls cc.NewAddress. -func (r *Resolver) NewAddress(addrs []resolver.Address) { - r.cc.NewAddress(addrs) -} - -// NewServiceConfig calls cc.NewServiceConfig. -func (r *Resolver) NewServiceConfig(sc string) { - r.cc.NewServiceConfig(sc) -} - -// GenerateAndRegisterManualResolver generates a random scheme and a Resolver -// with it. It also regieter this Resolver. -// It returns the Resolver and a cleanup function to unregister it. -func GenerateAndRegisterManualResolver() (*Resolver, func()) { - scheme := strconv.FormatInt(time.Now().UnixNano(), 36) - r := NewBuilderWithScheme(scheme) - resolver.Register(r) - return r, func() { resolver.UnregisterForTesting(scheme) } -} diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 5ef9cb2f7d9..00a99766066 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -26,6 +26,7 @@ import ( "io" "io/ioutil" "math" + "net/url" "strings" "sync" "time" @@ -662,6 +663,40 @@ func setCallInfoCodec(c *callInfo) error { return nil } +// parseDialTarget returns the network and address to pass to dialer +func parseDialTarget(target string) (net string, addr string) { + net = "tcp" + + m1 := strings.Index(target, ":") + m2 := strings.Index(target, ":/") + + // handle unix:addr which will fail with url.Parse + if m1 >= 0 && m2 < 0 { + if n := target[0:m1]; n == "unix" { + net = n + addr = target[m1+1:] + return net, addr + } + } + if m2 >= 0 { + t, err := url.Parse(target) + if err != nil { + return net, target + } + scheme := t.Scheme + addr = t.Path + if scheme == "unix" { + net = scheme + if addr == "" { + addr = t.Host + } + return net, addr + } + } + + return net, target +} + // The SupportPackageIsVersion variables are referenced from generated protocol // buffer files to ensure compatibility with the gRPC version used. The latest // support package version is 5. @@ -677,6 +712,6 @@ const ( ) // Version is the current grpc version. -const Version = "1.11.1" +const Version = "1.11.3" const grpcUA = "grpc-go/" + Version diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 0063906e469..c6b413b9d9d 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -1359,3 +1359,13 @@ func SetTrailer(ctx context.Context, md metadata.MD) error { } return stream.SetTrailer(md) } + +// Method returns the method string for the server context. The returned +// string is in the format of "/service/method". +func Method(ctx context.Context) (string, bool) { + s := serverTransportStreamFromContext(ctx) + if s == nil { + return "", false + } + return s.Method(), true +} diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index a79f385a740..75a4e8d45b7 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -732,9 +732,5 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { // MethodFromServerStream returns the method string for the input stream. // The returned string is in the format of "/service/method". func MethodFromServerStream(stream ServerStream) (string, bool) { - s := serverTransportStreamFromContext(stream.Context()) - if s == nil { - return "", false - } - return s.Method(), true + return Method(stream.Context()) } From 309208dbef33efaef64febf1ff3a2755f654debe Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Tue, 10 Apr 2018 10:44:50 -0700 Subject: [PATCH 13/39] clientv3: Split out grpc balancer builder to ensure there is a balancer per ClientConn --- clientv3/balancer/balancer.go | 110 +++++++++--------- .../balancer/resolver/endpoint/endpoint.go | 9 +- clientv3/client.go | 30 +++-- 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index c2533e8701c..15bc48cbe6b 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -28,47 +28,31 @@ import ( _ "google.golang.org/grpc/resolver/passthrough" // register passthrough resolver ) -// Balancer defines client balancer interface. -type Balancer interface { - // Builder is called at the beginning to initialize sub-connection states and picker. - balancer.Builder - - // Balancer is called on specified client connection. Client initiates gRPC - // connection with "grpc.Dial(addr, grpc.WithBalancerName)", and then those resolved - // addresses are passed to "grpc/balancer.Balancer.HandleResolvedAddrs". - // For each resolved address, balancer calls "balancer.ClientConn.NewSubConn". - // "grpc/balancer.Balancer.HandleSubConnStateChange" is called when connectivity state - // changes, thus requires failover logic in this method. - balancer.Balancer +// RegisterBuilder creates and registers a builder. Since this function calls balancer.Register, it +// must be invoked at initialization time. +func RegisterBuilder(cfg Config) { + bb := &builder{cfg} + balancer.Register(bb) - // Picker calls "Pick" for every client request. - picker.Picker + bb.cfg.Logger.Info( + "registered balancer", + zap.String("policy", bb.cfg.Policy.String()), + zap.String("name", bb.cfg.Name), + ) } -type baseBalancer struct { - policy picker.Policy - name string - lg *zap.Logger - - mu sync.RWMutex - - addrToSc map[resolver.Address]balancer.SubConn - scToAddr map[balancer.SubConn]resolver.Address - scToSt map[balancer.SubConn]connectivity.State - - currentConn balancer.ClientConn - currentState connectivity.State - csEvltr *connectivityStateEvaluator - - picker.Picker +type builder struct { + cfg Config } -// New returns a new balancer from specified picker policy. -func New(cfg Config) Balancer { +// Build is called initially when creating "ccBalancerWrapper". +// "grpc.Dial" is called to this client connection. +// Then, resolved addreses will be handled via "HandleResolvedAddrs". +func (b *builder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { bb := &baseBalancer{ - policy: cfg.Policy, - name: cfg.Policy.String(), - lg: cfg.Logger, + policy: b.cfg.Policy, + name: b.cfg.Policy.String(), + lg: b.cfg.Logger, addrToSc: make(map[resolver.Address]balancer.SubConn), scToAddr: make(map[balancer.SubConn]resolver.Address), @@ -80,30 +64,13 @@ func New(cfg Config) Balancer { // initialize picker always returns "ErrNoSubConnAvailable" Picker: picker.NewErr(balancer.ErrNoSubConnAvailable), } - if cfg.Name != "" { - bb.name = cfg.Name + if b.cfg.Name != "" { + bb.name = b.cfg.Name } if bb.lg == nil { bb.lg = zap.NewNop() } - balancer.Register(bb) - bb.lg.Info( - "registered balancer", - zap.String("policy", bb.policy.String()), - zap.String("name", bb.name), - ) - return bb -} - -// Name implements "grpc/balancer.Builder" interface. -func (bb *baseBalancer) Name() string { return bb.name } - -// Build implements "grpc/balancer.Builder" interface. -// Build is called initially when creating "ccBalancerWrapper". -// "grpc.Dial" is called to this client connection. -// Then, resolved addreses will be handled via "HandleResolvedAddrs". -func (bb *baseBalancer) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { // TODO: support multiple connections bb.mu.Lock() bb.currentConn = cc @@ -117,6 +84,41 @@ func (bb *baseBalancer) Build(cc balancer.ClientConn, opt balancer.BuildOptions) return bb } +// Name implements "grpc/balancer.Builder" interface. +func (b *builder) Name() string { return b.cfg.Name } + +// Balancer defines client balancer interface. +type Balancer interface { + // Balancer is called on specified client connection. Client initiates gRPC + // connection with "grpc.Dial(addr, grpc.WithBalancerName)", and then those resolved + // addresses are passed to "grpc/balancer.Balancer.HandleResolvedAddrs". + // For each resolved address, balancer calls "balancer.ClientConn.NewSubConn". + // "grpc/balancer.Balancer.HandleSubConnStateChange" is called when connectivity state + // changes, thus requires failover logic in this method. + balancer.Balancer + + // Picker calls "Pick" for every client request. + picker.Picker +} + +type baseBalancer struct { + policy picker.Policy + name string + lg *zap.Logger + + mu sync.RWMutex + + addrToSc map[resolver.Address]balancer.SubConn + scToAddr map[balancer.SubConn]resolver.Address + scToSt map[balancer.SubConn]connectivity.State + + currentConn balancer.ClientConn + currentState connectivity.State + csEvltr *connectivityStateEvaluator + + picker.Picker +} + // HandleResolvedAddrs implements "grpc/balancer.Balancer" interface. // gRPC sends initial or updated resolved addresses from "Build". func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) { diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 78950e5dac8..9bb6c19ffa6 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -112,8 +112,15 @@ func (r *Resolver) InitialAddrs(addrs []resolver.Address) { r.Unlock() } -func (r *Resolver) InitialEndpoints(eps []string) { +// InitialEndpoints sets the initial endpoints to for the resolver and returns a grpc dial target. +// This should be called before dialing. The endpoints may be updated after the dial using NewAddress. +// At least one endpoint is required. +func (r *Resolver) InitialEndpoints(eps []string) (string, error) { + if len(eps) < 1 { + return "", fmt.Errorf("At least one endpoint is required, but got: %v", eps) + } r.InitialAddrs(epsToAddrs(eps...)) + return r.Target(eps[0]), nil } // TODO: use balancer.epsToAddrs diff --git a/clientv3/client.go b/clientv3/client.go index 4075a6c7f4e..901ab4fb007 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -45,13 +45,13 @@ var ( ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints") ErrOldCluster = errors.New("etcdclient: old cluster version") - defaultBalancer balancer.Balancer + roundRobinBalancerName = fmt.Sprintf("etcd-%s", picker.RoundrobinBalanced.String()) ) func init() { - defaultBalancer = balancer.New(balancer.Config{ + balancer.RegisterBuilder(balancer.Config{ Policy: picker.RoundrobinBalanced, - Name: fmt.Sprintf("etcd-%s", picker.RoundrobinBalanced.String()), + Name: roundRobinBalancerName, Logger: zap.NewNop(), // zap.NewExample(), }) } @@ -433,24 +433,22 @@ func newClient(cfg *Config) (*Client, error) { client.callOpts = callOpts } - clientId := fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36)) - rsv := endpoint.EndpointResolver(clientId) - rsv.InitialEndpoints(cfg.Endpoints) - - targets := []string{} - for _, ep := range cfg.Endpoints { - targets = append(targets, fmt.Sprintf("endpoint://%s/%s", clientId, ep)) + // Prepare a 'endpoint:///' resolver for the client and create a endpoint target to pass + // to dial so the client knows to use this resolver. + client.resolver = endpoint.EndpointResolver(fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36))) + target, err := client.resolver.InitialEndpoints(cfg.Endpoints) + if err != nil { + client.cancel() + client.resolver.Close() + return nil, err } - client.resolver = rsv - client.balancer = defaultBalancer // TODO: allow alternate balancers to be passed in via config? - - // use Endpoints[0] so that for https:// without any tls config given, then + // Use an provided endpoint target so that for https:// without any tls config given, then // grpc will assume the certificate server name is the endpoint host. - conn, err := client.dial(targets[0], grpc.WithBalancerName(client.balancer.Name())) + conn, err := client.dial(target, grpc.WithBalancerName(roundRobinBalancerName)) if err != nil { client.cancel() - rsv.Close() + client.resolver.Close() return nil, err } // TODO: With the old grpc balancer interface, we waited until the dial timeout From 7ac2a2dd20dab5ced919b005faafc28360bd9d88 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Tue, 10 Apr 2018 16:58:52 -0700 Subject: [PATCH 14/39] clientv3: Fix dialer for new balancer to correctly handle first are as endpoint, not hostname --- clientv3/balancer/balancer.go | 20 ++++++++++-- .../balancer/resolver/endpoint/endpoint.go | 32 ++----------------- clientv3/client.go | 13 +++----- clientv3/ordering/kv_test.go | 3 +- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index 15bc48cbe6b..8356aaf00d2 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -16,7 +16,9 @@ package balancer import ( "fmt" + "strconv" "sync" + "time" "github.com/coreos/etcd/clientv3/balancer/picker" @@ -50,6 +52,7 @@ type builder struct { // Then, resolved addreses will be handled via "HandleResolvedAddrs". func (b *builder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { bb := &baseBalancer{ + id: strconv.FormatInt(time.Now().UnixNano(), 36), policy: b.cfg.Policy, name: b.cfg.Policy.String(), lg: b.cfg.Logger, @@ -78,6 +81,7 @@ func (b *builder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balan bb.lg.Info( "built balancer", + zap.String("balancer-id", bb.id), zap.String("policy", bb.policy.String()), zap.String("resolver-target", cc.Target()), ) @@ -102,6 +106,7 @@ type Balancer interface { } type baseBalancer struct { + id string policy picker.Policy name string lg *zap.Logger @@ -123,10 +128,10 @@ type baseBalancer struct { // gRPC sends initial or updated resolved addresses from "Build". func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) { if err != nil { - bb.lg.Warn("HandleResolvedAddrs called with error", zap.Error(err)) + bb.lg.Warn("HandleResolvedAddrs called with error", zap.String("balancer-id", bb.id), zap.Error(err)) return } - bb.lg.Info("resolved", zap.Strings("addresses", addrsToStrings(addrs))) + bb.lg.Info("resolved", zap.String("balancer-id", bb.id), zap.Strings("addresses", addrsToStrings(addrs))) bb.mu.Lock() defer bb.mu.Unlock() @@ -137,7 +142,7 @@ func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) if _, ok := bb.addrToSc[addr]; !ok { sc, err := bb.currentConn.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{}) if err != nil { - bb.lg.Warn("NewSubConn failed", zap.Error(err), zap.String("address", addr.Addr)) + bb.lg.Warn("NewSubConn failed", zap.String("balancer-id", bb.id), zap.Error(err), zap.String("address", addr.Addr)) continue } bb.addrToSc[addr] = sc @@ -155,6 +160,7 @@ func (bb *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) bb.lg.Info( "removed subconn", + zap.String("balancer-id", bb.id), zap.String("address", addr.Addr), zap.String("subconn", scToString(sc)), ) @@ -176,6 +182,7 @@ func (bb *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connecti if !ok { bb.lg.Warn( "state change for an unknown subconn", + zap.String("balancer-id", bb.id), zap.String("subconn", scToString(sc)), zap.String("state", s.String()), ) @@ -184,6 +191,7 @@ func (bb *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connecti bb.lg.Info( "state changed", + zap.String("balancer-id", bb.id), zap.Bool("connected", s == connectivity.Ready), zap.String("subconn", scToString(sc)), zap.String("address", bb.scToAddr[sc].Addr), @@ -221,6 +229,11 @@ func (bb *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connecti func (bb *baseBalancer) regeneratePicker() { if bb.currentState == connectivity.TransientFailure { + bb.lg.Info( + "generated transient error picker", + zap.String("balancer-id", bb.id), + zap.String("policy", bb.policy.String()), + ) bb.Picker = picker.NewErr(balancer.ErrTransientFailure) return } @@ -247,6 +260,7 @@ func (bb *baseBalancer) regeneratePicker() { bb.lg.Info( "generated picker", + zap.String("balancer-id", bb.id), zap.String("policy", bb.policy.String()), zap.Strings("subconn-ready", scsToStrings(addrToSc)), zap.Int("subconn-size", len(addrToSc)), diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 9bb6c19ffa6..75a7e8b74b3 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -100,7 +100,6 @@ type Resolver struct { clusterName string cc resolver.ClientConn addrs []resolver.Address - hostToAddr map[string]resolver.Address sync.RWMutex } @@ -108,7 +107,6 @@ type Resolver struct { func (r *Resolver) InitialAddrs(addrs []resolver.Address) { r.Lock() r.addrs = addrs - r.hostToAddr = keyAddrsByHost(addrs) r.Unlock() } @@ -133,37 +131,13 @@ func epsToAddrs(eps ...string) (addrs []resolver.Address) { } // NewAddress updates the addresses of the resolver. -func (r *Resolver) NewAddress(addrs []resolver.Address) error { - if r.cc == nil { - return fmt.Errorf("resolver not yet built, use InitialAddrs to provide initialization endpoints") - } +func (r *Resolver) NewAddress(addrs []resolver.Address) { r.Lock() r.addrs = addrs - r.hostToAddr = keyAddrsByHost(addrs) r.Unlock() - r.cc.NewAddress(addrs) - return nil -} - -func keyAddrsByHost(addrs []resolver.Address) map[string]resolver.Address { - // TODO: etcd may be is running on multiple ports on the same host, what to do? Keep a list of addresses? - byHost := make(map[string]resolver.Address, len(addrs)) - for _, addr := range addrs { - _, host, _ := ParseEndpoint(addr.Addr) - byHost[host] = addr - } - return byHost -} - -// Endpoint get the resolver address for the host, if any. -func (r *Resolver) Endpoint(host string) string { - var addr string - r.RLock() - if a, ok := r.hostToAddr[host]; ok { - addr = a.Addr + if r.cc != nil { + r.cc.NewAddress(addrs) } - r.RUnlock() - return addr } func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} diff --git a/clientv3/client.go b/clientv3/client.go index 901ab4fb007..2c573636811 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -143,17 +143,15 @@ func (c *Client) Endpoints() (eps []string) { // SetEndpoints updates client's endpoints. func (c *Client) SetEndpoints(eps ...string) { - c.mu.Lock() - c.cfg.Endpoints = eps - c.mu.Unlock() - var addrs []resolver.Address for _, ep := range eps { addrs = append(addrs, resolver.Address{Addr: ep}) } + c.mu.Lock() + defer c.mu.Unlock() + c.cfg.Endpoints = eps c.resolver.NewAddress(addrs) - // TODO: Does the new grpc balancer provide a way to block until the endpoint changes are propagated? /*if c.balancer.NeedUpdate() { select { @@ -252,9 +250,8 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] } opts = append(opts, dopts...) - f := func(host string, t time.Duration) (net.Conn, error) { - // TODO: eliminate this ParseEndpoint call, the endpoint is already parsed by the resolver. - proto, host, _ := endpoint.ParseEndpoint(c.resolver.Endpoint(host)) + f := func(dialEp string, t time.Duration) (net.Conn, error) { + proto, host, _ := endpoint.ParseEndpoint(dialEp) if host == "" && ep != "" { // dialing an endpoint not in the balancer; use // endpoint passed into dial diff --git a/clientv3/ordering/kv_test.go b/clientv3/ordering/kv_test.go index 9e884f3b291..ebe802334e5 100644 --- a/clientv3/ordering/kv_test.go +++ b/clientv3/ordering/kv_test.go @@ -82,6 +82,7 @@ func TestDetectKvOrderViolation(t *testing.T) { clus.Members[2].Restart(t) // force OrderingKv to query the third member cli.SetEndpoints(clus.Members[2].GRPCAddr()) + time.Sleep(2 * time.Second) // FIXME: Figure out how pause SetEndpoints sufficiently that this is not needed _, err = orderingKv.Get(ctx, "foo", clientv3.WithSerializable()) if err != errOrderViolation { @@ -147,7 +148,7 @@ func TestDetectTxnOrderViolation(t *testing.T) { clus.Members[2].Restart(t) // force OrderingKv to query the third member cli.SetEndpoints(clus.Members[2].GRPCAddr()) - + time.Sleep(2 * time.Second) // FIXME: Figure out how pause SetEndpoints sufficiently that this is not needed _, err = orderingKv.Get(ctx, "foo", clientv3.WithSerializable()) if err != errOrderViolation { t.Fatalf("expected %v, got %v", errOrderViolation, err) From 037d7b4abe75eda71379bade1d0ada151205b4c7 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 13 Apr 2018 10:24:34 -0700 Subject: [PATCH 15/39] clientv3: dial with context when creating authenticator Otherwise, "grpc.Dial" blocks when "grpc.WithTimeout" dial option gets deprecated. Signed-off-by: Gyuho Lee --- clientv3/auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clientv3/auth.go b/clientv3/auth.go index 0238920321c..f1241027e68 100644 --- a/clientv3/auth.go +++ b/clientv3/auth.go @@ -216,8 +216,8 @@ func (auth *authenticator) close() { auth.conn.Close() } -func newAuthenticator(endpoint string, opts []grpc.DialOption, c *Client) (*authenticator, error) { - conn, err := grpc.Dial(endpoint, opts...) +func newAuthenticator(ctx context.Context, endpoint string, opts []grpc.DialOption, c *Client) (*authenticator, error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) if err != nil { return nil, err } From 994a569f53f48129387027a532b5d1024ec5339b Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 13 Apr 2018 10:25:16 -0700 Subject: [PATCH 16/39] clientv3: pass "grpc.WithBlock" on "TestDialTimeout" Otherwise, grpc.DialContext would just return before connection is up. Signed-off-by: Gyuho Lee --- clientv3/client_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clientv3/client_test.go b/clientv3/client_test.go index d09e6433017..2f94fa62b7b 100644 --- a/clientv3/client_test.go +++ b/clientv3/client_test.go @@ -23,6 +23,8 @@ import ( "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/pkg/testutil" + + "google.golang.org/grpc" ) func TestDialCancel(t *testing.T) { @@ -80,14 +82,17 @@ func TestDialCancel(t *testing.T) { func TestDialTimeout(t *testing.T) { defer testutil.AfterTest(t) + // grpc.WithBlock to block until connection up or timeout testCfgs := []Config{ { Endpoints: []string{"http://254.0.0.1:12345"}, DialTimeout: 2 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, }, { Endpoints: []string{"http://254.0.0.1:12345"}, DialTimeout: time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, Username: "abc", Password: "def", }, From bb032f3e5fea8449dc89874c7962771d345f9238 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 13 Apr 2018 10:26:12 -0700 Subject: [PATCH 17/39] clientv3: deprecate "grpc.WithTimeout" in favor of "grpc.DialContext" "grpc.WithTimeout" dial option is being deprecated. Signed-off-by: Gyuho Lee --- clientv3/client.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index 2c573636811..008252f14a3 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -238,9 +238,6 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] return nil, fmt.Errorf("unable to parse target: %v", err) } - if c.cfg.DialTimeout > 0 { - opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)} - } if c.cfg.DialKeepAliveTime > 0 { params := keepalive.ClientParameters{ Time: c.cfg.DialKeepAliveTime, @@ -304,11 +301,11 @@ func (c *Client) getToken(ctx context.Context) error { host := getHost(endpoint) // use dial options without dopts to avoid reusing the client balancer var dOpts []grpc.DialOption - dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint)) + dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint), c.cfg.DialOptions...) if err != nil { continue } - auth, err = newAuthenticator(host, dOpts, c) + auth, err = newAuthenticator(ctx, host, dOpts, c) if err != nil { continue } @@ -341,11 +338,9 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo tokenMu: &sync.RWMutex{}, } - ctx := c.ctx + ctx, cancel := c.ctx, func() {} if c.cfg.DialTimeout > 0 { - cctx, cancel := context.WithTimeout(ctx, c.cfg.DialTimeout) - defer cancel() - ctx = cctx + ctx, cancel = context.WithTimeout(ctx, c.cfg.DialTimeout) } err = c.getToken(ctx) @@ -354,20 +349,29 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo if err == ctx.Err() && ctx.Err() != c.ctx.Err() { err = context.DeadlineExceeded } + cancel() return nil, err } } else { opts = append(opts, grpc.WithPerRPCCredentials(c.tokenCred)) } + cancel() } opts = append(opts, c.cfg.DialOptions...) + dctx := c.ctx + if c.cfg.DialTimeout > 0 { + var cancel context.CancelFunc + dctx, cancel = context.WithTimeout(c.ctx, c.cfg.DialTimeout) + defer cancel() + } + // TODO: The hosts check doesn't really make sense for a load balanced endpoint url for the new grpc load balancer interface. // Is it safe/sane to use the provided endpoint here? //host := getHost(endpoint) //conn, err := grpc.DialContext(c.ctx, host, opts...) - conn, err := grpc.DialContext(c.ctx, endpoint, opts...) + conn, err := grpc.DialContext(dctx, endpoint, opts...) if err != nil { return nil, err } From 66e65cd6608b028b240f0d8c3eaf69fd9bf95df7 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Mon, 16 Apr 2018 15:28:00 -0700 Subject: [PATCH 18/39] clientv3: Avoid timeouts in ordering test --- clientv3/balancer/balancer_test.go | 21 ++++++++++++--------- clientv3/ordering/util.go | 2 ++ clientv3/ordering/util_test.go | 3 ++- integration/cluster.go | 11 ++++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index 5de5b59302e..ac0ff303ff6 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -67,13 +67,14 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { defer rsv.Close() rsv.InitialAddrs(resolvedAddrs) + name := genName() cfg := Config{ Policy: picker.RoundrobinBalanced, - Name: genName(), + Name: name, Logger: zap.NewExample(), } - rrb := New(cfg) - conn, err := grpc.Dial(fmt.Sprintf("endpoint://nofailover/*"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + RegisterBuilder(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://nofailover/*"), grpc.WithInsecure(), grpc.WithBalancerName(name)) if err != nil { t.Fatalf("failed to dial mock server: %v", err) } @@ -129,13 +130,14 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { defer rsv.Close() rsv.InitialAddrs(resolvedAddrs) + name := genName() cfg := Config{ Policy: picker.RoundrobinBalanced, - Name: genName(), + Name: name, Logger: zap.NewExample(), } - rrb := New(cfg) - conn, err := grpc.Dial(fmt.Sprintf("endpoint://serverfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + RegisterBuilder(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://serverfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(name)) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } @@ -242,13 +244,14 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { defer rsv.Close() rsv.InitialAddrs(resolvedAddrs) + name := genName() cfg := Config{ Policy: picker.RoundrobinBalanced, - Name: genName(), + Name: name, Logger: zap.NewExample(), } - rrb := New(cfg) - conn, err := grpc.Dial(fmt.Sprintf("endpoint://requestfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(rrb.Name())) + RegisterBuilder(cfg) + conn, err := grpc.Dial(fmt.Sprintf("endpoint://requestfail/mock.server"), grpc.WithInsecure(), grpc.WithBalancerName(name)) if err != nil { t.Fatalf("failed to dial mock server: %s", err) } diff --git a/clientv3/ordering/util.go b/clientv3/ordering/util.go index 190a5919a52..124aebf4174 100644 --- a/clientv3/ordering/util.go +++ b/clientv3/ordering/util.go @@ -43,6 +43,8 @@ func NewOrderViolationSwitchEndpointClosure(c clientv3.Client) OrderViolationFun // set available endpoints back to all endpoints in to ensure // the client has access to all the endpoints. c.SetEndpoints(eps...) + // give enough time for operation + time.Sleep(1 * time.Second) violationCount++ return nil } diff --git a/clientv3/ordering/util_test.go b/clientv3/ordering/util_test.go index 9282f985f84..a370e0d07cc 100644 --- a/clientv3/ordering/util_test.go +++ b/clientv3/ordering/util_test.go @@ -142,8 +142,9 @@ func TestUnresolvableOrderViolation(t *testing.T) { if err != nil { t.Fatal(err) } + clus.Members[3].WaitStarted(t) cli.SetEndpoints(clus.Members[3].GRPCAddr()) - time.Sleep(1 * time.Second) // give enough time for operation + time.Sleep(5 * time.Second) // give enough time for operation _, err = OrderingKv.Get(ctx, "foo", clientv3.WithSerializable()) if err != ErrNoGreaterRev { diff --git a/integration/cluster.go b/integration/cluster.go index a1840f73409..e92aa95e5c7 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -950,6 +950,13 @@ func (m *member) Launch() error { } func (m *member) WaitOK(t *testing.T) { + m.WaitStarted(t) + for m.s.Leader() == 0 { + time.Sleep(tickDuration) + } +} + +func (m *member) WaitStarted(t *testing.T) { cc := MustNewHTTPClient(t, []string{m.URL()}, m.ClientTLSInfo) kapi := client.NewKeysAPI(cc) for { @@ -962,9 +969,7 @@ func (m *member) WaitOK(t *testing.T) { cancel() break } - for m.s.Leader() == 0 { - time.Sleep(tickDuration) - } + } func (m *member) URL() string { return m.ClientURLs[0].String() } From f84f554301ce1e0300a1b8147f491fbf82391db8 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 20 Apr 2018 10:43:44 -0700 Subject: [PATCH 19/39] clientv3: Fix auth client to use endpoints instead of host when dialing, fix tests to block on dial when required. --- .../balancer/resolver/endpoint/endpoint.go | 13 +++++++++- clientv3/client.go | 24 ++++++++++++------- integration/cluster.go | 4 ++++ integration/v3_alarm_test.go | 2 ++ integration/v3_grpc_inflight_test.go | 6 +++-- integration/v3_grpc_test.go | 2 ++ 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 75a7e8b74b3..3f2d115e1f9 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -146,8 +146,19 @@ func (r *Resolver) Close() { bldr.removeResolver(r) } +// Target constructs a endpoint target with current resolver's clusterName. func (r *Resolver) Target(endpoint string) string { - return fmt.Sprintf("%s://%s/%s", scheme, r.clusterName, endpoint) + return Target(r.clusterName, endpoint) +} + +// Target constructs a endpoint resolver target. +func Target(clusterName, endpoint string) string { + return fmt.Sprintf("%s://%s/%s", scheme, clusterName, endpoint) +} + +// IsTarget checks if a given target string in an endpoint resolver target. +func IsTarget(target string) bool { + return strings.HasPrefix(target, "endpoint://") } // Parse endpoint parses a endpoint of the form (http|https)://*|(unix|unixs)://) and returns a diff --git a/clientv3/client.go b/clientv3/client.go index 008252f14a3..b513b4e7d9f 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -298,14 +298,13 @@ func (c *Client) getToken(ctx context.Context) error { for i := 0; i < len(c.cfg.Endpoints); i++ { endpoint := c.cfg.Endpoints[i] - host := getHost(endpoint) // use dial options without dopts to avoid reusing the client balancer var dOpts []grpc.DialOption dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint), c.cfg.DialOptions...) if err != nil { continue } - auth, err = newAuthenticator(ctx, host, dOpts, c) + auth, err = newAuthenticator(ctx, endpoint, dOpts, c) if err != nil { continue } @@ -327,8 +326,8 @@ func (c *Client) getToken(ctx context.Context) error { return err } -func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { - opts, err := c.dialSetupOpts(endpoint, dopts...) +func (c *Client) dial(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { + opts, err := c.dialSetupOpts(ep, dopts...) if err != nil { return nil, err } @@ -367,11 +366,18 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo defer cancel() } - // TODO: The hosts check doesn't really make sense for a load balanced endpoint url for the new grpc load balancer interface. - // Is it safe/sane to use the provided endpoint here? - //host := getHost(endpoint) - //conn, err := grpc.DialContext(c.ctx, host, opts...) - conn, err := grpc.DialContext(dctx, endpoint, opts...) + // We pass a target to DialContext of the form: endpoint:/// that + // does not include scheme (http/https/unix/unixs) or path parts. + if endpoint.IsTarget(ep) { + clusterName, tep, err := endpoint.ParseTarget(ep) + if err != nil { + return nil, fmt.Errorf("failed to parse endpoint target '%s': %v", ep, err) + } + _, host, _ := endpoint.ParseEndpoint(tep) + ep = endpoint.Target(clusterName, host) + } + + conn, err := grpc.DialContext(dctx, ep, opts...) if err != nil { return nil, err } diff --git a/integration/cluster.go b/integration/cluster.go index e92aa95e5c7..6458b10724f 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -536,6 +536,7 @@ type member struct { PeerTLSInfo *transport.TLSInfo // ClientTLSInfo enables client TLS when set ClientTLSInfo *transport.TLSInfo + DialOptions []grpc.DialOption raftHandler *testutil.PauseableHandler s *etcdserver.EtcdServer @@ -744,6 +745,9 @@ func NewClientV3(m *member) (*clientv3.Client, error) { } cfg.TLS = tls } + if m.DialOptions != nil { + cfg.DialOptions = append(cfg.DialOptions, m.DialOptions...) + } return newClientV3(cfg) } diff --git a/integration/v3_alarm_test.go b/integration/v3_alarm_test.go index 01af6440689..049436bf6cd 100644 --- a/integration/v3_alarm_test.go +++ b/integration/v3_alarm_test.go @@ -191,10 +191,12 @@ func TestV3CorruptAlarm(t *testing.T) { } // Member 0 restarts into split brain. + clus.Members[0].WaitStarted(t) resp0, err0 := clus.Client(0).Get(context.TODO(), "abc") if err0 != nil { t.Fatal(err0) } + clus.Members[1].WaitStarted(t) resp1, err1 := clus.Client(1).Get(context.TODO(), "abc") if err1 != nil { t.Fatal(err1) diff --git a/integration/v3_grpc_inflight_test.go b/integration/v3_grpc_inflight_test.go index dd0d180cc0d..08c1a1f2369 100644 --- a/integration/v3_grpc_inflight_test.go +++ b/integration/v3_grpc_inflight_test.go @@ -25,6 +25,7 @@ import ( "github.com/coreos/etcd/pkg/testutil" "google.golang.org/grpc" + "google.golang.org/grpc/transport" ) // TestV3MaintenanceDefragmentInflightRange ensures inflight range requests @@ -81,8 +82,9 @@ func TestV3KVInflightRangeRequests(t *testing.T) { defer wg.Done() _, err := kvc.Range(ctx, &pb.RangeRequest{Key: []byte("foo"), Serializable: true}, grpc.FailFast(false)) if err != nil { - if err != nil && rpctypes.ErrorDesc(err) != context.Canceled.Error() { - t.Fatalf("inflight request should be canceld with %v, got %v", context.Canceled, err) + errDesc := rpctypes.ErrorDesc(err) + if err != nil && !(errDesc == context.Canceled.Error() || errDesc == transport.ErrConnClosing.Desc) { + t.Fatalf("inflight request should be canceled with '%v' or '%v', got '%v'", context.Canceled.Error(), transport.ErrConnClosing.Desc, errDesc) } } }() diff --git a/integration/v3_grpc_test.go b/integration/v3_grpc_test.go index c936703d5e3..b573881bb41 100644 --- a/integration/v3_grpc_test.go +++ b/integration/v3_grpc_test.go @@ -1570,6 +1570,7 @@ func TestTLSGRPCRejectSecureClient(t *testing.T) { defer clus.Terminate(t) clus.Members[0].ClientTLSInfo = &testTLSInfo + clus.Members[0].DialOptions = []grpc.DialOption{grpc.WithBlock()} client, err := NewClientV3(clus.Members[0]) if client != nil || err == nil { t.Fatalf("expected no client") @@ -1752,6 +1753,7 @@ func testTLSReload( continue } cli, cerr := clientv3.New(clientv3.Config{ + DialOptions: []grpc.DialOption{grpc.WithBlock()}, Endpoints: []string{clus.Members[0].GRPCAddr()}, DialTimeout: time.Second, TLS: cc, From ee2747eba89dc4ac63ee035cffe768874b1a69d3 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Thu, 26 Apr 2018 13:22:31 -0700 Subject: [PATCH 20/39] clientv3: Fix dial calls to consistently use endpoint resolver, attempt to deflake alarm test --- .../balancer/resolver/endpoint/endpoint.go | 8 ++--- clientv3/client.go | 34 +++++++++---------- clientv3/maintenance.go | 4 ++- integration/v3_alarm_test.go | 15 ++++++-- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 3f2d115e1f9..8d66f4c3cf1 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -110,15 +110,15 @@ func (r *Resolver) InitialAddrs(addrs []resolver.Address) { r.Unlock() } -// InitialEndpoints sets the initial endpoints to for the resolver and returns a grpc dial target. +// InitialEndpoints sets the initial endpoints to for the resolver. // This should be called before dialing. The endpoints may be updated after the dial using NewAddress. // At least one endpoint is required. -func (r *Resolver) InitialEndpoints(eps []string) (string, error) { +func (r *Resolver) InitialEndpoints(eps []string) error { if len(eps) < 1 { - return "", fmt.Errorf("At least one endpoint is required, but got: %v", eps) + return fmt.Errorf("At least one endpoint is required, but got: %v", eps) } r.InitialAddrs(epsToAddrs(eps...)) - return r.Target(eps[0]), nil + return nil } // TODO: use balancer.epsToAddrs diff --git a/clientv3/client.go b/clientv3/client.go index b513b4e7d9f..40ec43590a6 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -302,6 +302,7 @@ func (c *Client) getToken(ctx context.Context) error { var dOpts []grpc.DialOption dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint), c.cfg.DialOptions...) if err != nil { + err = fmt.Errorf("failed to configure auth dialer: %v", err) continue } auth, err = newAuthenticator(ctx, endpoint, dOpts, c) @@ -327,9 +328,14 @@ func (c *Client) getToken(ctx context.Context) error { } func (c *Client) dial(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { - opts, err := c.dialSetupOpts(ep, dopts...) + // We pass a target to DialContext of the form: endpoint:/// that + // does not include scheme (http/https/unix/unixs) or path parts. + _, host, _ := endpoint.ParseEndpoint(ep) + target := c.resolver.Target(host) + + opts, err := c.dialSetupOpts(target, dopts...) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to configure dialer: %v", err) } if c.Username != "" && c.Password != "" { @@ -366,18 +372,7 @@ func (c *Client) dial(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, er defer cancel() } - // We pass a target to DialContext of the form: endpoint:/// that - // does not include scheme (http/https/unix/unixs) or path parts. - if endpoint.IsTarget(ep) { - clusterName, tep, err := endpoint.ParseTarget(ep) - if err != nil { - return nil, fmt.Errorf("failed to parse endpoint target '%s': %v", ep, err) - } - _, host, _ := endpoint.ParseEndpoint(tep) - ep = endpoint.Target(clusterName, host) - } - - conn, err := grpc.DialContext(dctx, ep, opts...) + conn, err := grpc.DialContext(dctx, target, opts...) if err != nil { return nil, err } @@ -443,20 +438,25 @@ func newClient(cfg *Config) (*Client, error) { // Prepare a 'endpoint:///' resolver for the client and create a endpoint target to pass // to dial so the client knows to use this resolver. client.resolver = endpoint.EndpointResolver(fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36))) - target, err := client.resolver.InitialEndpoints(cfg.Endpoints) + err := client.resolver.InitialEndpoints(cfg.Endpoints) if err != nil { client.cancel() client.resolver.Close() return nil, err } + if len(cfg.Endpoints) < 1 { + return nil, fmt.Errorf("at least one Endpoint must is required in client config") + } + dialEndpoint := cfg.Endpoints[0] + // Use an provided endpoint target so that for https:// without any tls config given, then // grpc will assume the certificate server name is the endpoint host. - conn, err := client.dial(target, grpc.WithBalancerName(roundRobinBalancerName)) + conn, err := client.dial(dialEndpoint, grpc.WithBalancerName(roundRobinBalancerName)) if err != nil { client.cancel() client.resolver.Close() - return nil, err + return nil, fmt.Errorf("failed to dial initial client connection: %v", err) } // TODO: With the old grpc balancer interface, we waited until the dial timeout // for the balancer to be ready. Is there an equivalent wait we should do with the new grpc balancer interface? diff --git a/clientv3/maintenance.go b/clientv3/maintenance.go index ce05b3b6594..a6a2e7dec67 100644 --- a/clientv3/maintenance.go +++ b/clientv3/maintenance.go @@ -16,6 +16,7 @@ package clientv3 import ( "context" + "fmt" "io" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" @@ -77,7 +78,7 @@ func NewMaintenance(c *Client) Maintenance { dial: func(endpoint string) (pb.MaintenanceClient, func(), error) { conn, err := c.dial(endpoint) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to dial endpoint %s with maintenance client: %v", endpoint, err) } cancel := func() { conn.Close() } return RetryMaintenanceClient(c, conn), cancel, nil @@ -175,6 +176,7 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo func (m *maintenance) HashKV(ctx context.Context, endpoint string, rev int64) (*HashKVResponse, error) { remote, cancel, err := m.dial(endpoint) if err != nil { + return nil, toErr(ctx, err) } defer cancel() diff --git a/integration/v3_alarm_test.go b/integration/v3_alarm_test.go index 049436bf6cd..0486ead8075 100644 --- a/integration/v3_alarm_test.go +++ b/integration/v3_alarm_test.go @@ -175,12 +175,20 @@ func TestV3CorruptAlarm(t *testing.T) { s.Close() be.Close() + clus.Members[1].WaitOK(t) + clus.Members[2].WaitOK(t) + time.Sleep(time.Second * 2) + // Wait for cluster so Puts succeed in case member 0 was the leader. if _, err := clus.Client(1).Get(context.TODO(), "k"); err != nil { t.Fatal(err) } - clus.Client(1).Put(context.TODO(), "xyz", "321") - clus.Client(1).Put(context.TODO(), "abc", "fed") + if _, err := clus.Client(1).Put(context.TODO(), "xyz", "321"); err != nil { + t.Fatal(err) + } + if _, err := clus.Client(1).Put(context.TODO(), "abc", "fed"); err != nil { + t.Fatal(err) + } // Restart with corruption checking enabled. clus.Members[1].Stop(t) @@ -189,7 +197,8 @@ func TestV3CorruptAlarm(t *testing.T) { m.CorruptCheckTime = time.Second m.Restart(t) } - // Member 0 restarts into split brain. + clus.WaitLeader(t) + time.Sleep(time.Second * 2) clus.Members[0].WaitStarted(t) resp0, err0 := clus.Client(0).Get(context.TODO(), "abc") From 9304d1abd1efee0ead114b3f090a0bc05c4d2c8a Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Thu, 26 Apr 2018 14:54:07 -0700 Subject: [PATCH 21/39] clientv3: Fix TLS test failures by returning DeadlineExceeded error from dial without any additional wrapping --- clientv3/auth.go | 5 +- .../balancer/resolver/endpoint/endpoint.go | 40 ++++++------ clientv3/client.go | 12 ++-- clientv3/integration/black_hole_test.go | 15 +++-- clientv3/integration/dial_test.go | 27 ++++++-- clientv3/integration/kv_test.go | 14 ++-- clientv3/integration/leasing_test.go | 64 ++++++++++--------- clientv3/integration/maintenance_test.go | 9 ++- .../integration/network_partition_test.go | 14 ++-- clientv3/integration/server_shutdown_test.go | 36 ++++++++++- clientv3/integration/user_test.go | 8 ++- clientv3/integration/watch_test.go | 4 +- integration/cluster.go | 1 + 13 files changed, 157 insertions(+), 92 deletions(-) diff --git a/clientv3/auth.go b/clientv3/auth.go index f1241027e68..bedbd132c18 100644 --- a/clientv3/auth.go +++ b/clientv3/auth.go @@ -21,7 +21,6 @@ import ( "github.com/coreos/etcd/auth/authpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" - "google.golang.org/grpc" ) @@ -216,8 +215,8 @@ func (auth *authenticator) close() { auth.conn.Close() } -func newAuthenticator(ctx context.Context, endpoint string, opts []grpc.DialOption, c *Client) (*authenticator, error) { - conn, err := grpc.DialContext(ctx, endpoint, opts...) +func newAuthenticator(ctx context.Context, target string, opts []grpc.DialOption, c *Client) (*authenticator, error) { + conn, err := grpc.DialContext(ctx, target, opts...) if err != nil { return nil, err } diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 8d66f4c3cf1..120a0b9c57b 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// resolves to etcd entpoints for grpc targets of the form 'endpoint:///'. +// Package endpoint resolves etcd entpoints using grpc targets of the form 'endpoint:///'. package endpoint import ( @@ -36,13 +36,13 @@ var ( func init() { bldr = &builder{ - clusterResolvers: make(map[string]*Resolver), + clientResolvers: make(map[string]*Resolver), } resolver.Register(bldr) } type builder struct { - clusterResolvers map[string]*Resolver + clientResolvers map[string]*Resolver sync.RWMutex } @@ -59,16 +59,16 @@ func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts res return r, nil } -func (b *builder) getResolver(clusterName string) *Resolver { +func (b *builder) getResolver(clientId string) *Resolver { b.RLock() - r, ok := b.clusterResolvers[clusterName] + r, ok := b.clientResolvers[clientId] b.RUnlock() if !ok { r = &Resolver{ - clusterName: clusterName, + clientId: clientId, } b.Lock() - b.clusterResolvers[clusterName] = r + b.clientResolvers[clientId] = r b.Unlock() } return r @@ -76,13 +76,13 @@ func (b *builder) getResolver(clusterName string) *Resolver { func (b *builder) addResolver(r *Resolver) { bldr.Lock() - bldr.clusterResolvers[r.clusterName] = r + bldr.clientResolvers[r.clientId] = r bldr.Unlock() } func (b *builder) removeResolver(r *Resolver) { bldr.Lock() - delete(bldr.clusterResolvers, r.clusterName) + delete(bldr.clientResolvers, r.clientId) bldr.Unlock() } @@ -91,15 +91,15 @@ func (r *builder) Scheme() string { } // EndpointResolver gets the resolver for given etcd cluster name. -func EndpointResolver(clusterName string) *Resolver { - return bldr.getResolver(clusterName) +func EndpointResolver(clientId string) *Resolver { + return bldr.getResolver(clientId) } // Resolver provides a resolver for a single etcd cluster, identified by name. type Resolver struct { - clusterName string - cc resolver.ClientConn - addrs []resolver.Address + clientId string + cc resolver.ClientConn + addrs []resolver.Address sync.RWMutex } @@ -146,14 +146,14 @@ func (r *Resolver) Close() { bldr.removeResolver(r) } -// Target constructs a endpoint target with current resolver's clusterName. +// Target constructs a endpoint target with current resolver's clientId. func (r *Resolver) Target(endpoint string) string { - return Target(r.clusterName, endpoint) + return Target(r.clientId, endpoint) } // Target constructs a endpoint resolver target. -func Target(clusterName, endpoint string) string { - return fmt.Sprintf("%s://%s/%s", scheme, clusterName, endpoint) +func Target(clientId, endpoint string) string { + return fmt.Sprintf("%s://%s/%s", scheme, clientId, endpoint) } // IsTarget checks if a given target string in an endpoint resolver target. @@ -185,7 +185,7 @@ func ParseEndpoint(endpoint string) (proto string, host string, scheme string) { return proto, host, scheme } -// ParseTarget parses a endpoint:/// string and returns the parsed clusterName and endpoint. +// ParseTarget parses a endpoint:/// string and returns the parsed clientId and endpoint. // If the target is malformed, an error is returned. func ParseTarget(target string) (string, string, error) { noPrefix := strings.TrimPrefix(target, targetPrefix) @@ -194,7 +194,7 @@ func ParseTarget(target string) (string, string, error) { } parts := strings.SplitN(noPrefix, "/", 2) if len(parts) != 2 { - return "", "", fmt.Errorf("malformed target, expected %s:///, but got %s", scheme, target) + return "", "", fmt.Errorf("malformed target, expected %s:///, but got %s", scheme, target) } return parts[0], parts[1], nil } diff --git a/clientv3/client.go b/clientv3/client.go index 40ec43590a6..b2870f4ee74 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -297,15 +297,17 @@ func (c *Client) getToken(ctx context.Context) error { var auth *authenticator for i := 0; i < len(c.cfg.Endpoints); i++ { - endpoint := c.cfg.Endpoints[i] + ep := c.cfg.Endpoints[i] // use dial options without dopts to avoid reusing the client balancer var dOpts []grpc.DialOption - dOpts, err = c.dialSetupOpts(c.resolver.Target(endpoint), c.cfg.DialOptions...) + _, host, _ := endpoint.ParseEndpoint(ep) + target := c.resolver.Target(host) + dOpts, err = c.dialSetupOpts(target, c.cfg.DialOptions...) if err != nil { err = fmt.Errorf("failed to configure auth dialer: %v", err) continue } - auth, err = newAuthenticator(ctx, endpoint, dOpts, c) + auth, err = newAuthenticator(ctx, target, dOpts, c) if err != nil { continue } @@ -369,7 +371,7 @@ func (c *Client) dial(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, er if c.cfg.DialTimeout > 0 { var cancel context.CancelFunc dctx, cancel = context.WithTimeout(c.ctx, c.cfg.DialTimeout) - defer cancel() + defer cancel() // TODO: Is this right for cases where grpc.WithBlock() is not set on the dial options? } conn, err := grpc.DialContext(dctx, target, opts...) @@ -456,7 +458,7 @@ func newClient(cfg *Config) (*Client, error) { if err != nil { client.cancel() client.resolver.Close() - return nil, fmt.Errorf("failed to dial initial client connection: %v", err) + return nil, err } // TODO: With the old grpc balancer interface, we waited until the dial timeout // for the balancer to be ready. Is there an equivalent wait we should do with the new grpc balancer interface? diff --git a/clientv3/integration/black_hole_test.go b/clientv3/integration/black_hole_test.go index c59541e381d..8a907680cad 100644 --- a/clientv3/integration/black_hole_test.go +++ b/clientv3/integration/black_hole_test.go @@ -25,6 +25,7 @@ import ( "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/integration" "github.com/coreos/etcd/pkg/testutil" + "google.golang.org/grpc" ) // TestBalancerUnderBlackholeKeepAliveWatch tests when watch discovers it cannot talk to @@ -44,6 +45,7 @@ func TestBalancerUnderBlackholeKeepAliveWatch(t *testing.T) { ccfg := clientv3.Config{ Endpoints: []string{eps[0]}, DialTimeout: 1 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, DialKeepAliveTime: 1 * time.Second, DialKeepAliveTimeout: 500 * time.Millisecond, } @@ -106,7 +108,7 @@ func TestBalancerUnderBlackholeKeepAliveWatch(t *testing.T) { func TestBalancerUnderBlackholeNoKeepAlivePut(t *testing.T) { testBalancerUnderBlackholeNoKeepAlive(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Put(ctx, "foo", "bar") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -116,7 +118,7 @@ func TestBalancerUnderBlackholeNoKeepAlivePut(t *testing.T) { func TestBalancerUnderBlackholeNoKeepAliveDelete(t *testing.T) { testBalancerUnderBlackholeNoKeepAlive(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Delete(ctx, "foo") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -129,7 +131,7 @@ func TestBalancerUnderBlackholeNoKeepAliveTxn(t *testing.T) { If(clientv3.Compare(clientv3.Version("foo"), "=", 0)). Then(clientv3.OpPut("foo", "bar")). Else(clientv3.OpPut("foo", "baz")).Commit() - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -139,7 +141,7 @@ func TestBalancerUnderBlackholeNoKeepAliveTxn(t *testing.T) { func TestBalancerUnderBlackholeNoKeepAliveLinearizableGet(t *testing.T) { testBalancerUnderBlackholeNoKeepAlive(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Get(ctx, "a") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -149,7 +151,7 @@ func TestBalancerUnderBlackholeNoKeepAliveLinearizableGet(t *testing.T) { func TestBalancerUnderBlackholeNoKeepAliveSerializableGet(t *testing.T) { testBalancerUnderBlackholeNoKeepAlive(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Get(ctx, "a", clientv3.WithSerializable()) - if err == context.DeadlineExceeded || isServerCtxTimeout(err) { + if isClientTimeout(err) || isServerCtxTimeout(err) { return errExpected } return err @@ -172,6 +174,7 @@ func testBalancerUnderBlackholeNoKeepAlive(t *testing.T, op func(*clientv3.Clien ccfg := clientv3.Config{ Endpoints: []string{eps[0]}, DialTimeout: 1 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, } cli, err := clientv3.New(ccfg) if err != nil { @@ -193,7 +196,7 @@ func testBalancerUnderBlackholeNoKeepAlive(t *testing.T, op func(*clientv3.Clien // TODO: first operation can succeed // when gRPC supports better retry on non-delivered request for i := 0; i < 2; i++ { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) err = op(cli, ctx) cancel() if err == nil { diff --git a/clientv3/integration/dial_test.go b/clientv3/integration/dial_test.go index 91eb597ea04..1c2e96bb86f 100644 --- a/clientv3/integration/dial_test.go +++ b/clientv3/integration/dial_test.go @@ -26,6 +26,7 @@ import ( "github.com/coreos/etcd/integration" "github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/pkg/transport" + "google.golang.org/grpc" ) var ( @@ -58,10 +59,11 @@ func TestDialTLSExpired(t *testing.T) { _, err = clientv3.New(clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr()}, DialTimeout: 3 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, TLS: tls, }) - if err != context.DeadlineExceeded { - t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err) + if !isClientTimeout(err) { + t.Fatalf("expected dial timeout error, got %v", err) } } @@ -72,12 +74,19 @@ func TestDialTLSNoConfig(t *testing.T) { clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1, ClientTLS: &testTLSInfo, SkipCreatingClient: true}) defer clus.Terminate(t) // expect "signed by unknown authority" - _, err := clientv3.New(clientv3.Config{ + c, err := clientv3.New(clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr()}, DialTimeout: time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) - if err != context.DeadlineExceeded { - t.Fatalf("expected %v, got %v", context.DeadlineExceeded, err) + defer c.Close() + + // TODO: this should not be required when we set grpc.WithBlock() + if c != nil { + _, err = c.KV.Get(context.Background(), "/") + } + if !isClientTimeout(err) { + t.Fatalf("expected dial timeout error, got %v", err) } } @@ -104,7 +113,11 @@ func testDialSetEndpoints(t *testing.T, setBefore bool) { } toKill := rand.Intn(len(eps)) - cfg := clientv3.Config{Endpoints: []string{eps[toKill]}, DialTimeout: 1 * time.Second} + cfg := clientv3.Config{ + Endpoints: []string{eps[toKill]}, + DialTimeout: 1 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, + } cli, err := clientv3.New(cfg) if err != nil { t.Fatal(err) @@ -121,6 +134,7 @@ func testDialSetEndpoints(t *testing.T, setBefore bool) { if !setBefore { cli.SetEndpoints(eps[toKill%3], eps[(toKill+1)%3]) } + time.Sleep(time.Second * 2) ctx, cancel := context.WithTimeout(context.Background(), integration.RequestWaitTimeout) if _, err = cli.Get(ctx, "foo", clientv3.WithSerializable()); err != nil { t.Fatal(err) @@ -158,6 +172,7 @@ func TestRejectOldCluster(t *testing.T) { cfg := clientv3.Config{ Endpoints: []string{clus.Members[0].GRPCAddr(), clus.Members[1].GRPCAddr()}, DialTimeout: 5 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, RejectOldCluster: true, } cli, err := clientv3.New(cfg) diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index 66de753cdad..6451d2901c4 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -708,6 +708,7 @@ func TestKVGetRetry(t *testing.T) { time.Sleep(100 * time.Millisecond) clus.Members[fIdx].Restart(t) + clus.Members[fIdx].WaitOK(t) select { case <-time.After(5 * time.Second): @@ -792,7 +793,7 @@ func TestKVGetStoppedServerAndClose(t *testing.T) { // this Get fails and triggers an asynchronous connection retry _, err := cli.Get(ctx, "abc") cancel() - if err != nil && err != context.DeadlineExceeded { + if err != nil && !isServerUnavailable(err) { t.Fatal(err) } } @@ -814,14 +815,15 @@ func TestKVPutStoppedServerAndClose(t *testing.T) { // grpc finds out the original connection is down due to the member shutdown. _, err := cli.Get(ctx, "abc") cancel() - if err != nil && err != context.DeadlineExceeded { + if err != nil && !isServerUnavailable(err) { t.Fatal(err) } + ctx, cancel = context.WithTimeout(context.TODO(), time.Second) // TODO: How was this test not consistently failing with context canceled errors? // this Put fails and triggers an asynchronous connection retry _, err = cli.Put(ctx, "abc", "123") cancel() - if err != nil && err != context.DeadlineExceeded { + if err != nil && !isServerUnavailable(err) { t.Fatal(err) } } @@ -906,7 +908,7 @@ func TestKVLargeRequests(t *testing.T) { maxCallSendBytesClient: 10 * 1024 * 1024, maxCallRecvBytesClient: 0, valueSize: 10 * 1024 * 1024, - expectError: grpc.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max "), + expectError: grpc.Errorf(codes.ResourceExhausted, "trying to send message larger than max "), }, { maxRequestBytesServer: 10 * 1024 * 1024, @@ -920,7 +922,7 @@ func TestKVLargeRequests(t *testing.T) { maxCallSendBytesClient: 10 * 1024 * 1024, maxCallRecvBytesClient: 0, valueSize: 10*1024*1024 + 5, - expectError: grpc.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max "), + expectError: grpc.Errorf(codes.ResourceExhausted, "trying to send message larger than max "), }, } for i, test := range tests { @@ -940,7 +942,7 @@ func TestKVLargeRequests(t *testing.T) { t.Errorf("#%d: expected %v, got %v", i, test.expectError, err) } } else if err != nil && !strings.HasPrefix(err.Error(), test.expectError.Error()) { - t.Errorf("#%d: expected %v, got %v", i, test.expectError, err) + t.Errorf("#%d: expected error starting with '%s', got '%s'", i, test.expectError.Error(), err.Error()) } // put request went through, now expects large response back diff --git a/clientv3/integration/leasing_test.go b/clientv3/integration/leasing_test.go index bfed1ea4221..2383c197398 100644 --- a/clientv3/integration/leasing_test.go +++ b/clientv3/integration/leasing_test.go @@ -1920,10 +1920,6 @@ func TestLeasingSessionExpire(t *testing.T) { } func TestLeasingSessionExpireCancel(t *testing.T) { - defer testutil.AfterTest(t) - clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) - defer clus.Terminate(t) - tests := []func(context.Context, clientv3.KV) error{ func(ctx context.Context, kv clientv3.KV) error { _, err := kv.Get(ctx, "abc") @@ -1960,37 +1956,43 @@ func TestLeasingSessionExpireCancel(t *testing.T) { }, } for i := range tests { - lkv, closeLKV, err := leasing.NewKV(clus.Client(0), "foo/", concurrency.WithTTL(1)) - testutil.AssertNil(t, err) - defer closeLKV() + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + defer testutil.AfterTest(t) + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) + defer clus.Terminate(t) - if _, err = lkv.Get(context.TODO(), "abc"); err != nil { - t.Fatal(err) - } + lkv, closeLKV, err := leasing.NewKV(clus.Client(0), "foo/", concurrency.WithTTL(1)) + testutil.AssertNil(t, err) + defer closeLKV() - // down endpoint lkv uses for keepalives - clus.Members[0].Stop(t) - if err := waitForLeasingExpire(clus.Client(1), "foo/abc"); err != nil { - t.Fatal(err) - } - waitForExpireAck(t, lkv) + if _, err = lkv.Get(context.TODO(), "abc"); err != nil { + t.Fatal(err) + } - ctx, cancel := context.WithCancel(context.TODO()) - errc := make(chan error, 1) - go func() { errc <- tests[i](ctx, lkv) }() - // some delay to get past for ctx.Err() != nil {} loops - time.Sleep(100 * time.Millisecond) - cancel() + // down endpoint lkv uses for keepalives + clus.Members[0].Stop(t) + if err := waitForLeasingExpire(clus.Client(1), "foo/abc"); err != nil { + t.Fatal(err) + } + waitForExpireAck(t, lkv) - select { - case err := <-errc: - if err != ctx.Err() { - t.Errorf("#%d: expected %v, got %v", i, ctx.Err(), err) + ctx, cancel := context.WithCancel(context.TODO()) + errc := make(chan error, 1) + go func() { errc <- tests[i](ctx, lkv) }() + // some delay to get past for ctx.Err() != nil {} loops + time.Sleep(100 * time.Millisecond) + cancel() + + select { + case err := <-errc: + if err != ctx.Err() { + t.Errorf("#%d: expected %v, got %v", i, ctx.Err(), err) + } + case <-time.After(5 * time.Second): + t.Errorf("#%d: timed out waiting for cancel", i) } - case <-time.After(5 * time.Second): - t.Errorf("#%d: timed out waiting for cancel", i) - } - clus.Members[0].Restart(t) + clus.Members[0].Restart(t) + }) } } @@ -2016,6 +2018,8 @@ func waitForExpireAck(t *testing.T, kv clientv3.KV) { cancel() if err == ctx.Err() { return + } else if err != nil { + t.Logf("current error: %v", err) } time.Sleep(time.Second) } diff --git a/clientv3/integration/maintenance_test.go b/clientv3/integration/maintenance_test.go index 1edc6fdff76..72306c0cd49 100644 --- a/clientv3/integration/maintenance_test.go +++ b/clientv3/integration/maintenance_test.go @@ -21,7 +21,6 @@ import ( "io" "io/ioutil" "path/filepath" - "strings" "testing" "time" @@ -131,8 +130,8 @@ func TestMaintenanceSnapshotError(t *testing.T) { time.Sleep(2 * time.Second) _, err = io.Copy(ioutil.Discard, rc2) - if err != nil && err != context.DeadlineExceeded { - t.Errorf("expected %v, got %v", context.DeadlineExceeded, err) + if err != nil && !isClientTimeout(err) { + t.Errorf("expected client timeout, got %v", err) } } @@ -189,7 +188,7 @@ func TestMaintenanceSnapshotErrorInflight(t *testing.T) { // 300ms left and expect timeout while snapshot reading is in progress time.Sleep(700 * time.Millisecond) _, err = io.Copy(ioutil.Discard, rc2) - if err != nil && !strings.Contains(err.Error(), context.DeadlineExceeded.Error()) { - t.Errorf("expected %v from gRPC, got %v", context.DeadlineExceeded, err) + if err != nil && !isClientTimeout(err) { + t.Errorf("expected client timeout, got %v", err) } } diff --git a/clientv3/integration/network_partition_test.go b/clientv3/integration/network_partition_test.go index e175aa4f266..9588c4fb82f 100644 --- a/clientv3/integration/network_partition_test.go +++ b/clientv3/integration/network_partition_test.go @@ -26,6 +26,7 @@ import ( "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/integration" "github.com/coreos/etcd/pkg/testutil" + "google.golang.org/grpc" ) var errExpected = errors.New("expected error") @@ -36,7 +37,7 @@ var errExpected = errors.New("expected error") func TestBalancerUnderNetworkPartitionPut(t *testing.T) { testBalancerUnderNetworkPartition(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Put(ctx, "a", "b") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -46,7 +47,7 @@ func TestBalancerUnderNetworkPartitionPut(t *testing.T) { func TestBalancerUnderNetworkPartitionDelete(t *testing.T) { testBalancerUnderNetworkPartition(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Delete(ctx, "a") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -59,7 +60,7 @@ func TestBalancerUnderNetworkPartitionTxn(t *testing.T) { If(clientv3.Compare(clientv3.Version("foo"), "=", 0)). Then(clientv3.OpPut("foo", "bar")). Else(clientv3.OpPut("foo", "baz")).Commit() - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout { return errExpected } return err @@ -82,7 +83,7 @@ func TestBalancerUnderNetworkPartitionLinearizableGetWithLongTimeout(t *testing. func TestBalancerUnderNetworkPartitionLinearizableGetWithShortTimeout(t *testing.T) { testBalancerUnderNetworkPartition(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Get(ctx, "a") - if err == context.DeadlineExceeded || isServerCtxTimeout(err) { + if isClientTimeout(err) || isServerCtxTimeout(err) { return errExpected } return err @@ -111,6 +112,7 @@ func testBalancerUnderNetworkPartition(t *testing.T, op func(*clientv3.Client, c ccfg := clientv3.Config{ Endpoints: []string{eps[0]}, DialTimeout: 3 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, } cli, err := clientv3.New(ccfg) if err != nil { @@ -123,6 +125,7 @@ func testBalancerUnderNetworkPartition(t *testing.T, op func(*clientv3.Client, c // add other endpoints for later endpoint switch cli.SetEndpoints(eps...) + time.Sleep(time.Second * 2) clus.Members[0].InjectPartition(t, clus.Members[1:]...) for i := 0; i < 2; i++ { @@ -133,7 +136,7 @@ func testBalancerUnderNetworkPartition(t *testing.T, op func(*clientv3.Client, c break } if err != errExpected { - t.Errorf("#%d: expected %v, got %v", i, errExpected, err) + t.Errorf("#%d: expected '%v', got '%v'", i, errExpected, err) } // give enough time for endpoint switch // TODO: remove random sleep by syncing directly with balancer @@ -166,6 +169,7 @@ func TestBalancerUnderNetworkPartitionLinearizableGetLeaderElection(t *testing.T cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{eps[(lead+1)%2]}, DialTimeout: 1 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) if err != nil { t.Fatal(err) diff --git a/clientv3/integration/server_shutdown_test.go b/clientv3/integration/server_shutdown_test.go index 0a9f7b492b8..fdca6e3e69e 100644 --- a/clientv3/integration/server_shutdown_test.go +++ b/clientv3/integration/server_shutdown_test.go @@ -29,6 +29,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/grpc/transport" ) // TestBalancerUnderServerShutdownWatch expects that watch client @@ -105,7 +106,7 @@ func TestBalancerUnderServerShutdownWatch(t *testing.T) { if err == nil { break } - if err == context.DeadlineExceeded || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout || err == rpctypes.ErrTimeoutDueToLeaderFail { + if isClientTimeout(err) || isServerCtxTimeout(err) || err == rpctypes.ErrTimeout || err == rpctypes.ErrTimeoutDueToLeaderFail { continue } t.Fatal(err) @@ -341,10 +342,10 @@ func testBalancerUnderServerStopInflightRangeOnRestart(t *testing.T, linearizabl _, err := cli.Get(ctx, "abc", gops...) cancel() if err != nil { - if linearizable && strings.Contains(err.Error(), "context deadline exceeded") { + if linearizable && isServerUnavailable(err) { t.Logf("TODO: FIX THIS after balancer rewrite! %v %v", reflect.TypeOf(err), err) } else { - t.Fatal(err) + t.Fatalf("expected linearizable=true and a server unavailable error, but got linearizable=%t and '%v'", linearizable, err) } } }() @@ -373,3 +374,32 @@ func isServerCtxTimeout(err error) bool { code := ev.Code() return code == codes.DeadlineExceeded && strings.Contains(err.Error(), "context deadline exceeded") } + +// In grpc v1.11.3+ dial timeouts can error out with transport.ErrConnClosing. Previously dial timeouts +// would always error out with context.DeadlineExceeded. +func isClientTimeout(err error) bool { + if err == nil { + return false + } + if err == context.DeadlineExceeded { + return true + } + ev, ok := status.FromError(err) + if !ok { + return false + } + code := ev.Code() + return code == codes.DeadlineExceeded || ev.Message() == transport.ErrConnClosing.Desc +} + +func isServerUnavailable(err error) bool { + if err == nil { + return false + } + ev, ok := status.FromError(err) + if !ok { + return false + } + code := ev.Code() + return code == codes.Unavailable +} diff --git a/clientv3/integration/user_test.go b/clientv3/integration/user_test.go index 11548123ddb..da341d6604f 100644 --- a/clientv3/integration/user_test.go +++ b/clientv3/integration/user_test.go @@ -17,11 +17,13 @@ package integration import ( "context" "testing" + "time" "github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/integration" "github.com/coreos/etcd/pkg/testutil" + "google.golang.org/grpc" ) func TestUserError(t *testing.T) { @@ -68,7 +70,11 @@ func TestUserErrorAuth(t *testing.T) { } // wrong id or password - cfg := clientv3.Config{Endpoints: authapi.Endpoints()} + cfg := clientv3.Config{ + Endpoints: authapi.Endpoints(), + DialTimeout: 5 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, + } cfg.Username, cfg.Password = "wrong-id", "123" if _, err := clientv3.New(cfg); err != rpctypes.ErrAuthFailed { t.Fatalf("expected %v, got %v", rpctypes.ErrAuthFailed, err) diff --git a/clientv3/integration/watch_test.go b/clientv3/integration/watch_test.go index f9ac47b4ae4..f4879149f02 100644 --- a/clientv3/integration/watch_test.go +++ b/clientv3/integration/watch_test.go @@ -667,8 +667,8 @@ func TestWatchErrConnClosed(t *testing.T) { go func() { defer close(donec) ch := cli.Watch(context.TODO(), "foo") - if wr := <-ch; grpc.ErrorDesc(wr.Err()) != grpc.ErrClientConnClosing.Error() { - t.Fatalf("expected %v, got %v", grpc.ErrClientConnClosing, grpc.ErrorDesc(wr.Err())) + if wr := <-ch; wr.Err() != grpc.ErrClientConnClosing { + t.Fatalf("expected %v, got %v", grpc.ErrClientConnClosing, wr.Err()) } }() diff --git a/integration/cluster.go b/integration/cluster.go index 6458b10724f..e6a77767976 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -734,6 +734,7 @@ func NewClientV3(m *member) (*clientv3.Client, error) { cfg := clientv3.Config{ Endpoints: []string{m.grpcAddr}, DialTimeout: 5 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, MaxCallSendMsgSize: m.clientMaxCallSendMsgSize, MaxCallRecvMsgSize: m.clientMaxCallRecvMsgSize, } From 8569b9c782f9598455b349649ec8b7c9facdc966 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Tue, 1 May 2018 17:07:37 -0700 Subject: [PATCH 22/39] clientv3: Fix endpoint resolver to create a new resolver for each grpc client connection --- clientv3/balancer/balancer_test.go | 42 ++-- .../balancer/resolver/endpoint/endpoint.go | 180 ++++++++++-------- clientv3/client.go | 44 ++--- clientv3/integration/server_shutdown_test.go | 15 ++ clientv3/integration/watch_test.go | 10 +- 5 files changed, 165 insertions(+), 126 deletions(-) diff --git a/clientv3/balancer/balancer_test.go b/clientv3/balancer/balancer_test.go index ac0ff303ff6..be9d5ed24e0 100644 --- a/clientv3/balancer/balancer_test.go +++ b/clientv3/balancer/balancer_test.go @@ -30,7 +30,6 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" - "google.golang.org/grpc/resolver" "google.golang.org/grpc/status" ) @@ -58,14 +57,17 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) { } defer ms.Stop() - var resolvedAddrs []resolver.Address + var eps []string for _, svr := range ms.Servers { - resolvedAddrs = append(resolvedAddrs, svr.ResolverAddress()) + eps = append(eps, svr.ResolverAddress().Addr) } - rsv := endpoint.EndpointResolver("nofailover") + rsv, err := endpoint.NewResolverGroup("nofailover") + if err != nil { + t.Fatal(err) + } defer rsv.Close() - rsv.InitialAddrs(resolvedAddrs) + rsv.SetEndpoints(eps) name := genName() cfg := Config{ @@ -121,14 +123,17 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { t.Fatalf("failed to start mock servers: %s", err) } defer ms.Stop() - var resolvedAddrs []resolver.Address + var eps []string for _, svr := range ms.Servers { - resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + eps = append(eps, svr.ResolverAddress().Addr) } - rsv := endpoint.EndpointResolver("serverfail") + rsv, err := endpoint.NewResolverGroup("serverfail") + if err != nil { + t.Fatal(err) + } defer rsv.Close() - rsv.InitialAddrs(resolvedAddrs) + rsv.SetEndpoints(eps) name := genName() cfg := Config{ @@ -158,7 +163,7 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { ms.StopAt(0) available := make(map[string]struct{}) for i := 1; i < serverCount; i++ { - available[resolvedAddrs[i].Addr] = struct{}{} + available[eps[i]] = struct{}{} } reqN := 10 @@ -169,8 +174,8 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { continue } if prev == "" { // first failover - if resolvedAddrs[0].Addr == picked { - t.Fatalf("expected failover from %q, picked %q", resolvedAddrs[0].Addr, picked) + if eps[0] == picked { + t.Fatalf("expected failover from %q, picked %q", eps[0], picked) } prev = picked continue @@ -194,7 +199,7 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) { time.Sleep(time.Second) prev, switches = "", 0 - recoveredAddr, recovered := resolvedAddrs[0].Addr, 0 + recoveredAddr, recovered := eps[0], 0 available[recoveredAddr] = struct{}{} for i := 0; i < 2*reqN; i++ { @@ -234,15 +239,18 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) { t.Fatalf("failed to start mock servers: %s", err) } defer ms.Stop() - var resolvedAddrs []resolver.Address + var eps []string available := make(map[string]struct{}) for _, svr := range ms.Servers { - resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: svr.Address}) + eps = append(eps, svr.ResolverAddress().Addr) available[svr.Address] = struct{}{} } - rsv := endpoint.EndpointResolver("requestfail") + rsv, err := endpoint.NewResolverGroup("requestfail") + if err != nil { + t.Fatal(err) + } defer rsv.Close() - rsv.InitialAddrs(resolvedAddrs) + rsv.SetEndpoints(eps) name := genName() cfg := Config{ diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 120a0b9c57b..679f92e9a8d 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package endpoint resolves etcd entpoints using grpc targets of the form 'endpoint:///'. +// Package endpoint resolves etcd entpoints using grpc targets of the form 'endpoint:///'. package endpoint import ( @@ -36,91 +36,140 @@ var ( func init() { bldr = &builder{ - clientResolvers: make(map[string]*Resolver), + resolverGroups: make(map[string]*ResolverGroup), } resolver.Register(bldr) } type builder struct { - clientResolvers map[string]*Resolver + resolverGroups map[string]*ResolverGroup sync.RWMutex } +// NewResolverGroup creates a new ResolverGroup with the given id. +func NewResolverGroup(id string) (*ResolverGroup, error) { + return bldr.newResolverGroup(id) +} + +// ResolverGroup keeps all endpoints of resolvers using a common endpoint:/// target +// up-to-date. +type ResolverGroup struct { + id string + endpoints []string + resolvers []*Resolver + sync.RWMutex +} + +func (e *ResolverGroup) addResolver(r *Resolver) { + e.Lock() + addrs := epsToAddrs(e.endpoints...) + e.resolvers = append(e.resolvers, r) + e.Unlock() + r.cc.NewAddress(addrs) +} + +func (e *ResolverGroup) removeResolver(r *Resolver) { + e.Lock() + for i, er := range e.resolvers { + if er == r { + e.resolvers = append(e.resolvers[:i], e.resolvers[i+1:]...) + break + } + } + e.Unlock() +} + +// SetEndpoints updates the endpoints for ResolverGroup. All registered resolver are updated +// immediately with the new endpoints. +func (e *ResolverGroup) SetEndpoints(endpoints []string) { + addrs := epsToAddrs(endpoints...) + e.Lock() + e.endpoints = endpoints + for _, r := range e.resolvers { + r.cc.NewAddress(addrs) + } + e.Unlock() +} + +// Target constructs a endpoint target using the endpoint id of the ResolverGroup. +func (e *ResolverGroup) Target(endpoint string) string { + return Target(e.id, endpoint) +} + +// Target constructs a endpoint resolver target. +func Target(id, endpoint string) string { + return fmt.Sprintf("%s://%s/%s", scheme, id, endpoint) +} + +// IsTarget checks if a given target string in an endpoint resolver target. +func IsTarget(target string) bool { + return strings.HasPrefix(target, "endpoint://") +} + +func (e *ResolverGroup) Close() { + bldr.close(e.id) +} + // Build creates or reuses an etcd resolver for the etcd cluster name identified by the authority part of the target. func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) { if len(target.Authority) < 1 { return nil, fmt.Errorf("'etcd' target scheme requires non-empty authority identifying etcd cluster being routed to") } - r := b.getResolver(target.Authority) - r.cc = cc - if r.addrs != nil { - r.NewAddress(r.addrs) + id := target.Authority + es, err := b.getResolverGroup(id) + if err != nil { + return nil, fmt.Errorf("failed to build resolver: %v", err) + } + r := &Resolver{ + endpointId: id, + cc: cc, } + es.addResolver(r) return r, nil } -func (b *builder) getResolver(clientId string) *Resolver { +func (b *builder) newResolverGroup(id string) (*ResolverGroup, error) { b.RLock() - r, ok := b.clientResolvers[clientId] + es, ok := b.resolverGroups[id] b.RUnlock() if !ok { - r = &Resolver{ - clientId: clientId, - } + es = &ResolverGroup{id: id} b.Lock() - b.clientResolvers[clientId] = r + b.resolverGroups[id] = es b.Unlock() + } else { + return nil, fmt.Errorf("Endpoint already exists for id: %s", id) } - return r + return es, nil } -func (b *builder) addResolver(r *Resolver) { - bldr.Lock() - bldr.clientResolvers[r.clientId] = r - bldr.Unlock() +func (b *builder) getResolverGroup(id string) (*ResolverGroup, error) { + b.RLock() + es, ok := b.resolverGroups[id] + b.RUnlock() + if !ok { + return nil, fmt.Errorf("ResolverGroup not found for id: %s", id) + } + return es, nil } -func (b *builder) removeResolver(r *Resolver) { - bldr.Lock() - delete(bldr.clientResolvers, r.clientId) - bldr.Unlock() +func (b *builder) close(id string) { + b.Lock() + delete(b.resolverGroups, id) + b.Unlock() } func (r *builder) Scheme() string { return scheme } -// EndpointResolver gets the resolver for given etcd cluster name. -func EndpointResolver(clientId string) *Resolver { - return bldr.getResolver(clientId) -} - // Resolver provides a resolver for a single etcd cluster, identified by name. type Resolver struct { - clientId string - cc resolver.ClientConn - addrs []resolver.Address + endpointId string + cc resolver.ClientConn sync.RWMutex } -// InitialAddrs sets the initial endpoint addresses for the resolver. -func (r *Resolver) InitialAddrs(addrs []resolver.Address) { - r.Lock() - r.addrs = addrs - r.Unlock() -} - -// InitialEndpoints sets the initial endpoints to for the resolver. -// This should be called before dialing. The endpoints may be updated after the dial using NewAddress. -// At least one endpoint is required. -func (r *Resolver) InitialEndpoints(eps []string) error { - if len(eps) < 1 { - return fmt.Errorf("At least one endpoint is required, but got: %v", eps) - } - r.InitialAddrs(epsToAddrs(eps...)) - return nil -} - // TODO: use balancer.epsToAddrs func epsToAddrs(eps ...string) (addrs []resolver.Address) { addrs = make([]resolver.Address, 0, len(eps)) @@ -130,35 +179,14 @@ func epsToAddrs(eps ...string) (addrs []resolver.Address) { return addrs } -// NewAddress updates the addresses of the resolver. -func (r *Resolver) NewAddress(addrs []resolver.Address) { - r.Lock() - r.addrs = addrs - r.Unlock() - if r.cc != nil { - r.cc.NewAddress(addrs) - } -} - func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} func (r *Resolver) Close() { - bldr.removeResolver(r) -} - -// Target constructs a endpoint target with current resolver's clientId. -func (r *Resolver) Target(endpoint string) string { - return Target(r.clientId, endpoint) -} - -// Target constructs a endpoint resolver target. -func Target(clientId, endpoint string) string { - return fmt.Sprintf("%s://%s/%s", scheme, clientId, endpoint) -} - -// IsTarget checks if a given target string in an endpoint resolver target. -func IsTarget(target string) bool { - return strings.HasPrefix(target, "endpoint://") + es, err := bldr.getResolverGroup(r.endpointId) + if err != nil { + return + } + es.removeResolver(r) } // Parse endpoint parses a endpoint of the form (http|https)://*|(unix|unixs)://) and returns a @@ -185,7 +213,7 @@ func ParseEndpoint(endpoint string) (proto string, host string, scheme string) { return proto, host, scheme } -// ParseTarget parses a endpoint:/// string and returns the parsed clientId and endpoint. +// ParseTarget parses a endpoint:/// string and returns the parsed id and endpoint. // If the target is malformed, an error is returned. func ParseTarget(target string) (string, string, error) { noPrefix := strings.TrimPrefix(target, targetPrefix) @@ -194,7 +222,7 @@ func ParseTarget(target string) (string, string, error) { } parts := strings.SplitN(noPrefix, "/", 2) if len(parts) != 2 { - return "", "", fmt.Errorf("malformed target, expected %s:///, but got %s", scheme, target) + return "", "", fmt.Errorf("malformed target, expected %s:///, but got %s", scheme, target) } return parts[0], parts[1], nil } diff --git a/clientv3/client.go b/clientv3/client.go index b2870f4ee74..d861ef27b7a 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -37,7 +37,6 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/resolver" "google.golang.org/grpc/status" ) @@ -68,11 +67,11 @@ type Client struct { conn *grpc.ClientConn dialerrc chan error - cfg Config - creds *credentials.TransportCredentials - balancer balancer.Balancer - resolver *endpoint.Resolver - mu *sync.Mutex + cfg Config + creds *credentials.TransportCredentials + balancer balancer.Balancer + resolverGroup *endpoint.ResolverGroup + mu *sync.Mutex ctx context.Context cancel context.CancelFunc @@ -119,12 +118,12 @@ func (c *Client) Close() error { c.cancel() c.Watcher.Close() c.Lease.Close() + if c.resolverGroup != nil { + c.resolverGroup.Close() + } if c.conn != nil { return toErr(c.ctx, c.conn.Close()) } - if c.resolver != nil { - c.resolver.Close() - } return c.ctx.Err() } @@ -143,22 +142,10 @@ func (c *Client) Endpoints() (eps []string) { // SetEndpoints updates client's endpoints. func (c *Client) SetEndpoints(eps ...string) { - var addrs []resolver.Address - for _, ep := range eps { - addrs = append(addrs, resolver.Address{Addr: ep}) - } - c.mu.Lock() defer c.mu.Unlock() c.cfg.Endpoints = eps - c.resolver.NewAddress(addrs) - // TODO: Does the new grpc balancer provide a way to block until the endpoint changes are propagated? - /*if c.balancer.NeedUpdate() { - select { - case c.balancer.UpdateAddrsC() <- balancer.NotifyNext: - case <-c.balancer.StopC(): - } - }*/ + c.resolverGroup.SetEndpoints(eps) } // Sync synchronizes client's endpoints with the known endpoints from the etcd membership. @@ -301,12 +288,13 @@ func (c *Client) getToken(ctx context.Context) error { // use dial options without dopts to avoid reusing the client balancer var dOpts []grpc.DialOption _, host, _ := endpoint.ParseEndpoint(ep) - target := c.resolver.Target(host) + target := c.resolverGroup.Target(host) dOpts, err = c.dialSetupOpts(target, c.cfg.DialOptions...) if err != nil { err = fmt.Errorf("failed to configure auth dialer: %v", err) continue } + dOpts = append(dOpts, grpc.WithBalancerName(roundRobinBalancerName)) auth, err = newAuthenticator(ctx, target, dOpts, c) if err != nil { continue @@ -333,7 +321,7 @@ func (c *Client) dial(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, er // We pass a target to DialContext of the form: endpoint:/// that // does not include scheme (http/https/unix/unixs) or path parts. _, host, _ := endpoint.ParseEndpoint(ep) - target := c.resolver.Target(host) + target := c.resolverGroup.Target(host) opts, err := c.dialSetupOpts(target, dopts...) if err != nil { @@ -439,13 +427,13 @@ func newClient(cfg *Config) (*Client, error) { // Prepare a 'endpoint:///' resolver for the client and create a endpoint target to pass // to dial so the client knows to use this resolver. - client.resolver = endpoint.EndpointResolver(fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36))) - err := client.resolver.InitialEndpoints(cfg.Endpoints) + var err error + client.resolverGroup, err = endpoint.NewResolverGroup(fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36))) if err != nil { client.cancel() - client.resolver.Close() return nil, err } + client.resolverGroup.SetEndpoints(cfg.Endpoints) if len(cfg.Endpoints) < 1 { return nil, fmt.Errorf("at least one Endpoint must is required in client config") @@ -457,7 +445,7 @@ func newClient(cfg *Config) (*Client, error) { conn, err := client.dial(dialEndpoint, grpc.WithBalancerName(roundRobinBalancerName)) if err != nil { client.cancel() - client.resolver.Close() + client.resolverGroup.Close() return nil, err } // TODO: With the old grpc balancer interface, we waited until the dial timeout diff --git a/clientv3/integration/server_shutdown_test.go b/clientv3/integration/server_shutdown_test.go index fdca6e3e69e..1d20f25d183 100644 --- a/clientv3/integration/server_shutdown_test.go +++ b/clientv3/integration/server_shutdown_test.go @@ -403,3 +403,18 @@ func isServerUnavailable(err error) bool { code := ev.Code() return code == codes.Unavailable } + +func isCanceled(err error) bool { + if err == nil { + return false + } + if err == context.Canceled { + return true + } + ev, ok := status.FromError(err) + if !ok { + return false + } + code := ev.Code() + return code == codes.Canceled +} diff --git a/clientv3/integration/watch_test.go b/clientv3/integration/watch_test.go index f4879149f02..efe4387abdd 100644 --- a/clientv3/integration/watch_test.go +++ b/clientv3/integration/watch_test.go @@ -30,7 +30,6 @@ import ( mvccpb "github.com/coreos/etcd/mvcc/mvccpb" "github.com/coreos/etcd/pkg/testutil" - "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -667,8 +666,9 @@ func TestWatchErrConnClosed(t *testing.T) { go func() { defer close(donec) ch := cli.Watch(context.TODO(), "foo") - if wr := <-ch; wr.Err() != grpc.ErrClientConnClosing { - t.Fatalf("expected %v, got %v", grpc.ErrClientConnClosing, wr.Err()) + + if wr := <-ch; !isCanceled(wr.Err()) { + t.Fatalf("expected context canceled, got %v", wr.Err()) } }() @@ -699,8 +699,8 @@ func TestWatchAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { cli.Watch(context.TODO(), "foo") - if err := cli.Close(); err != nil && err != grpc.ErrClientConnClosing { - t.Fatalf("expected %v, got %v", grpc.ErrClientConnClosing, err) + if err := cli.Close(); err != nil && err != context.Canceled { + t.Fatalf("expected %v, got %v", context.Canceled, err) } close(donec) }() From 1f6548b751d9d17dcd8053c714436205118404f3 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Wed, 2 May 2018 16:45:38 -0700 Subject: [PATCH 23/39] clientv3: Stop expecting retry in integration tests with new grpc balancer --- clientv3/integration/black_hole_test.go | 17 ++++++------ clientv3/integration/dial_test.go | 4 +++ clientv3/integration/kv_test.go | 23 ++++++++++------ clientv3/integration/lease_test.go | 12 ++++----- clientv3/integration/leasing_test.go | 17 +++++++++--- clientv3/integration/maintenance_test.go | 4 ++- .../integration/network_partition_test.go | 26 ++++++++++++------- clientv3/integration/server_shutdown_test.go | 12 ++++++++- clientv3/integration/txn_test.go | 5 +++- clientv3/options.go | 11 ++++---- integration/cluster.go | 16 ++++++++++++ integration/v3_grpc_test.go | 17 ++++++++++-- 12 files changed, 118 insertions(+), 46 deletions(-) diff --git a/clientv3/integration/black_hole_test.go b/clientv3/integration/black_hole_test.go index 8a907680cad..151187e14b9 100644 --- a/clientv3/integration/black_hole_test.go +++ b/clientv3/integration/black_hole_test.go @@ -192,22 +192,23 @@ func testBalancerUnderBlackholeNoKeepAlive(t *testing.T, op func(*clientv3.Clien // blackhole eps[0] clus.Members[0].Blackhole() - // fail first due to blackhole, retry should succeed + // With round robin balancer, client will make a request to a healthy endpoint + // within a few requests. // TODO: first operation can succeed // when gRPC supports better retry on non-delivered request - for i := 0; i < 2; i++ { + for i := 0; i < 5; i++ { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) err = op(cli, ctx) cancel() if err == nil { break - } - if i == 0 { - if err != errExpected { - t.Errorf("#%d: expected %v, got %v", i, errExpected, err) - } - } else if err != nil { + } else if err == errExpected { + t.Logf("#%d: current error %v", i, err) + } else { t.Errorf("#%d: failed with error %v", i, err) } } + if err != nil { + t.Fatal(err) + } } diff --git a/clientv3/integration/dial_test.go b/clientv3/integration/dial_test.go index 1c2e96bb86f..587bcd56577 100644 --- a/clientv3/integration/dial_test.go +++ b/clientv3/integration/dial_test.go @@ -156,6 +156,10 @@ func TestSwitchSetEndpoints(t *testing.T) { clus.Members[0].InjectPartition(t, clus.Members[1:]...) cli.SetEndpoints(eps...) + + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, cli) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if _, err := cli.Get(ctx, "foo"); err != nil { diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index 6451d2901c4..994effa6fd6 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -438,12 +438,15 @@ func TestKVGetErrConnClosed(t *testing.T) { cli := clus.Client(0) + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, cli) + donec := make(chan struct{}) go func() { defer close(donec) _, err := cli.Get(context.TODO(), "foo") - if err != nil && err != context.Canceled && err != grpc.ErrClientConnClosing { - t.Fatalf("expected %v or %v, got %v", context.Canceled, grpc.ErrClientConnClosing, err) + if err != nil && err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { + t.Fatalf("expected %v, %v or server unavailable, got %v", context.Canceled, grpc.ErrClientConnClosing, err) } }() @@ -686,6 +689,8 @@ func TestKVGetRetry(t *testing.T) { donec := make(chan struct{}) go func() { + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, kv) // Get will fail, but reconnect will trigger gresp, gerr := kv.Get(ctx, "foo") if gerr != nil { @@ -711,7 +716,7 @@ func TestKVGetRetry(t *testing.T) { clus.Members[fIdx].WaitOK(t) select { - case <-time.After(5 * time.Second): + case <-time.After(20 * time.Second): t.Fatalf("timed out waiting for get") case <-donec: } @@ -736,6 +741,8 @@ func TestKVPutFailGetRetry(t *testing.T) { donec := make(chan struct{}) go func() { + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, kv) // Get will fail, but reconnect will trigger gresp, gerr := kv.Get(context.TODO(), "foo") if gerr != nil { @@ -751,7 +758,7 @@ func TestKVPutFailGetRetry(t *testing.T) { clus.Members[0].Restart(t) select { - case <-time.After(5 * time.Second): + case <-time.After(20 * time.Second): t.Fatalf("timed out waiting for get") case <-donec: } @@ -793,7 +800,7 @@ func TestKVGetStoppedServerAndClose(t *testing.T) { // this Get fails and triggers an asynchronous connection retry _, err := cli.Get(ctx, "abc") cancel() - if err != nil && !isServerUnavailable(err) { + if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { t.Fatal(err) } } @@ -815,15 +822,15 @@ func TestKVPutStoppedServerAndClose(t *testing.T) { // grpc finds out the original connection is down due to the member shutdown. _, err := cli.Get(ctx, "abc") cancel() - if err != nil && !isServerUnavailable(err) { + if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { t.Fatal(err) } - ctx, cancel = context.WithTimeout(context.TODO(), time.Second) // TODO: How was this test not consistently failing with context canceled errors? + ctx, cancel = context.WithTimeout(context.TODO(), time.Second) // this Put fails and triggers an asynchronous connection retry _, err = cli.Put(ctx, "abc", "123") cancel() - if err != nil && !isServerUnavailable(err) { + if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { t.Fatal(err) } } diff --git a/clientv3/integration/lease_test.go b/clientv3/integration/lease_test.go index ee7611b1e40..4329f7ac91c 100644 --- a/clientv3/integration/lease_test.go +++ b/clientv3/integration/lease_test.go @@ -292,10 +292,10 @@ func TestLeaseGrantErrConnClosed(t *testing.T) { go func() { defer close(donec) _, err := cli.Grant(context.TODO(), 5) - if err != nil && err != grpc.ErrClientConnClosing && err != context.Canceled { + if err != nil && err != grpc.ErrClientConnClosing && err != context.Canceled && !isServerUnavailable(err) { // grpc.ErrClientConnClosing if grpc-go balancer calls 'Get' after client.Close. // context.Canceled if grpc-go balancer calls 'Get' with an inflight client.Close. - t.Fatalf("expected %v or %v, got %v", grpc.ErrClientConnClosing, context.Canceled, err) + t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } }() @@ -324,8 +324,8 @@ func TestLeaseGrantNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Grant(context.TODO(), 5); err != context.Canceled && err != grpc.ErrClientConnClosing { - t.Fatalf("expected %v or %v, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) + if _, err := cli.Grant(context.TODO(), 5); err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { + t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) }() @@ -356,8 +356,8 @@ func TestLeaseRevokeNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Revoke(context.TODO(), leaseID); err != context.Canceled && err != grpc.ErrClientConnClosing { - t.Fatalf("expected %v or %v, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) + if _, err := cli.Revoke(context.TODO(), leaseID); err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { + t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) }() diff --git a/clientv3/integration/leasing_test.go b/clientv3/integration/leasing_test.go index 2383c197398..ff82ae4b3e9 100644 --- a/clientv3/integration/leasing_test.go +++ b/clientv3/integration/leasing_test.go @@ -869,6 +869,9 @@ func TestLeasingTxnCancel(t *testing.T) { } clus.Members[0].Stop(t) + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, clus.Client(1)) + // wait for leader election, if any if _, err = clus.Client(1).Get(context.TODO(), "abc"); err != nil { t.Fatal(err) @@ -1533,6 +1536,9 @@ func TestLeasingReconnectOwnerConsistency(t *testing.T) { } } + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, lkv) + lresp, lerr := lkv.Get(context.TODO(), "k") if lerr != nil { t.Fatal(lerr) @@ -1814,6 +1820,9 @@ func TestLeasingTxnOwnerPutBranch(t *testing.T) { // lkv shouldn't need to call out to server for updated leased keys clus.Members[0].Stop(t) + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, clus.Client(1)) + for i := 0; i < n; i++ { k := fmt.Sprintf("tree/%d", i) lkvResp, err := lkv.Get(context.TODO(), k) @@ -1905,7 +1914,7 @@ func TestLeasingSessionExpire(t *testing.T) { } waitForExpireAck(t, lkv) clus.Members[0].Restart(t) - + integration.WaitClientV3(t, lkv2) if _, err = lkv2.Put(context.TODO(), "abc", "def"); err != nil { t.Fatal(err) } @@ -1985,8 +1994,8 @@ func TestLeasingSessionExpireCancel(t *testing.T) { select { case err := <-errc: - if err != ctx.Err() { - t.Errorf("#%d: expected %v, got %v", i, ctx.Err(), err) + if !(err == ctx.Err() || isServerUnavailable(err)) { + t.Errorf("#%d: expected %v of server unavailable, got %v", i, ctx.Err(), err) } case <-time.After(5 * time.Second): t.Errorf("#%d: timed out waiting for cancel", i) @@ -2016,7 +2025,7 @@ func waitForExpireAck(t *testing.T, kv clientv3.KV) { ctx, cancel := context.WithTimeout(context.TODO(), time.Second) _, err := kv.Get(ctx, "abc") cancel() - if err == ctx.Err() { + if err == ctx.Err() || isServerUnavailable(err) { return } else if err != nil { t.Logf("current error: %v", err) diff --git a/clientv3/integration/maintenance_test.go b/clientv3/integration/maintenance_test.go index 72306c0cd49..0609c1c812b 100644 --- a/clientv3/integration/maintenance_test.go +++ b/clientv3/integration/maintenance_test.go @@ -156,9 +156,11 @@ func TestMaintenanceSnapshotErrorInflight(t *testing.T) { b.Close() clus.Members[0].Restart(t) + cli := clus.RandClient() + integration.WaitClientV3(t, cli) // reading snapshot with canceled context should error out ctx, cancel := context.WithCancel(context.Background()) - rc1, err := clus.RandClient().Snapshot(ctx) + rc1, err := cli.Snapshot(ctx) if err != nil { t.Fatal(err) } diff --git a/clientv3/integration/network_partition_test.go b/clientv3/integration/network_partition_test.go index 9588c4fb82f..41310f75400 100644 --- a/clientv3/integration/network_partition_test.go +++ b/clientv3/integration/network_partition_test.go @@ -73,6 +73,9 @@ func TestBalancerUnderNetworkPartitionTxn(t *testing.T) { func TestBalancerUnderNetworkPartitionLinearizableGetWithLongTimeout(t *testing.T) { testBalancerUnderNetworkPartition(t, func(cli *clientv3.Client, ctx context.Context) error { _, err := cli.Get(ctx, "a") + if err == rpctypes.ErrTimeout { + return errExpected + } return err }, 7*time.Second) } @@ -128,7 +131,7 @@ func testBalancerUnderNetworkPartition(t *testing.T, op func(*clientv3.Client, c time.Sleep(time.Second * 2) clus.Members[0].InjectPartition(t, clus.Members[1:]...) - for i := 0; i < 2; i++ { + for i := 0; i < 5; i++ { ctx, cancel := context.WithTimeout(context.Background(), timeout) err = op(cli, ctx) cancel() @@ -168,7 +171,7 @@ func TestBalancerUnderNetworkPartitionLinearizableGetLeaderElection(t *testing.T cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{eps[(lead+1)%2]}, - DialTimeout: 1 * time.Second, + DialTimeout: 2 * time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) if err != nil { @@ -176,9 +179,6 @@ func TestBalancerUnderNetworkPartitionLinearizableGetLeaderElection(t *testing.T } defer cli.Close() - // wait for non-leader to be pinned - mustWaitPinReady(t, cli) - // add all eps to list, so that when the original pined one fails // the client can switch to other available eps cli.SetEndpoints(eps[lead], eps[(lead+1)%2]) @@ -186,10 +186,18 @@ func TestBalancerUnderNetworkPartitionLinearizableGetLeaderElection(t *testing.T // isolate leader clus.Members[lead].InjectPartition(t, clus.Members[(lead+1)%3], clus.Members[(lead+2)%3]) - // expects balancer endpoint switch while ongoing leader election - ctx, cancel := context.WithTimeout(context.TODO(), timeout) - _, err = cli.Get(ctx, "a") - cancel() + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, cli) + + // expects balancer to round robin to leader within two attempts + for i := 0; i < 2; i++ { + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + _, err = cli.Get(ctx, "a") + cancel() + if err == nil { + break + } + } if err != nil { t.Fatal(err) } diff --git a/clientv3/integration/server_shutdown_test.go b/clientv3/integration/server_shutdown_test.go index 1d20f25d183..d1e5507cc5c 100644 --- a/clientv3/integration/server_shutdown_test.go +++ b/clientv3/integration/server_shutdown_test.go @@ -339,7 +339,17 @@ func testBalancerUnderServerStopInflightRangeOnRestart(t *testing.T, linearizabl defer close(donec) ctx, cancel := context.WithTimeout(context.TODO(), clientTimeout) readyc <- struct{}{} - _, err := cli.Get(ctx, "abc", gops...) + + // TODO: The new grpc load balancer will not pin to an endpoint + // as intended by this test. But it will round robin member within + // two attempts. + // Remove retry loop once the new grpc load balancer provides retry. + for i := 0; i < 2; i++ { + _, err = cli.Get(ctx, "abc", gops...) + if err == nil { + break + } + } cancel() if err != nil { if linearizable && isServerUnavailable(err) { diff --git a/clientv3/integration/txn_test.go b/clientv3/integration/txn_test.go index 14f8b81a7ba..672bf79032a 100644 --- a/clientv3/integration/txn_test.go +++ b/clientv3/integration/txn_test.go @@ -79,6 +79,9 @@ func TestTxnWriteFail(t *testing.T) { t.Fatalf("timed out waiting for txn fail") case <-txnc: } + // TODO: Remove wait once the new grpc load balancer provides retry. + integration.WaitClientV3(t, kv) + // and ensure the put didn't take gresp, gerr := clus.Client(1).Get(context.TODO(), "foo") if gerr != nil { @@ -90,7 +93,7 @@ func TestTxnWriteFail(t *testing.T) { }() select { - case <-time.After(2 * clus.Members[1].ServerConfig.ReqTimeout()): + case <-time.After(5 * clus.Members[1].ServerConfig.ReqTimeout()): t.Fatalf("timed out waiting for get") case <-getc: } diff --git a/clientv3/options.go b/clientv3/options.go index fa25811f3b0..a98b19f9b4b 100644 --- a/clientv3/options.go +++ b/clientv3/options.go @@ -21,12 +21,11 @@ import ( ) var ( - // Disable gRPC internal retrial logic - // TODO: enable when gRPC retry is stable (FailFast=false) - // Reference: - // - https://github.com/grpc/grpc-go/issues/1532 - // - https://github.com/grpc/proposal/blob/master/A6-client-retries.md - defaultFailFast = grpc.FailFast(true) + // client-side handling retrying of request failures where data was not written to the wire or + // where server indicates it did not process the data. gPRC default is default is "FailFast(true)" + // but for etcd we default to "FailFast(false)" to minimize client request error responses due to + // transident failures. + defaultFailFast = grpc.FailFast(false) // client-side request send limit, gRPC default is math.MaxInt32 // Make sure that "client-side send limit < server-side default send/recv limit" diff --git a/integration/cluster.go b/integration/cluster.go index e6a77767976..ead7b570ad2 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -974,7 +974,23 @@ func (m *member) WaitStarted(t *testing.T) { cancel() break } +} +func WaitClientV3(t *testing.T, kv clientv3.KV) { + timeout := time.Now().Add(requestTimeout) + var err error + for time.Now().Before(timeout) { + ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) + _, err := kv.Get(ctx, "/") + cancel() + if err == nil { + return + } + time.Sleep(tickDuration) + } + if err != nil { + t.Fatalf("timed out waiting for client: %v", err) + } } func (m *member) URL() string { return m.ClientURLs[0].String() } diff --git a/integration/v3_grpc_test.go b/integration/v3_grpc_test.go index b573881bb41..ab96b76dd86 100644 --- a/integration/v3_grpc_test.go +++ b/integration/v3_grpc_test.go @@ -32,7 +32,9 @@ import ( "github.com/coreos/etcd/pkg/transport" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) // TestV3PutOverwrite puts a key with the v3 api to a random cluster member, @@ -1934,7 +1936,18 @@ func eqErrGRPC(err1 error, err2 error) bool { // FailFast=false works with Put. func waitForRestart(t *testing.T, kvc pb.KVClient) { req := &pb.RangeRequest{Key: []byte("_"), Serializable: true} - if _, err := kvc.Range(context.TODO(), req, grpc.FailFast(false)); err != nil { - t.Fatal(err) + // TODO: Remove retry loop once the new grpc load balancer provides retry. + var err error + for i := 0; i < 10; i++ { + if _, err = kvc.Range(context.TODO(), req, grpc.FailFast(false)); err != nil { + if status, ok := status.FromError(err); ok && status.Code() == codes.Unavailable { + time.Sleep(time.Millisecond * 250) + } else { + t.Fatal(err) + } + } + } + if err != nil { + t.Fatal("timed out waiting for restart: %v", err) } } From 4065735845428e9d20e856d8d2d4fcc9598f5289 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 7 May 2018 14:22:54 -0700 Subject: [PATCH 24/39] clientv3: remove unused "dialerrc" Signed-off-by: Gyuho Lee --- clientv3/client.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index d861ef27b7a..2dd37abbf5e 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -64,8 +64,7 @@ type Client struct { Auth Maintenance - conn *grpc.ClientConn - dialerrc chan error + conn *grpc.ClientConn cfg Config creds *credentials.TransportCredentials @@ -250,14 +249,7 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] default: } dialer := &net.Dialer{Timeout: t} - conn, err := dialer.DialContext(c.ctx, proto, host) - if err != nil { - select { - case c.dialerrc <- err: - default: - } - } - return conn, err + return dialer.DialContext(c.ctx, proto, host) } opts = append(opts, grpc.WithDialer(f)) @@ -395,7 +387,6 @@ func newClient(cfg *Config) (*Client, error) { ctx, cancel := context.WithCancel(baseCtx) client := &Client{ conn: nil, - dialerrc: make(chan error, 1), cfg: *cfg, creds: creds, ctx: ctx, From a5b2fb55634fa6e51699675a35ba59d5f621b91b Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 11 May 2018 12:53:20 -0700 Subject: [PATCH 25/39] clientv3: Introduce custom retry interceptor based on go-grpc-middleware/retry --- clientv3/client.go | 47 ++- clientv3/integration/dial_test.go | 7 +- clientv3/integration/kv_test.go | 17 +- clientv3/integration/lease_test.go | 10 +- clientv3/integration/leasing_test.go | 13 +- clientv3/integration/maintenance_test.go | 1 - .../integration/network_partition_test.go | 3 - clientv3/integration/server_shutdown_test.go | 18 +- clientv3/integration/txn_test.go | 3 - clientv3/lease.go | 2 +- clientv3/maintenance.go | 2 +- clientv3/options.go | 17 + clientv3/retry.go | 98 +++-- clientv3/retry_interceptor.go | 355 ++++++++++++++++++ integration/v3_alarm_test.go | 7 +- 15 files changed, 487 insertions(+), 113 deletions(-) create mode 100644 clientv3/retry_interceptor.go diff --git a/clientv3/client.go b/clientv3/client.go index 2dd37abbf5e..672926bebee 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -30,6 +30,7 @@ import ( "github.com/coreos/etcd/clientv3/balancer/picker" "github.com/coreos/etcd/clientv3/balancer/resolver/endpoint" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" + "github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils" "go.uber.org/zap" "google.golang.org/grpc" @@ -45,13 +46,15 @@ var ( ErrOldCluster = errors.New("etcdclient: old cluster version") roundRobinBalancerName = fmt.Sprintf("etcd-%s", picker.RoundrobinBalanced.String()) + logger *zap.Logger ) func init() { + logger = zap.NewNop() // zap.NewExample() balancer.RegisterBuilder(balancer.Config{ Policy: picker.RoundrobinBalanced, Name: roundRobinBalancerName, - Logger: zap.NewNop(), // zap.NewExample(), + Logger: logger, }) } @@ -263,6 +266,18 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] opts = append(opts, grpc.WithInsecure()) } + // Interceptor retry and backoff. + // TODO: Replace all of clientv3/retry.go with interceptor based retry, or with + // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#retry-policy + // once it is available. + rrBackoff := withBackoff(c.roundRobinQuorumBackoff(defaultBackoffWaitBetween, defaultBackoffJitterFraction)) + opts = append(opts, + // Disable stream retry by default since go-grpc-middleware/retry does not support client streams. + // Streams that are safe to retry are enabled individually. + grpc.WithStreamInterceptor(c.streamClientInterceptor(logger, withMax(0), rrBackoff)), + grpc.WithUnaryInterceptor(c.unaryClientInterceptor(logger, withMax(defaultUnaryMaxRetries), rrBackoff)), + ) + return opts, nil } @@ -386,14 +401,14 @@ func newClient(cfg *Config) (*Client, error) { ctx, cancel := context.WithCancel(baseCtx) client := &Client{ - conn: nil, - cfg: *cfg, - creds: creds, - ctx: ctx, - cancel: cancel, - mu: new(sync.Mutex), - callOpts: defaultCallOpts, + conn: nil, + cfg: *cfg, + creds: creds, + ctx: ctx, + cancel: cancel, + mu: new(sync.Mutex), } + if cfg.Username != "" && cfg.Password != "" { client.Username = cfg.Username client.Password = cfg.Password @@ -461,6 +476,22 @@ func newClient(cfg *Config) (*Client, error) { return client, nil } +// roundRobinQuorumBackoff retries against quorum between each backoff. +// This is intended for use with a round robin load balancer. +func (c *Client) roundRobinQuorumBackoff(waitBetween time.Duration, jitterFraction float64) backoffFunc { + return func(attempt uint) time.Duration { + // after each round robin across quorum, backoff for our wait between duration + n := uint(len(c.Endpoints())) + quorum := (n/2 + 1) + if attempt%quorum == 0 { + logger.Info("backoff", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum), zap.Duration("waitBetween", waitBetween), zap.Float64("jitterFraction", jitterFraction)) + return backoffutils.JitterUp(waitBetween, jitterFraction) + } + logger.Info("backoff skipped", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum)) + return 0 + } +} + func (c *Client) checkVersion() (err error) { var wg sync.WaitGroup errc := make(chan error, len(c.cfg.Endpoints)) diff --git a/clientv3/integration/dial_test.go b/clientv3/integration/dial_test.go index 587bcd56577..574f224492f 100644 --- a/clientv3/integration/dial_test.go +++ b/clientv3/integration/dial_test.go @@ -83,7 +83,9 @@ func TestDialTLSNoConfig(t *testing.T) { // TODO: this should not be required when we set grpc.WithBlock() if c != nil { - _, err = c.KV.Get(context.Background(), "/") + ctx, cancel := context.WithTimeout(context.Background(), integration.RequestWaitTimeout) + _, err = c.KV.Get(ctx, "/") + cancel() } if !isClientTimeout(err) { t.Fatalf("expected dial timeout error, got %v", err) @@ -157,9 +159,6 @@ func TestSwitchSetEndpoints(t *testing.T) { cli.SetEndpoints(eps...) - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, cli) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if _, err := cli.Get(ctx, "foo"); err != nil { diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index 994effa6fd6..394a8242b9d 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -438,15 +438,12 @@ func TestKVGetErrConnClosed(t *testing.T) { cli := clus.Client(0) - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, cli) - donec := make(chan struct{}) go func() { defer close(donec) _, err := cli.Get(context.TODO(), "foo") - if err != nil && err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { - t.Fatalf("expected %v, %v or server unavailable, got %v", context.Canceled, grpc.ErrClientConnClosing, err) + if err != nil && err != context.Canceled && err != grpc.ErrClientConnClosing { + t.Fatalf("expected %v or %v, got %v", context.Canceled, grpc.ErrClientConnClosing, err) } }() @@ -689,8 +686,6 @@ func TestKVGetRetry(t *testing.T) { donec := make(chan struct{}) go func() { - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, kv) // Get will fail, but reconnect will trigger gresp, gerr := kv.Get(ctx, "foo") if gerr != nil { @@ -741,8 +736,6 @@ func TestKVPutFailGetRetry(t *testing.T) { donec := make(chan struct{}) go func() { - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, kv) // Get will fail, but reconnect will trigger gresp, gerr := kv.Get(context.TODO(), "foo") if gerr != nil { @@ -800,7 +793,7 @@ func TestKVGetStoppedServerAndClose(t *testing.T) { // this Get fails and triggers an asynchronous connection retry _, err := cli.Get(ctx, "abc") cancel() - if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { + if err != nil && !(isCanceled(err) || isClientTimeout(err)) { t.Fatal(err) } } @@ -822,7 +815,7 @@ func TestKVPutStoppedServerAndClose(t *testing.T) { // grpc finds out the original connection is down due to the member shutdown. _, err := cli.Get(ctx, "abc") cancel() - if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { + if err != nil && !(isCanceled(err) || isClientTimeout(err)) { t.Fatal(err) } @@ -830,7 +823,7 @@ func TestKVPutStoppedServerAndClose(t *testing.T) { // this Put fails and triggers an asynchronous connection retry _, err = cli.Put(ctx, "abc", "123") cancel() - if err != nil && !(isServerUnavailable(err) || isCanceled(err) || isClientTimeout(err)) { + if err != nil && !(isCanceled(err) || isClientTimeout(err) || isUnavailable(err)) { t.Fatal(err) } } diff --git a/clientv3/integration/lease_test.go b/clientv3/integration/lease_test.go index 4329f7ac91c..75a0987c52e 100644 --- a/clientv3/integration/lease_test.go +++ b/clientv3/integration/lease_test.go @@ -145,6 +145,10 @@ func TestLeaseKeepAlive(t *testing.T) { t.Errorf("chan is closed, want not closed") } + if kresp == nil { + t.Fatalf("unexpected null response") + } + if kresp.ID != resp.ID { t.Errorf("ID = %x, want %x", kresp.ID, resp.ID) } @@ -292,7 +296,7 @@ func TestLeaseGrantErrConnClosed(t *testing.T) { go func() { defer close(donec) _, err := cli.Grant(context.TODO(), 5) - if err != nil && err != grpc.ErrClientConnClosing && err != context.Canceled && !isServerUnavailable(err) { + if err != nil && err != grpc.ErrClientConnClosing && err != context.Canceled { // grpc.ErrClientConnClosing if grpc-go balancer calls 'Get' after client.Close. // context.Canceled if grpc-go balancer calls 'Get' with an inflight client.Close. t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) @@ -324,7 +328,7 @@ func TestLeaseGrantNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Grant(context.TODO(), 5); err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { + if _, err := cli.Grant(context.TODO(), 5); err != context.Canceled && err != grpc.ErrClientConnClosing { t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) @@ -356,7 +360,7 @@ func TestLeaseRevokeNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Revoke(context.TODO(), leaseID); err != context.Canceled && err != grpc.ErrClientConnClosing && !isServerUnavailable(err) { + if _, err := cli.Revoke(context.TODO(), leaseID); err != context.Canceled && err != grpc.ErrClientConnClosing { t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) diff --git a/clientv3/integration/leasing_test.go b/clientv3/integration/leasing_test.go index ff82ae4b3e9..7c3dcc3f179 100644 --- a/clientv3/integration/leasing_test.go +++ b/clientv3/integration/leasing_test.go @@ -869,9 +869,6 @@ func TestLeasingTxnCancel(t *testing.T) { } clus.Members[0].Stop(t) - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, clus.Client(1)) - // wait for leader election, if any if _, err = clus.Client(1).Get(context.TODO(), "abc"); err != nil { t.Fatal(err) @@ -1536,9 +1533,6 @@ func TestLeasingReconnectOwnerConsistency(t *testing.T) { } } - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, lkv) - lresp, lerr := lkv.Get(context.TODO(), "k") if lerr != nil { t.Fatal(lerr) @@ -1820,9 +1814,6 @@ func TestLeasingTxnOwnerPutBranch(t *testing.T) { // lkv shouldn't need to call out to server for updated leased keys clus.Members[0].Stop(t) - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, clus.Client(1)) - for i := 0; i < n; i++ { k := fmt.Sprintf("tree/%d", i) lkvResp, err := lkv.Get(context.TODO(), k) @@ -1994,7 +1985,7 @@ func TestLeasingSessionExpireCancel(t *testing.T) { select { case err := <-errc: - if !(err == ctx.Err() || isServerUnavailable(err)) { + if err != ctx.Err() { t.Errorf("#%d: expected %v of server unavailable, got %v", i, ctx.Err(), err) } case <-time.After(5 * time.Second): @@ -2025,7 +2016,7 @@ func waitForExpireAck(t *testing.T, kv clientv3.KV) { ctx, cancel := context.WithTimeout(context.TODO(), time.Second) _, err := kv.Get(ctx, "abc") cancel() - if err == ctx.Err() || isServerUnavailable(err) { + if err == ctx.Err() { return } else if err != nil { t.Logf("current error: %v", err) diff --git a/clientv3/integration/maintenance_test.go b/clientv3/integration/maintenance_test.go index 0609c1c812b..b4b73614a36 100644 --- a/clientv3/integration/maintenance_test.go +++ b/clientv3/integration/maintenance_test.go @@ -157,7 +157,6 @@ func TestMaintenanceSnapshotErrorInflight(t *testing.T) { clus.Members[0].Restart(t) cli := clus.RandClient() - integration.WaitClientV3(t, cli) // reading snapshot with canceled context should error out ctx, cancel := context.WithCancel(context.Background()) rc1, err := cli.Snapshot(ctx) diff --git a/clientv3/integration/network_partition_test.go b/clientv3/integration/network_partition_test.go index 41310f75400..f65807430ef 100644 --- a/clientv3/integration/network_partition_test.go +++ b/clientv3/integration/network_partition_test.go @@ -186,9 +186,6 @@ func TestBalancerUnderNetworkPartitionLinearizableGetLeaderElection(t *testing.T // isolate leader clus.Members[lead].InjectPartition(t, clus.Members[(lead+1)%3], clus.Members[(lead+2)%3]) - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, cli) - // expects balancer to round robin to leader within two attempts for i := 0; i < 2; i++ { ctx, cancel := context.WithTimeout(context.TODO(), timeout) diff --git a/clientv3/integration/server_shutdown_test.go b/clientv3/integration/server_shutdown_test.go index d1e5507cc5c..1f889edac03 100644 --- a/clientv3/integration/server_shutdown_test.go +++ b/clientv3/integration/server_shutdown_test.go @@ -17,7 +17,6 @@ package integration import ( "bytes" "context" - "reflect" "strings" "testing" "time" @@ -352,11 +351,7 @@ func testBalancerUnderServerStopInflightRangeOnRestart(t *testing.T, linearizabl } cancel() if err != nil { - if linearizable && isServerUnavailable(err) { - t.Logf("TODO: FIX THIS after balancer rewrite! %v %v", reflect.TypeOf(err), err) - } else { - t.Fatalf("expected linearizable=true and a server unavailable error, but got linearizable=%t and '%v'", linearizable, err) - } + t.Fatalf("unexpected error: %v", err) } }() @@ -402,19 +397,22 @@ func isClientTimeout(err error) bool { return code == codes.DeadlineExceeded || ev.Message() == transport.ErrConnClosing.Desc } -func isServerUnavailable(err error) bool { +func isCanceled(err error) bool { if err == nil { return false } + if err == context.Canceled { + return true + } ev, ok := status.FromError(err) if !ok { return false } code := ev.Code() - return code == codes.Unavailable + return code == codes.Canceled } -func isCanceled(err error) bool { +func isUnavailable(err error) bool { if err == nil { return false } @@ -426,5 +424,5 @@ func isCanceled(err error) bool { return false } code := ev.Code() - return code == codes.Canceled + return code == codes.Unavailable } diff --git a/clientv3/integration/txn_test.go b/clientv3/integration/txn_test.go index 672bf79032a..ca49ec075d8 100644 --- a/clientv3/integration/txn_test.go +++ b/clientv3/integration/txn_test.go @@ -79,9 +79,6 @@ func TestTxnWriteFail(t *testing.T) { t.Fatalf("timed out waiting for txn fail") case <-txnc: } - // TODO: Remove wait once the new grpc load balancer provides retry. - integration.WaitClientV3(t, kv) - // and ensure the put didn't take gresp, gerr := clus.Client(1).Get(context.TODO(), "foo") if gerr != nil { diff --git a/clientv3/lease.go b/clientv3/lease.go index 4097b3afa2a..3d5ff4f7226 100644 --- a/clientv3/lease.go +++ b/clientv3/lease.go @@ -466,7 +466,7 @@ func (l *lessor) recvKeepAliveLoop() (gerr error) { // resetRecv opens a new lease stream and starts sending keep alive requests. func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) { sctx, cancel := context.WithCancel(l.stopCtx) - stream, err := l.remote.LeaseKeepAlive(sctx, l.callOpts...) + stream, err := l.remote.LeaseKeepAlive(sctx, append(l.callOpts, withMax(0))...) if err != nil { cancel() return nil, err diff --git a/clientv3/maintenance.go b/clientv3/maintenance.go index a6a2e7dec67..f814874f2cb 100644 --- a/clientv3/maintenance.go +++ b/clientv3/maintenance.go @@ -188,7 +188,7 @@ func (m *maintenance) HashKV(ctx context.Context, endpoint string, rev int64) (* } func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) { - ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, m.callOpts...) + ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, append(m.callOpts, withMax(defaultStreamMaxRetries))...) if err != nil { return nil, toErr(ctx, err) } diff --git a/clientv3/options.go b/clientv3/options.go index a98b19f9b4b..e158be60ea3 100644 --- a/clientv3/options.go +++ b/clientv3/options.go @@ -16,6 +16,7 @@ package clientv3 import ( "math" + "time" "google.golang.org/grpc" ) @@ -37,6 +38,22 @@ var ( // because range response can easily exceed request send limits // Default to math.MaxInt32; writes exceeding server-side send limit fails anyway defaultMaxCallRecvMsgSize = grpc.MaxCallRecvMsgSize(math.MaxInt32) + + // client-side non-streaming retry limit, only applied to requests where server responds with + // a error code clearly indicating it was unable to process the request such as codes.Unavailable. + // If set to 0, retry is disabled. + defaultUnaryMaxRetries uint = 100 + + // client-side streaming retry limit, only applied to requests where server responds with + // a error code clearly indicating it was unable to process the request such as codes.Unavailable. + // If set to 0, retry is disabled. + defaultStreamMaxRetries uint = ^uint(0) // max uint + + // client-side retry backoff wait between requests. + defaultBackoffWaitBetween = 25 * time.Millisecond + + // client-side retry backoff default jitter fraction. + defaultBackoffJitterFraction = 0.10 ) // defaultCallOpts defines a list of default "gRPC.CallOption". diff --git a/clientv3/retry.go b/clientv3/retry.go index 6c7fcfcf6b6..66ec99dfe6e 100644 --- a/clientv3/retry.go +++ b/clientv3/retry.go @@ -32,6 +32,17 @@ const ( nonRepeatable ) +func (rp retryPolicy) String() string { + switch rp { + case repeatable: + return "repeatable" + case nonRepeatable: + return "nonRepeatable" + default: + return "UNKNOWN" + } +} + type rpcFunc func(ctx context.Context) error type retryRPCFunc func(context.Context, rpcFunc, retryPolicy) error type retryStopErrFunc func(error) bool @@ -78,8 +89,6 @@ func isNonRepeatableStopError(err error) bool { return desc != "there is no address available" && desc != "there is no connection available" } -// TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? -/* func (c *Client) newRetryWrapper() retryRPCFunc { return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { var isStop retryStopErrFunc @@ -90,21 +99,14 @@ func (c *Client) newRetryWrapper() retryRPCFunc { isStop = isNonRepeatableStopError } for { - if err := readyWait(rpcCtx, c.ctx, c.balancer.ConnectNotify()); err != nil { - return err - } - pinned := c.balancer.Pinned() err := f(rpcCtx) if err == nil { return nil } - lg.Lvl(4).Infof("clientv3/retry: error %q on pinned endpoint %q", err.Error(), pinned) + lg.Lvl(4).Infof("clientv3/retry: error %q", err.Error()) if s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded || s.Code() == codes.Internal) { - // mark this before endpoint switch is triggered - c.balancer.HostPortError(pinned, err) - c.balancer.Next() - lg.Lvl(4).Infof("clientv3/retry: switching from %q due to error %q", pinned, err.Error()) + lg.Lvl(4).Infof("clientv3/retry: retrying due to error %q", err.Error()) } if isStop(err) { @@ -112,23 +114,21 @@ func (c *Client) newRetryWrapper() retryRPCFunc { } } } -}*/ +} -/* func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc { return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { for { - pinned := c.balancer.Pinned() err := retryf(rpcCtx, f, rp) if err == nil { return nil } - lg.Lvl(4).Infof("clientv3/auth-retry: error %q on pinned endpoint %q", err.Error(), pinned) + lg.Lvl(4).Infof("clientv3/auth-retry: error %q", err.Error()) // always stop retry on etcd errors other than invalid auth token if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { gterr := c.getToken(rpcCtx) if gterr != nil { - lg.Lvl(4).Infof("clientv3/auth-retry: cannot retry due to error %q(%q) on pinned endpoint %q", err.Error(), gterr.Error(), pinned) + lg.Lvl(4).Infof("clientv3/auth-retry: cannot retry due to error %q(%q)", err.Error(), gterr.Error()) return err // return the original error for simplicity } continue @@ -136,7 +136,7 @@ func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc { return err } } -}*/ +} type retryKVClient struct { kc pb.KVClient @@ -145,16 +145,14 @@ type retryKVClient struct { // RetryKVClient implements a KVClient. func RetryKVClient(c *Client) pb.KVClient { - return pb.NewKVClient(c.conn) - // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? - /*return &retryKVClient{ + return &retryKVClient{ kc: pb.NewKVClient(c.conn), retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), - }*/ + } } func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) { err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.Range(rctx, in, opts...) + resp, err = rkv.kc.Range(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -200,17 +198,15 @@ type retryLeaseClient struct { // RetryLeaseClient implements a LeaseClient. func RetryLeaseClient(c *Client) pb.LeaseClient { - return pb.NewLeaseClient(c.conn) - // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? - /*return &retryLeaseClient{ + return &retryLeaseClient{ lc: pb.NewLeaseClient(c.conn), retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), - }*/ + } } func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTimeToLiveRequest, opts ...grpc.CallOption) (resp *pb.LeaseTimeToLiveResponse, err error) { err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseTimeToLive(rctx, in, opts...) + resp, err = rlc.lc.LeaseTimeToLive(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -218,7 +214,7 @@ func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTi func (rlc *retryLeaseClient) LeaseLeases(ctx context.Context, in *pb.LeaseLeasesRequest, opts ...grpc.CallOption) (resp *pb.LeaseLeasesResponse, err error) { err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseLeases(rctx, in, opts...) + resp, err = rlc.lc.LeaseLeases(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -226,7 +222,7 @@ func (rlc *retryLeaseClient) LeaseLeases(ctx context.Context, in *pb.LeaseLeases func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) { err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseGrant(rctx, in, opts...) + resp, err = rlc.lc.LeaseGrant(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -235,7 +231,7 @@ func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRe func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) { err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseRevoke(rctx, in, opts...) + resp, err = rlc.lc.LeaseRevoke(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -243,7 +239,7 @@ func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevoke func (rlc *retryLeaseClient) LeaseKeepAlive(ctx context.Context, opts ...grpc.CallOption) (stream pb.Lease_LeaseKeepAliveClient, err error) { err = rlc.retryf(ctx, func(rctx context.Context) error { - stream, err = rlc.lc.LeaseKeepAlive(rctx, opts...) + stream, err = rlc.lc.LeaseKeepAlive(rctx, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return stream, err @@ -256,17 +252,15 @@ type retryClusterClient struct { // RetryClusterClient implements a ClusterClient. func RetryClusterClient(c *Client) pb.ClusterClient { - return pb.NewClusterClient(c.conn) - // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? - /*return &retryClusterClient{ + return &retryClusterClient{ cc: pb.NewClusterClient(c.conn), retryf: c.newRetryWrapper(), - }*/ + } } func (rcc *retryClusterClient) MemberList(ctx context.Context, in *pb.MemberListRequest, opts ...grpc.CallOption) (resp *pb.MemberListResponse, err error) { err = rcc.retryf(ctx, func(rctx context.Context) error { - resp, err = rcc.cc.MemberList(rctx, in, opts...) + resp, err = rcc.cc.MemberList(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -303,17 +297,15 @@ type retryMaintenanceClient struct { // RetryMaintenanceClient implements a Maintenance. func RetryMaintenanceClient(c *Client, conn *grpc.ClientConn) pb.MaintenanceClient { - return pb.NewMaintenanceClient(conn) - // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? - /*return &retryMaintenanceClient{ + return &retryMaintenanceClient{ mc: pb.NewMaintenanceClient(conn), retryf: c.newRetryWrapper(), - }*/ + } } func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmRequest, opts ...grpc.CallOption) (resp *pb.AlarmResponse, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Alarm(rctx, in, opts...) + resp, err = rmc.mc.Alarm(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -321,7 +313,7 @@ func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmReques func (rmc *retryMaintenanceClient) Status(ctx context.Context, in *pb.StatusRequest, opts ...grpc.CallOption) (resp *pb.StatusResponse, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Status(rctx, in, opts...) + resp, err = rmc.mc.Status(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -329,7 +321,7 @@ func (rmc *retryMaintenanceClient) Status(ctx context.Context, in *pb.StatusRequ func (rmc *retryMaintenanceClient) Hash(ctx context.Context, in *pb.HashRequest, opts ...grpc.CallOption) (resp *pb.HashResponse, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Hash(rctx, in, opts...) + resp, err = rmc.mc.Hash(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -337,7 +329,7 @@ func (rmc *retryMaintenanceClient) Hash(ctx context.Context, in *pb.HashRequest, func (rmc *retryMaintenanceClient) HashKV(ctx context.Context, in *pb.HashKVRequest, opts ...grpc.CallOption) (resp *pb.HashKVResponse, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.HashKV(rctx, in, opts...) + resp, err = rmc.mc.HashKV(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -345,7 +337,7 @@ func (rmc *retryMaintenanceClient) HashKV(ctx context.Context, in *pb.HashKVRequ func (rmc *retryMaintenanceClient) Snapshot(ctx context.Context, in *pb.SnapshotRequest, opts ...grpc.CallOption) (stream pb.Maintenance_SnapshotClient, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - stream, err = rmc.mc.Snapshot(rctx, in, opts...) + stream, err = rmc.mc.Snapshot(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return stream, err @@ -353,7 +345,7 @@ func (rmc *retryMaintenanceClient) Snapshot(ctx context.Context, in *pb.Snapshot func (rmc *retryMaintenanceClient) MoveLeader(ctx context.Context, in *pb.MoveLeaderRequest, opts ...grpc.CallOption) (resp *pb.MoveLeaderResponse, err error) { err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.MoveLeader(rctx, in, opts...) + resp, err = rmc.mc.MoveLeader(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -374,17 +366,15 @@ type retryAuthClient struct { // RetryAuthClient implements a AuthClient. func RetryAuthClient(c *Client) pb.AuthClient { - return pb.NewAuthClient(c.conn) - // TODO: Remove retry logic entirely now that we're using the new grpc load balancer interface? - /*return &retryAuthClient{ + return &retryAuthClient{ ac: pb.NewAuthClient(c.conn), retryf: c.newRetryWrapper(), - }*/ + } } func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListRequest, opts ...grpc.CallOption) (resp *pb.AuthUserListResponse, err error) { err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserList(rctx, in, opts...) + resp, err = rac.ac.UserList(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -392,7 +382,7 @@ func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListReq func (rac *retryAuthClient) UserGet(ctx context.Context, in *pb.AuthUserGetRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGetResponse, err error) { err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserGet(rctx, in, opts...) + resp, err = rac.ac.UserGet(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -400,7 +390,7 @@ func (rac *retryAuthClient) UserGet(ctx context.Context, in *pb.AuthUserGetReque func (rac *retryAuthClient) RoleGet(ctx context.Context, in *pb.AuthRoleGetRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGetResponse, err error) { err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleGet(rctx, in, opts...) + resp, err = rac.ac.RoleGet(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err @@ -408,7 +398,7 @@ func (rac *retryAuthClient) RoleGet(ctx context.Context, in *pb.AuthRoleGetReque func (rac *retryAuthClient) RoleList(ctx context.Context, in *pb.AuthRoleListRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleListResponse, err error) { err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleList(rctx, in, opts...) + resp, err = rac.ac.RoleList(rctx, in, append(opts, withRetryPolicy(repeatable))...) return err }, repeatable) return resp, err diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go new file mode 100644 index 00000000000..399a3009213 --- /dev/null +++ b/clientv3/retry_interceptor.go @@ -0,0 +1,355 @@ +// Copyright 2016 The etcd 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. + +// Based on github.com/grpc-ecosystem/go-grpc-middleware/retry, but modified to support the more +// fine grained error checking required by write-at-most-once retry semantics of etcd. + +package clientv3 + +import ( + "context" + "io" + "sync" + "time" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +// unaryClientInterceptor returns a new retrying unary client interceptor. +// +// The default configuration of the interceptor is to not retry *at all*. This behaviour can be +// changed through options (e.g. WithMax) on creation of the interceptor or on call (through grpc.CallOptions). +func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.UnaryClientInterceptor { + intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs) + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + grpcOpts, retryOpts := filterCallOptions(opts) + callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts) + // short circuit for simplicity, and avoiding allocations. + if callOpts.max == 0 { + return invoker(ctx, method, req, reply, cc, grpcOpts...) + } + var lastErr error + for attempt := uint(0); attempt < callOpts.max; attempt++ { + if err := waitRetryBackoff(attempt, ctx, callOpts); err != nil { + return err + } + lastErr = invoker(ctx, method, req, reply, cc, grpcOpts...) + logger.Info("retry unary intercept", zap.Uint("attempt", attempt), zap.Error(lastErr)) + if lastErr == nil { + return nil + } + if isContextError(lastErr) { + if ctx.Err() != nil { + // its the context deadline or cancellation. + return lastErr + } else { + // its the callCtx deadline or cancellation, in which case try again. + continue + } + } + if !isRetriable(lastErr, callOpts) { + return lastErr + } + } + return lastErr + } +} + +// streamClientInterceptor returns a new retrying stream client interceptor for server side streaming calls. +// +// The default configuration of the interceptor is to not retry *at all*. This behaviour can be +// changed through options (e.g. WithMax) on creation of the interceptor or on call (through grpc.CallOptions). +// +// Retry logic is available *only for ServerStreams*, i.e. 1:n streams, as the internal logic needs +// to buffer the messages sent by the client. If retry is enabled on any other streams (ClientStreams, +// BidiStreams), the retry interceptor will fail the call. +func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.StreamClientInterceptor { + intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs) + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + grpcOpts, retryOpts := filterCallOptions(opts) + callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts) + // short circuit for simplicity, and avoiding allocations. + if callOpts.max == 0 { + return streamer(ctx, desc, cc, method, grpcOpts...) + } + if desc.ClientStreams { + return nil, grpc.Errorf(codes.Unimplemented, "clientv3/retry_interceptor: cannot retry on ClientStreams, set Disable()") + } + newStreamer, err := streamer(ctx, desc, cc, method, grpcOpts...) + logger.Info("retry stream intercept", zap.Error(err)) + if err != nil { + // TODO(mwitkow): Maybe dial and transport errors should be retriable? + return nil, err + } + retryingStreamer := &serverStreamingRetryingStream{ + ClientStream: newStreamer, + callOpts: callOpts, + ctx: ctx, + streamerCall: func(ctx context.Context) (grpc.ClientStream, error) { + return streamer(ctx, desc, cc, method, grpcOpts...) + }, + } + return retryingStreamer, nil + } +} + +// type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a +// proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish +// a new ClientStream according to the retry policy. +type serverStreamingRetryingStream struct { + grpc.ClientStream + bufferedSends []interface{} // single messsage that the client can sen + receivedGood bool // indicates whether any prior receives were successful + wasClosedSend bool // indicates that CloseSend was closed + ctx context.Context + callOpts *options + streamerCall func(ctx context.Context) (grpc.ClientStream, error) + mu sync.RWMutex +} + +func (s *serverStreamingRetryingStream) setStream(clientStream grpc.ClientStream) { + s.mu.Lock() + s.ClientStream = clientStream + s.mu.Unlock() +} + +func (s *serverStreamingRetryingStream) getStream() grpc.ClientStream { + s.mu.RLock() + defer s.mu.RUnlock() + return s.ClientStream +} + +func (s *serverStreamingRetryingStream) SendMsg(m interface{}) error { + s.mu.Lock() + s.bufferedSends = append(s.bufferedSends, m) + s.mu.Unlock() + return s.getStream().SendMsg(m) +} + +func (s *serverStreamingRetryingStream) CloseSend() error { + s.mu.Lock() + s.wasClosedSend = true + s.mu.Unlock() + return s.getStream().CloseSend() +} + +func (s *serverStreamingRetryingStream) Header() (metadata.MD, error) { + return s.getStream().Header() +} + +func (s *serverStreamingRetryingStream) Trailer() metadata.MD { + return s.getStream().Trailer() +} + +func (s *serverStreamingRetryingStream) RecvMsg(m interface{}) error { + attemptRetry, lastErr := s.receiveMsgAndIndicateRetry(m) + if !attemptRetry { + return lastErr // success or hard failure + } + // We start off from attempt 1, because zeroth was already made on normal SendMsg(). + for attempt := uint(1); attempt < s.callOpts.max; attempt++ { + if err := waitRetryBackoff(attempt, s.ctx, s.callOpts); err != nil { + return err + } + newStream, err := s.reestablishStreamAndResendBuffer(s.ctx) + if err != nil { + // TODO(mwitkow): Maybe dial and transport errors should be retriable? + return err + } + s.setStream(newStream) + attemptRetry, lastErr = s.receiveMsgAndIndicateRetry(m) + //fmt.Printf("Received message and indicate: %v %v\n", attemptRetry, lastErr) + if !attemptRetry { + return lastErr + } + } + return lastErr +} + +func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{}) (bool, error) { + s.mu.RLock() + wasGood := s.receivedGood + s.mu.RUnlock() + err := s.getStream().RecvMsg(m) + if err == nil || err == io.EOF { + s.mu.Lock() + s.receivedGood = true + s.mu.Unlock() + return false, err + } else if wasGood { + // previous RecvMsg in the stream succeeded, no retry logic should interfere + return false, err + } + if isContextError(err) { + if s.ctx.Err() != nil { + return false, err + } else { + // its the callCtx deadline or cancellation, in which case try again. + return true, err + } + } + return isRetriable(err, s.callOpts), err + +} + +func (s *serverStreamingRetryingStream) reestablishStreamAndResendBuffer(callCtx context.Context) (grpc.ClientStream, error) { + s.mu.RLock() + bufferedSends := s.bufferedSends + s.mu.RUnlock() + newStream, err := s.streamerCall(callCtx) + if err != nil { + return nil, err + } + for _, msg := range bufferedSends { + if err := newStream.SendMsg(msg); err != nil { + return nil, err + } + } + if err := newStream.CloseSend(); err != nil { + return nil, err + } + return newStream, nil +} + +func waitRetryBackoff(attempt uint, ctx context.Context, callOpts *options) error { + var waitTime time.Duration = 0 + if attempt > 0 { + waitTime = callOpts.backoffFunc(attempt) + } + if waitTime > 0 { + timer := time.NewTimer(waitTime) + select { + case <-ctx.Done(): + timer.Stop() + return contextErrToGrpcErr(ctx.Err()) + case <-timer.C: + } + } + return nil +} + +func isRetriable(err error, callOpts *options) bool { + if isContextError(err) { + return false + } + switch callOpts.retryPolicy { + case repeatable: + return !isRepeatableStopError(err) + case nonRepeatable: + return !isNonRepeatableStopError(err) + default: + logger.Warn("unrecognized retry policy", zap.String("retryPolicy", callOpts.retryPolicy.String())) + return false + } +} + +func isContextError(err error) bool { + return grpc.Code(err) == codes.DeadlineExceeded || grpc.Code(err) == codes.Canceled +} + +func contextErrToGrpcErr(err error) error { + switch err { + case context.DeadlineExceeded: + return grpc.Errorf(codes.DeadlineExceeded, err.Error()) + case context.Canceled: + return grpc.Errorf(codes.Canceled, err.Error()) + default: + return grpc.Errorf(codes.Unknown, err.Error()) + } +} + +var ( + defaultOptions = &options{ + retryPolicy: nonRepeatable, + max: 0, // disabed + backoffFunc: backoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10), + } +) + +// backoffFunc denotes a family of functions that control the backoff duration between call retries. +// +// They are called with an identifier of the attempt, and should return a time the system client should +// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request +// the deadline of the request takes precedence and the wait will be interrupted before proceeding +// with the next iteration. +type backoffFunc func(attempt uint) time.Duration + +// withRetryPolicy sets the retry policy of this call. +func withRetryPolicy(rp retryPolicy) retryOption { + return retryOption{applyFunc: func(o *options) { + o.retryPolicy = rp + }} +} + +// withMax sets the maximum number of retries on this call, or this interceptor. +func withMax(maxRetries uint) retryOption { + return retryOption{applyFunc: func(o *options) { + o.max = maxRetries + }} +} + +// WithBackoff sets the `BackoffFunc `used to control time between retries. +func withBackoff(bf backoffFunc) retryOption { + return retryOption{applyFunc: func(o *options) { + o.backoffFunc = bf + }} +} + +type options struct { + retryPolicy retryPolicy + max uint + backoffFunc backoffFunc +} + +// retryOption is a grpc.CallOption that is local to clientv3's retry interceptor. +type retryOption struct { + grpc.EmptyCallOption // make sure we implement private after() and before() fields so we don't panic. + applyFunc func(opt *options) +} + +func reuseOrNewWithCallOptions(opt *options, retryOptions []retryOption) *options { + if len(retryOptions) == 0 { + return opt + } + optCopy := &options{} + *optCopy = *opt + for _, f := range retryOptions { + f.applyFunc(optCopy) + } + return optCopy +} + +func filterCallOptions(callOptions []grpc.CallOption) (grpcOptions []grpc.CallOption, retryOptions []retryOption) { + for _, opt := range callOptions { + if co, ok := opt.(retryOption); ok { + retryOptions = append(retryOptions, co) + } else { + grpcOptions = append(grpcOptions, opt) + } + } + return grpcOptions, retryOptions +} + +// BackoffLinearWithJitter waits a set period of time, allowing for jitter (fractional adjustment). +// +// For example waitBetween=1s and jitter=0.10 can generate waits between 900ms and 1100ms. +func backoffLinearWithJitter(waitBetween time.Duration, jitterFraction float64) backoffFunc { + return func(attempt uint) time.Duration { + return backoffutils.JitterUp(waitBetween, jitterFraction) + } +} diff --git a/integration/v3_alarm_test.go b/integration/v3_alarm_test.go index 0486ead8075..e66cf0bab16 100644 --- a/integration/v3_alarm_test.go +++ b/integration/v3_alarm_test.go @@ -88,13 +88,16 @@ func TestV3StorageQuotaApply(t *testing.T) { } } + ctx, close := context.WithTimeout(context.TODO(), RequestWaitTimeout) + defer close() + // small quota machine should reject put - if _, err := kvc0.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { + if _, err := kvc0.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { t.Fatalf("past-quota instance should reject put") } // large quota machine should reject put - if _, err := kvc1.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { + if _, err := kvc1.Put(ctx, &pb.PutRequest{Key: key, Value: smallbuf}); err == nil { t.Fatalf("past-quota instance should reject put") } From 3130e4da1c4fbd9ee976a23be4d80c03cd859be3 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Tue, 22 May 2018 14:11:19 -0700 Subject: [PATCH 26/39] vendor: add "go-grpc-middleware/util/backoffutils" Signed-off-by: Gyuho Lee --- Gopkg.lock | 9 +- .../grpc-ecosystem/go-grpc-middleware/LICENSE | 201 ++++++++++++++++++ .../go-grpc-middleware/chain.go | 183 ++++++++++++++++ .../grpc-ecosystem/go-grpc-middleware/doc.go | 69 ++++++ .../util/backoffutils/backoff.go | 23 ++ .../go-grpc-middleware/wrappers.go | 29 +++ vendor/google.golang.org/genproto/regen.go | 123 +++++++++++ 7 files changed, 635 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE create mode 100644 vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go create mode 100644 vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go create mode 100644 vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go create mode 100644 vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go create mode 100644 vendor/google.golang.org/genproto/regen.go diff --git a/Gopkg.lock b/Gopkg.lock index 4b576cc6b30..300ebe0da50 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -100,6 +100,12 @@ packages = ["."] revision = "4201258b820c74ac8e6922fc9e6b52f71fe46f8d" +[[projects]] + name = "github.com/grpc-ecosystem/go-grpc-middleware" + packages = ["util/backoffutils"] + revision = "c250d6563d4d4c20252cd865923440e829844f4e" + version = "v1.0.0" + [[projects]] name = "github.com/grpc-ecosystem/go-grpc-prometheus" packages = ["."] @@ -353,7 +359,6 @@ "peer", "resolver", "resolver/dns", - "resolver/manual", "resolver/passthrough", "stats", "status", @@ -378,6 +383,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9b6e97c6524385aecaa477af059f4f6555b3609d4a283ecf27ac2ad4fbe08f06" + inputs-digest = "40c72e54abdb7d6c044f2f5fabb8fc7ac2eadcab047cc22b28d26b7b145b5e1b" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE new file mode 100644 index 00000000000..b2b065037fc --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go new file mode 100644 index 00000000000..45a2f5f49a7 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go @@ -0,0 +1,183 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +// gRPC Server Interceptor chaining middleware. + +package grpc_middleware + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// ChainUnaryServer creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryServer(one, two, three) will execute one before two before three, and three +// will see context changes of one and two. +func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { + n := len(interceptors) + + if n > 1 { + lastI := n - 1 + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + var ( + chainHandler grpc.UnaryHandler + curI int + ) + + chainHandler = func(currentCtx context.Context, currentReq interface{}) (interface{}, error) { + if curI == lastI { + return handler(currentCtx, currentReq) + } + curI++ + resp, err := interceptors[curI](currentCtx, currentReq, info, chainHandler) + curI-- + return resp, err + } + + return interceptors[0](ctx, req, info, chainHandler) + } + } + + if n == 1 { + return interceptors[0] + } + + // n == 0; Dummy interceptor maintained for backward compatibility to avoid returning nil. + return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return handler(ctx, req) + } +} + +// ChainStreamServer creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryServer(one, two, three) will execute one before two before three. +// If you want to pass context between interceptors, use WrapServerStream. +func ChainStreamServer(interceptors ...grpc.StreamServerInterceptor) grpc.StreamServerInterceptor { + n := len(interceptors) + + if n > 1 { + lastI := n - 1 + return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + var ( + chainHandler grpc.StreamHandler + curI int + ) + + chainHandler = func(currentSrv interface{}, currentStream grpc.ServerStream) error { + if curI == lastI { + return handler(currentSrv, currentStream) + } + curI++ + err := interceptors[curI](currentSrv, currentStream, info, chainHandler) + curI-- + return err + } + + return interceptors[0](srv, stream, info, chainHandler) + } + } + + if n == 1 { + return interceptors[0] + } + + // n == 0; Dummy interceptor maintained for backward compatibility to avoid returning nil. + return func(srv interface{}, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return handler(srv, stream) + } +} + +// ChainUnaryClient creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainUnaryClient(one, two, three) will execute one before two before three. +func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor { + n := len(interceptors) + + if n > 1 { + lastI := n - 1 + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + var ( + chainHandler grpc.UnaryInvoker + curI int + ) + + chainHandler = func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error { + if curI == lastI { + return invoker(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentOpts...) + } + curI++ + err := interceptors[curI](currentCtx, currentMethod, currentReq, currentRepl, currentConn, chainHandler, currentOpts...) + curI-- + return err + } + + return interceptors[0](ctx, method, req, reply, cc, chainHandler, opts...) + } + } + + if n == 1 { + return interceptors[0] + } + + // n == 0; Dummy interceptor maintained for backward compatibility to avoid returning nil. + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + return invoker(ctx, method, req, reply, cc, opts...) + } +} + +// ChainStreamClient creates a single interceptor out of a chain of many interceptors. +// +// Execution is done in left-to-right order, including passing of context. +// For example ChainStreamClient(one, two, three) will execute one before two before three. +func ChainStreamClient(interceptors ...grpc.StreamClientInterceptor) grpc.StreamClientInterceptor { + n := len(interceptors) + + if n > 1 { + lastI := n - 1 + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + var ( + chainHandler grpc.Streamer + curI int + ) + + chainHandler = func(currentCtx context.Context, currentDesc *grpc.StreamDesc, currentConn *grpc.ClientConn, currentMethod string, currentOpts ...grpc.CallOption) (grpc.ClientStream, error) { + if curI == lastI { + return streamer(currentCtx, currentDesc, currentConn, currentMethod, currentOpts...) + } + curI++ + stream, err := interceptors[curI](currentCtx, currentDesc, currentConn, currentMethod, chainHandler, currentOpts...) + curI-- + return stream, err + } + + return interceptors[0](ctx, desc, cc, method, chainHandler, opts...) + } + } + + if n == 1 { + return interceptors[0] + } + + // n == 0; Dummy interceptor maintained for backward compatibility to avoid returning nil. + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return streamer(ctx, desc, cc, method, opts...) + } +} + +// Chain creates a single interceptor out of a chain of many interceptors. +// +// WithUnaryServerChain is a grpc.Server config option that accepts multiple unary interceptors. +// Basically syntactic sugar. +func WithUnaryServerChain(interceptors ...grpc.UnaryServerInterceptor) grpc.ServerOption { + return grpc.UnaryInterceptor(ChainUnaryServer(interceptors...)) +} + +// WithStreamServerChain is a grpc.Server config option that accepts multiple stream interceptors. +// Basically syntactic sugar. +func WithStreamServerChain(interceptors ...grpc.StreamServerInterceptor) grpc.ServerOption { + return grpc.StreamInterceptor(ChainStreamServer(interceptors...)) +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go new file mode 100644 index 00000000000..71689503642 --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go @@ -0,0 +1,69 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +/* +`grpc_middleware` is a collection of gRPC middleware packages: interceptors, helpers and tools. + +Middleware + +gRPC is a fantastic RPC middleware, which sees a lot of adoption in the Golang world. However, the +upstream gRPC codebase is relatively bare bones. + +This package, and most of its child packages provides commonly needed middleware for gRPC: +client-side interceptors for retires, server-side interceptors for input validation and auth, +functions for chaining said interceptors, metadata convenience methods and more. + +Chaining + +By default, gRPC doesn't allow one to have more than one interceptor either on the client nor on +the server side. `grpc_middleware` provides convenient chaining methods + +Simple way of turning a multiple interceptors into a single interceptor. Here's an example for +server chaining: + + myServer := grpc.NewServer( + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(loggingStream, monitoringStream, authStream)), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(loggingUnary, monitoringUnary, authUnary), + ) + +These interceptors will be executed from left to right: logging, monitoring and auth. + +Here's an example for client side chaining: + + clientConn, err = grpc.Dial( + address, + grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(monitoringClientUnary, retryUnary)), + grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(monitoringClientStream, retryStream)), + ) + client = pb_testproto.NewTestServiceClient(clientConn) + resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"}) + +These interceptors will be executed from left to right: monitoring and then retry logic. + +The retry interceptor will call every interceptor that follows it whenever when a retry happens. + +Writing Your Own + +Implementing your own interceptor is pretty trivial: there are interfaces for that. But the interesting +bit exposing common data to handlers (and other middleware), similarly to HTTP Middleware design. +For example, you may want to pass the identity of the caller from the auth interceptor all the way +to the handling function. + +For example, a client side interceptor example for auth looks like: + + func FakeAuthUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + newCtx := context.WithValue(ctx, "user_id", "john@example.com") + return handler(newCtx, req) + } + +Unfortunately, it's not as easy for streaming RPCs. These have the `context.Context` embedded within +the `grpc.ServerStream` object. To pass values through context, a wrapper (`WrappedServerStream`) is +needed. For example: + + func FakeAuthStreamingInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + newStream := grpc_middleware.WrapServerStream(stream) + newStream.WrappedContext = context.WithValue(ctx, "user_id", "john@example.com") + return handler(srv, stream) + } +*/ +package grpc_middleware diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go new file mode 100644 index 00000000000..10aa7f96c6c --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils/backoff.go @@ -0,0 +1,23 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +/* +Backoff Helper Utilities + +Implements common backoff features. +*/ +package backoffutils + +import ( + "math/rand" + "time" +) + +// JitterUp adds random jitter to the duration. +// +// This adds or substracts time from the duration within a given jitter fraction. +// For example for 10s and jitter 0.1, it will returna time within [9s, 11s]) +func JitterUp(duration time.Duration, jitter float64) time.Duration { + multiplier := jitter * (rand.Float64()*2 - 1) + return time.Duration(float64(duration) * (1 + multiplier)) +} diff --git a/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go new file mode 100644 index 00000000000..597b862445f --- /dev/null +++ b/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go @@ -0,0 +1,29 @@ +// Copyright 2016 Michal Witkowski. All Rights Reserved. +// See LICENSE for licensing terms. + +package grpc_middleware + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// WrappedServerStream is a thin wrapper around grpc.ServerStream that allows modifying context. +type WrappedServerStream struct { + grpc.ServerStream + // WrappedContext is the wrapper's own Context. You can assign it. + WrappedContext context.Context +} + +// Context returns the wrapper's WrappedContext, overwriting the nested grpc.ServerStream.Context() +func (w *WrappedServerStream) Context() context.Context { + return w.WrappedContext +} + +// WrapServerStream returns a ServerStream that has the ability to overwrite context. +func WrapServerStream(stream grpc.ServerStream) *WrappedServerStream { + if existing, ok := stream.(*WrappedServerStream); ok { + return existing + } + return &WrappedServerStream{ServerStream: stream, WrappedContext: stream.Context()} +} diff --git a/vendor/google.golang.org/genproto/regen.go b/vendor/google.golang.org/genproto/regen.go new file mode 100644 index 00000000000..9c906f2095b --- /dev/null +++ b/vendor/google.golang.org/genproto/regen.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. +// +// 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. + +// +build ignore + +// Regen.go regenerates the genproto repository. +// +// Regen.go recursively walks through each directory named by given arguments, +// looking for all .proto files. (Symlinks are not followed.) +// If the pkg_prefix flag is not an empty string, +// any proto file without `go_package` option +// or whose option does not begin with the prefix is ignored. +// Protoc is executed on remaining files, +// one invocation per set of files declaring the same Go package. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +var goPkgOptRe = regexp.MustCompile(`(?m)^option go_package = (.*);`) + +func usage() { + fmt.Fprintln(os.Stderr, `usage: go run regen.go -go_out=path/to/output [-pkg_prefix=pkg/prefix] roots... + +Most users will not need to run this file directly. +To regenerate this repository, run regen.sh instead.`) + flag.PrintDefaults() +} + +func main() { + goOutDir := flag.String("go_out", "", "go_out argument to pass to protoc-gen-go") + pkgPrefix := flag.String("pkg_prefix", "", "only include proto files with go_package starting with this prefix") + flag.Usage = usage + flag.Parse() + + if *goOutDir == "" { + log.Fatal("need go_out flag") + } + + pkgFiles := make(map[string][]string) + walkFn := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.Mode().IsRegular() || !strings.HasSuffix(path, ".proto") { + return nil + } + pkg, err := goPkg(path) + if err != nil { + return err + } + pkgFiles[pkg] = append(pkgFiles[pkg], path) + return nil + } + for _, root := range flag.Args() { + if err := filepath.Walk(root, walkFn); err != nil { + log.Fatal(err) + } + } + for pkg, fnames := range pkgFiles { + if !strings.HasPrefix(pkg, *pkgPrefix) { + continue + } + if out, err := protoc(*goOutDir, flag.Args(), fnames); err != nil { + log.Fatalf("error executing protoc: %s\n%s", err, out) + } + } +} + +// goPkg reports the import path declared in the given file's +// `go_package` option. If the option is missing, goPkg returns empty string. +func goPkg(fname string) (string, error) { + content, err := ioutil.ReadFile(fname) + if err != nil { + return "", err + } + + var pkgName string + if match := goPkgOptRe.FindSubmatch(content); len(match) > 0 { + pn, err := strconv.Unquote(string(match[1])) + if err != nil { + return "", err + } + pkgName = pn + } + if p := strings.IndexRune(pkgName, ';'); p > 0 { + pkgName = pkgName[:p] + } + return pkgName, nil +} + +// protoc executes the "protoc" command on files named in fnames, +// passing go_out and include flags specified in goOut and includes respectively. +// protoc returns combined output from stdout and stderr. +func protoc(goOut string, includes, fnames []string) ([]byte, error) { + args := []string{"--go_out=plugins=grpc:" + goOut} + for _, inc := range includes { + args = append(args, "-I", inc) + } + args = append(args, fnames...) + return exec.Command("protoc", args...).CombinedOutput() +} From 55ef9cc1d0d393e577559f82d26b4217155000e7 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Tue, 22 May 2018 15:06:58 -0700 Subject: [PATCH 27/39] clientv3: Add auth retry to retry interceptor --- clientv3/integration/dial_test.go | 2 +- clientv3/retry.go | 303 +++++------------------------- clientv3/retry_interceptor.go | 39 +++- 3 files changed, 81 insertions(+), 263 deletions(-) diff --git a/clientv3/integration/dial_test.go b/clientv3/integration/dial_test.go index 574f224492f..41c1387b933 100644 --- a/clientv3/integration/dial_test.go +++ b/clientv3/integration/dial_test.go @@ -83,7 +83,7 @@ func TestDialTLSNoConfig(t *testing.T) { // TODO: this should not be required when we set grpc.WithBlock() if c != nil { - ctx, cancel := context.WithTimeout(context.Background(), integration.RequestWaitTimeout) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) _, err = c.KV.Get(ctx, "/") cancel() } diff --git a/clientv3/retry.go b/clientv3/retry.go index 66ec99dfe6e..5ab171c2aac 100644 --- a/clientv3/retry.go +++ b/clientv3/retry.go @@ -89,413 +89,204 @@ func isNonRepeatableStopError(err error) bool { return desc != "there is no address available" && desc != "there is no connection available" } -func (c *Client) newRetryWrapper() retryRPCFunc { - return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { - var isStop retryStopErrFunc - switch rp { - case repeatable: - isStop = isRepeatableStopError - case nonRepeatable: - isStop = isNonRepeatableStopError - } - for { - err := f(rpcCtx) - if err == nil { - return nil - } - lg.Lvl(4).Infof("clientv3/retry: error %q", err.Error()) - - if s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded || s.Code() == codes.Internal) { - lg.Lvl(4).Infof("clientv3/retry: retrying due to error %q", err.Error()) - } - - if isStop(err) { - return err - } - } - } -} - -func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc { - return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error { - for { - err := retryf(rpcCtx, f, rp) - if err == nil { - return nil - } - lg.Lvl(4).Infof("clientv3/auth-retry: error %q", err.Error()) - // always stop retry on etcd errors other than invalid auth token - if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { - gterr := c.getToken(rpcCtx) - if gterr != nil { - lg.Lvl(4).Infof("clientv3/auth-retry: cannot retry due to error %q(%q)", err.Error(), gterr.Error()) - return err // return the original error for simplicity - } - continue - } - return err - } - } -} - type retryKVClient struct { - kc pb.KVClient - retryf retryRPCFunc + kc pb.KVClient } // RetryKVClient implements a KVClient. func RetryKVClient(c *Client) pb.KVClient { return &retryKVClient{ - kc: pb.NewKVClient(c.conn), - retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), + kc: pb.NewKVClient(c.conn), } } func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) { - err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.Range(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rkv.kc.Range(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rkv *retryKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) { - err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.Put(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rkv.kc.Put(ctx, in, opts...) } func (rkv *retryKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) { - err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.DeleteRange(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rkv.kc.DeleteRange(ctx, in, opts...) } func (rkv *retryKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) { - // TODO: "repeatable" for read-only txn - err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.Txn(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rkv.kc.Txn(ctx, in, opts...) } func (rkv *retryKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) { - err = rkv.retryf(ctx, func(rctx context.Context) error { - resp, err = rkv.kc.Compact(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rkv.kc.Compact(ctx, in, opts...) } type retryLeaseClient struct { - lc pb.LeaseClient - retryf retryRPCFunc + lc pb.LeaseClient } // RetryLeaseClient implements a LeaseClient. func RetryLeaseClient(c *Client) pb.LeaseClient { return &retryLeaseClient{ - lc: pb.NewLeaseClient(c.conn), - retryf: c.newAuthRetryWrapper(c.newRetryWrapper()), + lc: pb.NewLeaseClient(c.conn), } } func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTimeToLiveRequest, opts ...grpc.CallOption) (resp *pb.LeaseTimeToLiveResponse, err error) { - err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseTimeToLive(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rlc.lc.LeaseTimeToLive(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rlc *retryLeaseClient) LeaseLeases(ctx context.Context, in *pb.LeaseLeasesRequest, opts ...grpc.CallOption) (resp *pb.LeaseLeasesResponse, err error) { - err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseLeases(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rlc.lc.LeaseLeases(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) { - err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseGrant(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err - + return rlc.lc.LeaseGrant(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) { - err = rlc.retryf(ctx, func(rctx context.Context) error { - resp, err = rlc.lc.LeaseRevoke(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rlc.lc.LeaseRevoke(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rlc *retryLeaseClient) LeaseKeepAlive(ctx context.Context, opts ...grpc.CallOption) (stream pb.Lease_LeaseKeepAliveClient, err error) { - err = rlc.retryf(ctx, func(rctx context.Context) error { - stream, err = rlc.lc.LeaseKeepAlive(rctx, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return stream, err + return rlc.lc.LeaseKeepAlive(ctx, append(opts, withRetryPolicy(repeatable))...) } type retryClusterClient struct { - cc pb.ClusterClient - retryf retryRPCFunc + cc pb.ClusterClient } // RetryClusterClient implements a ClusterClient. func RetryClusterClient(c *Client) pb.ClusterClient { return &retryClusterClient{ - cc: pb.NewClusterClient(c.conn), - retryf: c.newRetryWrapper(), + cc: pb.NewClusterClient(c.conn), } } func (rcc *retryClusterClient) MemberList(ctx context.Context, in *pb.MemberListRequest, opts ...grpc.CallOption) (resp *pb.MemberListResponse, err error) { - err = rcc.retryf(ctx, func(rctx context.Context) error { - resp, err = rcc.cc.MemberList(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rcc.cc.MemberList(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) { - err = rcc.retryf(ctx, func(rctx context.Context) error { - resp, err = rcc.cc.MemberAdd(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rcc.cc.MemberAdd(ctx, in, opts...) } func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) { - err = rcc.retryf(ctx, func(rctx context.Context) error { - resp, err = rcc.cc.MemberRemove(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rcc.cc.MemberRemove(ctx, in, opts...) } func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) { - err = rcc.retryf(ctx, func(rctx context.Context) error { - resp, err = rcc.cc.MemberUpdate(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rcc.cc.MemberUpdate(ctx, in, opts...) } type retryMaintenanceClient struct { - mc pb.MaintenanceClient - retryf retryRPCFunc + mc pb.MaintenanceClient } // RetryMaintenanceClient implements a Maintenance. func RetryMaintenanceClient(c *Client, conn *grpc.ClientConn) pb.MaintenanceClient { return &retryMaintenanceClient{ - mc: pb.NewMaintenanceClient(conn), - retryf: c.newRetryWrapper(), + mc: pb.NewMaintenanceClient(conn), } } func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmRequest, opts ...grpc.CallOption) (resp *pb.AlarmResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Alarm(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rmc.mc.Alarm(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) Status(ctx context.Context, in *pb.StatusRequest, opts ...grpc.CallOption) (resp *pb.StatusResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Status(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rmc.mc.Status(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) Hash(ctx context.Context, in *pb.HashRequest, opts ...grpc.CallOption) (resp *pb.HashResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Hash(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rmc.mc.Hash(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) HashKV(ctx context.Context, in *pb.HashKVRequest, opts ...grpc.CallOption) (resp *pb.HashKVResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.HashKV(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rmc.mc.HashKV(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) Snapshot(ctx context.Context, in *pb.SnapshotRequest, opts ...grpc.CallOption) (stream pb.Maintenance_SnapshotClient, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - stream, err = rmc.mc.Snapshot(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return stream, err + return rmc.mc.Snapshot(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) MoveLeader(ctx context.Context, in *pb.MoveLeaderRequest, opts ...grpc.CallOption) (resp *pb.MoveLeaderResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.MoveLeader(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rmc.mc.MoveLeader(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rmc *retryMaintenanceClient) Defragment(ctx context.Context, in *pb.DefragmentRequest, opts ...grpc.CallOption) (resp *pb.DefragmentResponse, err error) { - err = rmc.retryf(ctx, func(rctx context.Context) error { - resp, err = rmc.mc.Defragment(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rmc.mc.Defragment(ctx, in, opts...) } type retryAuthClient struct { - ac pb.AuthClient - retryf retryRPCFunc + ac pb.AuthClient } // RetryAuthClient implements a AuthClient. func RetryAuthClient(c *Client) pb.AuthClient { return &retryAuthClient{ - ac: pb.NewAuthClient(c.conn), - retryf: c.newRetryWrapper(), + ac: pb.NewAuthClient(c.conn), } } func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListRequest, opts ...grpc.CallOption) (resp *pb.AuthUserListResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserList(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rac.ac.UserList(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rac *retryAuthClient) UserGet(ctx context.Context, in *pb.AuthUserGetRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGetResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserGet(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rac.ac.UserGet(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rac *retryAuthClient) RoleGet(ctx context.Context, in *pb.AuthRoleGetRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGetResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleGet(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rac.ac.RoleGet(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rac *retryAuthClient) RoleList(ctx context.Context, in *pb.AuthRoleListRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleListResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleList(rctx, in, append(opts, withRetryPolicy(repeatable))...) - return err - }, repeatable) - return resp, err + return rac.ac.RoleList(ctx, in, append(opts, withRetryPolicy(repeatable))...) } func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.AuthEnable(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.AuthEnable(ctx, in, opts...) } func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.AuthDisable(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.AuthDisable(ctx, in, opts...) } func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserAdd(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.UserAdd(ctx, in, opts...) } func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserDelete(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.UserDelete(ctx, in, opts...) } func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserChangePassword(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.UserChangePassword(ctx, in, opts...) } func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserGrantRole(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.UserGrantRole(ctx, in, opts...) } func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.UserRevokeRole(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.UserRevokeRole(ctx, in, opts...) } func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleAdd(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.RoleAdd(ctx, in, opts...) } func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleDelete(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.RoleDelete(ctx, in, opts...) } func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleGrantPermission(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.RoleGrantPermission(ctx, in, opts...) } func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.RoleRevokePermission(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.RoleRevokePermission(ctx, in, opts...) } func (rac *retryAuthClient) Authenticate(ctx context.Context, in *pb.AuthenticateRequest, opts ...grpc.CallOption) (resp *pb.AuthenticateResponse, err error) { - err = rac.retryf(ctx, func(rctx context.Context) error { - resp, err = rac.ac.Authenticate(rctx, in, opts...) - return err - }, nonRepeatable) - return resp, err + return rac.ac.Authenticate(ctx, in, opts...) } diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go index 399a3009213..3ace7f0f94a 100644 --- a/clientv3/retry_interceptor.go +++ b/clientv3/retry_interceptor.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils" "go.uber.org/zap" "google.golang.org/grpc" @@ -57,10 +58,17 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt if ctx.Err() != nil { // its the context deadline or cancellation. return lastErr - } else { - // its the callCtx deadline or cancellation, in which case try again. - continue } + // its the callCtx deadline or cancellation, in which case try again. + continue + } + if callOpts.retryAuth && rpctypes.Error(lastErr) == rpctypes.ErrInvalidAuthToken { + gterr := c.getToken(ctx) + if gterr != nil { + logger.Info("retry failed to fetch new auth token", zap.Error(gterr)) + return lastErr // return the original error for simplicity + } + continue } if !isRetriable(lastErr, callOpts) { return lastErr @@ -97,6 +105,7 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp return nil, err } retryingStreamer := &serverStreamingRetryingStream{ + client: c, ClientStream: newStreamer, callOpts: callOpts, ctx: ctx, @@ -113,6 +122,7 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp // a new ClientStream according to the retry policy. type serverStreamingRetryingStream struct { grpc.ClientStream + client *Client bufferedSends []interface{} // single messsage that the client can sen receivedGood bool // indicates whether any prior receives were successful wasClosedSend bool // indicates that CloseSend was closed @@ -198,10 +208,18 @@ func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{} if isContextError(err) { if s.ctx.Err() != nil { return false, err - } else { - // its the callCtx deadline or cancellation, in which case try again. - return true, err } + // its the callCtx deadline or cancellation, in which case try again. + return true, err + } + if s.callOpts.retryAuth && rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { + gterr := s.client.getToken(s.ctx) + if gterr != nil { + logger.Info("retry failed to fetch new auth token", zap.Error(gterr)) + return false, err // return the original error for simplicity + } + return true, err + } return isRetriable(err, s.callOpts), err @@ -278,6 +296,7 @@ var ( retryPolicy: nonRepeatable, max: 0, // disabed backoffFunc: backoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10), + retryAuth: true, } ) @@ -296,6 +315,13 @@ func withRetryPolicy(rp retryPolicy) retryOption { }} } +// withAuthRetry sets enables authentication retries. +func withAuthRetry(retryAuth bool) retryOption { + return retryOption{applyFunc: func(o *options) { + o.retryAuth = retryAuth + }} +} + // withMax sets the maximum number of retries on this call, or this interceptor. func withMax(maxRetries uint) retryOption { return retryOption{applyFunc: func(o *options) { @@ -314,6 +340,7 @@ type options struct { retryPolicy retryPolicy max uint backoffFunc backoffFunc + retryAuth bool } // retryOption is a grpc.CallOption that is local to clientv3's retry interceptor. From 05c57a0ea49186369741113302f6fb2bdd3be64d Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Thu, 24 May 2018 16:14:22 -0700 Subject: [PATCH 28/39] integration: Fix unit test failures from new grpc LB changes, fix bom --- bill-of-materials.json | 9 +++++++++ integration/cluster.go | 2 +- integration/v3_grpc_test.go | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bill-of-materials.json b/bill-of-materials.json index 42e73e61f12..c11992df879 100644 --- a/bill-of-materials.json +++ b/bill-of-materials.json @@ -134,6 +134,15 @@ } ] }, + { + "project": "github.com/grpc-ecosystem/go-grpc-middleware/util/backoffutils", + "licenses": [ + { + "type": "Apache License 2.0", + "confidence": 1 + } + ] + }, { "project": "github.com/grpc-ecosystem/go-grpc-prometheus", "licenses": [ diff --git a/integration/cluster.go b/integration/cluster.go index ead7b570ad2..e3924e64d11 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -981,7 +981,7 @@ func WaitClientV3(t *testing.T, kv clientv3.KV) { var err error for time.Now().Before(timeout) { ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) - _, err := kv.Get(ctx, "/") + _, err = kv.Get(ctx, "/") cancel() if err == nil { return diff --git a/integration/v3_grpc_test.go b/integration/v3_grpc_test.go index ab96b76dd86..b193f661f3d 100644 --- a/integration/v3_grpc_test.go +++ b/integration/v3_grpc_test.go @@ -1948,6 +1948,6 @@ func waitForRestart(t *testing.T, kvc pb.KVClient) { } } if err != nil { - t.Fatal("timed out waiting for restart: %v", err) + t.Fatalf("timed out waiting for restart: %v", err) } } From 3b84117f54669273f8f1f82fdbfd3a578576d515 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 25 May 2018 13:59:37 -0700 Subject: [PATCH 29/39] clientv3/integration: Add err check to TestDialTLSNoConfig to prevent nil pointer dereference on c.Close() --- clientv3/integration/dial_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clientv3/integration/dial_test.go b/clientv3/integration/dial_test.go index 41c1387b933..e996a132bbc 100644 --- a/clientv3/integration/dial_test.go +++ b/clientv3/integration/dial_test.go @@ -79,6 +79,9 @@ func TestDialTLSNoConfig(t *testing.T) { DialTimeout: time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) + if err != nil { + t.Fatal(err) + } defer c.Close() // TODO: this should not be required when we set grpc.WithBlock() From a3032d3d0bedff89687ee972645bc89b0acbe31d Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 8 Jun 2018 13:44:53 -0700 Subject: [PATCH 30/39] *: fix fmt tests, reenable "testEmbedEtcdGracefulStop" Signed-off-by: Gyuho Lee --- clientv3/balancer/balancer.go | 2 +- .../balancer/resolver/endpoint/endpoint.go | 59 ++++++++++--------- clientv3/options.go | 4 +- clientv3/retry_interceptor.go | 4 +- integration/embed_test.go | 5 +- pkg/mock/mockserver/mockserver.go | 3 +- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/clientv3/balancer/balancer.go b/clientv3/balancer/balancer.go index 8356aaf00d2..6ecc5b5f828 100644 --- a/clientv3/balancer/balancer.go +++ b/clientv3/balancer/balancer.go @@ -49,7 +49,7 @@ type builder struct { // Build is called initially when creating "ccBalancerWrapper". // "grpc.Dial" is called to this client connection. -// Then, resolved addreses will be handled via "HandleResolvedAddrs". +// Then, resolved addresses will be handled via "HandleResolvedAddrs". func (b *builder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer { bb := &baseBalancer{ id: strconv.FormatInt(time.Now().UnixNano(), 36), diff --git a/clientv3/balancer/resolver/endpoint/endpoint.go b/clientv3/balancer/resolver/endpoint/endpoint.go index 679f92e9a8d..104ec773c0b 100644 --- a/clientv3/balancer/resolver/endpoint/endpoint.go +++ b/clientv3/balancer/resolver/endpoint/endpoint.go @@ -24,9 +24,7 @@ import ( "google.golang.org/grpc/resolver" ) -const ( - scheme = "endpoint" -) +const scheme = "endpoint" var ( targetPrefix = fmt.Sprintf("%s://", scheme) @@ -42,8 +40,8 @@ func init() { } type builder struct { + mu sync.RWMutex resolverGroups map[string]*ResolverGroup - sync.RWMutex } // NewResolverGroup creates a new ResolverGroup with the given id. @@ -54,41 +52,41 @@ func NewResolverGroup(id string) (*ResolverGroup, error) { // ResolverGroup keeps all endpoints of resolvers using a common endpoint:/// target // up-to-date. type ResolverGroup struct { + mu sync.RWMutex id string endpoints []string resolvers []*Resolver - sync.RWMutex } func (e *ResolverGroup) addResolver(r *Resolver) { - e.Lock() + e.mu.Lock() addrs := epsToAddrs(e.endpoints...) e.resolvers = append(e.resolvers, r) - e.Unlock() + e.mu.Unlock() r.cc.NewAddress(addrs) } func (e *ResolverGroup) removeResolver(r *Resolver) { - e.Lock() + e.mu.Lock() for i, er := range e.resolvers { if er == r { e.resolvers = append(e.resolvers[:i], e.resolvers[i+1:]...) break } } - e.Unlock() + e.mu.Unlock() } // SetEndpoints updates the endpoints for ResolverGroup. All registered resolver are updated // immediately with the new endpoints. func (e *ResolverGroup) SetEndpoints(endpoints []string) { addrs := epsToAddrs(endpoints...) - e.Lock() + e.mu.Lock() e.endpoints = endpoints for _, r := range e.resolvers { r.cc.NewAddress(addrs) } - e.Unlock() + e.mu.Unlock() } // Target constructs a endpoint target using the endpoint id of the ResolverGroup. @@ -121,7 +119,7 @@ func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts res return nil, fmt.Errorf("failed to build resolver: %v", err) } r := &Resolver{ - endpointId: id, + endpointID: id, cc: cc, } es.addResolver(r) @@ -129,24 +127,24 @@ func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts res } func (b *builder) newResolverGroup(id string) (*ResolverGroup, error) { - b.RLock() - es, ok := b.resolverGroups[id] - b.RUnlock() - if !ok { - es = &ResolverGroup{id: id} - b.Lock() - b.resolverGroups[id] = es - b.Unlock() - } else { + b.mu.RLock() + _, ok := b.resolverGroups[id] + b.mu.RUnlock() + if ok { return nil, fmt.Errorf("Endpoint already exists for id: %s", id) } + + es := &ResolverGroup{id: id} + b.mu.Lock() + b.resolverGroups[id] = es + b.mu.Unlock() return es, nil } func (b *builder) getResolverGroup(id string) (*ResolverGroup, error) { - b.RLock() + b.mu.RLock() es, ok := b.resolverGroups[id] - b.RUnlock() + b.mu.RUnlock() if !ok { return nil, fmt.Errorf("ResolverGroup not found for id: %s", id) } @@ -154,9 +152,9 @@ func (b *builder) getResolverGroup(id string) (*ResolverGroup, error) { } func (b *builder) close(id string) { - b.Lock() + b.mu.Lock() delete(b.resolverGroups, id) - b.Unlock() + b.mu.Unlock() } func (r *builder) Scheme() string { @@ -165,7 +163,7 @@ func (r *builder) Scheme() string { // Resolver provides a resolver for a single etcd cluster, identified by name. type Resolver struct { - endpointId string + endpointID string cc resolver.ClientConn sync.RWMutex } @@ -182,15 +180,18 @@ func epsToAddrs(eps ...string) (addrs []resolver.Address) { func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} func (r *Resolver) Close() { - es, err := bldr.getResolverGroup(r.endpointId) + es, err := bldr.getResolverGroup(r.endpointID) if err != nil { return } es.removeResolver(r) } -// Parse endpoint parses a endpoint of the form (http|https)://*|(unix|unixs)://) and returns a -// protocol ('tcp' or 'unix'), host (or filepath if a unix socket) and scheme (http, https, unix, unixs). +// ParseEndpoint endpoint parses an endpoint of the form +// (http|https)://*|(unix|unixs)://) +// and returns a protocol ('tcp' or 'unix'), +// host (or filepath if a unix socket), +// scheme (http, https, unix, unixs). func ParseEndpoint(endpoint string) (proto string, host string, scheme string) { proto = "tcp" host = endpoint diff --git a/clientv3/options.go b/clientv3/options.go index e158be60ea3..b82b7554d7c 100644 --- a/clientv3/options.go +++ b/clientv3/options.go @@ -23,9 +23,9 @@ import ( var ( // client-side handling retrying of request failures where data was not written to the wire or - // where server indicates it did not process the data. gPRC default is default is "FailFast(true)" + // where server indicates it did not process the data. gRPC default is default is "FailFast(true)" // but for etcd we default to "FailFast(false)" to minimize client request error responses due to - // transident failures. + // transient failures. defaultFailFast = grpc.FailFast(false) // client-side request send limit, gRPC default is math.MaxInt32 diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go index 3ace7f0f94a..c63047d38f7 100644 --- a/clientv3/retry_interceptor.go +++ b/clientv3/retry_interceptor.go @@ -123,7 +123,7 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp type serverStreamingRetryingStream struct { grpc.ClientStream client *Client - bufferedSends []interface{} // single messsage that the client can sen + bufferedSends []interface{} // single message that the client can sen receivedGood bool // indicates whether any prior receives were successful wasClosedSend bool // indicates that CloseSend was closed ctx context.Context @@ -294,7 +294,7 @@ func contextErrToGrpcErr(err error) error { var ( defaultOptions = &options{ retryPolicy: nonRepeatable, - max: 0, // disabed + max: 0, // disable backoffFunc: backoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10), retryAuth: true, } diff --git a/integration/embed_test.go b/integration/embed_test.go index 32e614ff742..6af58ea1bd8 100644 --- a/integration/embed_test.go +++ b/integration/embed_test.go @@ -108,9 +108,8 @@ func TestEmbedEtcd(t *testing.T) { } } -// TODO: reenable -//func TestEmbedEtcdGracefulStopSecure(t *testing.T) { testEmbedEtcdGracefulStop(t, true) } -//func TestEmbedEtcdGracefulStopInsecure(t *testing.T) { testEmbedEtcdGracefulStop(t, false) } +func TestEmbedEtcdGracefulStopSecure(t *testing.T) { testEmbedEtcdGracefulStop(t, true) } +func TestEmbedEtcdGracefulStopInsecure(t *testing.T) { testEmbedEtcdGracefulStop(t, false) } // testEmbedEtcdGracefulStop ensures embedded server stops // cutting existing transports. diff --git a/pkg/mock/mockserver/mockserver.go b/pkg/mock/mockserver/mockserver.go index e1ed10559e0..d72b40b45e7 100644 --- a/pkg/mock/mockserver/mockserver.go +++ b/pkg/mock/mockserver/mockserver.go @@ -135,11 +135,10 @@ func (ms *MockServers) StartAt(idx int) (err error) { pb.RegisterKVServer(svr, &mockKVServer{}) ms.Servers[idx].GrpcServer = svr + ms.wg.Add(1) go func(svr *grpc.Server, l net.Listener) { - ms.wg.Add(1) svr.Serve(l) }(ms.Servers[idx].GrpcServer, ms.Servers[idx].ln) - return nil } From dd520cebd4ab8828492b125c6f26aa2929be9d14 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 8 Jun 2018 14:28:43 -0700 Subject: [PATCH 31/39] clientv3: put "defaultCallOpts" back to "Client" object Signed-off-by: Gyuho Lee --- clientv3/client.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index 672926bebee..43210f69b98 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -401,12 +401,13 @@ func newClient(cfg *Config) (*Client, error) { ctx, cancel := context.WithCancel(baseCtx) client := &Client{ - conn: nil, - cfg: *cfg, - creds: creds, - ctx: ctx, - cancel: cancel, - mu: new(sync.Mutex), + conn: nil, + cfg: *cfg, + creds: creds, + ctx: ctx, + cancel: cancel, + mu: new(sync.Mutex), + callOpts: defaultCallOpts, } if cfg.Username != "" && cfg.Password != "" { From 08da08bb19d3dac63682018229f42176b6fb4113 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 8 Jun 2018 15:24:15 -0700 Subject: [PATCH 32/39] clientv3: clarify retry function names, do not retry on dial error Signed-off-by: Gyuho Lee --- clientv3/retry.go | 44 ++++++++++++++++++++--------------- clientv3/retry_interceptor.go | 11 +++++---- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/clientv3/retry.go b/clientv3/retry.go index 5ab171c2aac..6118aa55a05 100644 --- a/clientv3/retry.go +++ b/clientv3/retry.go @@ -47,46 +47,52 @@ type rpcFunc func(ctx context.Context) error type retryRPCFunc func(context.Context, rpcFunc, retryPolicy) error type retryStopErrFunc func(error) bool +// isSafeRetryImmutableRPC returns "true" when an immutable request is safe for retry. +// // immutable requests (e.g. Get) should be retried unless it's // an obvious server-side error (e.g. rpctypes.ErrRequestTooLarge). // -// "isRepeatableStopError" returns "true" when an immutable request -// is interrupted by server-side or gRPC-side error and its status -// code is not transient (!= codes.Unavailable). -// -// Returning "true" means retry should stop, since client cannot +// Returning "false" means retry should stop, since client cannot // handle itself even with retries. -func isRepeatableStopError(err error) bool { +func isSafeRetryImmutableRPC(err error) bool { eErr := rpctypes.Error(err) - // always stop retry on etcd errors if serverErr, ok := eErr.(rpctypes.EtcdError); ok && serverErr.Code() != codes.Unavailable { - return true + // interrupted by non-transient server-side or gRPC-side error + // client cannot handle itself (e.g. rpctypes.ErrCompacted) + return false } // only retry if unavailable ev, ok := status.FromError(err) if !ok { + // all errors from RPC is typed "grpc/status.(*statusError)" + // (ref. https://github.com/grpc/grpc-go/pull/1782) + // + // if the error type is not "grpc/status.(*statusError)", + // it could be from "Dial" + // TODO: do not retry for now + // ref. https://github.com/grpc/grpc-go/issues/1581 return false } - return ev.Code() != codes.Unavailable + return ev.Code() == codes.Unavailable } +// isSafeRetryMutableRPC returns "true" when a mutable request is safe for retry. +// // mutable requests (e.g. Put, Delete, Txn) should only be retried // when the status code is codes.Unavailable when initial connection -// has not been established (no pinned endpoint). -// -// "isNonRepeatableStopError" returns "true" when a mutable request -// is interrupted by non-transient error that client cannot handle itself, -// or transient error while the connection has already been established -// (pinned endpoint exists). +// has not been established (no endpoint is up). // -// Returning "true" means retry should stop, otherwise it violates +// Returning "false" means retry should stop, otherwise it violates // write-at-most-once semantics. -func isNonRepeatableStopError(err error) bool { +func isSafeRetryMutableRPC(err error) bool { if ev, ok := status.FromError(err); ok && ev.Code() != codes.Unavailable { - return true + // not safe for mutable RPCs + // e.g. interrupted by non-transient error that client cannot handle itself, + // or transient error while the connection has already been established + return false } desc := rpctypes.ErrorDesc(err) - return desc != "there is no address available" && desc != "there is no connection available" + return desc == "there is no address available" || desc == "there is no connection available" } type retryKVClient struct { diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go index c63047d38f7..21e0add7614 100644 --- a/clientv3/retry_interceptor.go +++ b/clientv3/retry_interceptor.go @@ -70,7 +70,7 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt } continue } - if !isRetriable(lastErr, callOpts) { + if !isSafeRetry(lastErr, callOpts) { return lastErr } } @@ -221,7 +221,7 @@ func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{} return true, err } - return isRetriable(err, s.callOpts), err + return isSafeRetry(err, s.callOpts), err } @@ -261,15 +261,16 @@ func waitRetryBackoff(attempt uint, ctx context.Context, callOpts *options) erro return nil } -func isRetriable(err error, callOpts *options) bool { +// isSafeRetry returns "true", if request is safe for retry with the given error. +func isSafeRetry(err error, callOpts *options) bool { if isContextError(err) { return false } switch callOpts.retryPolicy { case repeatable: - return !isRepeatableStopError(err) + return isSafeRetryImmutableRPC(err) case nonRepeatable: - return !isNonRepeatableStopError(err) + return isSafeRetryMutableRPC(err) default: logger.Warn("unrecognized retry policy", zap.String("retryPolicy", callOpts.retryPolicy.String())) return false From a76681073d5784ea2f71e51b5628048b8929356e Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Thu, 14 Jun 2018 13:33:52 -0700 Subject: [PATCH 33/39] clientv3: add "zap.Config" to replace global logger Signed-off-by: Gyuho Lee --- clientv3/client.go | 28 ++++++++++++++++++++-------- clientv3/config.go | 24 ++++++++++++++++++++++++ clientv3/retry_interceptor.go | 11 +++++------ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index 43210f69b98..8a0cc1cc4f6 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -46,15 +46,16 @@ var ( ErrOldCluster = errors.New("etcdclient: old cluster version") roundRobinBalancerName = fmt.Sprintf("etcd-%s", picker.RoundrobinBalanced.String()) - logger *zap.Logger ) func init() { - logger = zap.NewNop() // zap.NewExample() balancer.RegisterBuilder(balancer.Config{ Policy: picker.RoundrobinBalanced, Name: roundRobinBalancerName, - Logger: logger, + + // TODO: configure from clientv3.Config + Logger: zap.NewNop(), + // Logger: zap.NewExample(), }) } @@ -86,6 +87,8 @@ type Client struct { tokenCred *authTokenCredential callOpts []grpc.CallOption + + lg *zap.Logger } // New creates a new etcdv3 client from a given configuration. @@ -274,8 +277,8 @@ func (c *Client) dialSetupOpts(target string, dopts ...grpc.DialOption) (opts [] opts = append(opts, // Disable stream retry by default since go-grpc-middleware/retry does not support client streams. // Streams that are safe to retry are enabled individually. - grpc.WithStreamInterceptor(c.streamClientInterceptor(logger, withMax(0), rrBackoff)), - grpc.WithUnaryInterceptor(c.unaryClientInterceptor(logger, withMax(defaultUnaryMaxRetries), rrBackoff)), + grpc.WithStreamInterceptor(c.streamClientInterceptor(c.lg, withMax(0), rrBackoff)), + grpc.WithUnaryInterceptor(c.unaryClientInterceptor(c.lg, withMax(defaultUnaryMaxRetries), rrBackoff)), ) return opts, nil @@ -410,6 +413,16 @@ func newClient(cfg *Config) (*Client, error) { callOpts: defaultCallOpts, } + lcfg := DefaultLogConfig + if cfg.LogConfig != nil { + lcfg = *cfg.LogConfig + } + var err error + client.lg, err = lcfg.Build() + if err != nil { + return nil, err + } + if cfg.Username != "" && cfg.Password != "" { client.Username = cfg.Username client.Password = cfg.Password @@ -434,7 +447,6 @@ func newClient(cfg *Config) (*Client, error) { // Prepare a 'endpoint:///' resolver for the client and create a endpoint target to pass // to dial so the client knows to use this resolver. - var err error client.resolverGroup, err = endpoint.NewResolverGroup(fmt.Sprintf("client-%s", strconv.FormatInt(time.Now().UnixNano(), 36))) if err != nil { client.cancel() @@ -485,10 +497,10 @@ func (c *Client) roundRobinQuorumBackoff(waitBetween time.Duration, jitterFracti n := uint(len(c.Endpoints())) quorum := (n/2 + 1) if attempt%quorum == 0 { - logger.Info("backoff", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum), zap.Duration("waitBetween", waitBetween), zap.Float64("jitterFraction", jitterFraction)) + c.lg.Info("backoff", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum), zap.Duration("waitBetween", waitBetween), zap.Float64("jitterFraction", jitterFraction)) return backoffutils.JitterUp(waitBetween, jitterFraction) } - logger.Info("backoff skipped", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum)) + c.lg.Info("backoff skipped", zap.Uint("attempt", attempt), zap.Uint("quorum", quorum)) return 0 } } diff --git a/clientv3/config.go b/clientv3/config.go index 79d6e2a984f..c81d56466fc 100644 --- a/clientv3/config.go +++ b/clientv3/config.go @@ -19,6 +19,7 @@ import ( "crypto/tls" "time" + "go.uber.org/zap" "google.golang.org/grpc" ) @@ -72,4 +73,27 @@ type Config struct { // Context is the default client context; it can be used to cancel grpc dial out and // other operations that do not have an explicit context. Context context.Context + + // LogConfig configures client-side logger. + // If nil, use the default logger. + // TODO: configure balancer and gRPC logger + LogConfig *zap.Config +} + +// DefaultLogConfig is the default client logging configuration. +// Default log level is "Warn". Use "zap.InfoLevel" for debugging. +// Use "/dev/null" for output paths, to discard all logs. +var DefaultLogConfig = zap.Config{ + Level: zap.NewAtomicLevelAt(zap.WarnLevel), + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: zap.NewProductionEncoderConfig(), + + // Use "/dev/null" to discard all + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, } diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go index 21e0add7614..9fcec4291c0 100644 --- a/clientv3/retry_interceptor.go +++ b/clientv3/retry_interceptor.go @@ -70,7 +70,7 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt } continue } - if !isSafeRetry(lastErr, callOpts) { + if !isSafeRetry(c.lg, lastErr, callOpts) { return lastErr } } @@ -215,14 +215,13 @@ func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{} if s.callOpts.retryAuth && rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { gterr := s.client.getToken(s.ctx) if gterr != nil { - logger.Info("retry failed to fetch new auth token", zap.Error(gterr)) + s.client.lg.Info("retry failed to fetch new auth token", zap.Error(gterr)) return false, err // return the original error for simplicity } return true, err } - return isSafeRetry(err, s.callOpts), err - + return isSafeRetry(s.client.lg, err, s.callOpts), err } func (s *serverStreamingRetryingStream) reestablishStreamAndResendBuffer(callCtx context.Context) (grpc.ClientStream, error) { @@ -262,7 +261,7 @@ func waitRetryBackoff(attempt uint, ctx context.Context, callOpts *options) erro } // isSafeRetry returns "true", if request is safe for retry with the given error. -func isSafeRetry(err error, callOpts *options) bool { +func isSafeRetry(lg *zap.Logger, err error, callOpts *options) bool { if isContextError(err) { return false } @@ -272,7 +271,7 @@ func isSafeRetry(err error, callOpts *options) bool { case nonRepeatable: return isSafeRetryMutableRPC(err) default: - logger.Warn("unrecognized retry policy", zap.String("retryPolicy", callOpts.retryPolicy.String())) + lg.Warn("unrecognized retry policy", zap.String("retryPolicy", callOpts.retryPolicy.String())) return false } } From 6e521d2f3f1ec4e8dc7d9a4fb16079fc265da803 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 15 Jun 2018 07:19:11 -0700 Subject: [PATCH 34/39] clientv3: add "IsConnCanceled", deprecate "grpc.ErrClientConnClosing" Signed-off-by: Gyuho Lee --- clientv3/client.go | 20 ++++++++++++++++++++ clientv3/doc.go | 5 +++++ clientv3/integration/kv_test.go | 4 ++-- clientv3/integration/lease_test.go | 8 +++++--- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index 8a0cc1cc4f6..4d1270e8aef 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -614,6 +614,26 @@ func canceledByCaller(stopCtx context.Context, err error) bool { return err == context.Canceled || err == context.DeadlineExceeded } +// IsConnCanceled returns true, if error is from a closed gRPC connection. +// ref. https://github.com/grpc/grpc-go/pull/1854 +func IsConnCanceled(err error) bool { + if err == nil { + return false + } + // >= gRPC v1.10.x + s, ok := status.FromError(err) + if ok { + // connection is canceled or server has already closed the connection + return s.Code() == codes.Canceled || s.Message() == "transport is closing" + } + // >= gRPC v1.10.x + if err == context.Canceled { + return true + } + // <= gRPC v1.7.x returns 'errors.New("grpc: the client connection is closing")' + return strings.Contains(err.Error(), "grpc: the client connection is closing") +} + func getHost(ep string) string { url, uerr := url.Parse(ep) if uerr != nil || !strings.Contains(ep, "://") { diff --git a/clientv3/doc.go b/clientv3/doc.go index 717fbe435ea..0bb68618965 100644 --- a/clientv3/doc.go +++ b/clientv3/doc.go @@ -87,11 +87,16 @@ // go func() { cli.Close() }() // _, err := kvc.Get(ctx, "a") // if err != nil { +// // with etcd clientv3 <= v3.3 // if err == context.Canceled { // // grpc balancer calls 'Get' with an inflight client.Close // } else if err == grpc.ErrClientConnClosing { // // grpc balancer calls 'Get' after client.Close. // } +// // with etcd clientv3 >= v3.4 +// if clientv3.IsConnCanceled(err) { +// // gRPC client connection is closed +// } // } // package clientv3 diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index 394a8242b9d..1c7724acdfb 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -442,7 +442,7 @@ func TestKVGetErrConnClosed(t *testing.T) { go func() { defer close(donec) _, err := cli.Get(context.TODO(), "foo") - if err != nil && err != context.Canceled && err != grpc.ErrClientConnClosing { + if !clientv3.IsConnCanceled(err) { t.Fatalf("expected %v or %v, got %v", context.Canceled, grpc.ErrClientConnClosing, err) } }() @@ -474,7 +474,7 @@ func TestKVNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { _, err := cli.Get(context.TODO(), "foo") - if err != context.Canceled && err != grpc.ErrClientConnClosing { + if !clientv3.IsConnCanceled(err) { t.Fatalf("expected %v or %v, got %v", context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) diff --git a/clientv3/integration/lease_test.go b/clientv3/integration/lease_test.go index 75a0987c52e..bd7ee897bbf 100644 --- a/clientv3/integration/lease_test.go +++ b/clientv3/integration/lease_test.go @@ -296,7 +296,7 @@ func TestLeaseGrantErrConnClosed(t *testing.T) { go func() { defer close(donec) _, err := cli.Grant(context.TODO(), 5) - if err != nil && err != grpc.ErrClientConnClosing && err != context.Canceled { + if !clientv3.IsConnCanceled(err) { // grpc.ErrClientConnClosing if grpc-go balancer calls 'Get' after client.Close. // context.Canceled if grpc-go balancer calls 'Get' with an inflight client.Close. t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) @@ -328,7 +328,8 @@ func TestLeaseGrantNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Grant(context.TODO(), 5); err != context.Canceled && err != grpc.ErrClientConnClosing { + _, err := cli.Grant(context.TODO(), 5) + if !clientv3.IsConnCanceled(err) { t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) @@ -360,7 +361,8 @@ func TestLeaseRevokeNewAfterClose(t *testing.T) { donec := make(chan struct{}) go func() { - if _, err := cli.Revoke(context.TODO(), leaseID); err != context.Canceled && err != grpc.ErrClientConnClosing { + _, err := cli.Revoke(context.TODO(), leaseID) + if !clientv3.IsConnCanceled(err) { t.Fatalf("expected %v, %v or server unavailable, got %v", err != context.Canceled, grpc.ErrClientConnClosing, err) } close(donec) From d922069713d9b884b467823c99dd69528e6da099 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Fri, 15 Jun 2018 07:31:23 -0700 Subject: [PATCH 35/39] grpcproxy: fix "grpc.ErrClientConnClosing" handling Fix ``` go test -v -tags cluster_proxy -run TestWatchErrConnClosed ``` with gRPC >= v1.10 Signed-off-by: Gyuho Lee --- proxy/grpcproxy/leader.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/proxy/grpcproxy/leader.go b/proxy/grpcproxy/leader.go index 042c949b708..2e01b466f20 100644 --- a/proxy/grpcproxy/leader.go +++ b/proxy/grpcproxy/leader.go @@ -20,10 +20,8 @@ import ( "sync" "github.com/coreos/etcd/clientv3" - "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "golang.org/x/time/rate" - "google.golang.org/grpc" ) const ( @@ -69,7 +67,7 @@ func (l *leader) recvLoop() { } if cresp.Err() != nil { l.loseLeader() - if rpctypes.ErrorDesc(cresp.Err()) == grpc.ErrClientConnClosing.Error() { + if clientv3.IsConnCanceled(cresp.Err()) { close(l.disconnc) return } From 6572d605ad98cedb8d41528bb14d087e68978581 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 15 Jun 2018 13:05:47 -0700 Subject: [PATCH 36/39] vendor: Bump to grpc v1.12.2 --- Gopkg.lock | 5 +- vendor/google.golang.org/grpc/balancer.go | 13 +- .../grpc/balancer/balancer.go | 13 +- .../grpc/balancer/base/balancer.go | 1 - .../grpc/balancer_conn_wrappers.go | 2 +- .../grpc/balancer_v1_wrapper.go | 3 - .../google.golang.org/grpc/channelz/funcs.go | 573 +++++++++ .../google.golang.org/grpc/channelz/types.go | 418 +++++++ vendor/google.golang.org/grpc/clientconn.go | 216 +++- .../grpc/encoding/encoding.go | 2 +- vendor/google.golang.org/grpc/envconfig.go | 37 + vendor/google.golang.org/grpc/grpclb.go | 9 +- .../grpclb/grpc_lb_v1/messages/messages.pb.go | 410 +++++-- .../grpc/grpclb_remote_balancer.go | 36 +- vendor/google.golang.org/grpc/grpclb_util.go | 124 ++ .../google.golang.org/grpc/grpclog/grpclog.go | 3 + .../google.golang.org/grpc/grpclog/logger.go | 2 + .../grpc/health/grpc_health_v1/health.pb.go | 91 +- .../google.golang.org/grpc/health/health.go | 2 +- .../grpc/metadata/metadata.go | 28 +- .../grpc/naming/dns_resolver.go | 6 +- .../google.golang.org/grpc/naming/naming.go | 12 +- .../google.golang.org/grpc/picker_wrapper.go | 175 ++- .../grpc/resolver/dns/dns_resolver.go | 44 +- .../grpc/resolver/resolver.go | 10 +- .../grpc/resolver_conn_wrapper.go | 4 +- vendor/google.golang.org/grpc/rpc_util.go | 32 +- vendor/google.golang.org/grpc/server.go | 151 ++- .../google.golang.org/grpc/service_config.go | 15 +- vendor/google.golang.org/grpc/stream.go | 31 +- .../grpc/transport/controlbuf.go | 769 ++++++++++++ .../transport/{control.go => flowcontrol.go} | 272 ++--- .../grpc/transport/handler_server.go | 8 +- .../grpc/transport/http2_client.go | 1062 ++++++++--------- .../grpc/transport/http2_server.go | 763 ++++++------ .../grpc/transport/http_util.go | 80 +- .../grpc/transport/transport.go | 265 ++-- 37 files changed, 4068 insertions(+), 1619 deletions(-) create mode 100644 vendor/google.golang.org/grpc/channelz/funcs.go create mode 100644 vendor/google.golang.org/grpc/channelz/types.go create mode 100644 vendor/google.golang.org/grpc/envconfig.go create mode 100644 vendor/google.golang.org/grpc/transport/controlbuf.go rename vendor/google.golang.org/grpc/transport/{control.go => flowcontrol.go} (52%) diff --git a/Gopkg.lock b/Gopkg.lock index 300ebe0da50..19ab053bac6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -343,6 +343,7 @@ "balancer", "balancer/base", "balancer/roundrobin", + "channelz", "codes", "connectivity", "credentials", @@ -365,8 +366,8 @@ "tap", "transport" ] - revision = "d11072e7ca9811b1100b80ca0269ac831f06d024" - version = "v1.11.3" + revision = "7a6a684ca69eb4cae85ad0a484f2e531598c047b" + version = "v1.12.2" [[projects]] name = "gopkg.in/cheggaaa/pb.v1" diff --git a/vendor/google.golang.org/grpc/balancer.go b/vendor/google.golang.org/grpc/balancer.go index 300da6c5e87..e1730166cde 100644 --- a/vendor/google.golang.org/grpc/balancer.go +++ b/vendor/google.golang.org/grpc/balancer.go @@ -32,7 +32,8 @@ import ( ) // Address represents a server the client connects to. -// This is the EXPERIMENTAL API and may be changed or extended in the future. +// +// Deprecated: please use package balancer. type Address struct { // Addr is the server address on which a connection will be established. Addr string @@ -42,6 +43,8 @@ type Address struct { } // BalancerConfig specifies the configurations for Balancer. +// +// Deprecated: please use package balancer. type BalancerConfig struct { // DialCreds is the transport credential the Balancer implementation can // use to dial to a remote load balancer server. The Balancer implementations @@ -54,7 +57,8 @@ type BalancerConfig struct { } // BalancerGetOptions configures a Get call. -// This is the EXPERIMENTAL API and may be changed or extended in the future. +// +// Deprecated: please use package balancer. type BalancerGetOptions struct { // BlockingWait specifies whether Get should block when there is no // connected address. @@ -62,7 +66,8 @@ type BalancerGetOptions struct { } // Balancer chooses network addresses for RPCs. -// This is the EXPERIMENTAL API and may be changed or extended in the future. +// +// Deprecated: please use package balancer. type Balancer interface { // Start does the initialization work to bootstrap a Balancer. For example, // this function may start the name resolution and watch the updates. It will @@ -135,6 +140,8 @@ func downErrorf(timeout, temporary bool, format string, a ...interface{}) downEr // RoundRobin returns a Balancer that selects addresses round-robin. It uses r to watch // the name resolution updates and updates the addresses available correspondingly. +// +// Deprecated: please use package balancer/roundrobin. func RoundRobin(r naming.Resolver) Balancer { return &roundRobin{r: r} } diff --git a/vendor/google.golang.org/grpc/balancer/balancer.go b/vendor/google.golang.org/grpc/balancer/balancer.go index 219a2940c65..63b8d71371e 100644 --- a/vendor/google.golang.org/grpc/balancer/balancer.go +++ b/vendor/google.golang.org/grpc/balancer/balancer.go @@ -36,9 +36,12 @@ var ( m = make(map[string]Builder) ) -// Register registers the balancer builder to the balancer map. -// b.Name (lowercased) will be used as the name registered with -// this builder. +// Register registers the balancer builder to the balancer map. b.Name +// (lowercased) will be used as the name registered with this builder. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple Balancers are +// registered with the same name, the one registered last will take effect. func Register(b Builder) { m[strings.ToLower(b.Name())] = b } @@ -126,6 +129,8 @@ type BuildOptions struct { // to a remote load balancer server. The Balancer implementations // can ignore this if it doesn't need to talk to remote balancer. Dialer func(context.Context, string) (net.Conn, error) + // ChannelzParentID is the entity parent's channelz unique identification number. + ChannelzParentID int64 } // Builder creates a balancer. @@ -160,7 +165,7 @@ var ( ) // Picker is used by gRPC to pick a SubConn to send an RPC. -// Balancer is expected to generate a new picker from its snapshot everytime its +// Balancer is expected to generate a new picker from its snapshot every time its // internal state has changed. // // The pickers used by gRPC can be updated by ClientConn.UpdateBalancerState(). diff --git a/vendor/google.golang.org/grpc/balancer/base/balancer.go b/vendor/google.golang.org/grpc/balancer/base/balancer.go index 1e962b72403..23d13511bb2 100644 --- a/vendor/google.golang.org/grpc/balancer/base/balancer.go +++ b/vendor/google.golang.org/grpc/balancer/base/balancer.go @@ -146,7 +146,6 @@ func (b *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectiv } b.cc.UpdateBalancerState(b.state, b.picker) - return } // Close is a nop because base balancer doesn't have internal state to clean up, diff --git a/vendor/google.golang.org/grpc/balancer_conn_wrappers.go b/vendor/google.golang.org/grpc/balancer_conn_wrappers.go index db6f0ae3f09..c23f81706fb 100644 --- a/vendor/google.golang.org/grpc/balancer_conn_wrappers.go +++ b/vendor/google.golang.org/grpc/balancer_conn_wrappers.go @@ -115,7 +115,7 @@ func newCCBalancerWrapper(cc *ClientConn, b balancer.Builder, bopts balancer.Bui return ccb } -// watcher balancer functions sequencially, so the balancer can be implemeneted +// watcher balancer functions sequentially, so the balancer can be implemented // lock-free. func (ccb *ccBalancerWrapper) watcher() { for { diff --git a/vendor/google.golang.org/grpc/balancer_v1_wrapper.go b/vendor/google.golang.org/grpc/balancer_v1_wrapper.go index faabf87d001..b7abc6b7457 100644 --- a/vendor/google.golang.org/grpc/balancer_v1_wrapper.go +++ b/vendor/google.golang.org/grpc/balancer_v1_wrapper.go @@ -257,7 +257,6 @@ func (bw *balancerWrapper) HandleSubConnStateChange(sc balancer.SubConn, s conne // Remove state for this sc. delete(bw.connSt, sc) } - return } func (bw *balancerWrapper) HandleResolvedAddrs([]resolver.Address, error) { @@ -270,7 +269,6 @@ func (bw *balancerWrapper) HandleResolvedAddrs([]resolver.Address, error) { } // There should be a resolver inside the balancer. // All updates here, if any, are ignored. - return } func (bw *balancerWrapper) Close() { @@ -282,7 +280,6 @@ func (bw *balancerWrapper) Close() { close(bw.startCh) } bw.balancer.Close() - return } // The picker is the balancerWrapper itself. diff --git a/vendor/google.golang.org/grpc/channelz/funcs.go b/vendor/google.golang.org/grpc/channelz/funcs.go new file mode 100644 index 00000000000..586a0336b47 --- /dev/null +++ b/vendor/google.golang.org/grpc/channelz/funcs.go @@ -0,0 +1,573 @@ +/* + * + * Copyright 2018 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 channelz defines APIs for enabling channelz service, entry +// registration/deletion, and accessing channelz data. It also defines channelz +// metric struct formats. +// +// All APIs in this package are experimental. +package channelz + +import ( + "sort" + "sync" + "sync/atomic" + + "google.golang.org/grpc/grpclog" +) + +var ( + db dbWrapper + idGen idGenerator + // EntryPerPage defines the number of channelz entries to be shown on a web page. + EntryPerPage = 50 + curState int32 +) + +// TurnOn turns on channelz data collection. +func TurnOn() { + if !IsOn() { + NewChannelzStorage() + atomic.StoreInt32(&curState, 1) + } +} + +// IsOn returns whether channelz data collection is on. +func IsOn() bool { + return atomic.CompareAndSwapInt32(&curState, 1, 1) +} + +// dbWarpper wraps around a reference to internal channelz data storage, and +// provide synchronized functionality to set and get the reference. +type dbWrapper struct { + mu sync.RWMutex + DB *channelMap +} + +func (d *dbWrapper) set(db *channelMap) { + d.mu.Lock() + d.DB = db + d.mu.Unlock() +} + +func (d *dbWrapper) get() *channelMap { + d.mu.RLock() + defer d.mu.RUnlock() + return d.DB +} + +// NewChannelzStorage initializes channelz data storage and id generator. +// +// Note: This function is exported for testing purpose only. User should not call +// it in most cases. +func NewChannelzStorage() { + db.set(&channelMap{ + topLevelChannels: make(map[int64]struct{}), + channels: make(map[int64]*channel), + listenSockets: make(map[int64]*listenSocket), + normalSockets: make(map[int64]*normalSocket), + servers: make(map[int64]*server), + subChannels: make(map[int64]*subChannel), + }) + idGen.reset() +} + +// GetTopChannels returns a slice of top channel's ChannelMetric, along with a +// boolean indicating whether there's more top channels to be queried for. +// +// The arg id specifies that only top channel with id at or above it will be included +// in the result. The returned slice is up to a length of EntryPerPage, and is +// sorted in ascending id order. +func GetTopChannels(id int64) ([]*ChannelMetric, bool) { + return db.get().GetTopChannels(id) +} + +// GetServers returns a slice of server's ServerMetric, along with a +// boolean indicating whether there's more servers to be queried for. +// +// The arg id specifies that only server with id at or above it will be included +// in the result. The returned slice is up to a length of EntryPerPage, and is +// sorted in ascending id order. +func GetServers(id int64) ([]*ServerMetric, bool) { + return db.get().GetServers(id) +} + +// GetServerSockets returns a slice of server's (identified by id) normal socket's +// SocketMetric, along with a boolean indicating whether there's more sockets to +// be queried for. +// +// The arg startID specifies that only sockets with id at or above it will be +// included in the result. The returned slice is up to a length of EntryPerPage, +// and is sorted in ascending id order. +func GetServerSockets(id int64, startID int64) ([]*SocketMetric, bool) { + return db.get().GetServerSockets(id, startID) +} + +// GetChannel returns the ChannelMetric for the channel (identified by id). +func GetChannel(id int64) *ChannelMetric { + return db.get().GetChannel(id) +} + +// GetSubChannel returns the SubChannelMetric for the subchannel (identified by id). +func GetSubChannel(id int64) *SubChannelMetric { + return db.get().GetSubChannel(id) +} + +// GetSocket returns the SocketInternalMetric for the socket (identified by id). +func GetSocket(id int64) *SocketMetric { + return db.get().GetSocket(id) +} + +// RegisterChannel registers the given channel c in channelz database with ref +// as its reference name, and add it to the child list of its parent (identified +// by pid). pid = 0 means no parent. It returns the unique channelz tracking id +// assigned to this channel. +func RegisterChannel(c Channel, pid int64, ref string) int64 { + id := idGen.genID() + cn := &channel{ + refName: ref, + c: c, + subChans: make(map[int64]string), + nestedChans: make(map[int64]string), + id: id, + pid: pid, + } + if pid == 0 { + db.get().addChannel(id, cn, true, pid, ref) + } else { + db.get().addChannel(id, cn, false, pid, ref) + } + return id +} + +// RegisterSubChannel registers the given channel c in channelz database with ref +// as its reference name, and add it to the child list of its parent (identified +// by pid). It returns the unique channelz tracking id assigned to this subchannel. +func RegisterSubChannel(c Channel, pid int64, ref string) int64 { + if pid == 0 { + grpclog.Error("a SubChannel's parent id cannot be 0") + return 0 + } + id := idGen.genID() + sc := &subChannel{ + refName: ref, + c: c, + sockets: make(map[int64]string), + id: id, + pid: pid, + } + db.get().addSubChannel(id, sc, pid, ref) + return id +} + +// RegisterServer registers the given server s in channelz database. It returns +// the unique channelz tracking id assigned to this server. +func RegisterServer(s Server, ref string) int64 { + id := idGen.genID() + svr := &server{ + refName: ref, + s: s, + sockets: make(map[int64]string), + listenSockets: make(map[int64]string), + id: id, + } + db.get().addServer(id, svr) + return id +} + +// RegisterListenSocket registers the given listen socket s in channelz database +// with ref as its reference name, and add it to the child list of its parent +// (identified by pid). It returns the unique channelz tracking id assigned to +// this listen socket. +func RegisterListenSocket(s Socket, pid int64, ref string) int64 { + if pid == 0 { + grpclog.Error("a ListenSocket's parent id cannot be 0") + return 0 + } + id := idGen.genID() + ls := &listenSocket{refName: ref, s: s, id: id, pid: pid} + db.get().addListenSocket(id, ls, pid, ref) + return id +} + +// RegisterNormalSocket registers the given normal socket s in channelz database +// with ref as its reference name, and add it to the child list of its parent +// (identified by pid). It returns the unique channelz tracking id assigned to +// this normal socket. +func RegisterNormalSocket(s Socket, pid int64, ref string) int64 { + if pid == 0 { + grpclog.Error("a NormalSocket's parent id cannot be 0") + return 0 + } + id := idGen.genID() + ns := &normalSocket{refName: ref, s: s, id: id, pid: pid} + db.get().addNormalSocket(id, ns, pid, ref) + return id +} + +// RemoveEntry removes an entry with unique channelz trakcing id to be id from +// channelz database. +func RemoveEntry(id int64) { + db.get().removeEntry(id) +} + +// channelMap is the storage data structure for channelz. +// Methods of channelMap can be divided in two two categories with respect to locking. +// 1. Methods acquire the global lock. +// 2. Methods that can only be called when global lock is held. +// A second type of method need always to be called inside a first type of method. +type channelMap struct { + mu sync.RWMutex + topLevelChannels map[int64]struct{} + servers map[int64]*server + channels map[int64]*channel + subChannels map[int64]*subChannel + listenSockets map[int64]*listenSocket + normalSockets map[int64]*normalSocket +} + +func (c *channelMap) addServer(id int64, s *server) { + c.mu.Lock() + s.cm = c + c.servers[id] = s + c.mu.Unlock() +} + +func (c *channelMap) addChannel(id int64, cn *channel, isTopChannel bool, pid int64, ref string) { + c.mu.Lock() + cn.cm = c + c.channels[id] = cn + if isTopChannel { + c.topLevelChannels[id] = struct{}{} + } else { + c.findEntry(pid).addChild(id, cn) + } + c.mu.Unlock() +} + +func (c *channelMap) addSubChannel(id int64, sc *subChannel, pid int64, ref string) { + c.mu.Lock() + sc.cm = c + c.subChannels[id] = sc + c.findEntry(pid).addChild(id, sc) + c.mu.Unlock() +} + +func (c *channelMap) addListenSocket(id int64, ls *listenSocket, pid int64, ref string) { + c.mu.Lock() + ls.cm = c + c.listenSockets[id] = ls + c.findEntry(pid).addChild(id, ls) + c.mu.Unlock() +} + +func (c *channelMap) addNormalSocket(id int64, ns *normalSocket, pid int64, ref string) { + c.mu.Lock() + ns.cm = c + c.normalSockets[id] = ns + c.findEntry(pid).addChild(id, ns) + c.mu.Unlock() +} + +// removeEntry triggers the removal of an entry, which may not indeed delete the +// entry, if it has to wait on the deletion of its children, or may lead to a chain +// of entry deletion. For example, deleting the last socket of a gracefully shutting +// down server will lead to the server being also deleted. +func (c *channelMap) removeEntry(id int64) { + c.mu.Lock() + c.findEntry(id).triggerDelete() + c.mu.Unlock() +} + +// c.mu must be held by the caller. +func (c *channelMap) findEntry(id int64) entry { + var v entry + var ok bool + if v, ok = c.channels[id]; ok { + return v + } + if v, ok = c.subChannels[id]; ok { + return v + } + if v, ok = c.servers[id]; ok { + return v + } + if v, ok = c.listenSockets[id]; ok { + return v + } + if v, ok = c.normalSockets[id]; ok { + return v + } + return &dummyEntry{idNotFound: id} +} + +// c.mu must be held by the caller +// deleteEntry simply deletes an entry from the channelMap. Before calling this +// method, caller must check this entry is ready to be deleted, i.e removeEntry() +// has been called on it, and no children still exist. +// Conditionals are ordered by the expected frequency of deletion of each entity +// type, in order to optimize performance. +func (c *channelMap) deleteEntry(id int64) { + var ok bool + if _, ok = c.normalSockets[id]; ok { + delete(c.normalSockets, id) + return + } + if _, ok = c.subChannels[id]; ok { + delete(c.subChannels, id) + return + } + if _, ok = c.channels[id]; ok { + delete(c.channels, id) + delete(c.topLevelChannels, id) + return + } + if _, ok = c.listenSockets[id]; ok { + delete(c.listenSockets, id) + return + } + if _, ok = c.servers[id]; ok { + delete(c.servers, id) + return + } +} + +type int64Slice []int64 + +func (s int64Slice) Len() int { return len(s) } +func (s int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s int64Slice) Less(i, j int) bool { return s[i] < s[j] } + +func copyMap(m map[int64]string) map[int64]string { + n := make(map[int64]string) + for k, v := range m { + n[k] = v + } + return n +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func (c *channelMap) GetTopChannels(id int64) ([]*ChannelMetric, bool) { + c.mu.RLock() + l := len(c.topLevelChannels) + ids := make([]int64, 0, l) + cns := make([]*channel, 0, min(l, EntryPerPage)) + + for k := range c.topLevelChannels { + ids = append(ids, k) + } + sort.Sort(int64Slice(ids)) + idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= id }) + count := 0 + var end bool + var t []*ChannelMetric + for i, v := range ids[idx:] { + if count == EntryPerPage { + break + } + if cn, ok := c.channels[v]; ok { + cns = append(cns, cn) + t = append(t, &ChannelMetric{ + NestedChans: copyMap(cn.nestedChans), + SubChans: copyMap(cn.subChans), + }) + count++ + } + if i == len(ids[idx:])-1 { + end = true + break + } + } + c.mu.RUnlock() + if count == 0 { + end = true + } + + for i, cn := range cns { + t[i].ChannelData = cn.c.ChannelzMetric() + t[i].ID = cn.id + t[i].RefName = cn.refName + } + return t, end +} + +func (c *channelMap) GetServers(id int64) ([]*ServerMetric, bool) { + c.mu.RLock() + l := len(c.servers) + ids := make([]int64, 0, l) + ss := make([]*server, 0, min(l, EntryPerPage)) + for k := range c.servers { + ids = append(ids, k) + } + sort.Sort(int64Slice(ids)) + idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= id }) + count := 0 + var end bool + var s []*ServerMetric + for i, v := range ids[idx:] { + if count == EntryPerPage { + break + } + if svr, ok := c.servers[v]; ok { + ss = append(ss, svr) + s = append(s, &ServerMetric{ + ListenSockets: copyMap(svr.listenSockets), + }) + count++ + } + if i == len(ids[idx:])-1 { + end = true + break + } + } + c.mu.RUnlock() + if count == 0 { + end = true + } + + for i, svr := range ss { + s[i].ServerData = svr.s.ChannelzMetric() + s[i].ID = svr.id + s[i].RefName = svr.refName + } + return s, end +} + +func (c *channelMap) GetServerSockets(id int64, startID int64) ([]*SocketMetric, bool) { + var svr *server + var ok bool + c.mu.RLock() + if svr, ok = c.servers[id]; !ok { + // server with id doesn't exist. + c.mu.RUnlock() + return nil, true + } + svrskts := svr.sockets + l := len(svrskts) + ids := make([]int64, 0, l) + sks := make([]*normalSocket, 0, min(l, EntryPerPage)) + for k := range svrskts { + ids = append(ids, k) + } + sort.Sort((int64Slice(ids))) + idx := sort.Search(len(ids), func(i int) bool { return ids[i] >= id }) + count := 0 + var end bool + for i, v := range ids[idx:] { + if count == EntryPerPage { + break + } + if ns, ok := c.normalSockets[v]; ok { + sks = append(sks, ns) + count++ + } + if i == len(ids[idx:])-1 { + end = true + break + } + } + c.mu.RUnlock() + if count == 0 { + end = true + } + var s []*SocketMetric + for _, ns := range sks { + sm := &SocketMetric{} + sm.SocketData = ns.s.ChannelzMetric() + sm.ID = ns.id + sm.RefName = ns.refName + s = append(s, sm) + } + return s, end +} + +func (c *channelMap) GetChannel(id int64) *ChannelMetric { + cm := &ChannelMetric{} + var cn *channel + var ok bool + c.mu.RLock() + if cn, ok = c.channels[id]; !ok { + // channel with id doesn't exist. + c.mu.RUnlock() + return nil + } + cm.NestedChans = copyMap(cn.nestedChans) + cm.SubChans = copyMap(cn.subChans) + c.mu.RUnlock() + cm.ChannelData = cn.c.ChannelzMetric() + cm.ID = cn.id + cm.RefName = cn.refName + return cm +} + +func (c *channelMap) GetSubChannel(id int64) *SubChannelMetric { + cm := &SubChannelMetric{} + var sc *subChannel + var ok bool + c.mu.RLock() + if sc, ok = c.subChannels[id]; !ok { + // subchannel with id doesn't exist. + c.mu.RUnlock() + return nil + } + cm.Sockets = copyMap(sc.sockets) + c.mu.RUnlock() + cm.ChannelData = sc.c.ChannelzMetric() + cm.ID = sc.id + cm.RefName = sc.refName + return cm +} + +func (c *channelMap) GetSocket(id int64) *SocketMetric { + sm := &SocketMetric{} + c.mu.RLock() + if ls, ok := c.listenSockets[id]; ok { + c.mu.RUnlock() + sm.SocketData = ls.s.ChannelzMetric() + sm.ID = ls.id + sm.RefName = ls.refName + return sm + } + if ns, ok := c.normalSockets[id]; ok { + c.mu.RUnlock() + sm.SocketData = ns.s.ChannelzMetric() + sm.ID = ns.id + sm.RefName = ns.refName + return sm + } + c.mu.RUnlock() + return nil +} + +type idGenerator struct { + id int64 +} + +func (i *idGenerator) reset() { + atomic.StoreInt64(&i.id, 0) +} + +func (i *idGenerator) genID() int64 { + return atomic.AddInt64(&i.id, 1) +} diff --git a/vendor/google.golang.org/grpc/channelz/types.go b/vendor/google.golang.org/grpc/channelz/types.go new file mode 100644 index 00000000000..153d75340e4 --- /dev/null +++ b/vendor/google.golang.org/grpc/channelz/types.go @@ -0,0 +1,418 @@ +/* + * + * Copyright 2018 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 channelz + +import ( + "net" + "time" + + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/grpclog" +) + +// entry represents a node in the channelz database. +type entry interface { + // addChild adds a child e, whose channelz id is id to child list + addChild(id int64, e entry) + // deleteChild deletes a child with channelz id to be id from child list + deleteChild(id int64) + // triggerDelete tries to delete self from channelz database. However, if child + // list is not empty, then deletion from the database is on hold until the last + // child is deleted from database. + triggerDelete() + // deleteSelfIfReady check whether triggerDelete() has been called before, and whether child + // list is now empty. If both conditions are met, then delete self from database. + deleteSelfIfReady() +} + +// dummyEntry is a fake entry to handle entry not found case. +type dummyEntry struct { + idNotFound int64 +} + +func (d *dummyEntry) addChild(id int64, e entry) { + // Note: It is possible for a normal program to reach here under race condition. + // For example, there could be a race between ClientConn.Close() info being propagated + // to addrConn and http2Client. ClientConn.Close() cancel the context and result + // in http2Client to error. The error info is then caught by transport monitor + // and before addrConn.tearDown() is called in side ClientConn.Close(). Therefore, + // the addrConn will create a new transport. And when registering the new transport in + // channelz, its parent addrConn could have already been torn down and deleted + // from channelz tracking, and thus reach the code here. + grpclog.Infof("attempt to add child of type %T with id %d to a parent (id=%d) that doesn't currently exist", e, id, d.idNotFound) +} + +func (d *dummyEntry) deleteChild(id int64) { + // It is possible for a normal program to reach here under race condition. + // Refer to the example described in addChild(). + grpclog.Infof("attempt to delete child with id %d from a parent (id=%d) that doesn't currently exist", id, d.idNotFound) +} + +func (d *dummyEntry) triggerDelete() { + grpclog.Warningf("attempt to delete an entry (id=%d) that doesn't currently exist", d.idNotFound) +} + +func (*dummyEntry) deleteSelfIfReady() { + // code should not reach here. deleteSelfIfReady is always called on an existing entry. +} + +// ChannelMetric defines the info channelz provides for a specific Channel, which +// includes ChannelInternalMetric and channelz-specific data, such as channelz id, +// child list, etc. +type ChannelMetric struct { + // ID is the channelz id of this channel. + ID int64 + // RefName is the human readable reference string of this channel. + RefName string + // ChannelData contains channel internal metric reported by the channel through + // ChannelzMetric(). + ChannelData *ChannelInternalMetric + // NestedChans tracks the nested channel type children of this channel in the format of + // a map from nested channel channelz id to corresponding reference string. + NestedChans map[int64]string + // SubChans tracks the subchannel type children of this channel in the format of a + // map from subchannel channelz id to corresponding reference string. + SubChans map[int64]string + // Sockets tracks the socket type children of this channel in the format of a map + // from socket channelz id to corresponding reference string. + // Note current grpc implementation doesn't allow channel having sockets directly, + // therefore, this is field is unused. + Sockets map[int64]string +} + +// SubChannelMetric defines the info channelz provides for a specific SubChannel, +// which includes ChannelInternalMetric and channelz-specific data, such as +// channelz id, child list, etc. +type SubChannelMetric struct { + // ID is the channelz id of this subchannel. + ID int64 + // RefName is the human readable reference string of this subchannel. + RefName string + // ChannelData contains subchannel internal metric reported by the subchannel + // through ChannelzMetric(). + ChannelData *ChannelInternalMetric + // NestedChans tracks the nested channel type children of this subchannel in the format of + // a map from nested channel channelz id to corresponding reference string. + // Note current grpc implementation doesn't allow subchannel to have nested channels + // as children, therefore, this field is unused. + NestedChans map[int64]string + // SubChans tracks the subchannel type children of this subchannel in the format of a + // map from subchannel channelz id to corresponding reference string. + // Note current grpc implementation doesn't allow subchannel to have subchannels + // as children, therefore, this field is unused. + SubChans map[int64]string + // Sockets tracks the socket type children of this subchannel in the format of a map + // from socket channelz id to corresponding reference string. + Sockets map[int64]string +} + +// ChannelInternalMetric defines the struct that the implementor of Channel interface +// should return from ChannelzMetric(). +type ChannelInternalMetric struct { + // current connectivity state of the channel. + State connectivity.State + // The target this channel originally tried to connect to. May be absent + Target string + // The number of calls started on the channel. + CallsStarted int64 + // The number of calls that have completed with an OK status. + CallsSucceeded int64 + // The number of calls that have a completed with a non-OK status. + CallsFailed int64 + // The last time a call was started on the channel. + LastCallStartedTimestamp time.Time + //TODO: trace +} + +// Channel is the interface that should be satisfied in order to be tracked by +// channelz as Channel or SubChannel. +type Channel interface { + ChannelzMetric() *ChannelInternalMetric +} + +type channel struct { + refName string + c Channel + closeCalled bool + nestedChans map[int64]string + subChans map[int64]string + id int64 + pid int64 + cm *channelMap +} + +func (c *channel) addChild(id int64, e entry) { + switch v := e.(type) { + case *subChannel: + c.subChans[id] = v.refName + case *channel: + c.nestedChans[id] = v.refName + default: + grpclog.Errorf("cannot add a child (id = %d) of type %T to a channel", id, e) + } +} + +func (c *channel) deleteChild(id int64) { + delete(c.subChans, id) + delete(c.nestedChans, id) + c.deleteSelfIfReady() +} + +func (c *channel) triggerDelete() { + c.closeCalled = true + c.deleteSelfIfReady() +} + +func (c *channel) deleteSelfIfReady() { + if !c.closeCalled || len(c.subChans)+len(c.nestedChans) != 0 { + return + } + c.cm.deleteEntry(c.id) + // not top channel + if c.pid != 0 { + c.cm.findEntry(c.pid).deleteChild(c.id) + } +} + +type subChannel struct { + refName string + c Channel + closeCalled bool + sockets map[int64]string + id int64 + pid int64 + cm *channelMap +} + +func (sc *subChannel) addChild(id int64, e entry) { + if v, ok := e.(*normalSocket); ok { + sc.sockets[id] = v.refName + } else { + grpclog.Errorf("cannot add a child (id = %d) of type %T to a subChannel", id, e) + } +} + +func (sc *subChannel) deleteChild(id int64) { + delete(sc.sockets, id) + sc.deleteSelfIfReady() +} + +func (sc *subChannel) triggerDelete() { + sc.closeCalled = true + sc.deleteSelfIfReady() +} + +func (sc *subChannel) deleteSelfIfReady() { + if !sc.closeCalled || len(sc.sockets) != 0 { + return + } + sc.cm.deleteEntry(sc.id) + sc.cm.findEntry(sc.pid).deleteChild(sc.id) +} + +// SocketMetric defines the info channelz provides for a specific Socket, which +// includes SocketInternalMetric and channelz-specific data, such as channelz id, etc. +type SocketMetric struct { + // ID is the channelz id of this socket. + ID int64 + // RefName is the human readable reference string of this socket. + RefName string + // SocketData contains socket internal metric reported by the socket through + // ChannelzMetric(). + SocketData *SocketInternalMetric +} + +// SocketInternalMetric defines the struct that the implementor of Socket interface +// should return from ChannelzMetric(). +type SocketInternalMetric struct { + // The number of streams that have been started. + StreamsStarted int64 + // The number of streams that have ended successfully: + // On client side, receiving frame with eos bit set. + // On server side, sending frame with eos bit set. + StreamsSucceeded int64 + // The number of streams that have ended unsuccessfully: + // On client side, termination without receiving frame with eos bit set. + // On server side, termination without sending frame with eos bit set. + StreamsFailed int64 + // The number of messages successfully sent on this socket. + MessagesSent int64 + MessagesReceived int64 + // The number of keep alives sent. This is typically implemented with HTTP/2 + // ping messages. + KeepAlivesSent int64 + // The last time a stream was created by this endpoint. Usually unset for + // servers. + LastLocalStreamCreatedTimestamp time.Time + // The last time a stream was created by the remote endpoint. Usually unset + // for clients. + LastRemoteStreamCreatedTimestamp time.Time + // The last time a message was sent by this endpoint. + LastMessageSentTimestamp time.Time + // The last time a message was received by this endpoint. + LastMessageReceivedTimestamp time.Time + // The amount of window, granted to the local endpoint by the remote endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + LocalFlowControlWindow int64 + // The amount of window, granted to the remote endpoint by the local endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + RemoteFlowControlWindow int64 + // The locally bound address. + LocalAddr net.Addr + // The remote bound address. May be absent. + RemoteAddr net.Addr + // Optional, represents the name of the remote endpoint, if different than + // the original target name. + RemoteName string + //TODO: socket options + //TODO: Security +} + +// Socket is the interface that should be satisfied in order to be tracked by +// channelz as Socket. +type Socket interface { + ChannelzMetric() *SocketInternalMetric +} + +type listenSocket struct { + refName string + s Socket + id int64 + pid int64 + cm *channelMap +} + +func (ls *listenSocket) addChild(id int64, e entry) { + grpclog.Errorf("cannot add a child (id = %d) of type %T to a listen socket", id, e) +} + +func (ls *listenSocket) deleteChild(id int64) { + grpclog.Errorf("cannot delete a child (id = %d) from a listen socket", id) +} + +func (ls *listenSocket) triggerDelete() { + ls.cm.deleteEntry(ls.id) + ls.cm.findEntry(ls.pid).deleteChild(ls.id) +} + +func (ls *listenSocket) deleteSelfIfReady() { + grpclog.Errorf("cannot call deleteSelfIfReady on a listen socket") +} + +type normalSocket struct { + refName string + s Socket + id int64 + pid int64 + cm *channelMap +} + +func (ns *normalSocket) addChild(id int64, e entry) { + grpclog.Errorf("cannot add a child (id = %d) of type %T to a normal socket", id, e) +} + +func (ns *normalSocket) deleteChild(id int64) { + grpclog.Errorf("cannot delete a child (id = %d) from a normal socket", id) +} + +func (ns *normalSocket) triggerDelete() { + ns.cm.deleteEntry(ns.id) + ns.cm.findEntry(ns.pid).deleteChild(ns.id) +} + +func (ns *normalSocket) deleteSelfIfReady() { + grpclog.Errorf("cannot call deleteSelfIfReady on a normal socket") +} + +// ServerMetric defines the info channelz provides for a specific Server, which +// includes ServerInternalMetric and channelz-specific data, such as channelz id, +// child list, etc. +type ServerMetric struct { + // ID is the channelz id of this server. + ID int64 + // RefName is the human readable reference string of this server. + RefName string + // ServerData contains server internal metric reported by the server through + // ChannelzMetric(). + ServerData *ServerInternalMetric + // ListenSockets tracks the listener socket type children of this server in the + // format of a map from socket channelz id to corresponding reference string. + ListenSockets map[int64]string +} + +// ServerInternalMetric defines the struct that the implementor of Server interface +// should return from ChannelzMetric(). +type ServerInternalMetric struct { + // The number of incoming calls started on the server. + CallsStarted int64 + // The number of incoming calls that have completed with an OK status. + CallsSucceeded int64 + // The number of incoming calls that have a completed with a non-OK status. + CallsFailed int64 + // The last time a call was started on the server. + LastCallStartedTimestamp time.Time + //TODO: trace +} + +// Server is the interface to be satisfied in order to be tracked by channelz as +// Server. +type Server interface { + ChannelzMetric() *ServerInternalMetric +} + +type server struct { + refName string + s Server + closeCalled bool + sockets map[int64]string + listenSockets map[int64]string + id int64 + cm *channelMap +} + +func (s *server) addChild(id int64, e entry) { + switch v := e.(type) { + case *normalSocket: + s.sockets[id] = v.refName + case *listenSocket: + s.listenSockets[id] = v.refName + default: + grpclog.Errorf("cannot add a child (id = %d) of type %T to a server", id, e) + } +} + +func (s *server) deleteChild(id int64) { + delete(s.sockets, id) + delete(s.listenSockets, id) + s.deleteSelfIfReady() +} + +func (s *server) triggerDelete() { + s.closeCalled = true + s.deleteSelfIfReady() +} + +func (s *server) deleteSelfIfReady() { + if !s.closeCalled || len(s.sockets)+len(s.listenSockets) != 0 { + return + } + s.cm.deleteEntry(s.id) +} diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 6385407292a..e8d95b43b74 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -32,6 +32,7 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/balancer" _ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin. + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials" @@ -107,8 +108,10 @@ type dialOptions struct { // balancer, and also by WithBalancerName dial option. balancerBuilder balancer.Builder // This is to support grpclb. - resolverBuilder resolver.Builder - waitForHandshake bool + resolverBuilder resolver.Builder + waitForHandshake bool + channelzParentID int64 + disableServiceConfig bool } const ( @@ -116,6 +119,12 @@ const ( defaultClientMaxSendMessageSize = math.MaxInt32 ) +// RegisterChannelz turns on channelz service. +// This is an EXPERIMENTAL API. +func RegisterChannelz() { + channelz.TurnOn() +} + // DialOption configures how we set up the connection. type DialOption func(*dialOptions) @@ -160,7 +169,9 @@ func WithInitialConnWindowSize(s int32) DialOption { } } -// WithMaxMsgSize returns a DialOption which sets the maximum message size the client can receive. Deprecated: use WithDefaultCallOptions(MaxCallRecvMsgSize(s)) instead. +// WithMaxMsgSize returns a DialOption which sets the maximum message size the client can receive. +// +// Deprecated: use WithDefaultCallOptions(MaxCallRecvMsgSize(s)) instead. func WithMaxMsgSize(s int) DialOption { return WithDefaultCallOptions(MaxCallRecvMsgSize(s)) } @@ -243,7 +254,8 @@ func withResolverBuilder(b resolver.Builder) DialOption { } // WithServiceConfig returns a DialOption which has a channel to read the service configuration. -// DEPRECATED: service config should be received through name resolver, as specified here. +// +// Deprecated: service config should be received through name resolver, as specified here. // https://github.com/grpc/grpc/blob/master/doc/service_config.md func WithServiceConfig(c <-chan ServiceConfig) DialOption { return func(o *dialOptions) { @@ -314,6 +326,7 @@ func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption { // WithTimeout returns a DialOption that configures a timeout for dialing a ClientConn // initially. This is valid if and only if WithBlock() is present. +// // Deprecated: use DialContext and context.WithTimeout instead. func WithTimeout(d time.Duration) DialOption { return func(o *dialOptions) { @@ -396,15 +409,40 @@ func WithAuthority(a string) DialOption { } } +// WithChannelzParentID returns a DialOption that specifies the channelz ID of current ClientConn's +// parent. This function is used in nested channel creation (e.g. grpclb dial). +func WithChannelzParentID(id int64) DialOption { + return func(o *dialOptions) { + o.channelzParentID = id + } +} + +// WithDisableServiceConfig returns a DialOption that causes grpc to ignore any +// service config provided by the resolver and provides a hint to the resolver +// to not fetch service configs. +func WithDisableServiceConfig() DialOption { + return func(o *dialOptions) { + o.disableServiceConfig = true + } +} + // Dial creates a client connection to the given target. func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) } -// DialContext creates a client connection to the given target. ctx can be used to -// cancel or expire the pending connection. Once this function returns, the -// cancellation and expiration of ctx will be noop. Users should call ClientConn.Close -// to terminate all the pending operations after this function returns. +// DialContext creates a client connection to the given target. By default, it's +// a non-blocking dial (the function won't wait for connections to be +// established, and connecting happens in the background). To make it a blocking +// dial, use WithBlock() dial option. +// +// In the non-blocking case, the ctx does not act against the connection. It +// only controls the setup steps. +// +// In the blocking case, ctx can be used to cancel or expire the pending +// connection. Once this function returns, the cancellation and expiration of +// ctx will be noop. Users should call ClientConn.Close to terminate all the +// pending operations after this function returns. // // The target name syntax is defined in // https://github.com/grpc/grpc/blob/master/doc/naming.md. @@ -423,6 +461,14 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * opt(&cc.dopts) } + if channelz.IsOn() { + if cc.dopts.channelzParentID != 0 { + cc.channelzID = channelz.RegisterChannel(cc, cc.dopts.channelzParentID, target) + } else { + cc.channelzID = channelz.RegisterChannel(cc, 0, target) + } + } + if !cc.dopts.insecure { if cc.dopts.copts.TransportCredentials == nil { return nil, errNoTransportSecurity @@ -538,8 +584,9 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * credsClone = creds.Clone() } cc.balancerBuildOpts = balancer.BuildOptions{ - DialCreds: credsClone, - Dialer: cc.dopts.copts.Dialer, + DialCreds: credsClone, + Dialer: cc.dopts.copts.Dialer, + ChannelzParentID: cc.channelzID, } // Build the resolver. @@ -641,6 +688,13 @@ type ClientConn struct { preBalancerName string // previous balancer name. curAddresses []resolver.Address balancerWrapper *ccBalancerWrapper + + channelzID int64 // channelz unique identification number + czmu sync.RWMutex + callsStarted int64 + callsSucceeded int64 + callsFailed int64 + lastCallStartedTime time.Time } // WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or @@ -765,6 +819,8 @@ func (cc *ClientConn) switchBalancer(name string) { if cc.balancerWrapper != nil { cc.balancerWrapper.close() } + // Clear all stickiness state. + cc.blockingpicker.clearStickinessState() builder := balancer.Get(name) if builder == nil { @@ -804,6 +860,9 @@ func (cc *ClientConn) newAddrConn(addrs []resolver.Address) (*addrConn, error) { cc.mu.Unlock() return nil, ErrClientConnClosing } + if channelz.IsOn() { + ac.channelzID = channelz.RegisterSubChannel(ac, cc.channelzID, "") + } cc.conns[ac] = struct{}{} cc.mu.Unlock() return ac, nil @@ -822,6 +881,42 @@ func (cc *ClientConn) removeAddrConn(ac *addrConn, err error) { ac.tearDown(err) } +// ChannelzMetric returns ChannelInternalMetric of current ClientConn. +// This is an EXPERIMENTAL API. +func (cc *ClientConn) ChannelzMetric() *channelz.ChannelInternalMetric { + state := cc.GetState() + cc.czmu.RLock() + defer cc.czmu.RUnlock() + return &channelz.ChannelInternalMetric{ + State: state, + Target: cc.target, + CallsStarted: cc.callsStarted, + CallsSucceeded: cc.callsSucceeded, + CallsFailed: cc.callsFailed, + LastCallStartedTimestamp: cc.lastCallStartedTime, + } +} + +func (cc *ClientConn) incrCallsStarted() { + cc.czmu.Lock() + cc.callsStarted++ + // TODO(yuxuanli): will make this a time.Time pointer improve performance? + cc.lastCallStartedTime = time.Now() + cc.czmu.Unlock() +} + +func (cc *ClientConn) incrCallsSucceeded() { + cc.czmu.Lock() + cc.callsSucceeded++ + cc.czmu.Unlock() +} + +func (cc *ClientConn) incrCallsFailed() { + cc.czmu.Lock() + cc.callsFailed++ + cc.czmu.Unlock() +} + // connect starts to creating transport and also starts the transport monitor // goroutine for this ac. // It does nothing if the ac is not IDLE. @@ -901,7 +996,7 @@ func (cc *ClientConn) GetMethodConfig(method string) MethodConfig { m, ok := cc.sc.Methods[method] if !ok { i := strings.LastIndex(method, "/") - m, _ = cc.sc.Methods[method[:i+1]] + m = cc.sc.Methods[method[:i+1]] } return m } @@ -917,6 +1012,9 @@ func (cc *ClientConn) getTransport(ctx context.Context, failfast bool) (transpor // handleServiceConfig parses the service config string in JSON format to Go native // struct ServiceConfig, and store both the struct and the JSON string in ClientConn. func (cc *ClientConn) handleServiceConfig(js string) error { + if cc.dopts.disableServiceConfig { + return nil + } sc, err := parseServiceConfig(js) if err != nil { return err @@ -937,6 +1035,18 @@ func (cc *ClientConn) handleServiceConfig(js string) error { cc.balancerWrapper.handleResolvedAddrs(cc.curAddresses, nil) } } + + if envConfigStickinessOn { + var newStickinessMDKey string + if sc.stickinessMetadataKey != nil && *sc.stickinessMetadataKey != "" { + newStickinessMDKey = *sc.stickinessMetadataKey + } + // newStickinessMDKey is "" if one of the following happens: + // - stickinessMetadataKey is set to "" + // - stickinessMetadataKey field doesn't exist in service config + cc.blockingpicker.updateStickinessMDKey(strings.ToLower(newStickinessMDKey)) + } + cc.mu.Unlock() return nil } @@ -969,16 +1079,22 @@ func (cc *ClientConn) Close() error { bWrapper := cc.balancerWrapper cc.balancerWrapper = nil cc.mu.Unlock() + cc.blockingpicker.close() + if rWrapper != nil { rWrapper.close() } if bWrapper != nil { bWrapper.close() } + for ac := range conns { ac.tearDown(ErrClientConnClosing) } + if channelz.IsOn() { + channelz.RemoveEntry(cc.channelzID) + } return nil } @@ -1012,6 +1128,13 @@ type addrConn struct { // connectDeadline is the time by which all connection // negotiations must complete. connectDeadline time.Time + + channelzID int64 // channelz unique identification number + czmu sync.RWMutex + callsStarted int64 + callsSucceeded int64 + callsFailed int64 + lastCallStartedTime time.Time } // adjustParams updates parameters used to create transports upon @@ -1047,7 +1170,7 @@ func (ac *addrConn) errorf(format string, a ...interface{}) { // resetTransport recreates a transport to the address for ac. The old // transport will close itself on error or when the clientconn is closed. // The created transport must receive initial settings frame from the server. -// In case that doesnt happen, transportMonitor will kill the newly created +// In case that doesn't happen, transportMonitor will kill the newly created // transport after connectDeadline has expired. // In case there was an error on the transport before the settings frame was // received, resetTransport resumes connecting to backends after the one that @@ -1092,7 +1215,7 @@ func (ac *addrConn) resetTransport() error { connectDeadline = start.Add(dialDuration) ridx = 0 // Start connecting from the beginning. } else { - // Continue trying to conect with the same deadlines. + // Continue trying to connect with the same deadlines. connectRetryNum = ac.connectRetryNum backoffDeadline = ac.backoffDeadline connectDeadline = ac.connectDeadline @@ -1153,6 +1276,9 @@ func (ac *addrConn) createTransport(connectRetryNum, ridx int, backoffDeadline, // Do not cancel in the success path because of // this issue in Go1.6: https://github.com/golang/go/issues/15078. connectCtx, cancel := context.WithDeadline(ac.ctx, connectDeadline) + if channelz.IsOn() { + copts.ChannelzParentID = ac.channelzID + } newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, target, copts, onPrefaceReceipt) if err != nil { cancel() @@ -1208,6 +1334,10 @@ func (ac *addrConn) createTransport(connectRetryNum, ridx int, backoffDeadline, return true, nil } ac.mu.Lock() + if ac.state == connectivity.Shutdown { + ac.mu.Unlock() + return false, errConnClosing + } ac.state = connectivity.TransientFailure ac.cc.handleSubConnStateChange(ac.acbw, ac.state) ac.cc.resolveNow(resolver.ResolveNowOption{}) @@ -1242,7 +1372,20 @@ func (ac *addrConn) transportMonitor() { // Block until we receive a goaway or an error occurs. select { case <-t.GoAway(): + done := t.Error() + cleanup := t.Close + // Since this transport will be orphaned (won't have a transportMonitor) + // we need to launch a goroutine to keep track of clientConn.Close() + // happening since it might not be noticed by any other goroutine for a while. + go func() { + <-done + cleanup() + }() case <-t.Error(): + // In case this is triggered because clientConn.Close() + // was called, we want to immeditately close the transport + // since no other goroutine might notice it for a while. + t.Close() case <-cdeadline: ac.mu.Lock() // This implies that client received server preface. @@ -1386,7 +1529,9 @@ func (ac *addrConn) tearDown(err error) { close(ac.ready) ac.ready = nil } - return + if channelz.IsOn() { + channelz.RemoveEntry(ac.channelzID) + } } func (ac *addrConn) getState() connectivity.State { @@ -1395,6 +1540,49 @@ func (ac *addrConn) getState() connectivity.State { return ac.state } +func (ac *addrConn) getCurAddr() (ret resolver.Address) { + ac.mu.Lock() + ret = ac.curAddr + ac.mu.Unlock() + return +} + +func (ac *addrConn) ChannelzMetric() *channelz.ChannelInternalMetric { + ac.mu.Lock() + addr := ac.curAddr.Addr + ac.mu.Unlock() + state := ac.getState() + ac.czmu.RLock() + defer ac.czmu.RUnlock() + return &channelz.ChannelInternalMetric{ + State: state, + Target: addr, + CallsStarted: ac.callsStarted, + CallsSucceeded: ac.callsSucceeded, + CallsFailed: ac.callsFailed, + LastCallStartedTimestamp: ac.lastCallStartedTime, + } +} + +func (ac *addrConn) incrCallsStarted() { + ac.czmu.Lock() + ac.callsStarted++ + ac.lastCallStartedTime = time.Now() + ac.czmu.Unlock() +} + +func (ac *addrConn) incrCallsSucceeded() { + ac.czmu.Lock() + ac.callsSucceeded++ + ac.czmu.Unlock() +} + +func (ac *addrConn) incrCallsFailed() { + ac.czmu.Lock() + ac.callsFailed++ + ac.czmu.Unlock() +} + // ErrClientConnTimeout indicates that the ClientConn cannot establish the // underlying connections within the specified timeout. // diff --git a/vendor/google.golang.org/grpc/encoding/encoding.go b/vendor/google.golang.org/grpc/encoding/encoding.go index 8e26c194364..ade8b7cec73 100644 --- a/vendor/google.golang.org/grpc/encoding/encoding.go +++ b/vendor/google.golang.org/grpc/encoding/encoding.go @@ -82,7 +82,7 @@ type Codec interface { Name() string } -var registeredCodecs = make(map[string]Codec, 0) +var registeredCodecs = make(map[string]Codec) // RegisterCodec registers the provided Codec for use with all gRPC clients and // servers. diff --git a/vendor/google.golang.org/grpc/envconfig.go b/vendor/google.golang.org/grpc/envconfig.go new file mode 100644 index 00000000000..d50178e5171 --- /dev/null +++ b/vendor/google.golang.org/grpc/envconfig.go @@ -0,0 +1,37 @@ +/* + * + * Copyright 2018 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 grpc + +import ( + "os" + "strings" +) + +const ( + envConfigPrefix = "GRPC_GO_" + envConfigStickinessStr = envConfigPrefix + "STICKINESS" +) + +var ( + envConfigStickinessOn bool +) + +func init() { + envConfigStickinessOn = strings.EqualFold(os.Getenv(envConfigStickinessStr), "on") +} diff --git a/vendor/google.golang.org/grpc/grpclb.go b/vendor/google.golang.org/grpc/grpclb.go index d14a5d4090f..bc2b4452558 100644 --- a/vendor/google.golang.org/grpc/grpclb.go +++ b/vendor/google.golang.org/grpc/grpclb.go @@ -58,7 +58,7 @@ func (c *loadBalancerClient) BalanceLoad(ctx context.Context, opts ...CallOption ServerStreams: true, ClientStreams: true, } - stream, err := NewClientStream(ctx, desc, c.cc, "/grpc.lb.v1.LoadBalancer/BalanceLoad", opts...) + stream, err := c.cc.NewStream(ctx, desc, "/grpc.lb.v1.LoadBalancer/BalanceLoad", opts...) if err != nil { return nil, err } @@ -127,7 +127,7 @@ func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) bal } lb := &lbBalancer{ - cc: cc, + cc: newLBCacheClientConn(cc), target: target, opt: opt, fallbackTimeout: b.fallbackTimeout, @@ -145,7 +145,7 @@ func (b *lbBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) bal } type lbBalancer struct { - cc balancer.ClientConn + cc *lbCacheClientConn target string opt balancer.BuildOptions fallbackTimeout time.Duration @@ -220,7 +220,6 @@ func (lb *lbBalancer) regeneratePicker() { subConns: readySCs, stats: lb.clientStats, } - return } func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) { @@ -257,7 +256,6 @@ func (lb *lbBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivi } lb.cc.UpdateBalancerState(lb.state, lb.picker) - return } // fallbackToBackendsAfter blocks for fallbackTimeout and falls back to use @@ -339,4 +337,5 @@ func (lb *lbBalancer) Close() { if lb.ccRemoteLB != nil { lb.ccRemoteLB.Close() } + lb.cc.close() } diff --git a/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/messages/messages.pb.go b/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/messages/messages.pb.go index f4a27125a4f..b3b32b48e86 100644 --- a/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/messages/messages.pb.go +++ b/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/messages/messages.pb.go @@ -1,24 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // source: grpc_lb_v1/messages/messages.proto -/* -Package messages is a generated protocol buffer package. - -It is generated from these files: - grpc_lb_v1/messages/messages.proto - -It has these top-level messages: - Duration - Timestamp - LoadBalanceRequest - InitialLoadBalanceRequest - ClientStats - LoadBalanceResponse - InitialLoadBalanceResponse - ServerList - Server -*/ -package messages +package messages // import "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" import proto "github.com/golang/protobuf/proto" import fmt "fmt" @@ -45,13 +28,35 @@ type Duration struct { // of one second or more, a non-zero value for the `nanos` field must be // of the same sign as the `seconds` field. Must be from -999,999,999 // to +999,999,999 inclusive. - Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *Duration) Reset() { *m = Duration{} } -func (m *Duration) String() string { return proto.CompactTextString(m) } -func (*Duration) ProtoMessage() {} -func (*Duration) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *Duration) Reset() { *m = Duration{} } +func (m *Duration) String() string { return proto.CompactTextString(m) } +func (*Duration) ProtoMessage() {} +func (*Duration) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{0} +} +func (m *Duration) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Duration.Unmarshal(m, b) +} +func (m *Duration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Duration.Marshal(b, m, deterministic) +} +func (dst *Duration) XXX_Merge(src proto.Message) { + xxx_messageInfo_Duration.Merge(dst, src) +} +func (m *Duration) XXX_Size() int { + return xxx_messageInfo_Duration.Size(m) +} +func (m *Duration) XXX_DiscardUnknown() { + xxx_messageInfo_Duration.DiscardUnknown(m) +} + +var xxx_messageInfo_Duration proto.InternalMessageInfo func (m *Duration) GetSeconds() int64 { if m != nil { @@ -76,13 +81,35 @@ type Timestamp struct { // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. - Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Timestamp) Reset() { *m = Timestamp{} } +func (m *Timestamp) String() string { return proto.CompactTextString(m) } +func (*Timestamp) ProtoMessage() {} +func (*Timestamp) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{1} +} +func (m *Timestamp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Timestamp.Unmarshal(m, b) +} +func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic) +} +func (dst *Timestamp) XXX_Merge(src proto.Message) { + xxx_messageInfo_Timestamp.Merge(dst, src) +} +func (m *Timestamp) XXX_Size() int { + return xxx_messageInfo_Timestamp.Size(m) +} +func (m *Timestamp) XXX_DiscardUnknown() { + xxx_messageInfo_Timestamp.DiscardUnknown(m) } -func (m *Timestamp) Reset() { *m = Timestamp{} } -func (m *Timestamp) String() string { return proto.CompactTextString(m) } -func (*Timestamp) ProtoMessage() {} -func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +var xxx_messageInfo_Timestamp proto.InternalMessageInfo func (m *Timestamp) GetSeconds() int64 { if m != nil { @@ -103,12 +130,34 @@ type LoadBalanceRequest struct { // *LoadBalanceRequest_InitialRequest // *LoadBalanceRequest_ClientStats LoadBalanceRequestType isLoadBalanceRequest_LoadBalanceRequestType `protobuf_oneof:"load_balance_request_type"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LoadBalanceRequest) Reset() { *m = LoadBalanceRequest{} } +func (m *LoadBalanceRequest) String() string { return proto.CompactTextString(m) } +func (*LoadBalanceRequest) ProtoMessage() {} +func (*LoadBalanceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{2} +} +func (m *LoadBalanceRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LoadBalanceRequest.Unmarshal(m, b) +} +func (m *LoadBalanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LoadBalanceRequest.Marshal(b, m, deterministic) +} +func (dst *LoadBalanceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_LoadBalanceRequest.Merge(dst, src) +} +func (m *LoadBalanceRequest) XXX_Size() int { + return xxx_messageInfo_LoadBalanceRequest.Size(m) +} +func (m *LoadBalanceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_LoadBalanceRequest.DiscardUnknown(m) } -func (m *LoadBalanceRequest) Reset() { *m = LoadBalanceRequest{} } -func (m *LoadBalanceRequest) String() string { return proto.CompactTextString(m) } -func (*LoadBalanceRequest) ProtoMessage() {} -func (*LoadBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +var xxx_messageInfo_LoadBalanceRequest proto.InternalMessageInfo type isLoadBalanceRequest_LoadBalanceRequestType interface { isLoadBalanceRequest_LoadBalanceRequestType() @@ -204,12 +253,12 @@ func _LoadBalanceRequest_OneofSizer(msg proto.Message) (n int) { switch x := m.LoadBalanceRequestType.(type) { case *LoadBalanceRequest_InitialRequest: s := proto.Size(x.InitialRequest) - n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += 1 // tag and wire n += proto.SizeVarint(uint64(s)) n += s case *LoadBalanceRequest_ClientStats: s := proto.Size(x.ClientStats) - n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += 1 // tag and wire n += proto.SizeVarint(uint64(s)) n += s case nil: @@ -222,13 +271,35 @@ func _LoadBalanceRequest_OneofSizer(msg proto.Message) (n int) { type InitialLoadBalanceRequest struct { // Name of load balanced service (IE, balancer.service.com) // length should be less than 256 bytes. - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InitialLoadBalanceRequest) Reset() { *m = InitialLoadBalanceRequest{} } +func (m *InitialLoadBalanceRequest) String() string { return proto.CompactTextString(m) } +func (*InitialLoadBalanceRequest) ProtoMessage() {} +func (*InitialLoadBalanceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{3} +} +func (m *InitialLoadBalanceRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InitialLoadBalanceRequest.Unmarshal(m, b) +} +func (m *InitialLoadBalanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InitialLoadBalanceRequest.Marshal(b, m, deterministic) +} +func (dst *InitialLoadBalanceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_InitialLoadBalanceRequest.Merge(dst, src) +} +func (m *InitialLoadBalanceRequest) XXX_Size() int { + return xxx_messageInfo_InitialLoadBalanceRequest.Size(m) +} +func (m *InitialLoadBalanceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_InitialLoadBalanceRequest.DiscardUnknown(m) } -func (m *InitialLoadBalanceRequest) Reset() { *m = InitialLoadBalanceRequest{} } -func (m *InitialLoadBalanceRequest) String() string { return proto.CompactTextString(m) } -func (*InitialLoadBalanceRequest) ProtoMessage() {} -func (*InitialLoadBalanceRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } +var xxx_messageInfo_InitialLoadBalanceRequest proto.InternalMessageInfo func (m *InitialLoadBalanceRequest) GetName() string { if m != nil { @@ -256,13 +327,35 @@ type ClientStats struct { NumCallsFinishedWithClientFailedToSend int64 `protobuf:"varint,6,opt,name=num_calls_finished_with_client_failed_to_send,json=numCallsFinishedWithClientFailedToSend" json:"num_calls_finished_with_client_failed_to_send,omitempty"` // The total number of RPCs that finished and are known to have been received // by a server. - NumCallsFinishedKnownReceived int64 `protobuf:"varint,7,opt,name=num_calls_finished_known_received,json=numCallsFinishedKnownReceived" json:"num_calls_finished_known_received,omitempty"` + NumCallsFinishedKnownReceived int64 `protobuf:"varint,7,opt,name=num_calls_finished_known_received,json=numCallsFinishedKnownReceived" json:"num_calls_finished_known_received,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClientStats) Reset() { *m = ClientStats{} } +func (m *ClientStats) String() string { return proto.CompactTextString(m) } +func (*ClientStats) ProtoMessage() {} +func (*ClientStats) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{4} +} +func (m *ClientStats) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClientStats.Unmarshal(m, b) +} +func (m *ClientStats) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClientStats.Marshal(b, m, deterministic) +} +func (dst *ClientStats) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientStats.Merge(dst, src) +} +func (m *ClientStats) XXX_Size() int { + return xxx_messageInfo_ClientStats.Size(m) +} +func (m *ClientStats) XXX_DiscardUnknown() { + xxx_messageInfo_ClientStats.DiscardUnknown(m) } -func (m *ClientStats) Reset() { *m = ClientStats{} } -func (m *ClientStats) String() string { return proto.CompactTextString(m) } -func (*ClientStats) ProtoMessage() {} -func (*ClientStats) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +var xxx_messageInfo_ClientStats proto.InternalMessageInfo func (m *ClientStats) GetTimestamp() *Timestamp { if m != nil { @@ -318,12 +411,34 @@ type LoadBalanceResponse struct { // *LoadBalanceResponse_InitialResponse // *LoadBalanceResponse_ServerList LoadBalanceResponseType isLoadBalanceResponse_LoadBalanceResponseType `protobuf_oneof:"load_balance_response_type"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LoadBalanceResponse) Reset() { *m = LoadBalanceResponse{} } +func (m *LoadBalanceResponse) String() string { return proto.CompactTextString(m) } +func (*LoadBalanceResponse) ProtoMessage() {} +func (*LoadBalanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{5} +} +func (m *LoadBalanceResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LoadBalanceResponse.Unmarshal(m, b) +} +func (m *LoadBalanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LoadBalanceResponse.Marshal(b, m, deterministic) +} +func (dst *LoadBalanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_LoadBalanceResponse.Merge(dst, src) +} +func (m *LoadBalanceResponse) XXX_Size() int { + return xxx_messageInfo_LoadBalanceResponse.Size(m) +} +func (m *LoadBalanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_LoadBalanceResponse.DiscardUnknown(m) } -func (m *LoadBalanceResponse) Reset() { *m = LoadBalanceResponse{} } -func (m *LoadBalanceResponse) String() string { return proto.CompactTextString(m) } -func (*LoadBalanceResponse) ProtoMessage() {} -func (*LoadBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } +var xxx_messageInfo_LoadBalanceResponse proto.InternalMessageInfo type isLoadBalanceResponse_LoadBalanceResponseType interface { isLoadBalanceResponse_LoadBalanceResponseType() @@ -419,12 +534,12 @@ func _LoadBalanceResponse_OneofSizer(msg proto.Message) (n int) { switch x := m.LoadBalanceResponseType.(type) { case *LoadBalanceResponse_InitialResponse: s := proto.Size(x.InitialResponse) - n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += 1 // tag and wire n += proto.SizeVarint(uint64(s)) n += s case *LoadBalanceResponse_ServerList: s := proto.Size(x.ServerList) - n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += 1 // tag and wire n += proto.SizeVarint(uint64(s)) n += s case nil: @@ -445,12 +560,34 @@ type InitialLoadBalanceResponse struct { // to the load balancer. Stats should only be reported when the duration is // positive. ClientStatsReportInterval *Duration `protobuf:"bytes,2,opt,name=client_stats_report_interval,json=clientStatsReportInterval" json:"client_stats_report_interval,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *InitialLoadBalanceResponse) Reset() { *m = InitialLoadBalanceResponse{} } +func (m *InitialLoadBalanceResponse) String() string { return proto.CompactTextString(m) } +func (*InitialLoadBalanceResponse) ProtoMessage() {} +func (*InitialLoadBalanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{6} +} +func (m *InitialLoadBalanceResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_InitialLoadBalanceResponse.Unmarshal(m, b) +} +func (m *InitialLoadBalanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_InitialLoadBalanceResponse.Marshal(b, m, deterministic) +} +func (dst *InitialLoadBalanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_InitialLoadBalanceResponse.Merge(dst, src) +} +func (m *InitialLoadBalanceResponse) XXX_Size() int { + return xxx_messageInfo_InitialLoadBalanceResponse.Size(m) +} +func (m *InitialLoadBalanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_InitialLoadBalanceResponse.DiscardUnknown(m) } -func (m *InitialLoadBalanceResponse) Reset() { *m = InitialLoadBalanceResponse{} } -func (m *InitialLoadBalanceResponse) String() string { return proto.CompactTextString(m) } -func (*InitialLoadBalanceResponse) ProtoMessage() {} -func (*InitialLoadBalanceResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } +var xxx_messageInfo_InitialLoadBalanceResponse proto.InternalMessageInfo func (m *InitialLoadBalanceResponse) GetLoadBalancerDelegate() string { if m != nil { @@ -471,13 +608,35 @@ type ServerList struct { // be updated when server resolutions change or as needed to balance load // across more servers. The client should consume the server list in order // unless instructed otherwise via the client_config. - Servers []*Server `protobuf:"bytes,1,rep,name=servers" json:"servers,omitempty"` + Servers []*Server `protobuf:"bytes,1,rep,name=servers" json:"servers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServerList) Reset() { *m = ServerList{} } +func (m *ServerList) String() string { return proto.CompactTextString(m) } +func (*ServerList) ProtoMessage() {} +func (*ServerList) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{7} +} +func (m *ServerList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServerList.Unmarshal(m, b) +} +func (m *ServerList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServerList.Marshal(b, m, deterministic) +} +func (dst *ServerList) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServerList.Merge(dst, src) +} +func (m *ServerList) XXX_Size() int { + return xxx_messageInfo_ServerList.Size(m) +} +func (m *ServerList) XXX_DiscardUnknown() { + xxx_messageInfo_ServerList.DiscardUnknown(m) } -func (m *ServerList) Reset() { *m = ServerList{} } -func (m *ServerList) String() string { return proto.CompactTextString(m) } -func (*ServerList) ProtoMessage() {} -func (*ServerList) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } +var xxx_messageInfo_ServerList proto.InternalMessageInfo func (m *ServerList) GetServers() []*Server { if m != nil { @@ -508,13 +667,35 @@ type Server struct { DropForRateLimiting bool `protobuf:"varint,4,opt,name=drop_for_rate_limiting,json=dropForRateLimiting" json:"drop_for_rate_limiting,omitempty"` // Indicates whether this particular request should be dropped by the client // for load balancing. - DropForLoadBalancing bool `protobuf:"varint,5,opt,name=drop_for_load_balancing,json=dropForLoadBalancing" json:"drop_for_load_balancing,omitempty"` + DropForLoadBalancing bool `protobuf:"varint,5,opt,name=drop_for_load_balancing,json=dropForLoadBalancing" json:"drop_for_load_balancing,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *Server) Reset() { *m = Server{} } -func (m *Server) String() string { return proto.CompactTextString(m) } -func (*Server) ProtoMessage() {} -func (*Server) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } +func (m *Server) Reset() { *m = Server{} } +func (m *Server) String() string { return proto.CompactTextString(m) } +func (*Server) ProtoMessage() {} +func (*Server) Descriptor() ([]byte, []int) { + return fileDescriptor_messages_b81c731f0e83edbd, []int{8} +} +func (m *Server) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Server.Unmarshal(m, b) +} +func (m *Server) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Server.Marshal(b, m, deterministic) +} +func (dst *Server) XXX_Merge(src proto.Message) { + xxx_messageInfo_Server.Merge(dst, src) +} +func (m *Server) XXX_Size() int { + return xxx_messageInfo_Server.Size(m) +} +func (m *Server) XXX_DiscardUnknown() { + xxx_messageInfo_Server.DiscardUnknown(m) +} + +var xxx_messageInfo_Server proto.InternalMessageInfo func (m *Server) GetIpAddress() []byte { if m != nil { @@ -563,53 +744,56 @@ func init() { proto.RegisterType((*Server)(nil), "grpc.lb.v1.Server") } -func init() { proto.RegisterFile("grpc_lb_v1/messages/messages.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 709 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdd, 0x4e, 0x1b, 0x3b, - 0x10, 0x26, 0x27, 0x01, 0x92, 0x09, 0x3a, 0xe4, 0x98, 0x1c, 0x08, 0x14, 0x24, 0xba, 0x52, 0x69, - 0x54, 0xd1, 0x20, 0xa0, 0xbd, 0xe8, 0xcf, 0x45, 0x1b, 0x10, 0x0a, 0x2d, 0x17, 0x95, 0x43, 0x55, - 0xa9, 0x52, 0x65, 0x39, 0xd9, 0x21, 0x58, 0x6c, 0xec, 0xad, 0xed, 0x04, 0xf5, 0x11, 0xfa, 0x28, - 0x7d, 0x8c, 0xaa, 0xcf, 0xd0, 0xf7, 0xa9, 0xd6, 0xbb, 0x9b, 0x5d, 0x20, 0x80, 0x7a, 0x67, 0x8f, - 0xbf, 0xf9, 0xbe, 0xf1, 0xac, 0xbf, 0x59, 0xf0, 0x06, 0x3a, 0xec, 0xb3, 0xa0, 0xc7, 0xc6, 0xbb, - 0x3b, 0x43, 0x34, 0x86, 0x0f, 0xd0, 0x4c, 0x16, 0xad, 0x50, 0x2b, 0xab, 0x08, 0x44, 0x98, 0x56, - 0xd0, 0x6b, 0x8d, 0x77, 0xbd, 0x97, 0x50, 0x3e, 0x1c, 0x69, 0x6e, 0x85, 0x92, 0xa4, 0x01, 0xf3, - 0x06, 0xfb, 0x4a, 0xfa, 0xa6, 0x51, 0xd8, 0x2c, 0x34, 0x8b, 0x34, 0xdd, 0x92, 0x3a, 0xcc, 0x4a, - 0x2e, 0x95, 0x69, 0xfc, 0xb3, 0x59, 0x68, 0xce, 0xd2, 0x78, 0xe3, 0xbd, 0x82, 0xca, 0xa9, 0x18, - 0xa2, 0xb1, 0x7c, 0x18, 0xfe, 0x75, 0xf2, 0xcf, 0x02, 0x90, 0x13, 0xc5, 0xfd, 0x36, 0x0f, 0xb8, - 0xec, 0x23, 0xc5, 0xaf, 0x23, 0x34, 0x96, 0x7c, 0x80, 0x45, 0x21, 0x85, 0x15, 0x3c, 0x60, 0x3a, - 0x0e, 0x39, 0xba, 0xea, 0xde, 0xa3, 0x56, 0x56, 0x75, 0xeb, 0x38, 0x86, 0xdc, 0xcc, 0xef, 0xcc, - 0xd0, 0x7f, 0x93, 0xfc, 0x94, 0xf1, 0x35, 0x2c, 0xf4, 0x03, 0x81, 0xd2, 0x32, 0x63, 0xb9, 0x8d, - 0xab, 0xa8, 0xee, 0xad, 0xe4, 0xe9, 0x0e, 0xdc, 0x79, 0x37, 0x3a, 0xee, 0xcc, 0xd0, 0x6a, 0x3f, - 0xdb, 0xb6, 0x1f, 0xc0, 0x6a, 0xa0, 0xb8, 0xcf, 0x7a, 0xb1, 0x4c, 0x5a, 0x14, 0xb3, 0xdf, 0x42, - 0xf4, 0x76, 0x60, 0xf5, 0xd6, 0x4a, 0x08, 0x81, 0x92, 0xe4, 0x43, 0x74, 0xe5, 0x57, 0xa8, 0x5b, - 0x7b, 0xdf, 0x4b, 0x50, 0xcd, 0x89, 0x91, 0x7d, 0xa8, 0xd8, 0xb4, 0x83, 0xc9, 0x3d, 0xff, 0xcf, - 0x17, 0x36, 0x69, 0x2f, 0xcd, 0x70, 0xe4, 0x09, 0xfc, 0x27, 0x47, 0x43, 0xd6, 0xe7, 0x41, 0x60, - 0xa2, 0x3b, 0x69, 0x8b, 0xbe, 0xbb, 0x55, 0x91, 0x2e, 0xca, 0xd1, 0xf0, 0x20, 0x8a, 0x77, 0xe3, - 0x30, 0xd9, 0x06, 0x92, 0x61, 0xcf, 0x84, 0x14, 0xe6, 0x1c, 0xfd, 0x46, 0xd1, 0x81, 0x6b, 0x29, - 0xf8, 0x28, 0x89, 0x13, 0x06, 0xad, 0x9b, 0x68, 0x76, 0x29, 0xec, 0x39, 0xf3, 0xb5, 0x0a, 0xd9, - 0x99, 0xd2, 0x4c, 0x73, 0x8b, 0x2c, 0x10, 0x43, 0x61, 0x85, 0x1c, 0x34, 0x4a, 0x8e, 0xe9, 0xf1, - 0x75, 0xa6, 0x4f, 0xc2, 0x9e, 0x1f, 0x6a, 0x15, 0x1e, 0x29, 0x4d, 0xb9, 0xc5, 0x93, 0x04, 0x4e, - 0x38, 0xec, 0xdc, 0x2b, 0x90, 0x6b, 0x77, 0xa4, 0x30, 0xeb, 0x14, 0x9a, 0x77, 0x28, 0x64, 0xbd, - 0x8f, 0x24, 0xbe, 0xc0, 0xd3, 0xdb, 0x24, 0x92, 0x67, 0x70, 0xc6, 0x45, 0x80, 0x3e, 0xb3, 0x8a, - 0x19, 0x94, 0x7e, 0x63, 0xce, 0x09, 0x6c, 0x4d, 0x13, 0x88, 0x3f, 0xd5, 0x91, 0xc3, 0x9f, 0xaa, - 0x2e, 0x4a, 0x9f, 0x74, 0xe0, 0xe1, 0x14, 0xfa, 0x0b, 0xa9, 0x2e, 0x25, 0xd3, 0xd8, 0x47, 0x31, - 0x46, 0xbf, 0x31, 0xef, 0x28, 0x37, 0xae, 0x53, 0xbe, 0x8f, 0x50, 0x34, 0x01, 0x79, 0xbf, 0x0a, - 0xb0, 0x74, 0xe5, 0xd9, 0x98, 0x50, 0x49, 0x83, 0xa4, 0x0b, 0xb5, 0xcc, 0x01, 0x71, 0x2c, 0x79, - 0x1a, 0x5b, 0xf7, 0x59, 0x20, 0x46, 0x77, 0x66, 0xe8, 0xe2, 0xc4, 0x03, 0x09, 0xe9, 0x0b, 0xa8, - 0x1a, 0xd4, 0x63, 0xd4, 0x2c, 0x10, 0xc6, 0x26, 0x1e, 0x58, 0xce, 0xf3, 0x75, 0xdd, 0xf1, 0x89, - 0x70, 0x1e, 0x02, 0x33, 0xd9, 0xb5, 0xd7, 0x61, 0xed, 0x9a, 0x03, 0x62, 0xce, 0xd8, 0x02, 0x3f, - 0x0a, 0xb0, 0x76, 0x7b, 0x29, 0xe4, 0x19, 0x2c, 0xe7, 0x93, 0x35, 0xf3, 0x31, 0xc0, 0x01, 0xb7, - 0xa9, 0x2d, 0xea, 0x41, 0x96, 0xa4, 0x0f, 0x93, 0x33, 0xf2, 0x11, 0xd6, 0xf3, 0x96, 0x65, 0x1a, - 0x43, 0xa5, 0x2d, 0x13, 0xd2, 0xa2, 0x1e, 0xf3, 0x20, 0x29, 0xbf, 0x9e, 0x2f, 0x3f, 0x1d, 0x62, - 0x74, 0x35, 0xe7, 0x5e, 0xea, 0xf2, 0x8e, 0x93, 0x34, 0xef, 0x0d, 0x40, 0x76, 0x4b, 0xb2, 0x1d, - 0x0d, 0xac, 0x68, 0x17, 0x0d, 0xac, 0x62, 0xb3, 0xba, 0x47, 0x6e, 0xb6, 0x83, 0xa6, 0x90, 0x77, - 0xa5, 0x72, 0xb1, 0x56, 0xf2, 0x7e, 0x17, 0x60, 0x2e, 0x3e, 0x21, 0x1b, 0x00, 0x22, 0x64, 0xdc, - 0xf7, 0x35, 0x9a, 0x78, 0xe4, 0x2d, 0xd0, 0x8a, 0x08, 0xdf, 0xc6, 0x81, 0xc8, 0xfd, 0x91, 0x76, - 0x32, 0xf3, 0xdc, 0x3a, 0x32, 0xe3, 0x95, 0x4e, 0x5a, 0x75, 0x81, 0xd2, 0x99, 0xb1, 0x42, 0x6b, - 0xb9, 0x46, 0x9c, 0x46, 0x71, 0xb2, 0x0f, 0xcb, 0x77, 0x98, 0xae, 0x4c, 0x97, 0xfc, 0x29, 0x06, - 0x7b, 0x0e, 0x2b, 0x77, 0x19, 0xa9, 0x4c, 0xeb, 0xfe, 0x14, 0xd3, 0xb4, 0xe1, 0x73, 0x39, 0xfd, - 0x47, 0xf4, 0xe6, 0xdc, 0x4f, 0x62, 0xff, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa3, 0x36, 0x86, - 0xa6, 0x4a, 0x06, 0x00, 0x00, +func init() { + proto.RegisterFile("grpc_lb_v1/messages/messages.proto", fileDescriptor_messages_b81c731f0e83edbd) +} + +var fileDescriptor_messages_b81c731f0e83edbd = []byte{ + // 731 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdd, 0x4e, 0x1b, 0x39, + 0x14, 0x26, 0x9b, 0x00, 0xc9, 0x09, 0x5a, 0xb2, 0x26, 0x0b, 0x81, 0x05, 0x89, 0x1d, 0x69, 0xd9, + 0x68, 0xc5, 0x4e, 0x04, 0xd9, 0xbd, 0xe8, 0xcf, 0x45, 0x1b, 0x10, 0x0a, 0x2d, 0x17, 0x95, 0x43, + 0x55, 0xa9, 0x52, 0x65, 0x39, 0x19, 0x33, 0x58, 0x38, 0xf6, 0xd4, 0x76, 0x82, 0xfa, 0x08, 0x7d, + 0x94, 0x3e, 0x46, 0xd5, 0x67, 0xe8, 0xfb, 0x54, 0xe3, 0x99, 0xc9, 0x0c, 0x10, 0x40, 0xbd, 0x89, + 0xec, 0xe3, 0xef, 0x7c, 0xdf, 0xf1, 0x89, 0xbf, 0x33, 0xe0, 0x85, 0x3a, 0x1a, 0x11, 0x31, 0x24, + 0xd3, 0x83, 0xce, 0x98, 0x19, 0x43, 0x43, 0x66, 0x66, 0x0b, 0x3f, 0xd2, 0xca, 0x2a, 0x04, 0x31, + 0xc6, 0x17, 0x43, 0x7f, 0x7a, 0xe0, 0x3d, 0x85, 0xea, 0xf1, 0x44, 0x53, 0xcb, 0x95, 0x44, 0x2d, + 0x58, 0x36, 0x6c, 0xa4, 0x64, 0x60, 0x5a, 0xa5, 0xdd, 0x52, 0xbb, 0x8c, 0xb3, 0x2d, 0x6a, 0xc2, + 0xa2, 0xa4, 0x52, 0x99, 0xd6, 0x2f, 0xbb, 0xa5, 0xf6, 0x22, 0x4e, 0x36, 0xde, 0x33, 0xa8, 0x9d, + 0xf3, 0x31, 0x33, 0x96, 0x8e, 0xa3, 0x9f, 0x4e, 0xfe, 0x5a, 0x02, 0x74, 0xa6, 0x68, 0xd0, 0xa3, + 0x82, 0xca, 0x11, 0xc3, 0xec, 0xe3, 0x84, 0x19, 0x8b, 0xde, 0xc0, 0x2a, 0x97, 0xdc, 0x72, 0x2a, + 0x88, 0x4e, 0x42, 0x8e, 0xae, 0x7e, 0xf8, 0x97, 0x9f, 0x57, 0xed, 0x9f, 0x26, 0x90, 0xbb, 0xf9, + 0xfd, 0x05, 0xfc, 0x6b, 0x9a, 0x9f, 0x31, 0x3e, 0x87, 0x95, 0x91, 0xe0, 0x4c, 0x5a, 0x62, 0x2c, + 0xb5, 0x49, 0x15, 0xf5, 0xc3, 0x8d, 0x22, 0xdd, 0x91, 0x3b, 0x1f, 0xc4, 0xc7, 0xfd, 0x05, 0x5c, + 0x1f, 0xe5, 0xdb, 0xde, 0x1f, 0xb0, 0x29, 0x14, 0x0d, 0xc8, 0x30, 0x91, 0xc9, 0x8a, 0x22, 0xf6, + 0x53, 0xc4, 0xbc, 0x0e, 0x6c, 0xde, 0x5b, 0x09, 0x42, 0x50, 0x91, 0x74, 0xcc, 0x5c, 0xf9, 0x35, + 0xec, 0xd6, 0xde, 0xe7, 0x0a, 0xd4, 0x0b, 0x62, 0xa8, 0x0b, 0x35, 0x9b, 0x75, 0x30, 0xbd, 0xe7, + 0xef, 0xc5, 0xc2, 0x66, 0xed, 0xc5, 0x39, 0x0e, 0xfd, 0x03, 0xbf, 0xc9, 0xc9, 0x98, 0x8c, 0xa8, + 0x10, 0x26, 0xbe, 0x93, 0xb6, 0x2c, 0x70, 0xb7, 0x2a, 0xe3, 0x55, 0x39, 0x19, 0x1f, 0xc5, 0xf1, + 0x41, 0x12, 0x46, 0xfb, 0x80, 0x72, 0xec, 0x05, 0x97, 0xdc, 0x5c, 0xb2, 0xa0, 0x55, 0x76, 0xe0, + 0x46, 0x06, 0x3e, 0x49, 0xe3, 0x88, 0x80, 0x7f, 0x17, 0x4d, 0xae, 0xb9, 0xbd, 0x24, 0x81, 0x56, + 0x11, 0xb9, 0x50, 0x9a, 0x68, 0x6a, 0x19, 0x11, 0x7c, 0xcc, 0x2d, 0x97, 0x61, 0xab, 0xe2, 0x98, + 0xfe, 0xbe, 0xcd, 0xf4, 0x8e, 0xdb, 0xcb, 0x63, 0xad, 0xa2, 0x13, 0xa5, 0x31, 0xb5, 0xec, 0x2c, + 0x85, 0x23, 0x0a, 0x9d, 0x47, 0x05, 0x0a, 0xed, 0x8e, 0x15, 0x16, 0x9d, 0x42, 0xfb, 0x01, 0x85, + 0xbc, 0xf7, 0xb1, 0xc4, 0x07, 0xf8, 0xf7, 0x3e, 0x89, 0xf4, 0x19, 0x5c, 0x50, 0x2e, 0x58, 0x40, + 0xac, 0x22, 0x86, 0xc9, 0xa0, 0xb5, 0xe4, 0x04, 0xf6, 0xe6, 0x09, 0x24, 0x7f, 0xd5, 0x89, 0xc3, + 0x9f, 0xab, 0x01, 0x93, 0x01, 0xea, 0xc3, 0x9f, 0x73, 0xe8, 0xaf, 0xa4, 0xba, 0x96, 0x44, 0xb3, + 0x11, 0xe3, 0x53, 0x16, 0xb4, 0x96, 0x1d, 0xe5, 0xce, 0x6d, 0xca, 0xd7, 0x31, 0x0a, 0xa7, 0x20, + 0xef, 0x5b, 0x09, 0xd6, 0x6e, 0x3c, 0x1b, 0x13, 0x29, 0x69, 0x18, 0x1a, 0x40, 0x23, 0x77, 0x40, + 0x12, 0x4b, 0x9f, 0xc6, 0xde, 0x63, 0x16, 0x48, 0xd0, 0xfd, 0x05, 0xbc, 0x3a, 0xf3, 0x40, 0x4a, + 0xfa, 0x04, 0xea, 0x86, 0xe9, 0x29, 0xd3, 0x44, 0x70, 0x63, 0x53, 0x0f, 0xac, 0x17, 0xf9, 0x06, + 0xee, 0xf8, 0x8c, 0x3b, 0x0f, 0x81, 0x99, 0xed, 0x7a, 0xdb, 0xb0, 0x75, 0xcb, 0x01, 0x09, 0x67, + 0x62, 0x81, 0x2f, 0x25, 0xd8, 0xba, 0xbf, 0x14, 0xf4, 0x1f, 0xac, 0x17, 0x93, 0x35, 0x09, 0x98, + 0x60, 0x21, 0xb5, 0x99, 0x2d, 0x9a, 0x22, 0x4f, 0xd2, 0xc7, 0xe9, 0x19, 0x7a, 0x0b, 0xdb, 0x45, + 0xcb, 0x12, 0xcd, 0x22, 0xa5, 0x2d, 0xe1, 0xd2, 0x32, 0x3d, 0xa5, 0x22, 0x2d, 0xbf, 0x59, 0x2c, + 0x3f, 0x1b, 0x62, 0x78, 0xb3, 0xe0, 0x5e, 0xec, 0xf2, 0x4e, 0xd3, 0x34, 0xef, 0x05, 0x40, 0x7e, + 0x4b, 0xb4, 0x1f, 0x0f, 0xac, 0x78, 0x17, 0x0f, 0xac, 0x72, 0xbb, 0x7e, 0x88, 0xee, 0xb6, 0x03, + 0x67, 0x90, 0x57, 0x95, 0x6a, 0xb9, 0x51, 0xf1, 0xbe, 0x97, 0x60, 0x29, 0x39, 0x41, 0x3b, 0x00, + 0x3c, 0x22, 0x34, 0x08, 0x34, 0x33, 0xc9, 0xc8, 0x5b, 0xc1, 0x35, 0x1e, 0xbd, 0x4c, 0x02, 0xb1, + 0xfb, 0x63, 0xed, 0x74, 0xe6, 0xb9, 0x75, 0x6c, 0xc6, 0x1b, 0x9d, 0xb4, 0xea, 0x8a, 0x49, 0x67, + 0xc6, 0x1a, 0x6e, 0x14, 0x1a, 0x71, 0x1e, 0xc7, 0x51, 0x17, 0xd6, 0x1f, 0x30, 0x5d, 0x15, 0xaf, + 0x05, 0x73, 0x0c, 0xf6, 0x3f, 0x6c, 0x3c, 0x64, 0xa4, 0x2a, 0x6e, 0x06, 0x73, 0x4c, 0xd3, 0xeb, + 0xbe, 0x3f, 0x08, 0x95, 0x0a, 0x05, 0xf3, 0x43, 0x25, 0xa8, 0x0c, 0x7d, 0xa5, 0xc3, 0x4e, 0xdc, + 0x0d, 0xf7, 0x23, 0x86, 0x9d, 0x39, 0x5f, 0x95, 0xe1, 0x92, 0xfb, 0x9a, 0x74, 0x7f, 0x04, 0x00, + 0x00, 0xff, 0xff, 0x8e, 0xd0, 0x70, 0xb7, 0x73, 0x06, 0x00, 0x00, } diff --git a/vendor/google.golang.org/grpc/grpclb_remote_balancer.go b/vendor/google.golang.org/grpc/grpclb_remote_balancer.go index 1b580df26dd..b8dd4f18ce5 100644 --- a/vendor/google.golang.org/grpc/grpclb_remote_balancer.go +++ b/vendor/google.golang.org/grpc/grpclb_remote_balancer.go @@ -26,6 +26,8 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/channelz" + "google.golang.org/grpc/connectivity" lbpb "google.golang.org/grpc/grpclb/grpc_lb_v1/messages" "google.golang.org/grpc/grpclog" @@ -74,15 +76,16 @@ func (lb *lbBalancer) processServerList(l *lbpb.ServerList) { } // Call refreshSubConns to create/remove SubConns. - backendsUpdated := lb.refreshSubConns(backendAddrs) - // If no backend was updated, no SubConn will be newed/removed. But since - // the full serverList was different, there might be updates in drops or - // pick weights(different number of duplicates). We need to update picker - // with the fulllist. - if !backendsUpdated { - lb.regeneratePicker() - lb.cc.UpdateBalancerState(lb.state, lb.picker) - } + lb.refreshSubConns(backendAddrs) + // Regenerate and update picker no matter if there's update on backends (if + // any SubConn will be newed/removed). Because since the full serverList was + // different, there might be updates in drops or pick weights(different + // number of duplicates). We need to update picker with the fulllist. + // + // Now with cache, even if SubConn was newed/removed, there might be no + // state changes. + lb.regeneratePicker() + lb.cc.UpdateBalancerState(lb.state, lb.picker) } // refreshSubConns creates/removes SubConns with backendAddrs. It returns a bool @@ -112,7 +115,11 @@ func (lb *lbBalancer) refreshSubConns(backendAddrs []resolver.Address) bool { continue } lb.subConns[addrWithoutMD] = sc // Use the addr without MD as key for the map. - lb.scStates[sc] = connectivity.Idle + if _, ok := lb.scStates[sc]; !ok { + // Only set state of new sc to IDLE. The state could already be + // READY for cached SubConns. + lb.scStates[sc] = connectivity.Idle + } sc.Connect() } } @@ -168,6 +175,7 @@ func (lb *lbBalancer) sendLoadReport(s *balanceLoadClientStream, interval time.D } } } + func (lb *lbBalancer) callRemoteBalancer() error { lbClient := &loadBalancerClient{cc: lb.ccRemoteLB} ctx, cancel := context.WithCancel(context.Background()) @@ -243,9 +251,13 @@ func (lb *lbBalancer) dialRemoteLB(remoteLBName string) { // Explicitly set pickfirst as the balancer. dopts = append(dopts, WithBalancerName(PickFirstBalancerName)) dopts = append(dopts, withResolverBuilder(lb.manualResolver)) - // Dial using manualResolver.Scheme, which is a random scheme generated + if channelz.IsOn() { + dopts = append(dopts, WithChannelzParentID(lb.opt.ChannelzParentID)) + } + + // DialContext using manualResolver.Scheme, which is a random scheme generated // when init grpclb. The target name is not important. - cc, err := Dial("grpclb:///grpclb.server", dopts...) + cc, err := DialContext(context.Background(), "grpclb:///grpclb.server", dopts...) if err != nil { grpclog.Fatalf("failed to dial: %v", err) } diff --git a/vendor/google.golang.org/grpc/grpclb_util.go b/vendor/google.golang.org/grpc/grpclb_util.go index 93ab2db3234..063ba9d8590 100644 --- a/vendor/google.golang.org/grpc/grpclb_util.go +++ b/vendor/google.golang.org/grpc/grpclb_util.go @@ -19,7 +19,12 @@ package grpc import ( + "fmt" + "sync" + "time" + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" "google.golang.org/grpc/resolver" ) @@ -88,3 +93,122 @@ func (r *lbManualResolver) NewAddress(addrs []resolver.Address) { func (r *lbManualResolver) NewServiceConfig(sc string) { r.ccr.NewServiceConfig(sc) } + +const subConnCacheTime = time.Second * 10 + +// lbCacheClientConn is a wrapper balancer.ClientConn with a SubConn cache. +// SubConns will be kept in cache for subConnCacheTime before being removed. +// +// Its new and remove methods are updated to do cache first. +type lbCacheClientConn struct { + cc balancer.ClientConn + timeout time.Duration + + mu sync.Mutex + // subConnCache only keeps subConns that are being deleted. + subConnCache map[resolver.Address]*subConnCacheEntry + subConnToAddr map[balancer.SubConn]resolver.Address +} + +type subConnCacheEntry struct { + sc balancer.SubConn + + cancel func() + abortDeleting bool +} + +func newLBCacheClientConn(cc balancer.ClientConn) *lbCacheClientConn { + return &lbCacheClientConn{ + cc: cc, + timeout: subConnCacheTime, + subConnCache: make(map[resolver.Address]*subConnCacheEntry), + subConnToAddr: make(map[balancer.SubConn]resolver.Address), + } +} + +func (ccc *lbCacheClientConn) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { + if len(addrs) != 1 { + return nil, fmt.Errorf("grpclb calling NewSubConn with addrs of length %v", len(addrs)) + } + addrWithoutMD := addrs[0] + addrWithoutMD.Metadata = nil + + ccc.mu.Lock() + defer ccc.mu.Unlock() + if entry, ok := ccc.subConnCache[addrWithoutMD]; ok { + // If entry is in subConnCache, the SubConn was being deleted. + // cancel function will never be nil. + entry.cancel() + delete(ccc.subConnCache, addrWithoutMD) + return entry.sc, nil + } + + scNew, err := ccc.cc.NewSubConn(addrs, opts) + if err != nil { + return nil, err + } + + ccc.subConnToAddr[scNew] = addrWithoutMD + return scNew, nil +} + +func (ccc *lbCacheClientConn) RemoveSubConn(sc balancer.SubConn) { + ccc.mu.Lock() + defer ccc.mu.Unlock() + addr, ok := ccc.subConnToAddr[sc] + if !ok { + return + } + + if entry, ok := ccc.subConnCache[addr]; ok { + if entry.sc != sc { + // This could happen if NewSubConn was called multiple times for the + // same address, and those SubConns are all removed. We remove sc + // immediately here. + delete(ccc.subConnToAddr, sc) + ccc.cc.RemoveSubConn(sc) + } + return + } + + entry := &subConnCacheEntry{ + sc: sc, + } + ccc.subConnCache[addr] = entry + + timer := time.AfterFunc(ccc.timeout, func() { + ccc.mu.Lock() + if entry.abortDeleting { + return + } + ccc.cc.RemoveSubConn(sc) + delete(ccc.subConnToAddr, sc) + delete(ccc.subConnCache, addr) + ccc.mu.Unlock() + }) + entry.cancel = func() { + if !timer.Stop() { + // If stop was not successful, the timer has fired (this can only + // happen in a race). But the deleting function is blocked on ccc.mu + // because the mutex was held by the caller of this function. + // + // Set abortDeleting to true to abort the deleting function. When + // the lock is released, the deleting function will acquire the + // lock, check the value of abortDeleting and return. + entry.abortDeleting = true + } + } +} + +func (ccc *lbCacheClientConn) UpdateBalancerState(s connectivity.State, p balancer.Picker) { + ccc.cc.UpdateBalancerState(s, p) +} + +func (ccc *lbCacheClientConn) close() { + ccc.mu.Lock() + // Only cancel all existing timers. There's no need to remove SubConns. + for _, entry := range ccc.subConnCache { + entry.cancel() + } + ccc.mu.Unlock() +} diff --git a/vendor/google.golang.org/grpc/grpclog/grpclog.go b/vendor/google.golang.org/grpc/grpclog/grpclog.go index 16a7d88867e..1fabb11e1ba 100644 --- a/vendor/google.golang.org/grpc/grpclog/grpclog.go +++ b/vendor/google.golang.org/grpc/grpclog/grpclog.go @@ -105,18 +105,21 @@ func Fatalln(args ...interface{}) { } // Print prints to the logger. Arguments are handled in the manner of fmt.Print. +// // Deprecated: use Info. func Print(args ...interface{}) { logger.Info(args...) } // Printf prints to the logger. Arguments are handled in the manner of fmt.Printf. +// // Deprecated: use Infof. func Printf(format string, args ...interface{}) { logger.Infof(format, args...) } // Println prints to the logger. Arguments are handled in the manner of fmt.Println. +// // Deprecated: use Infoln. func Println(args ...interface{}) { logger.Infoln(args...) diff --git a/vendor/google.golang.org/grpc/grpclog/logger.go b/vendor/google.golang.org/grpc/grpclog/logger.go index d03b2397bfa..097494f710f 100644 --- a/vendor/google.golang.org/grpc/grpclog/logger.go +++ b/vendor/google.golang.org/grpc/grpclog/logger.go @@ -19,6 +19,7 @@ package grpclog // Logger mimics golang's standard Logger as an interface. +// // Deprecated: use LoggerV2. type Logger interface { Fatal(args ...interface{}) @@ -31,6 +32,7 @@ type Logger interface { // SetLogger sets the logger that is used in grpc. Call only from // init() functions. +// // Deprecated: use SetLoggerV2. func SetLogger(l Logger) { logger = &loggerWrapper{Logger: l} diff --git a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go index fdcbb9e0b7d..e5906de7d43 100644 --- a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go +++ b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go @@ -1,17 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // source: grpc_health_v1/health.proto -/* -Package grpc_health_v1 is a generated protocol buffer package. - -It is generated from these files: - grpc_health_v1/health.proto - -It has these top-level messages: - HealthCheckRequest - HealthCheckResponse -*/ -package grpc_health_v1 +package grpc_health_v1 // import "google.golang.org/grpc/health/grpc_health_v1" import proto "github.com/golang/protobuf/proto" import fmt "fmt" @@ -56,17 +46,39 @@ func (x HealthCheckResponse_ServingStatus) String() string { return proto.EnumName(HealthCheckResponse_ServingStatus_name, int32(x)) } func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor0, []int{1, 0} + return fileDescriptor_health_8e5b8a3074428511, []int{1, 0} } type HealthCheckRequest struct { - Service string `protobuf:"bytes,1,opt,name=service" json:"service,omitempty"` + Service string `protobuf:"bytes,1,opt,name=service" json:"service,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HealthCheckRequest) Reset() { *m = HealthCheckRequest{} } +func (m *HealthCheckRequest) String() string { return proto.CompactTextString(m) } +func (*HealthCheckRequest) ProtoMessage() {} +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_health_8e5b8a3074428511, []int{0} +} +func (m *HealthCheckRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HealthCheckRequest.Unmarshal(m, b) +} +func (m *HealthCheckRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HealthCheckRequest.Marshal(b, m, deterministic) +} +func (dst *HealthCheckRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HealthCheckRequest.Merge(dst, src) +} +func (m *HealthCheckRequest) XXX_Size() int { + return xxx_messageInfo_HealthCheckRequest.Size(m) +} +func (m *HealthCheckRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HealthCheckRequest.DiscardUnknown(m) } -func (m *HealthCheckRequest) Reset() { *m = HealthCheckRequest{} } -func (m *HealthCheckRequest) String() string { return proto.CompactTextString(m) } -func (*HealthCheckRequest) ProtoMessage() {} -func (*HealthCheckRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +var xxx_messageInfo_HealthCheckRequest proto.InternalMessageInfo func (m *HealthCheckRequest) GetService() string { if m != nil { @@ -76,13 +88,35 @@ func (m *HealthCheckRequest) GetService() string { } type HealthCheckResponse struct { - Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,enum=grpc.health.v1.HealthCheckResponse_ServingStatus" json:"status,omitempty"` + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,enum=grpc.health.v1.HealthCheckResponse_ServingStatus" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HealthCheckResponse) Reset() { *m = HealthCheckResponse{} } +func (m *HealthCheckResponse) String() string { return proto.CompactTextString(m) } +func (*HealthCheckResponse) ProtoMessage() {} +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_health_8e5b8a3074428511, []int{1} +} +func (m *HealthCheckResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HealthCheckResponse.Unmarshal(m, b) +} +func (m *HealthCheckResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HealthCheckResponse.Marshal(b, m, deterministic) +} +func (dst *HealthCheckResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_HealthCheckResponse.Merge(dst, src) +} +func (m *HealthCheckResponse) XXX_Size() int { + return xxx_messageInfo_HealthCheckResponse.Size(m) +} +func (m *HealthCheckResponse) XXX_DiscardUnknown() { + xxx_messageInfo_HealthCheckResponse.DiscardUnknown(m) } -func (m *HealthCheckResponse) Reset() { *m = HealthCheckResponse{} } -func (m *HealthCheckResponse) String() string { return proto.CompactTextString(m) } -func (*HealthCheckResponse) ProtoMessage() {} -func (*HealthCheckResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +var xxx_messageInfo_HealthCheckResponse proto.InternalMessageInfo func (m *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { if m != nil { @@ -169,10 +203,10 @@ var _Health_serviceDesc = grpc.ServiceDesc{ Metadata: "grpc_health_v1/health.proto", } -func init() { proto.RegisterFile("grpc_health_v1/health.proto", fileDescriptor0) } +func init() { proto.RegisterFile("grpc_health_v1/health.proto", fileDescriptor_health_8e5b8a3074428511) } -var fileDescriptor0 = []byte{ - // 213 bytes of a gzipped FileDescriptorProto +var fileDescriptor_health_8e5b8a3074428511 = []byte{ + // 269 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4e, 0x2f, 0x2a, 0x48, 0x8e, 0xcf, 0x48, 0x4d, 0xcc, 0x29, 0xc9, 0x88, 0x2f, 0x33, 0xd4, 0x87, 0xb0, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xf8, 0x40, 0x92, 0x7a, 0x50, 0xa1, 0x32, 0x43, 0x25, 0x3d, 0x2e, 0x21, @@ -185,6 +219,9 @@ var fileDescriptor0 = []byte{ 0x0f, 0xf7, 0x13, 0x60, 0x00, 0x71, 0x82, 0x5d, 0x83, 0xc2, 0x3c, 0xfd, 0xdc, 0x05, 0x18, 0x85, 0xf8, 0xb9, 0xb8, 0xfd, 0xfc, 0x43, 0xe2, 0x61, 0x02, 0x4c, 0x46, 0x51, 0x5c, 0x6c, 0x10, 0x8b, 0x84, 0x02, 0xb8, 0x58, 0xc1, 0x96, 0x09, 0x29, 0xe1, 0x75, 0x09, 0xd8, 0xbf, 0x52, 0xca, 0x44, - 0xb8, 0x36, 0x89, 0x0d, 0x1c, 0x82, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x53, 0x2b, 0x65, - 0x20, 0x60, 0x01, 0x00, 0x00, + 0xb8, 0xd6, 0x29, 0x91, 0x4b, 0x30, 0x33, 0x1f, 0x4d, 0xa1, 0x13, 0x37, 0x44, 0x65, 0x00, 0x28, + 0x70, 0x03, 0x18, 0xa3, 0x74, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xd2, 0xf3, 0x73, 0x12, + 0xf3, 0xd2, 0xf5, 0xf2, 0x8b, 0xd2, 0xf5, 0x41, 0x1a, 0xa0, 0x71, 0xa0, 0x8f, 0x1a, 0x33, 0xab, + 0x98, 0xf8, 0xdc, 0x41, 0xa6, 0x41, 0x8c, 0xd0, 0x0b, 0x33, 0x4c, 0x62, 0x03, 0x47, 0x92, 0x31, + 0x20, 0x00, 0x00, 0xff, 0xff, 0xb7, 0x70, 0xc4, 0xa7, 0xc3, 0x01, 0x00, 0x00, } diff --git a/vendor/google.golang.org/grpc/health/health.go b/vendor/google.golang.org/grpc/health/health.go index 30a78667e60..de7f9ba79e2 100644 --- a/vendor/google.golang.org/grpc/health/health.go +++ b/vendor/google.golang.org/grpc/health/health.go @@ -16,7 +16,7 @@ * */ -//go:generate protoc --go_out=plugins=grpc:. grpc_health_v1/health.proto +//go:generate protoc --go_out=plugins=grpc,paths=source_relative:. grpc_health_v1/health.proto // Package health provides some utility functions to health-check a server. The implementation // is based on protobuf. Users need to write their own implementations if other IDLs are used. diff --git a/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/google.golang.org/grpc/metadata/metadata.go index e7c994673c0..bd2eaf40837 100644 --- a/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/google.golang.org/grpc/metadata/metadata.go @@ -28,7 +28,9 @@ import ( "golang.org/x/net/context" ) -// DecodeKeyValue returns k, v, nil. It is deprecated and should not be used. +// DecodeKeyValue returns k, v, nil. +// +// Deprecated: use k and v directly instead. func DecodeKeyValue(k, v string) (string, string, error) { return k, v, nil } @@ -95,6 +97,30 @@ func (md MD) Copy() MD { return Join(md) } +// Get obtains the values for a given key. +func (md MD) Get(k string) []string { + k = strings.ToLower(k) + return md[k] +} + +// Set sets the value of a given key with a slice of values. +func (md MD) Set(k string, vals ...string) { + if len(vals) == 0 { + return + } + k = strings.ToLower(k) + md[k] = vals +} + +// Append adds the values to key k, not overwriting what was already stored at that key. +func (md MD) Append(k string, vals ...string) { + if len(vals) == 0 { + return + } + k = strings.ToLower(k) + md[k] = append(md[k], vals...) +} + // Join joins any number of mds into a single MD. // The order of values for each key is determined by the order in which // the mds containing those values are presented to Join. diff --git a/vendor/google.golang.org/grpc/naming/dns_resolver.go b/vendor/google.golang.org/grpc/naming/dns_resolver.go index 7e69a2ca0a6..0f8a908ea9c 100644 --- a/vendor/google.golang.org/grpc/naming/dns_resolver.go +++ b/vendor/google.golang.org/grpc/naming/dns_resolver.go @@ -153,10 +153,10 @@ type ipWatcher struct { updateChan chan *Update } -// Next returns the adrress resolution Update for the target. For IP address, -// the resolution is itself, thus polling name server is unncessary. Therefore, +// Next returns the address resolution Update for the target. For IP address, +// the resolution is itself, thus polling name server is unnecessary. Therefore, // Next() will return an Update the first time it is called, and will be blocked -// for all following calls as no Update exisits until watcher is closed. +// for all following calls as no Update exists until watcher is closed. func (i *ipWatcher) Next() ([]*Update, error) { u, ok := <-i.updateChan if !ok { diff --git a/vendor/google.golang.org/grpc/naming/naming.go b/vendor/google.golang.org/grpc/naming/naming.go index 1af7e32f86d..8cc39e93758 100644 --- a/vendor/google.golang.org/grpc/naming/naming.go +++ b/vendor/google.golang.org/grpc/naming/naming.go @@ -18,20 +18,26 @@ // Package naming defines the naming API and related data structures for gRPC. // The interface is EXPERIMENTAL and may be suject to change. +// +// Deprecated: please use package resolver. package naming // Operation defines the corresponding operations for a name resolution change. +// +// Deprecated: please use package resolver. type Operation uint8 const ( // Add indicates a new address is added. Add Operation = iota - // Delete indicates an exisiting address is deleted. + // Delete indicates an existing address is deleted. Delete ) // Update defines a name resolution update. Notice that it is not valid having both // empty string Addr and nil Metadata in an Update. +// +// Deprecated: please use package resolver. type Update struct { // Op indicates the operation of the update. Op Operation @@ -43,12 +49,16 @@ type Update struct { } // Resolver creates a Watcher for a target to track its resolution changes. +// +// Deprecated: please use package resolver. type Resolver interface { // Resolve creates a Watcher for target. Resolve(target string) (Watcher, error) } // Watcher watches for the updates on the specified target. +// +// Deprecated: please use package resolver. type Watcher interface { // Next blocks until an update or error happens. It may return one or more // updates. The first call should get the full set of the results. It should diff --git a/vendor/google.golang.org/grpc/picker_wrapper.go b/vendor/google.golang.org/grpc/picker_wrapper.go index 4d0082593d1..0a984e6c8af 100644 --- a/vendor/google.golang.org/grpc/picker_wrapper.go +++ b/vendor/google.golang.org/grpc/picker_wrapper.go @@ -19,12 +19,17 @@ package grpc import ( + "io" "sync" + "sync/atomic" "golang.org/x/net/context" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/resolver" "google.golang.org/grpc/status" "google.golang.org/grpc/transport" ) @@ -40,10 +45,16 @@ type pickerWrapper struct { // The latest connection happened. connErrMu sync.Mutex connErr error + + stickinessMDKey atomic.Value + stickiness *stickyStore } func newPickerWrapper() *pickerWrapper { - bp := &pickerWrapper{blockingCh: make(chan struct{})} + bp := &pickerWrapper{ + blockingCh: make(chan struct{}), + stickiness: newStickyStore(), + } return bp } @@ -60,6 +71,27 @@ func (bp *pickerWrapper) connectionError() error { return err } +func (bp *pickerWrapper) updateStickinessMDKey(newKey string) { + // No need to check ok because mdKey == "" if ok == false. + if oldKey, _ := bp.stickinessMDKey.Load().(string); oldKey != newKey { + bp.stickinessMDKey.Store(newKey) + bp.stickiness.reset(newKey) + } +} + +func (bp *pickerWrapper) getStickinessMDKey() string { + // No need to check ok because mdKey == "" if ok == false. + mdKey, _ := bp.stickinessMDKey.Load().(string) + return mdKey +} + +func (bp *pickerWrapper) clearStickinessState() { + if oldKey := bp.getStickinessMDKey(); oldKey != "" { + // There's no need to reset store if mdKey was "". + bp.stickiness.reset(oldKey) + } +} + // updatePicker is called by UpdateBalancerState. It unblocks all blocked pick. func (bp *pickerWrapper) updatePicker(p balancer.Picker) { bp.mu.Lock() @@ -74,6 +106,23 @@ func (bp *pickerWrapper) updatePicker(p balancer.Picker) { bp.mu.Unlock() } +func doneChannelzWrapper(acw *acBalancerWrapper, done func(balancer.DoneInfo)) func(balancer.DoneInfo) { + acw.mu.Lock() + ac := acw.ac + acw.mu.Unlock() + ac.incrCallsStarted() + return func(b balancer.DoneInfo) { + if b.Err != nil && b.Err != io.EOF { + ac.incrCallsFailed() + } else { + ac.incrCallsSucceeded() + } + if done != nil { + done(b) + } + } +} + // pick returns the transport that will be used for the RPC. // It may block in the following cases: // - there's no picker @@ -82,6 +131,27 @@ func (bp *pickerWrapper) updatePicker(p balancer.Picker) { // - the subConn returned by the current picker is not READY // When one of these situations happens, pick blocks until the picker gets updated. func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer.PickOptions) (transport.ClientTransport, func(balancer.DoneInfo), error) { + + mdKey := bp.getStickinessMDKey() + stickyKey, isSticky := stickyKeyFromContext(ctx, mdKey) + + // Potential race here: if stickinessMDKey is updated after the above two + // lines, and this pick is a sticky pick, the following put could add an + // entry to sticky store with an outdated sticky key. + // + // The solution: keep the current md key in sticky store, and at the + // beginning of each get/put, check the mdkey against store.curMDKey. + // - Cons: one more string comparing for each get/put. + // - Pros: the string matching happens inside get/put, so the overhead for + // non-sticky RPCs will be minimal. + + if isSticky { + if t, ok := bp.stickiness.get(mdKey, stickyKey); ok { + // Done function returned is always nil. + return t, nil, nil + } + } + var ( p balancer.Picker ch chan struct{} @@ -137,6 +207,12 @@ func (bp *pickerWrapper) pick(ctx context.Context, failfast bool, opts balancer. continue } if t, ok := acw.getAddrConn().getReadyTransport(); ok { + if isSticky { + bp.stickiness.put(mdKey, stickyKey, acw) + } + if channelz.IsOn() { + return t, doneChannelzWrapper(acw, done), nil + } return t, done, nil } grpclog.Infof("blockingPicker: the picked transport is not ready, loop back to repick") @@ -156,3 +232,100 @@ func (bp *pickerWrapper) close() { bp.done = true close(bp.blockingCh) } + +type stickyStoreEntry struct { + acw *acBalancerWrapper + addr resolver.Address +} + +type stickyStore struct { + mu sync.Mutex + // curMDKey is check before every get/put to avoid races. The operation will + // abort immediately when the given mdKey is different from the curMDKey. + curMDKey string + store map[string]*stickyStoreEntry +} + +func newStickyStore() *stickyStore { + return &stickyStore{ + store: make(map[string]*stickyStoreEntry), + } +} + +// reset clears the map in stickyStore, and set the currentMDKey to newMDKey. +func (ss *stickyStore) reset(newMDKey string) { + ss.mu.Lock() + ss.curMDKey = newMDKey + ss.store = make(map[string]*stickyStoreEntry) + ss.mu.Unlock() +} + +// stickyKey is the key to look up in store. mdKey will be checked against +// curMDKey to avoid races. +func (ss *stickyStore) put(mdKey, stickyKey string, acw *acBalancerWrapper) { + ss.mu.Lock() + defer ss.mu.Unlock() + if mdKey != ss.curMDKey { + return + } + // TODO(stickiness): limit the total number of entries. + ss.store[stickyKey] = &stickyStoreEntry{ + acw: acw, + addr: acw.getAddrConn().getCurAddr(), + } +} + +// stickyKey is the key to look up in store. mdKey will be checked against +// curMDKey to avoid races. +func (ss *stickyStore) get(mdKey, stickyKey string) (transport.ClientTransport, bool) { + ss.mu.Lock() + defer ss.mu.Unlock() + if mdKey != ss.curMDKey { + return nil, false + } + entry, ok := ss.store[stickyKey] + if !ok { + return nil, false + } + ac := entry.acw.getAddrConn() + if ac.getCurAddr() != entry.addr { + delete(ss.store, stickyKey) + return nil, false + } + t, ok := ac.getReadyTransport() + if !ok { + delete(ss.store, stickyKey) + return nil, false + } + return t, true +} + +// Get one value from metadata in ctx with key stickinessMDKey. +// +// It returns "", false if stickinessMDKey is an empty string. +func stickyKeyFromContext(ctx context.Context, stickinessMDKey string) (string, bool) { + if stickinessMDKey == "" { + return "", false + } + + md, added, ok := metadata.FromOutgoingContextRaw(ctx) + if !ok { + return "", false + } + + if vv, ok := md[stickinessMDKey]; ok { + if len(vv) > 0 { + return vv[0], true + } + } + + for _, ss := range added { + for i := 0; i < len(ss)-1; i += 2 { + if ss[i] == stickinessMDKey { + return ss[i+1], true + } + } + } + + return "", false +} diff --git a/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go b/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go index a543a709a62..c1cabfc995f 100644 --- a/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go +++ b/vendor/google.golang.org/grpc/resolver/dns/dns_resolver.go @@ -50,7 +50,10 @@ const ( txtAttribute = "grpc_config=" ) -var errMissingAddr = errors.New("missing address") +var ( + errMissingAddr = errors.New("missing address") + randomGen = rand.New(rand.NewSource(time.Now().UnixNano())) +) // NewBuilder creates a dnsBuilder which is used to factory DNS resolvers. func NewBuilder() resolver.Builder { @@ -87,14 +90,15 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts // DNS address (non-IP). ctx, cancel := context.WithCancel(context.Background()) d := &dnsResolver{ - freq: b.freq, - host: host, - port: port, - ctx: ctx, - cancel: cancel, - cc: cc, - t: time.NewTimer(0), - rn: make(chan struct{}, 1), + freq: b.freq, + host: host, + port: port, + ctx: ctx, + cancel: cancel, + cc: cc, + t: time.NewTimer(0), + rn: make(chan struct{}, 1), + disableServiceConfig: opts.DisableServiceConfig, } d.wg.Add(1) @@ -157,7 +161,8 @@ type dnsResolver struct { // If Close() doesn't wait for watcher() goroutine finishes, race detector sometimes // will warns lookup (READ the lookup function pointers) inside watcher() goroutine // has data race with replaceNetFunc (WRITE the lookup function pointers). - wg sync.WaitGroup + wg sync.WaitGroup + disableServiceConfig bool } // ResolveNow invoke an immediate resolution of the target that this dnsResolver watches. @@ -187,7 +192,7 @@ func (d *dnsResolver) watcher() { result, sc := d.lookup() // Next lookup should happen after an interval defined by d.freq. d.t.Reset(d.freq) - d.cc.NewServiceConfig(string(sc)) + d.cc.NewServiceConfig(sc) d.cc.NewAddress(result) } } @@ -202,7 +207,7 @@ func (d *dnsResolver) lookupSRV() []resolver.Address { for _, s := range srvs { lbAddrs, err := lookupHost(d.ctx, s.Target) if err != nil { - grpclog.Warningf("grpc: failed load banlacer address dns lookup due to %v.\n", err) + grpclog.Infof("grpc: failed load balancer address dns lookup due to %v.\n", err) continue } for _, a := range lbAddrs { @@ -221,7 +226,7 @@ func (d *dnsResolver) lookupSRV() []resolver.Address { func (d *dnsResolver) lookupTXT() string { ss, err := lookupTXT(d.ctx, d.host) if err != nil { - grpclog.Warningf("grpc: failed dns TXT record lookup due to %v.\n", err) + grpclog.Infof("grpc: failed dns TXT record lookup due to %v.\n", err) return "" } var res string @@ -257,10 +262,12 @@ func (d *dnsResolver) lookupHost() []resolver.Address { } func (d *dnsResolver) lookup() ([]resolver.Address, string) { - var newAddrs []resolver.Address - newAddrs = d.lookupSRV() + newAddrs := d.lookupSRV() // Support fallback to non-balancer address. newAddrs = append(newAddrs, d.lookupHost()...) + if d.disableServiceConfig { + return newAddrs, "" + } sc := d.lookupTXT() return newAddrs, canaryingSC(sc) } @@ -339,12 +346,7 @@ func chosenByPercentage(a *int) bool { if a == nil { return true } - s := rand.NewSource(time.Now().UnixNano()) - r := rand.New(s) - if r.Intn(100)+1 > *a { - return false - } - return true + return randomGen.Intn(100)+1 <= *a } func canaryingSC(js string) string { diff --git a/vendor/google.golang.org/grpc/resolver/resolver.go b/vendor/google.golang.org/grpc/resolver/resolver.go index 775ee4d0d27..506afac88ae 100644 --- a/vendor/google.golang.org/grpc/resolver/resolver.go +++ b/vendor/google.golang.org/grpc/resolver/resolver.go @@ -29,8 +29,12 @@ var ( // TODO(bar) install dns resolver in init(){}. -// Register registers the resolver builder to the resolver map. -// b.Scheme will be used as the scheme registered with this builder. +// Register registers the resolver builder to the resolver map. b.Scheme will be +// used as the scheme registered with this builder. +// +// NOTE: this function must only be called during initialization time (i.e. in +// an init() function), and is not thread-safe. If multiple Resolvers are +// registered with the same name, the one registered last will take effect. func Register(b Builder) { m[b.Scheme()] = b } @@ -86,6 +90,8 @@ type Address struct { // BuildOption includes additional information for the builder to create // the resolver. type BuildOption struct { + // DisableServiceConfig indicates whether resolver should fetch service config data. + DisableServiceConfig bool } // ClientConn contains the callbacks for resolver to notify any updates diff --git a/vendor/google.golang.org/grpc/resolver_conn_wrapper.go b/vendor/google.golang.org/grpc/resolver_conn_wrapper.go index 75b8ce1eb6c..1b493db2e6c 100644 --- a/vendor/google.golang.org/grpc/resolver_conn_wrapper.go +++ b/vendor/google.golang.org/grpc/resolver_conn_wrapper.go @@ -84,7 +84,7 @@ func newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error) { } var err error - ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, resolver.BuildOption{}) + ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, resolver.BuildOption{DisableServiceConfig: cc.dopts.disableServiceConfig}) if err != nil { return nil, err } @@ -95,7 +95,7 @@ func (ccr *ccResolverWrapper) start() { go ccr.watcher() } -// watcher processes address updates and service config updates sequencially. +// watcher processes address updates and service config updates sequentially. // Otherwise, we need to resolve possible races between address and service // config (e.g. they specify different balancer types). func (ccr *ccResolverWrapper) watcher() { diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 00a99766066..5de1b031ec2 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -44,6 +44,8 @@ import ( ) // Compressor defines the interface gRPC uses to compress a message. +// +// Deprecated: use package encoding. type Compressor interface { // Do compresses p into w. Do(w io.Writer, p []byte) error @@ -56,6 +58,8 @@ type gzipCompressor struct { } // NewGZIPCompressor creates a Compressor based on GZIP. +// +// Deprecated: use package encoding/gzip. func NewGZIPCompressor() Compressor { c, _ := NewGZIPCompressorWithLevel(gzip.DefaultCompression) return c @@ -65,6 +69,8 @@ func NewGZIPCompressor() Compressor { // of assuming DefaultCompression. // // The error returned will be nil if the level is valid. +// +// Deprecated: use package encoding/gzip. func NewGZIPCompressorWithLevel(level int) (Compressor, error) { if level < gzip.DefaultCompression || level > gzip.BestCompression { return nil, fmt.Errorf("grpc: invalid compression level: %d", level) @@ -97,6 +103,8 @@ func (c *gzipCompressor) Type() string { } // Decompressor defines the interface gRPC uses to decompress a message. +// +// Deprecated: use package encoding. type Decompressor interface { // Do reads the data from r and uncompress them. Do(r io.Reader) ([]byte, error) @@ -109,6 +117,8 @@ type gzipDecompressor struct { } // NewGZIPDecompressor creates a Decompressor based on GZIP. +// +// Deprecated: use package encoding/gzip. func NewGZIPDecompressor() Decompressor { return &gzipDecompressor{} } @@ -218,8 +228,8 @@ func (o TrailerCallOption) after(c *callInfo) { } } -// Peer returns a CallOption that retrieves peer information for a -// unary RPC. +// Peer returns a CallOption that retrieves peer information for a unary RPC. +// The peer field will be populated *after* the RPC completes. func Peer(p *peer.Peer) CallOption { return PeerCallOption{PeerAddr: p} } @@ -265,7 +275,7 @@ func (o FailFastCallOption) before(c *callInfo) error { c.failFast = o.FailFast return nil } -func (o FailFastCallOption) after(c *callInfo) { return } +func (o FailFastCallOption) after(c *callInfo) {} // MaxCallRecvMsgSize returns a CallOption which sets the maximum message size the client can receive. func MaxCallRecvMsgSize(s int) CallOption { @@ -283,7 +293,7 @@ func (o MaxRecvMsgSizeCallOption) before(c *callInfo) error { c.maxReceiveMessageSize = &o.MaxRecvMsgSize return nil } -func (o MaxRecvMsgSizeCallOption) after(c *callInfo) { return } +func (o MaxRecvMsgSizeCallOption) after(c *callInfo) {} // MaxCallSendMsgSize returns a CallOption which sets the maximum message size the client can send. func MaxCallSendMsgSize(s int) CallOption { @@ -301,7 +311,7 @@ func (o MaxSendMsgSizeCallOption) before(c *callInfo) error { c.maxSendMessageSize = &o.MaxSendMsgSize return nil } -func (o MaxSendMsgSizeCallOption) after(c *callInfo) { return } +func (o MaxSendMsgSizeCallOption) after(c *callInfo) {} // PerRPCCredentials returns a CallOption that sets credentials.PerRPCCredentials // for a call. @@ -320,7 +330,7 @@ func (o PerRPCCredsCallOption) before(c *callInfo) error { c.creds = o.Creds return nil } -func (o PerRPCCredsCallOption) after(c *callInfo) { return } +func (o PerRPCCredsCallOption) after(c *callInfo) {} // UseCompressor returns a CallOption which sets the compressor used when // sending the request. If WithCompressor is also set, UseCompressor has @@ -341,7 +351,7 @@ func (o CompressorCallOption) before(c *callInfo) error { c.compressorType = o.CompressorType return nil } -func (o CompressorCallOption) after(c *callInfo) { return } +func (o CompressorCallOption) after(c *callInfo) {} // CallContentSubtype returns a CallOption that will set the content-subtype // for a call. For example, if content-subtype is "json", the Content-Type over @@ -352,7 +362,7 @@ func (o CompressorCallOption) after(c *callInfo) { return } // // If CallCustomCodec is not also used, the content-subtype will be used to // look up the Codec to use in the registry controlled by RegisterCodec. See -// the documention on RegisterCodec for details on registration. The lookup +// the documentation on RegisterCodec for details on registration. The lookup // of content-subtype is case-insensitive. If no such Codec is found, the call // will result in an error with code codes.Internal. // @@ -374,7 +384,7 @@ func (o ContentSubtypeCallOption) before(c *callInfo) error { c.contentSubtype = o.ContentSubtype return nil } -func (o ContentSubtypeCallOption) after(c *callInfo) { return } +func (o ContentSubtypeCallOption) after(c *callInfo) {} // CallCustomCodec returns a CallOption that will set the given Codec to be // used for all request and response messages for a call. The result of calling @@ -403,7 +413,7 @@ func (o CustomCodecCallOption) before(c *callInfo) error { c.codec = o.Codec return nil } -func (o CustomCodecCallOption) after(c *callInfo) { return } +func (o CustomCodecCallOption) after(c *callInfo) {} // The format of the payload: compressed or not? type payloadFormat uint8 @@ -712,6 +722,6 @@ const ( ) // Version is the current grpc version. -const Version = "1.11.3" +const Version = "1.12.2" const grpcUA = "grpc-go/" + Version diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index c6b413b9d9d..4969331cb3d 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -37,6 +37,8 @@ import ( "golang.org/x/net/context" "golang.org/x/net/http2" "golang.org/x/net/trace" + + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding" @@ -97,11 +99,19 @@ type Server struct { m map[string]*service // service name -> service info events trace.EventLog - quit chan struct{} - done chan struct{} - quitOnce sync.Once - doneOnce sync.Once - serveWG sync.WaitGroup // counts active Serve goroutines for GracefulStop + quit chan struct{} + done chan struct{} + quitOnce sync.Once + doneOnce sync.Once + channelzRemoveOnce sync.Once + serveWG sync.WaitGroup // counts active Serve goroutines for GracefulStop + + channelzID int64 // channelz unique identification number + czmu sync.RWMutex + callsStarted int64 + callsFailed int64 + callsSucceeded int64 + lastCallStartedTime time.Time } type options struct { @@ -216,7 +226,9 @@ func RPCDecompressor(dc Decompressor) ServerOption { } // MaxMsgSize returns a ServerOption to set the max message size in bytes the server can receive. -// If this is not set, gRPC uses the default limit. Deprecated: use MaxRecvMsgSize instead. +// If this is not set, gRPC uses the default limit. +// +// Deprecated: use MaxRecvMsgSize instead. func MaxMsgSize(m int) ServerOption { return MaxRecvMsgSize(m) } @@ -343,6 +355,10 @@ func NewServer(opt ...ServerOption) *Server { _, file, line, _ := runtime.Caller(1) s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line)) } + + if channelz.IsOn() { + s.channelzID = channelz.RegisterServer(s, "") + } return s } @@ -458,6 +474,25 @@ func (s *Server) useTransportAuthenticator(rawConn net.Conn) (net.Conn, credenti return s.opts.creds.ServerHandshake(rawConn) } +type listenSocket struct { + net.Listener + channelzID int64 +} + +func (l *listenSocket) ChannelzMetric() *channelz.SocketInternalMetric { + return &channelz.SocketInternalMetric{ + LocalAddr: l.Listener.Addr(), + } +} + +func (l *listenSocket) Close() error { + err := l.Listener.Close() + if channelz.IsOn() { + channelz.RemoveEntry(l.channelzID) + } + return err +} + // Serve accepts incoming connections on the listener lis, creating a new // ServerTransport and service goroutine for each. The service goroutines // read gRPC requests and then call the registered handlers to reply to them. @@ -486,13 +521,19 @@ func (s *Server) Serve(lis net.Listener) error { } }() - s.lis[lis] = true + ls := &listenSocket{Listener: lis} + s.lis[ls] = true + + if channelz.IsOn() { + ls.channelzID = channelz.RegisterListenSocket(ls, s.channelzID, "") + } s.mu.Unlock() + defer func() { s.mu.Lock() - if s.lis != nil && s.lis[lis] { - lis.Close() - delete(s.lis, lis) + if s.lis != nil && s.lis[ls] { + ls.Close() + delete(s.lis, ls) } s.mu.Unlock() }() @@ -614,6 +655,7 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr InitialConnWindowSize: s.opts.initialConnWindowSize, WriteBufferSize: s.opts.writeBufferSize, ReadBufferSize: s.opts.readBufferSize, + ChannelzParentID: s.channelzID, } st, err := transport.NewServerTransport("http2", c, config) if err != nil { @@ -624,6 +666,7 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr grpclog.Warningln("grpc: Server.Serve failed to create ServerTransport: ", err) return nil } + return st } @@ -751,6 +794,38 @@ func (s *Server) removeConn(c io.Closer) { } } +// ChannelzMetric returns ServerInternalMetric of current server. +// This is an EXPERIMENTAL API. +func (s *Server) ChannelzMetric() *channelz.ServerInternalMetric { + s.czmu.RLock() + defer s.czmu.RUnlock() + return &channelz.ServerInternalMetric{ + CallsStarted: s.callsStarted, + CallsSucceeded: s.callsSucceeded, + CallsFailed: s.callsFailed, + LastCallStartedTimestamp: s.lastCallStartedTime, + } +} + +func (s *Server) incrCallsStarted() { + s.czmu.Lock() + s.callsStarted++ + s.lastCallStartedTime = time.Now() + s.czmu.Unlock() +} + +func (s *Server) incrCallsSucceeded() { + s.czmu.Lock() + s.callsSucceeded++ + s.czmu.Unlock() +} + +func (s *Server) incrCallsFailed() { + s.czmu.Lock() + s.callsFailed++ + s.czmu.Unlock() +} + func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error { var ( outPayload *stats.OutPayload @@ -775,6 +850,16 @@ func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Str } func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc, trInfo *traceInfo) (err error) { + if channelz.IsOn() { + s.incrCallsStarted() + defer func() { + if err != nil && err != io.EOF { + s.incrCallsFailed() + } else { + s.incrCallsSucceeded() + } + }() + } sh := s.opts.statsHandler if sh != nil { beginTime := time.Now() @@ -869,6 +954,9 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } return err } + if channelz.IsOn() { + t.IncrMsgRecv() + } if st := checkRecvPayload(pf, stream.RecvCompress(), dc != nil || decomp != nil); st != nil { if e := t.WriteStatus(stream, st); e != nil { grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status %v", e) @@ -968,6 +1056,9 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } return err } + if channelz.IsOn() { + t.IncrMsgSent() + } if trInfo != nil { trInfo.tr.LazyLog(&payload{sent: true, msg: reply}, true) } @@ -978,6 +1069,16 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc, trInfo *traceInfo) (err error) { + if channelz.IsOn() { + s.incrCallsStarted() + defer func() { + if err != nil && err != io.EOF { + s.incrCallsFailed() + } else { + s.incrCallsSucceeded() + } + }() + } sh := s.opts.statsHandler if sh != nil { beginTime := time.Now() @@ -1199,10 +1300,12 @@ type ServerTransportStream interface { SetTrailer(md metadata.MD) error } -// serverStreamFromContext returns the server stream saved in ctx. Returns -// nil if the given context has no stream associated with it (which implies -// it is not an RPC invocation context). -func serverTransportStreamFromContext(ctx context.Context) ServerTransportStream { +// ServerTransportStreamFromContext returns the ServerTransportStream saved in +// ctx. Returns nil if the given context has no stream associated with it +// (which implies it is not an RPC invocation context). +// +// This API is EXPERIMENTAL. +func ServerTransportStreamFromContext(ctx context.Context) ServerTransportStream { s, _ := ctx.Value(streamKey{}).(ServerTransportStream) return s } @@ -1224,6 +1327,12 @@ func (s *Server) Stop() { }) }() + s.channelzRemoveOnce.Do(func() { + if channelz.IsOn() { + channelz.RemoveEntry(s.channelzID) + } + }) + s.mu.Lock() listeners := s.lis s.lis = nil @@ -1262,11 +1371,17 @@ func (s *Server) GracefulStop() { }) }() + s.channelzRemoveOnce.Do(func() { + if channelz.IsOn() { + channelz.RemoveEntry(s.channelzID) + } + }) s.mu.Lock() if s.conns == nil { s.mu.Unlock() return } + for lis := range s.lis { lis.Close() } @@ -1327,7 +1442,7 @@ func SetHeader(ctx context.Context, md metadata.MD) error { if md.Len() == 0 { return nil } - stream := serverTransportStreamFromContext(ctx) + stream := ServerTransportStreamFromContext(ctx) if stream == nil { return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } @@ -1337,7 +1452,7 @@ func SetHeader(ctx context.Context, md metadata.MD) error { // SendHeader sends header metadata. It may be called at most once. // The provided md and headers set by SetHeader() will be sent. func SendHeader(ctx context.Context, md metadata.MD) error { - stream := serverTransportStreamFromContext(ctx) + stream := ServerTransportStreamFromContext(ctx) if stream == nil { return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } @@ -1353,7 +1468,7 @@ func SetTrailer(ctx context.Context, md metadata.MD) error { if md.Len() == 0 { return nil } - stream := serverTransportStreamFromContext(ctx) + stream := ServerTransportStreamFromContext(ctx) if stream == nil { return status.Errorf(codes.Internal, "grpc: failed to fetch the stream from the context %v", ctx) } @@ -1363,7 +1478,7 @@ func SetTrailer(ctx context.Context, md metadata.MD) error { // Method returns the method string for the server context. The returned // string is in the format of "/service/method". func Method(ctx context.Context) (string, bool) { - s := serverTransportStreamFromContext(ctx) + s := ServerTransportStreamFromContext(ctx) if s == nil { return "", false } diff --git a/vendor/google.golang.org/grpc/service_config.go b/vendor/google.golang.org/grpc/service_config.go index 53fa88f3793..015631d8d38 100644 --- a/vendor/google.golang.org/grpc/service_config.go +++ b/vendor/google.golang.org/grpc/service_config.go @@ -32,7 +32,8 @@ const maxInt = int(^uint(0) >> 1) // MethodConfig defines the configuration recommended by the service providers for a // particular method. -// DEPRECATED: Users should not use this struct. Service config should be received +// +// Deprecated: Users should not use this struct. Service config should be received // through name resolver, as specified here // https://github.com/grpc/grpc/blob/master/doc/service_config.md type MethodConfig struct { @@ -59,7 +60,8 @@ type MethodConfig struct { // ServiceConfig is provided by the service provider and contains parameters for how // clients that connect to the service should behave. -// DEPRECATED: Users should not use this struct. Service config should be received +// +// Deprecated: Users should not use this struct. Service config should be received // through name resolver, as specified here // https://github.com/grpc/grpc/blob/master/doc/service_config.md type ServiceConfig struct { @@ -71,6 +73,8 @@ type ServiceConfig struct { // If there's no exact match, look for the default config for the service (/service/) and use the corresponding MethodConfig if it exists. // Otherwise, the method has no MethodConfig to use. Methods map[string]MethodConfig + + stickinessMetadataKey *string } func parseDuration(s *string) (*time.Duration, error) { @@ -144,8 +148,9 @@ type jsonMC struct { // TODO(lyuxuan): delete this struct after cleaning up old service config implementation. type jsonSC struct { - LoadBalancingPolicy *string - MethodConfig *[]jsonMC + LoadBalancingPolicy *string + StickinessMetadataKey *string + MethodConfig *[]jsonMC } func parseServiceConfig(js string) (ServiceConfig, error) { @@ -158,6 +163,8 @@ func parseServiceConfig(js string) (ServiceConfig, error) { sc := ServiceConfig{ LB: rsc.LoadBalancingPolicy, Methods: make(map[string]MethodConfig), + + stickinessMetadataKey: rsc.StickinessMetadataKey, } if rsc.MethodConfig == nil { return sc, nil diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index 75a4e8d45b7..82921a15a3a 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -27,6 +27,7 @@ import ( "golang.org/x/net/context" "golang.org/x/net/trace" "google.golang.org/grpc/balancer" + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/encoding" "google.golang.org/grpc/metadata" @@ -121,6 +122,14 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) { + if channelz.IsOn() { + cc.incrCallsStarted() + defer func() { + if err != nil { + cc.incrCallsFailed() + } + }() + } c := defaultCallInfo() mc := cc.GetMethodConfig(method) if mc.WaitForReady != nil { @@ -272,6 +281,7 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth cs := &clientStream{ opts: opts, c: c, + cc: cc, desc: desc, codec: c.codec, cp: cp, @@ -313,6 +323,7 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth type clientStream struct { opts []CallOption c *callInfo + cc *ClientConn desc *StreamDesc codec baseCodec @@ -401,6 +412,13 @@ func (cs *clientStream) finish(err error) { } cs.finished = true cs.mu.Unlock() + if channelz.IsOn() { + if err != nil { + cs.cc.incrCallsFailed() + } else { + cs.cc.incrCallsSucceeded() + } + } // TODO(retry): commit current attempt if necessary. cs.attempt.finish(err) for _, o := range cs.opts { @@ -470,6 +488,9 @@ func (a *csAttempt) sendMsg(m interface{}) (err error) { outPayload.SentTime = time.Now() a.statsHandler.HandleRPC(a.ctx, outPayload) } + if channelz.IsOn() { + a.t.IncrMsgSent() + } return nil } return io.EOF @@ -525,6 +546,9 @@ func (a *csAttempt) recvMsg(m interface{}) (err error) { if inPayload != nil { a.statsHandler.HandleRPC(a.ctx, inPayload) } + if channelz.IsOn() { + a.t.IncrMsgRecv() + } if cs.desc.ServerStreams { // Subsequent messages should be received by subsequent RecvMsg calls. return nil @@ -648,7 +672,6 @@ func (ss *serverStream) SetTrailer(md metadata.MD) { return } ss.s.SetTrailer(md) - return } func (ss *serverStream) SendMsg(m interface{}) (err error) { @@ -669,6 +692,9 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { st, _ := status.FromError(toRPCErr(err)) ss.t.WriteStatus(ss.s, st) } + if channelz.IsOn() && err == nil { + ss.t.IncrMsgSent() + } }() var outPayload *stats.OutPayload if ss.statsHandler != nil { @@ -709,6 +735,9 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { st, _ := status.FromError(toRPCErr(err)) ss.t.WriteStatus(ss.s, st) } + if channelz.IsOn() && err == nil { + ss.t.IncrMsgRecv() + } }() var inPayload *stats.InPayload if ss.statsHandler != nil { diff --git a/vendor/google.golang.org/grpc/transport/controlbuf.go b/vendor/google.golang.org/grpc/transport/controlbuf.go new file mode 100644 index 00000000000..e147cd51bf1 --- /dev/null +++ b/vendor/google.golang.org/grpc/transport/controlbuf.go @@ -0,0 +1,769 @@ +/* + * + * Copyright 2014 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 transport + +import ( + "bytes" + "fmt" + "runtime" + "sync" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +type itemNode struct { + it interface{} + next *itemNode +} + +type itemList struct { + head *itemNode + tail *itemNode +} + +func (il *itemList) enqueue(i interface{}) { + n := &itemNode{it: i} + if il.tail == nil { + il.head, il.tail = n, n + return + } + il.tail.next = n + il.tail = n +} + +// peek returns the first item in the list without removing it from the +// list. +func (il *itemList) peek() interface{} { + return il.head.it +} + +func (il *itemList) dequeue() interface{} { + if il.head == nil { + return nil + } + i := il.head.it + il.head = il.head.next + if il.head == nil { + il.tail = nil + } + return i +} + +func (il *itemList) dequeueAll() *itemNode { + h := il.head + il.head, il.tail = nil, nil + return h +} + +func (il *itemList) isEmpty() bool { + return il.head == nil +} + +// The following defines various control items which could flow through +// the control buffer of transport. They represent different aspects of +// control tasks, e.g., flow control, settings, streaming resetting, etc. + +type headerFrame struct { + streamID uint32 + hf []hpack.HeaderField + endStream bool // Valid on server side. + initStream func(uint32) (bool, error) // Used only on the client side. + onWrite func() + wq *writeQuota // write quota for the stream created. + cleanup *cleanupStream // Valid on the server side. + onOrphaned func(error) // Valid on client-side +} + +type cleanupStream struct { + streamID uint32 + idPtr *uint32 + rst bool + rstCode http2.ErrCode + onWrite func() +} + +type dataFrame struct { + streamID uint32 + endStream bool + h []byte + d []byte + // onEachWrite is called every time + // a part of d is written out. + onEachWrite func() +} + +type incomingWindowUpdate struct { + streamID uint32 + increment uint32 +} + +type outgoingWindowUpdate struct { + streamID uint32 + increment uint32 +} + +type incomingSettings struct { + ss []http2.Setting +} + +type outgoingSettings struct { + ss []http2.Setting +} + +type settingsAck struct { +} + +type incomingGoAway struct { +} + +type goAway struct { + code http2.ErrCode + debugData []byte + headsUp bool + closeConn bool +} + +type ping struct { + ack bool + data [8]byte +} + +type outFlowControlSizeRequest struct { + resp chan uint32 +} + +type outStreamState int + +const ( + active outStreamState = iota + empty + waitingOnStreamQuota +) + +type outStream struct { + id uint32 + state outStreamState + itl *itemList + bytesOutStanding int + wq *writeQuota + + next *outStream + prev *outStream +} + +func (s *outStream) deleteSelf() { + if s.prev != nil { + s.prev.next = s.next + } + if s.next != nil { + s.next.prev = s.prev + } + s.next, s.prev = nil, nil +} + +type outStreamList struct { + // Following are sentinel objects that mark the + // beginning and end of the list. They do not + // contain any item lists. All valid objects are + // inserted in between them. + // This is needed so that an outStream object can + // deleteSelf() in O(1) time without knowing which + // list it belongs to. + head *outStream + tail *outStream +} + +func newOutStreamList() *outStreamList { + head, tail := new(outStream), new(outStream) + head.next = tail + tail.prev = head + return &outStreamList{ + head: head, + tail: tail, + } +} + +func (l *outStreamList) enqueue(s *outStream) { + e := l.tail.prev + e.next = s + s.prev = e + s.next = l.tail + l.tail.prev = s +} + +// remove from the beginning of the list. +func (l *outStreamList) dequeue() *outStream { + b := l.head.next + if b == l.tail { + return nil + } + b.deleteSelf() + return b +} + +type controlBuffer struct { + ch chan struct{} + done <-chan struct{} + mu sync.Mutex + consumerWaiting bool + list *itemList + err error +} + +func newControlBuffer(done <-chan struct{}) *controlBuffer { + return &controlBuffer{ + ch: make(chan struct{}, 1), + list: &itemList{}, + done: done, + } +} + +func (c *controlBuffer) put(it interface{}) error { + _, err := c.executeAndPut(nil, it) + return err +} + +func (c *controlBuffer) executeAndPut(f func(it interface{}) bool, it interface{}) (bool, error) { + var wakeUp bool + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return false, c.err + } + if f != nil { + if !f(it) { // f wasn't successful + c.mu.Unlock() + return false, nil + } + } + if c.consumerWaiting { + wakeUp = true + c.consumerWaiting = false + } + c.list.enqueue(it) + c.mu.Unlock() + if wakeUp { + select { + case c.ch <- struct{}{}: + default: + } + } + return true, nil +} + +func (c *controlBuffer) get(block bool) (interface{}, error) { + for { + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return nil, c.err + } + if !c.list.isEmpty() { + h := c.list.dequeue() + c.mu.Unlock() + return h, nil + } + if !block { + c.mu.Unlock() + return nil, nil + } + c.consumerWaiting = true + c.mu.Unlock() + select { + case <-c.ch: + case <-c.done: + c.finish() + return nil, ErrConnClosing + } + } +} + +func (c *controlBuffer) finish() { + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return + } + c.err = ErrConnClosing + // There may be headers for streams in the control buffer. + // These streams need to be cleaned out since the transport + // is still not aware of these yet. + for head := c.list.dequeueAll(); head != nil; head = head.next { + hdr, ok := head.it.(*headerFrame) + if !ok { + continue + } + if hdr.onOrphaned != nil { // It will be nil on the server-side. + hdr.onOrphaned(ErrConnClosing) + } + } + c.mu.Unlock() +} + +type side int + +const ( + clientSide side = iota + serverSide +) + +type loopyWriter struct { + side side + cbuf *controlBuffer + sendQuota uint32 + oiws uint32 // outbound initial window size. + estdStreams map[uint32]*outStream // Established streams. + activeStreams *outStreamList // Streams that are sending data. + framer *framer + hBuf *bytes.Buffer // The buffer for HPACK encoding. + hEnc *hpack.Encoder // HPACK encoder. + bdpEst *bdpEstimator + draining bool + + // Side-specific handlers + ssGoAwayHandler func(*goAway) (bool, error) +} + +func newLoopyWriter(s side, fr *framer, cbuf *controlBuffer, bdpEst *bdpEstimator) *loopyWriter { + var buf bytes.Buffer + l := &loopyWriter{ + side: s, + cbuf: cbuf, + sendQuota: defaultWindowSize, + oiws: defaultWindowSize, + estdStreams: make(map[uint32]*outStream), + activeStreams: newOutStreamList(), + framer: fr, + hBuf: &buf, + hEnc: hpack.NewEncoder(&buf), + bdpEst: bdpEst, + } + return l +} + +const minBatchSize = 1000 + +// run should be run in a separate goroutine. +func (l *loopyWriter) run() { + var ( + it interface{} + err error + isEmpty bool + ) + defer func() { + errorf("transport: loopyWriter.run returning. Err: %v", err) + }() + for { + it, err = l.cbuf.get(true) + if err != nil { + return + } + if err = l.handle(it); err != nil { + return + } + if _, err = l.processData(); err != nil { + return + } + gosched := true + hasdata: + for { + it, err = l.cbuf.get(false) + if err != nil { + return + } + if it != nil { + if err = l.handle(it); err != nil { + return + } + if _, err = l.processData(); err != nil { + return + } + continue hasdata + } + if isEmpty, err = l.processData(); err != nil { + return + } + if !isEmpty { + continue hasdata + } + if gosched { + gosched = false + if l.framer.writer.offset < minBatchSize { + runtime.Gosched() + continue hasdata + } + } + l.framer.writer.Flush() + break hasdata + + } + } +} + +func (l *loopyWriter) outgoingWindowUpdateHandler(w *outgoingWindowUpdate) error { + return l.framer.fr.WriteWindowUpdate(w.streamID, w.increment) +} + +func (l *loopyWriter) incomingWindowUpdateHandler(w *incomingWindowUpdate) error { + // Otherwise update the quota. + if w.streamID == 0 { + l.sendQuota += w.increment + return nil + } + // Find the stream and update it. + if str, ok := l.estdStreams[w.streamID]; ok { + str.bytesOutStanding -= int(w.increment) + if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota > 0 && str.state == waitingOnStreamQuota { + str.state = active + l.activeStreams.enqueue(str) + return nil + } + } + return nil +} + +func (l *loopyWriter) outgoingSettingsHandler(s *outgoingSettings) error { + return l.framer.fr.WriteSettings(s.ss...) +} + +func (l *loopyWriter) incomingSettingsHandler(s *incomingSettings) error { + if err := l.applySettings(s.ss); err != nil { + return err + } + return l.framer.fr.WriteSettingsAck() +} + +func (l *loopyWriter) headerHandler(h *headerFrame) error { + if l.side == serverSide { + if h.endStream { // Case 1.A: Server wants to close stream. + // Make sure it's not a trailers only response. + if str, ok := l.estdStreams[h.streamID]; ok { + if str.state != empty { // either active or waiting on stream quota. + // add it str's list of items. + str.itl.enqueue(h) + return nil + } + } + if err := l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite); err != nil { + return err + } + return l.cleanupStreamHandler(h.cleanup) + } + // Case 1.B: Server is responding back with headers. + str := &outStream{ + state: empty, + itl: &itemList{}, + wq: h.wq, + } + l.estdStreams[h.streamID] = str + return l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite) + } + // Case 2: Client wants to originate stream. + str := &outStream{ + id: h.streamID, + state: empty, + itl: &itemList{}, + wq: h.wq, + } + str.itl.enqueue(h) + return l.originateStream(str) +} + +func (l *loopyWriter) originateStream(str *outStream) error { + hdr := str.itl.dequeue().(*headerFrame) + sendPing, err := hdr.initStream(str.id) + if err != nil { + if err == ErrConnClosing { + return err + } + // Other errors(errStreamDrain) need not close transport. + return nil + } + if err = l.writeHeader(str.id, hdr.endStream, hdr.hf, hdr.onWrite); err != nil { + return err + } + l.estdStreams[str.id] = str + if sendPing { + return l.pingHandler(&ping{data: [8]byte{}}) + } + return nil +} + +func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.HeaderField, onWrite func()) error { + if onWrite != nil { + onWrite() + } + l.hBuf.Reset() + for _, f := range hf { + if err := l.hEnc.WriteField(f); err != nil { + warningf("transport: loopyWriter.writeHeader encountered error while encoding headers:", err) + } + } + var ( + err error + endHeaders, first bool + ) + first = true + for !endHeaders { + size := l.hBuf.Len() + if size > http2MaxFrameLen { + size = http2MaxFrameLen + } else { + endHeaders = true + } + if first { + first = false + err = l.framer.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: streamID, + BlockFragment: l.hBuf.Next(size), + EndStream: endStream, + EndHeaders: endHeaders, + }) + } else { + err = l.framer.fr.WriteContinuation( + streamID, + endHeaders, + l.hBuf.Next(size), + ) + } + if err != nil { + return err + } + } + return nil +} + +func (l *loopyWriter) preprocessData(df *dataFrame) error { + str, ok := l.estdStreams[df.streamID] + if !ok { + return nil + } + // If we got data for a stream it means that + // stream was originated and the headers were sent out. + str.itl.enqueue(df) + if str.state == empty { + str.state = active + l.activeStreams.enqueue(str) + } + return nil +} + +func (l *loopyWriter) pingHandler(p *ping) error { + if !p.ack { + l.bdpEst.timesnap(p.data) + } + return l.framer.fr.WritePing(p.ack, p.data) + +} + +func (l *loopyWriter) outFlowControlSizeRequestHandler(o *outFlowControlSizeRequest) error { + o.resp <- l.sendQuota + return nil +} + +func (l *loopyWriter) cleanupStreamHandler(c *cleanupStream) error { + c.onWrite() + if str, ok := l.estdStreams[c.streamID]; ok { + // On the server side it could be a trailers-only response or + // a RST_STREAM before stream initialization thus the stream might + // not be established yet. + delete(l.estdStreams, c.streamID) + str.deleteSelf() + } + if c.rst { // If RST_STREAM needs to be sent. + if err := l.framer.fr.WriteRSTStream(c.streamID, c.rstCode); err != nil { + return err + } + } + if l.side == clientSide && l.draining && len(l.estdStreams) == 0 { + return ErrConnClosing + } + return nil +} + +func (l *loopyWriter) incomingGoAwayHandler(*incomingGoAway) error { + if l.side == clientSide { + l.draining = true + if len(l.estdStreams) == 0 { + return ErrConnClosing + } + } + return nil +} + +func (l *loopyWriter) goAwayHandler(g *goAway) error { + // Handling of outgoing GoAway is very specific to side. + if l.ssGoAwayHandler != nil { + draining, err := l.ssGoAwayHandler(g) + if err != nil { + return err + } + l.draining = draining + } + return nil +} + +func (l *loopyWriter) handle(i interface{}) error { + switch i := i.(type) { + case *incomingWindowUpdate: + return l.incomingWindowUpdateHandler(i) + case *outgoingWindowUpdate: + return l.outgoingWindowUpdateHandler(i) + case *incomingSettings: + return l.incomingSettingsHandler(i) + case *outgoingSettings: + return l.outgoingSettingsHandler(i) + case *headerFrame: + return l.headerHandler(i) + case *cleanupStream: + return l.cleanupStreamHandler(i) + case *incomingGoAway: + return l.incomingGoAwayHandler(i) + case *dataFrame: + return l.preprocessData(i) + case *ping: + return l.pingHandler(i) + case *goAway: + return l.goAwayHandler(i) + case *outFlowControlSizeRequest: + return l.outFlowControlSizeRequestHandler(i) + default: + return fmt.Errorf("transport: unknown control message type %T", i) + } +} + +func (l *loopyWriter) applySettings(ss []http2.Setting) error { + for _, s := range ss { + switch s.ID { + case http2.SettingInitialWindowSize: + o := l.oiws + l.oiws = s.Val + if o < l.oiws { + // If the new limit is greater make all depleted streams active. + for _, stream := range l.estdStreams { + if stream.state == waitingOnStreamQuota { + stream.state = active + l.activeStreams.enqueue(stream) + } + } + } + } + } + return nil +} + +func (l *loopyWriter) processData() (bool, error) { + if l.sendQuota == 0 { + return true, nil + } + str := l.activeStreams.dequeue() + if str == nil { + return true, nil + } + dataItem := str.itl.peek().(*dataFrame) + if len(dataItem.h) == 0 && len(dataItem.d) == 0 { + // Client sends out empty data frame with endStream = true + if err := l.framer.fr.WriteData(dataItem.streamID, dataItem.endStream, nil); err != nil { + return false, err + } + str.itl.dequeue() + if str.itl.isEmpty() { + str.state = empty + } else if trailer, ok := str.itl.peek().(*headerFrame); ok { // the next item is trailers. + if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil { + return false, err + } + if err := l.cleanupStreamHandler(trailer.cleanup); err != nil { + return false, nil + } + } else { + l.activeStreams.enqueue(str) + } + return false, nil + } + var ( + idx int + buf []byte + ) + if len(dataItem.h) != 0 { // data header has not been written out yet. + buf = dataItem.h + } else { + idx = 1 + buf = dataItem.d + } + size := http2MaxFrameLen + if len(buf) < size { + size = len(buf) + } + if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { + str.state = waitingOnStreamQuota + return false, nil + } else if strQuota < size { + size = strQuota + } + + if l.sendQuota < uint32(size) { + size = int(l.sendQuota) + } + // Now that outgoing flow controls are checked we can replenish str's write quota + str.wq.replenish(size) + var endStream bool + // This last data message on this stream and all + // of it can be written in this go. + if dataItem.endStream && size == len(buf) { + // buf contains either data or it contains header but data is empty. + if idx == 1 || len(dataItem.d) == 0 { + endStream = true + } + } + if dataItem.onEachWrite != nil { + dataItem.onEachWrite() + } + if err := l.framer.fr.WriteData(dataItem.streamID, endStream, buf[:size]); err != nil { + return false, err + } + buf = buf[size:] + str.bytesOutStanding += size + l.sendQuota -= uint32(size) + if idx == 0 { + dataItem.h = buf + } else { + dataItem.d = buf + } + + if len(dataItem.h) == 0 && len(dataItem.d) == 0 { // All the data from that message was written out. + str.itl.dequeue() + } + if str.itl.isEmpty() { + str.state = empty + } else if trailer, ok := str.itl.peek().(*headerFrame); ok { // The next item is trailers. + if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil { + return false, err + } + if err := l.cleanupStreamHandler(trailer.cleanup); err != nil { + return false, err + } + } else if int(l.oiws)-str.bytesOutStanding <= 0 { // Ran out of stream quota. + str.state = waitingOnStreamQuota + } else { // Otherwise add it back to the list of active streams. + l.activeStreams.enqueue(str) + } + return false, nil +} diff --git a/vendor/google.golang.org/grpc/transport/control.go b/vendor/google.golang.org/grpc/transport/flowcontrol.go similarity index 52% rename from vendor/google.golang.org/grpc/transport/control.go rename to vendor/google.golang.org/grpc/transport/flowcontrol.go index 0474b09074b..378f5c4502c 100644 --- a/vendor/google.golang.org/grpc/transport/control.go +++ b/vendor/google.golang.org/grpc/transport/flowcontrol.go @@ -20,13 +20,10 @@ package transport import ( "fmt" - "io" "math" "sync" + "sync/atomic" "time" - - "golang.org/x/net/http2" - "golang.org/x/net/http2/hpack" ) const ( @@ -36,202 +33,109 @@ const ( initialWindowSize = defaultWindowSize // for an RPC infinity = time.Duration(math.MaxInt64) defaultClientKeepaliveTime = infinity - defaultClientKeepaliveTimeout = time.Duration(20 * time.Second) + defaultClientKeepaliveTimeout = 20 * time.Second defaultMaxStreamsClient = 100 defaultMaxConnectionIdle = infinity defaultMaxConnectionAge = infinity defaultMaxConnectionAgeGrace = infinity - defaultServerKeepaliveTime = time.Duration(2 * time.Hour) - defaultServerKeepaliveTimeout = time.Duration(20 * time.Second) - defaultKeepalivePolicyMinTime = time.Duration(5 * time.Minute) + defaultServerKeepaliveTime = 2 * time.Hour + defaultServerKeepaliveTimeout = 20 * time.Second + defaultKeepalivePolicyMinTime = 5 * time.Minute // max window limit set by HTTP2 Specs. maxWindowSize = math.MaxInt32 - // defaultLocalSendQuota sets is default value for number of data + // defaultWriteQuota is the default value for number of data // bytes that each stream can schedule before some of it being // flushed out. - defaultLocalSendQuota = 128 * 1024 + defaultWriteQuota = 64 * 1024 ) -// The following defines various control items which could flow through -// the control buffer of transport. They represent different aspects of -// control tasks, e.g., flow control, settings, streaming resetting, etc. - -type headerFrame struct { - streamID uint32 - hf []hpack.HeaderField - endStream bool -} - -func (*headerFrame) item() {} - -type continuationFrame struct { - streamID uint32 - endHeaders bool - headerBlockFragment []byte -} - -type dataFrame struct { - streamID uint32 - endStream bool - d []byte - f func() -} - -func (*dataFrame) item() {} - -func (*continuationFrame) item() {} - -type windowUpdate struct { - streamID uint32 - increment uint32 -} - -func (*windowUpdate) item() {} - -type settings struct { - ss []http2.Setting -} - -func (*settings) item() {} - -type settingsAck struct { -} - -func (*settingsAck) item() {} - -type resetStream struct { - streamID uint32 - code http2.ErrCode -} - -func (*resetStream) item() {} - -type goAway struct { - code http2.ErrCode - debugData []byte - headsUp bool - closeConn bool -} - -func (*goAway) item() {} - -type flushIO struct { - closeTr bool -} - -func (*flushIO) item() {} - -type ping struct { - ack bool - data [8]byte -} - -func (*ping) item() {} - -// quotaPool is a pool which accumulates the quota and sends it to acquire() -// when it is available. -type quotaPool struct { - mu sync.Mutex - c chan struct{} - version uint32 - quota int -} - -// newQuotaPool creates a quotaPool which has quota q available to consume. -func newQuotaPool(q int) *quotaPool { - qb := "aPool{ - quota: q, - c: make(chan struct{}, 1), +// writeQuota is a soft limit on the amount of data a stream can +// schedule before some of it is written out. +type writeQuota struct { + quota int32 + // get waits on read from when quota goes less than or equal to zero. + // replenish writes on it when quota goes positive again. + ch chan struct{} + // done is triggered in error case. + done <-chan struct{} +} + +func newWriteQuota(sz int32, done <-chan struct{}) *writeQuota { + return &writeQuota{ + quota: sz, + ch: make(chan struct{}, 1), + done: done, } - return qb } -// add cancels the pending quota sent on acquired, incremented by v and sends -// it back on acquire. -func (qb *quotaPool) add(v int) { - qb.mu.Lock() - defer qb.mu.Unlock() - qb.lockedAdd(v) +func (w *writeQuota) get(sz int32) error { + for { + if atomic.LoadInt32(&w.quota) > 0 { + atomic.AddInt32(&w.quota, -sz) + return nil + } + select { + case <-w.ch: + continue + case <-w.done: + return errStreamDone + } + } } -func (qb *quotaPool) lockedAdd(v int) { - var wakeUp bool - if qb.quota <= 0 { - wakeUp = true // Wake up potential waiters. - } - qb.quota += v - if wakeUp && qb.quota > 0 { +func (w *writeQuota) replenish(n int) { + sz := int32(n) + a := atomic.AddInt32(&w.quota, sz) + b := a - sz + if b <= 0 && a > 0 { select { - case qb.c <- struct{}{}: + case w.ch <- struct{}{}: default: } } } -func (qb *quotaPool) addAndUpdate(v int) { - qb.mu.Lock() - qb.lockedAdd(v) - qb.version++ - qb.mu.Unlock() +type trInFlow struct { + limit uint32 + unacked uint32 + effectiveWindowSize uint32 } -func (qb *quotaPool) get(v int, wc waiters) (int, uint32, error) { - qb.mu.Lock() - if qb.quota > 0 { - if v > qb.quota { - v = qb.quota - } - qb.quota -= v - ver := qb.version - qb.mu.Unlock() - return v, ver, nil - } - qb.mu.Unlock() - for { - select { - case <-wc.ctx.Done(): - return 0, 0, ContextErr(wc.ctx.Err()) - case <-wc.tctx.Done(): - return 0, 0, ErrConnClosing - case <-wc.done: - return 0, 0, io.EOF - case <-wc.goAway: - return 0, 0, errStreamDrain - case <-qb.c: - qb.mu.Lock() - if qb.quota > 0 { - if v > qb.quota { - v = qb.quota - } - qb.quota -= v - ver := qb.version - if qb.quota > 0 { - select { - case qb.c <- struct{}{}: - default: - } - } - qb.mu.Unlock() - return v, ver, nil +func (f *trInFlow) newLimit(n uint32) uint32 { + d := n - f.limit + f.limit = n + f.updateEffectiveWindowSize() + return d +} - } - qb.mu.Unlock() - } +func (f *trInFlow) onData(n uint32) uint32 { + f.unacked += n + if f.unacked >= f.limit/4 { + w := f.unacked + f.unacked = 0 + f.updateEffectiveWindowSize() + return w } + f.updateEffectiveWindowSize() + return 0 } -func (qb *quotaPool) compareAndExecute(version uint32, success, failure func()) bool { - qb.mu.Lock() - if version == qb.version { - success() - qb.mu.Unlock() - return true - } - failure() - qb.mu.Unlock() - return false +func (f *trInFlow) reset() uint32 { + w := f.unacked + f.unacked = 0 + f.updateEffectiveWindowSize() + return w } +func (f *trInFlow) updateEffectiveWindowSize() { + atomic.StoreUint32(&f.effectiveWindowSize, f.limit-f.unacked) +} + +func (f *trInFlow) getSize() uint32 { + return atomic.LoadUint32(&f.effectiveWindowSize) +} + +// TODO(mmukhi): Simplify this code. // inFlow deals with inbound flow control type inFlow struct { mu sync.Mutex @@ -252,9 +156,9 @@ type inFlow struct { // It assumes that n is always greater than the old limit. func (f *inFlow) newLimit(n uint32) uint32 { f.mu.Lock() - defer f.mu.Unlock() d := n - f.limit f.limit = n + f.mu.Unlock() return d } @@ -263,7 +167,6 @@ func (f *inFlow) maybeAdjust(n uint32) uint32 { n = uint32(math.MaxInt32) } f.mu.Lock() - defer f.mu.Unlock() // estSenderQuota is the receiver's view of the maximum number of bytes the sender // can send without a window update. estSenderQuota := int32(f.limit - (f.pendingData + f.pendingUpdate)) @@ -275,7 +178,7 @@ func (f *inFlow) maybeAdjust(n uint32) uint32 { // for this message. Therefore we must send an update over the limit since there's an active read // request from the application. if estUntransmittedData > estSenderQuota { - // Sender's window shouldn't go more than 2^31 - 1 as speecified in the HTTP spec. + // Sender's window shouldn't go more than 2^31 - 1 as specified in the HTTP spec. if f.limit+n > maxWindowSize { f.delta = maxWindowSize - f.limit } else { @@ -284,19 +187,24 @@ func (f *inFlow) maybeAdjust(n uint32) uint32 { // is padded; We will fallback on the current available window(at least a 1/4th of the limit). f.delta = n } + f.mu.Unlock() return f.delta } + f.mu.Unlock() return 0 } // onData is invoked when some data frame is received. It updates pendingData. func (f *inFlow) onData(n uint32) error { f.mu.Lock() - defer f.mu.Unlock() f.pendingData += n if f.pendingData+f.pendingUpdate > f.limit+f.delta { - return fmt.Errorf("received %d-bytes data exceeding the limit %d bytes", f.pendingData+f.pendingUpdate, f.limit) + limit := f.limit + rcvd := f.pendingData + f.pendingUpdate + f.mu.Unlock() + return fmt.Errorf("received %d-bytes data exceeding the limit %d bytes", rcvd, limit) } + f.mu.Unlock() return nil } @@ -304,8 +212,8 @@ func (f *inFlow) onData(n uint32) error { // to be sent to the peer. func (f *inFlow) onRead(n uint32) uint32 { f.mu.Lock() - defer f.mu.Unlock() if f.pendingData == 0 { + f.mu.Unlock() return 0 } f.pendingData -= n @@ -320,15 +228,9 @@ func (f *inFlow) onRead(n uint32) uint32 { if f.pendingUpdate >= f.limit/4 { wu := f.pendingUpdate f.pendingUpdate = 0 + f.mu.Unlock() return wu } + f.mu.Unlock() return 0 } - -func (f *inFlow) resetPendingUpdate() uint32 { - f.mu.Lock() - defer f.mu.Unlock() - n := f.pendingUpdate - f.pendingUpdate = 0 - return n -} diff --git a/vendor/google.golang.org/grpc/transport/handler_server.go b/vendor/google.golang.org/grpc/transport/handler_server.go index 1a5e96c5a17..f71b7482174 100644 --- a/vendor/google.golang.org/grpc/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/transport/handler_server.go @@ -92,7 +92,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats sta } for k, vv := range r.Header { k = strings.ToLower(k) - if isReservedHeader(k) && !isWhitelistedPseudoHeader(k) { + if isReservedHeader(k) && !isWhitelistedHeader(k) { continue } for _, v := range vv { @@ -365,7 +365,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace ht.stats.HandleRPC(s.ctx, inHeader) } s.trReader = &transportReader{ - reader: &recvBufferReader{ctx: s.ctx, recv: s.buf}, + reader: &recvBufferReader{ctx: s.ctx, ctxDone: s.ctx.Done(), recv: s.buf}, windowHandler: func(int) {}, } @@ -420,6 +420,10 @@ func (ht *serverHandlerTransport) runStream() { } } +func (ht *serverHandlerTransport) IncrMsgSent() {} + +func (ht *serverHandlerTransport) IncrMsgRecv() {} + func (ht *serverHandlerTransport) Drain() { panic("Drain() is not implemented") } diff --git a/vendor/google.golang.org/grpc/transport/http2_client.go b/vendor/google.golang.org/grpc/transport/http2_client.go index 8b5be0d6d51..1fdabd954ef 100644 --- a/vendor/google.golang.org/grpc/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/transport/http2_client.go @@ -19,8 +19,6 @@ package transport import ( - "bytes" - "fmt" "io" "math" "net" @@ -32,6 +30,8 @@ import ( "golang.org/x/net/context" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" + + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" @@ -45,14 +45,17 @@ import ( type http2Client struct { ctx context.Context cancel context.CancelFunc + ctxDone <-chan struct{} // Cache the ctx.Done() chan. userAgent string md interface{} conn net.Conn // underlying communication channel + loopy *loopyWriter remoteAddr net.Addr localAddr net.Addr authInfo credentials.AuthInfo // auth info about the connection - nextID uint32 // the next stream ID to be used + readerDone chan struct{} // sync point to enable testing. + writerDone chan struct{} // sync point to enable testing. // goAway is closed to notify the upper layer (i.e., addrConn.transportMonitor) // that the server sent GoAway on this transport. goAway chan struct{} @@ -60,21 +63,10 @@ type http2Client struct { awakenKeepalive chan struct{} framer *framer - hBuf *bytes.Buffer // the buffer for HPACK encoding - hEnc *hpack.Encoder // HPACK encoder - // controlBuf delivers all the control related tasks (e.g., window // updates, reset streams, and various settings) to the controller. controlBuf *controlBuffer - fc *inFlow - // sendQuotaPool provides flow control to outbound message. - sendQuotaPool *quotaPool - // localSendQuota limits the amount of data that can be scheduled - // for writing before it is actually written out. - localSendQuota *quotaPool - // streamsQuota limits the max number of concurrent streams. - streamsQuota *quotaPool - + fc *trInFlow // The scheme used: https if TLS is on, http otherwise. scheme string @@ -91,26 +83,42 @@ type http2Client struct { initialWindowSize int32 - bdpEst *bdpEstimator - outQuotaVersion uint32 - + bdpEst *bdpEstimator // onSuccess is a callback that client transport calls upon // receiving server preface to signal that a succefull HTTP2 // connection was established. onSuccess func() - mu sync.Mutex // guard the following variables - state transportState // the state of underlying connection + maxConcurrentStreams uint32 + streamQuota int64 + streamsQuotaAvailable chan struct{} + waitingStreams uint32 + nextID uint32 + + mu sync.Mutex // guard the following variables + state transportState activeStreams map[uint32]*Stream - // The max number of concurrent streams - maxStreams int - // the per-stream outbound flow control window size set by the peer. - streamSendQuota uint32 // prevGoAway ID records the Last-Stream-ID in the previous GOAway frame. prevGoAwayID uint32 // goAwayReason records the http2.ErrCode and debug data received with the // GoAway frame. goAwayReason GoAwayReason + + // Fields below are for channelz metric collection. + channelzID int64 // channelz unique identification number + czmu sync.RWMutex + kpCount int64 + // The number of streams that have started, including already finished ones. + streamsStarted int64 + // The number of streams that have ended successfully by receiving EoS bit set + // frame from server. + streamsSucceeded int64 + streamsFailed int64 + lastStreamCreated time.Time + msgSent int64 + msgRecv int64 + lastMsgSent time.Time + lastMsgRecv time.Time } func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr string) (net.Conn, error) { @@ -187,7 +195,6 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne icwz = opts.InitialConnWindowSize dynamicWindow = false } - var buf bytes.Buffer writeBufSize := defaultWriteBufSize if opts.WriteBufferSize > 0 { writeBufSize = opts.WriteBufferSize @@ -197,38 +204,35 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne readBufSize = opts.ReadBufferSize } t := &http2Client{ - ctx: ctx, - cancel: cancel, - userAgent: opts.UserAgent, - md: addr.Metadata, - conn: conn, - remoteAddr: conn.RemoteAddr(), - localAddr: conn.LocalAddr(), - authInfo: authInfo, - // The client initiated stream id is odd starting from 1. - nextID: 1, - goAway: make(chan struct{}), - awakenKeepalive: make(chan struct{}, 1), - hBuf: &buf, - hEnc: hpack.NewEncoder(&buf), - framer: newFramer(conn, writeBufSize, readBufSize), - controlBuf: newControlBuffer(), - fc: &inFlow{limit: uint32(icwz)}, - sendQuotaPool: newQuotaPool(defaultWindowSize), - localSendQuota: newQuotaPool(defaultLocalSendQuota), - scheme: scheme, - state: reachable, - activeStreams: make(map[uint32]*Stream), - isSecure: isSecure, - creds: opts.PerRPCCredentials, - maxStreams: defaultMaxStreamsClient, - streamsQuota: newQuotaPool(defaultMaxStreamsClient), - streamSendQuota: defaultWindowSize, - kp: kp, - statsHandler: opts.StatsHandler, - initialWindowSize: initialWindowSize, - onSuccess: onSuccess, - } + ctx: ctx, + ctxDone: ctx.Done(), // Cache Done chan. + cancel: cancel, + userAgent: opts.UserAgent, + md: addr.Metadata, + conn: conn, + remoteAddr: conn.RemoteAddr(), + localAddr: conn.LocalAddr(), + authInfo: authInfo, + readerDone: make(chan struct{}), + writerDone: make(chan struct{}), + goAway: make(chan struct{}), + awakenKeepalive: make(chan struct{}, 1), + framer: newFramer(conn, writeBufSize, readBufSize), + fc: &trInFlow{limit: uint32(icwz)}, + scheme: scheme, + activeStreams: make(map[uint32]*Stream), + isSecure: isSecure, + creds: opts.PerRPCCredentials, + kp: kp, + statsHandler: opts.StatsHandler, + initialWindowSize: initialWindowSize, + onSuccess: onSuccess, + nextID: 1, + maxConcurrentStreams: defaultMaxStreamsClient, + streamQuota: defaultMaxStreamsClient, + streamsQuotaAvailable: make(chan struct{}, 1), + } + t.controlBuf = newControlBuffer(t.ctxDone) if opts.InitialWindowSize >= defaultWindowSize { t.initialWindowSize = opts.InitialWindowSize dynamicWindow = false @@ -252,6 +256,9 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne } t.statsHandler.HandleConn(t.ctx, connBegin) } + if channelz.IsOn() { + t.channelzID = channelz.RegisterNormalSocket(t, opts.ChannelzParentID, "") + } // Start the reader goroutine for incoming message. Each transport has // a dedicated goroutine which reads HTTP2 frame from network. Then it // dispatches the frame to the corresponding stream entity. @@ -287,8 +294,10 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne } t.framer.writer.Flush() go func() { - loopyWriter(t.ctx, t.controlBuf, t.itemHandler) + t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst) + t.loopy.run() t.conn.Close() + close(t.writerDone) }() if t.kp.Time != infinity { go t.keepalive() @@ -299,18 +308,14 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { // TODO(zhaoq): Handle uint32 overflow of Stream.id. s := &Stream{ - id: t.nextID, done: make(chan struct{}), - goAway: make(chan struct{}), method: callHdr.Method, sendCompress: callHdr.SendCompress, buf: newRecvBuffer(), - fc: &inFlow{limit: uint32(t.initialWindowSize)}, - sendQuotaPool: newQuotaPool(int(t.streamSendQuota)), headerChan: make(chan struct{}), contentSubtype: callHdr.ContentSubtype, } - t.nextID += 2 + s.wq = newWriteQuota(defaultWriteQuota, s.done) s.requestRead = func(n int) { t.adjustWindow(s, uint32(n)) } @@ -320,26 +325,18 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { s.ctx = ctx s.trReader = &transportReader{ reader: &recvBufferReader{ - ctx: s.ctx, - goAway: s.goAway, - recv: s.buf, + ctx: s.ctx, + ctxDone: s.ctx.Done(), + recv: s.buf, }, windowHandler: func(n int) { t.updateWindow(s, uint32(n)) }, } - s.waiters = waiters{ - ctx: s.ctx, - tctx: t.ctx, - done: s.done, - goAway: s.goAway, - } return s } -// NewStream creates a stream and registers it into the transport as "active" -// streams. -func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) { +func (t *http2Client) getPeer() *peer.Peer { pr := &peer.Peer{ Addr: t.remoteAddr, } @@ -347,71 +344,17 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea if t.authInfo != nil { pr.AuthInfo = t.authInfo } - ctx = peer.NewContext(ctx, pr) - var ( - authData = make(map[string]string) - audience string - ) - // Create an audience string only if needed. - if len(t.creds) > 0 || callHdr.Creds != nil { - // Construct URI required to get auth request metadata. - // Omit port if it is the default one. - host := strings.TrimSuffix(callHdr.Host, ":443") - pos := strings.LastIndex(callHdr.Method, "/") - if pos == -1 { - pos = len(callHdr.Method) - } - audience = "https://" + host + callHdr.Method[:pos] - } - for _, c := range t.creds { - data, err := c.GetRequestMetadata(ctx, audience) - if err != nil { - if _, ok := status.FromError(err); ok { - return nil, err - } + return pr +} - return nil, streamErrorf(codes.Unauthenticated, "transport: %v", err) - } - for k, v := range data { - // Capital header names are illegal in HTTP/2. - k = strings.ToLower(k) - authData[k] = v - } - } - callAuthData := map[string]string{} - // Check if credentials.PerRPCCredentials were provided via call options. - // Note: if these credentials are provided both via dial options and call - // options, then both sets of credentials will be applied. - if callCreds := callHdr.Creds; callCreds != nil { - if !t.isSecure && callCreds.RequireTransportSecurity() { - return nil, streamErrorf(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection") - } - data, err := callCreds.GetRequestMetadata(ctx, audience) - if err != nil { - return nil, streamErrorf(codes.Internal, "transport: %v", err) - } - for k, v := range data { - // Capital header names are illegal in HTTP/2 - k = strings.ToLower(k) - callAuthData[k] = v - } - } - t.mu.Lock() - if t.activeStreams == nil { - t.mu.Unlock() - return nil, ErrConnClosing - } - if t.state == draining { - t.mu.Unlock() - return nil, errStreamDrain - } - if t.state != reachable { - t.mu.Unlock() - return nil, ErrConnClosing +func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) ([]hpack.HeaderField, error) { + aud := t.createAudience(callHdr) + authData, err := t.getTrAuthData(ctx, aud) + if err != nil { + return nil, err } - t.mu.Unlock() - // Get a quota of 1 from streamsQuota. - if _, _, err := t.streamsQuota.get(1, waiters{ctx: ctx, tctx: t.ctx}); err != nil { + callAuthData, err := t.getCallAuthData(ctx, aud, callHdr) + if err != nil { return nil, err } // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields @@ -485,38 +428,178 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea } } } - t.mu.Lock() - if t.state == draining { - t.mu.Unlock() - t.streamsQuota.add(1) - return nil, errStreamDrain + return headerFields, nil +} + +func (t *http2Client) createAudience(callHdr *CallHdr) string { + // Create an audience string only if needed. + if len(t.creds) == 0 && callHdr.Creds == nil { + return "" } - if t.state != reachable { - t.mu.Unlock() - return nil, ErrConnClosing + // Construct URI required to get auth request metadata. + // Omit port if it is the default one. + host := strings.TrimSuffix(callHdr.Host, ":443") + pos := strings.LastIndex(callHdr.Method, "/") + if pos == -1 { + pos = len(callHdr.Method) + } + return "https://" + host + callHdr.Method[:pos] +} + +func (t *http2Client) getTrAuthData(ctx context.Context, audience string) (map[string]string, error) { + authData := map[string]string{} + for _, c := range t.creds { + data, err := c.GetRequestMetadata(ctx, audience) + if err != nil { + if _, ok := status.FromError(err); ok { + return nil, err + } + + return nil, streamErrorf(codes.Unauthenticated, "transport: %v", err) + } + for k, v := range data { + // Capital header names are illegal in HTTP/2. + k = strings.ToLower(k) + authData[k] = v + } + } + return authData, nil +} + +func (t *http2Client) getCallAuthData(ctx context.Context, audience string, callHdr *CallHdr) (map[string]string, error) { + callAuthData := map[string]string{} + // Check if credentials.PerRPCCredentials were provided via call options. + // Note: if these credentials are provided both via dial options and call + // options, then both sets of credentials will be applied. + if callCreds := callHdr.Creds; callCreds != nil { + if !t.isSecure && callCreds.RequireTransportSecurity() { + return nil, streamErrorf(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection") + } + data, err := callCreds.GetRequestMetadata(ctx, audience) + if err != nil { + return nil, streamErrorf(codes.Internal, "transport: %v", err) + } + for k, v := range data { + // Capital header names are illegal in HTTP/2 + k = strings.ToLower(k) + callAuthData[k] = v + } + } + return callAuthData, nil +} + +// NewStream creates a stream and registers it into the transport as "active" +// streams. +func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) { + ctx = peer.NewContext(ctx, t.getPeer()) + headerFields, err := t.createHeaderFields(ctx, callHdr) + if err != nil { + return nil, err } s := t.newStream(ctx, callHdr) - t.activeStreams[s.id] = s - // If the number of active streams change from 0 to 1, then check if keepalive - // has gone dormant. If so, wake it up. - if len(t.activeStreams) == 1 { - select { - case t.awakenKeepalive <- struct{}{}: - t.controlBuf.put(&ping{data: [8]byte{}}) - // Fill the awakenKeepalive channel again as this channel must be - // kept non-writable except at the point that the keepalive() - // goroutine is waiting either to be awaken or shutdown. - t.awakenKeepalive <- struct{}{} - default: + cleanup := func(err error) { + if s.swapState(streamDone) == streamDone { + // If it was already done, return. + return } + // The stream was unprocessed by the server. + atomic.StoreUint32(&s.unprocessed, 1) + s.write(recvMsg{err: err}) + close(s.done) + // If headerChan isn't closed, then close it. + if atomic.SwapUint32(&s.headerDone, 1) == 0 { + close(s.headerChan) + } + } - t.controlBuf.put(&headerFrame{ - streamID: s.id, + hdr := &headerFrame{ hf: headerFields, endStream: false, - }) - t.mu.Unlock() - + initStream: func(id uint32) (bool, error) { + t.mu.Lock() + if state := t.state; state != reachable { + t.mu.Unlock() + // Do a quick cleanup. + err := error(errStreamDrain) + if state == closing { + err = ErrConnClosing + } + cleanup(err) + return false, err + } + t.activeStreams[id] = s + if channelz.IsOn() { + t.czmu.Lock() + t.streamsStarted++ + t.lastStreamCreated = time.Now() + t.czmu.Unlock() + } + var sendPing bool + // If the number of active streams change from 0 to 1, then check if keepalive + // has gone dormant. If so, wake it up. + if len(t.activeStreams) == 1 { + select { + case t.awakenKeepalive <- struct{}{}: + sendPing = true + // Fill the awakenKeepalive channel again as this channel must be + // kept non-writable except at the point that the keepalive() + // goroutine is waiting either to be awaken or shutdown. + t.awakenKeepalive <- struct{}{} + default: + } + } + t.mu.Unlock() + return sendPing, nil + }, + onOrphaned: cleanup, + wq: s.wq, + } + firstTry := true + var ch chan struct{} + checkForStreamQuota := func(it interface{}) bool { + if t.streamQuota <= 0 { // Can go negative if server decreases it. + if firstTry { + t.waitingStreams++ + } + ch = t.streamsQuotaAvailable + return false + } + if !firstTry { + t.waitingStreams-- + } + t.streamQuota-- + h := it.(*headerFrame) + h.streamID = t.nextID + t.nextID += 2 + s.id = h.streamID + s.fc = &inFlow{limit: uint32(t.initialWindowSize)} + if t.streamQuota > 0 && t.waitingStreams > 0 { + select { + case t.streamsQuotaAvailable <- struct{}{}: + default: + } + } + return true + } + for { + success, err := t.controlBuf.executeAndPut(checkForStreamQuota, hdr) + if err != nil { + return nil, err + } + if success { + break + } + firstTry = false + select { + case <-ch: + case <-s.ctx.Done(): + return nil, ContextErr(s.ctx.Err()) + case <-t.goAway: + return nil, errStreamDrain + case <-t.ctx.Done(): + return nil, ErrConnClosing + } + } if t.statsHandler != nil { outHeader := &stats.OutHeader{ Client: true, @@ -533,58 +616,72 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea // CloseStream clears the footprint of a stream when the stream is not needed any more. // This must not be executed in reader's goroutine. func (t *http2Client) CloseStream(s *Stream, err error) { - t.mu.Lock() - if t.activeStreams == nil { - t.mu.Unlock() - return - } + var ( + rst bool + rstCode http2.ErrCode + ) if err != nil { - // notify in-flight streams, before the deletion - s.write(recvMsg{err: err}) + rst = true + rstCode = http2.ErrCodeCancel } - delete(t.activeStreams, s.id) - if t.state == draining && len(t.activeStreams) == 0 { - // The transport is draining and s is the last live stream on t. - t.mu.Unlock() - t.Close() + t.closeStream(s, err, rst, rstCode, nil, nil, false) +} + +func (t *http2Client) closeStream(s *Stream, err error, rst bool, rstCode http2.ErrCode, st *status.Status, mdata map[string][]string, eosReceived bool) { + // Set stream status to done. + if s.swapState(streamDone) == streamDone { + // If it was already done, return. return } - t.mu.Unlock() - // rstStream is true in case the stream is being closed at the client-side - // and the server needs to be intimated about it by sending a RST_STREAM - // frame. - // To make sure this frame is written to the wire before the headers of the - // next stream waiting for streamsQuota, we add to streamsQuota pool only - // after having acquired the writableChan to send RST_STREAM out (look at - // the controller() routine). - var rstStream bool - var rstError http2.ErrCode - defer func() { - // In case, the client doesn't have to send RST_STREAM to server - // we can safely add back to streamsQuota pool now. - if !rstStream { - t.streamsQuota.add(1) - return - } - t.controlBuf.put(&resetStream{s.id, rstError}) - }() - s.mu.Lock() - rstStream = s.rstStream - rstError = s.rstError - if s.state == streamDone { - s.mu.Unlock() - return + // status and trailers can be updated here without any synchronization because the stream goroutine will + // only read it after it sees an io.EOF error from read or write and we'll write those errors + // only after updating this. + s.status = st + if len(mdata) > 0 { + s.trailer = mdata } - if !s.headerDone { + if err != nil { + // This will unblock reads eventually. + s.write(recvMsg{err: err}) + } + // This will unblock write. + close(s.done) + // If headerChan isn't closed, then close it. + if atomic.SwapUint32(&s.headerDone, 1) == 0 { close(s.headerChan) - s.headerDone = true } - s.state = streamDone - s.mu.Unlock() - if err != nil && !rstStream { - rstStream = true - rstError = http2.ErrCodeCancel + cleanup := &cleanupStream{ + streamID: s.id, + onWrite: func() { + t.mu.Lock() + if t.activeStreams != nil { + delete(t.activeStreams, s.id) + } + t.mu.Unlock() + if channelz.IsOn() { + t.czmu.Lock() + if eosReceived { + t.streamsSucceeded++ + } else { + t.streamsFailed++ + } + t.czmu.Unlock() + } + }, + rst: rst, + rstCode: rstCode, + } + addBackStreamQuota := func(interface{}) bool { + t.streamQuota++ + if t.streamQuota > 0 && t.waitingStreams > 0 { + select { + case t.streamsQuotaAvailable <- struct{}{}: + default: + } + } + return true } + t.controlBuf.executeAndPut(addBackStreamQuota, cleanup) } // Close kicks off the shutdown process of the transport. This should be called @@ -592,27 +689,24 @@ func (t *http2Client) CloseStream(s *Stream, err error) { // accessed any more. func (t *http2Client) Close() error { t.mu.Lock() + // Make sure we only Close once. if t.state == closing { t.mu.Unlock() return nil } t.state = closing - t.mu.Unlock() - t.cancel() - err := t.conn.Close() - t.mu.Lock() streams := t.activeStreams t.activeStreams = nil t.mu.Unlock() + t.controlBuf.finish() + t.cancel() + err := t.conn.Close() + if channelz.IsOn() { + channelz.RemoveEntry(t.channelzID) + } // Notify all active streams. for _, s := range streams { - s.mu.Lock() - if !s.headerDone { - close(s.headerChan) - s.headerDone = true - } - s.mu.Unlock() - s.write(recvMsg{err: ErrConnClosing}) + t.closeStream(s, ErrConnClosing, false, http2.ErrCodeNo, nil, nil, false) } if t.statsHandler != nil { connEnd := &stats.ConnEnd{ @@ -630,8 +724,8 @@ func (t *http2Client) Close() error { // closing. func (t *http2Client) GracefulClose() error { t.mu.Lock() - switch t.state { - case closing, draining: + // Make sure we move to draining only from active. + if t.state == draining || t.state == closing { t.mu.Unlock() return nil } @@ -647,110 +741,34 @@ func (t *http2Client) GracefulClose() error { // Write formats the data into HTTP2 data frame(s) and sends it out. The caller // should proceed only if Write returns nil. func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { - select { - case <-s.ctx.Done(): - return ContextErr(s.ctx.Err()) - case <-s.done: - return io.EOF - case <-t.ctx.Done(): - return ErrConnClosing - default: - } - - if hdr == nil && data == nil && opts.Last { - // stream.CloseSend uses this to send an empty frame with endStream=True - t.controlBuf.put(&dataFrame{streamID: s.id, endStream: true, f: func() {}}) - return nil - } - // Add data to header frame so that we can equally distribute data across frames. - emptyLen := http2MaxFrameLen - len(hdr) - if emptyLen > len(data) { - emptyLen = len(data) - } - hdr = append(hdr, data[:emptyLen]...) - data = data[emptyLen:] - var ( - streamQuota int - streamQuotaVer uint32 - err error - ) - for idx, r := range [][]byte{hdr, data} { - for len(r) > 0 { - size := http2MaxFrameLen - if size > len(r) { - size = len(r) - } - if streamQuota == 0 { // Used up all the locally cached stream quota. - // Get all the stream quota there is. - streamQuota, streamQuotaVer, err = s.sendQuotaPool.get(math.MaxInt32, s.waiters) - if err != nil { - return err - } - } - if size > streamQuota { - size = streamQuota - } - - // Get size worth quota from transport. - tq, _, err := t.sendQuotaPool.get(size, s.waiters) - if err != nil { - return err - } - if tq < size { - size = tq - } - ltq, _, err := t.localSendQuota.get(size, s.waiters) - if err != nil { - // Add the acquired quota back to transport. - t.sendQuotaPool.add(tq) - return err - } - // even if ltq is smaller than size we don't adjust size since - // ltq is only a soft limit. - streamQuota -= size - p := r[:size] - var endStream bool - // See if this is the last frame to be written. - if opts.Last { - if len(r)-size == 0 { // No more data in r after this iteration. - if idx == 0 { // We're writing data header. - if len(data) == 0 { // There's no data to follow. - endStream = true - } - } else { // We're writing data. - endStream = true - } - } - } - success := func() { - ltq := ltq - t.controlBuf.put(&dataFrame{streamID: s.id, endStream: endStream, d: p, f: func() { t.localSendQuota.add(ltq) }}) - r = r[size:] - } - failure := func() { // The stream quota version must have changed. - // Our streamQuota cache is invalidated now, so give it back. - s.sendQuotaPool.lockedAdd(streamQuota + size) - } - if !s.sendQuotaPool.compareAndExecute(streamQuotaVer, success, failure) { - // Couldn't send this chunk out. - t.sendQuotaPool.add(size) - t.localSendQuota.add(ltq) - streamQuota = 0 - } + if opts.Last { + // If it's the last message, update stream state. + if !s.compareAndSwapState(streamActive, streamWriteDone) { + return errStreamDone } + } else if s.getState() != streamActive { + return errStreamDone } - if streamQuota > 0 { // Add the left over quota back to stream. - s.sendQuotaPool.add(streamQuota) - } - if !opts.Last { - return nil - } - s.mu.Lock() - if s.state != streamDone { - s.state = streamWriteDone + df := &dataFrame{ + streamID: s.id, + endStream: opts.Last, + } + if hdr != nil || data != nil { // If it's not an empty data frame. + // Add some data to grpc message header so that we can equally + // distribute bytes across frames. + emptyLen := http2MaxFrameLen - len(hdr) + if emptyLen > len(data) { + emptyLen = len(data) + } + hdr = append(hdr, data[:emptyLen]...) + data = data[emptyLen:] + df.h, df.d = hdr, data + // TODO(mmukhi): The above logic in this if can be moved to loopyWriter's data handler. + if err := s.wq.get(int32(len(hdr) + len(data))); err != nil { + return err + } } - s.mu.Unlock() - return nil + return t.controlBuf.put(df) } func (t *http2Client) getStream(f http2.Frame) (*Stream, bool) { @@ -764,34 +782,17 @@ func (t *http2Client) getStream(f http2.Frame) (*Stream, bool) { // of stream if the application is requesting data larger in size than // the window. func (t *http2Client) adjustWindow(s *Stream, n uint32) { - s.mu.Lock() - defer s.mu.Unlock() - if s.state == streamDone { - return - } if w := s.fc.maybeAdjust(n); w > 0 { - // Piggyback connection's window update along. - if cw := t.fc.resetPendingUpdate(); cw > 0 { - t.controlBuf.put(&windowUpdate{0, cw}) - } - t.controlBuf.put(&windowUpdate{s.id, w}) + t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w}) } } -// updateWindow adjusts the inbound quota for the stream and the transport. -// Window updates will deliver to the controller for sending when -// the cumulative quota exceeds the corresponding threshold. +// updateWindow adjusts the inbound quota for the stream. +// Window updates will be sent out when the cumulative quota +// exceeds the corresponding threshold. func (t *http2Client) updateWindow(s *Stream, n uint32) { - s.mu.Lock() - defer s.mu.Unlock() - if s.state == streamDone { - return - } if w := s.fc.onRead(n); w > 0 { - if cw := t.fc.resetPendingUpdate(); cw > 0 { - t.controlBuf.put(&windowUpdate{0, cw}) - } - t.controlBuf.put(&windowUpdate{s.id, w}) + t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w}) } } @@ -803,14 +804,17 @@ func (t *http2Client) updateFlowControl(n uint32) { for _, s := range t.activeStreams { s.fc.newLimit(n) } - t.initialWindowSize = int32(n) t.mu.Unlock() - t.controlBuf.put(&windowUpdate{0, t.fc.newLimit(n)}) - t.controlBuf.put(&settings{ + updateIWS := func(interface{}) bool { + t.initialWindowSize = int32(n) + return true + } + t.controlBuf.executeAndPut(updateIWS, &outgoingWindowUpdate{streamID: 0, increment: t.fc.newLimit(n)}) + t.controlBuf.put(&outgoingSettings{ ss: []http2.Setting{ { ID: http2.SettingInitialWindowSize, - Val: uint32(n), + Val: n, }, }, }) @@ -820,7 +824,7 @@ func (t *http2Client) handleData(f *http2.DataFrame) { size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { - sendBDPPing = t.bdpEst.add(uint32(size)) + sendBDPPing = t.bdpEst.add(size) } // Decouple connection's flow control from application's read. // An update on connection's flow control should not depend on @@ -831,21 +835,24 @@ func (t *http2Client) handleData(f *http2.DataFrame) { // active(fast) streams from starving in presence of slow or // inactive streams. // - // Furthermore, if a bdpPing is being sent out we can piggyback - // connection's window update for the bytes we just received. + if w := t.fc.onData(size); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{ + streamID: 0, + increment: w, + }) + } if sendBDPPing { - if size != 0 { // Could've been an empty data frame. - t.controlBuf.put(&windowUpdate{0, uint32(size)}) + // Avoid excessive ping detection (e.g. in an L7 proxy) + // by sending a window update prior to the BDP ping. + + if w := t.fc.reset(); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{ + streamID: 0, + increment: w, + }) } + t.controlBuf.put(bdpPing) - } else { - if err := t.fc.onData(uint32(size)); err != nil { - t.Close() - return - } - if w := t.fc.onRead(uint32(size)); w > 0 { - t.controlBuf.put(&windowUpdate{0, w}) - } } // Select the right stream to dispatch. s, ok := t.getStream(f) @@ -853,25 +860,15 @@ func (t *http2Client) handleData(f *http2.DataFrame) { return } if size > 0 { - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return - } - if err := s.fc.onData(uint32(size)); err != nil { - s.rstStream = true - s.rstError = http2.ErrCodeFlowControl - s.finish(status.New(codes.Internal, err.Error())) - s.mu.Unlock() - s.write(recvMsg{err: io.EOF}) + if err := s.fc.onData(size); err != nil { + t.closeStream(s, io.EOF, true, http2.ErrCodeFlowControl, status.New(codes.Internal, err.Error()), nil, false) return } if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := s.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { - t.controlBuf.put(&windowUpdate{s.id, w}) + if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{s.id, w}) } } - s.mu.Unlock() // TODO(bradfitz, zhaoq): A copy is required here because there is no // guarantee f.Data() is consumed before the arrival of next frame. // Can this copy be eliminated? @@ -884,14 +881,7 @@ func (t *http2Client) handleData(f *http2.DataFrame) { // The server has closed the stream without sending trailers. Record that // the read direction is closed, and set the status appropriately. if f.FrameHeader.Flags.Has(http2.FlagDataEndStream) { - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return - } - s.finish(status.New(codes.Internal, "server closed the stream without sending trailers")) - s.mu.Unlock() - s.write(recvMsg{err: io.EOF}) + t.closeStream(s, io.EOF, false, http2.ErrCodeNo, status.New(codes.Internal, "server closed the stream without sending trailers"), nil, true) } } @@ -900,73 +890,55 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) { if !ok { return } - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return - } - if !s.headerDone { - close(s.headerChan) - s.headerDone = true - } - - code := http2.ErrCode(f.ErrCode) - if code == http2.ErrCodeRefusedStream { + if f.ErrCode == http2.ErrCodeRefusedStream { // The stream was unprocessed by the server. - s.unprocessed = true + atomic.StoreUint32(&s.unprocessed, 1) } - statusCode, ok := http2ErrConvTab[code] + statusCode, ok := http2ErrConvTab[f.ErrCode] if !ok { warningf("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error %v", f.ErrCode) statusCode = codes.Unknown } - s.finish(status.Newf(statusCode, "stream terminated by RST_STREAM with error code: %v", f.ErrCode)) - s.mu.Unlock() - s.write(recvMsg{err: io.EOF}) + t.closeStream(s, io.EOF, false, http2.ErrCodeNo, status.Newf(statusCode, "stream terminated by RST_STREAM with error code: %v", f.ErrCode), nil, false) } func (t *http2Client) handleSettings(f *http2.SettingsFrame, isFirst bool) { if f.IsAck() { return } - var rs []http2.Setting - var ps []http2.Setting - isMaxConcurrentStreamsMissing := true + var maxStreams *uint32 + var ss []http2.Setting f.ForeachSetting(func(s http2.Setting) error { if s.ID == http2.SettingMaxConcurrentStreams { - isMaxConcurrentStreamsMissing = false - } - if t.isRestrictive(s) { - rs = append(rs, s) - } else { - ps = append(ps, s) + maxStreams = new(uint32) + *maxStreams = s.Val + return nil } + ss = append(ss, s) return nil }) - if isFirst && isMaxConcurrentStreamsMissing { - // This means server is imposing no limits on - // maximum number of concurrent streams initiated by client. - // So we must remove our self-imposed limit. - ps = append(ps, http2.Setting{ - ID: http2.SettingMaxConcurrentStreams, - Val: math.MaxUint32, - }) + if isFirst && maxStreams == nil { + maxStreams = new(uint32) + *maxStreams = math.MaxUint32 } - t.applySettings(rs) - t.controlBuf.put(&settingsAck{}) - t.applySettings(ps) -} - -func (t *http2Client) isRestrictive(s http2.Setting) bool { - switch s.ID { - case http2.SettingMaxConcurrentStreams: - return int(s.Val) < t.maxStreams - case http2.SettingInitialWindowSize: - // Note: we don't acquire a lock here to read streamSendQuota - // because the same goroutine updates it later. - return s.Val < t.streamSendQuota - } - return false + sf := &incomingSettings{ + ss: ss, + } + if maxStreams == nil { + t.controlBuf.put(sf) + return + } + updateStreamQuota := func(interface{}) bool { + delta := int64(*maxStreams) - int64(t.maxConcurrentStreams) + t.maxConcurrentStreams = *maxStreams + t.streamQuota += delta + if delta > 0 && t.waitingStreams > 0 { + close(t.streamsQuotaAvailable) // wake all of them up. + t.streamsQuotaAvailable = make(chan struct{}, 1) + } + return true + } + t.controlBuf.executeAndPut(updateStreamQuota, sf) } func (t *http2Client) handlePing(f *http2.PingFrame) { @@ -984,7 +956,7 @@ func (t *http2Client) handlePing(f *http2.PingFrame) { func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { t.mu.Lock() - if t.state != reachable && t.state != draining { + if t.state == closing { t.mu.Unlock() return } @@ -1019,6 +991,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { t.setGoAwayReason(f) close(t.goAway) t.state = draining + t.controlBuf.put(&incomingGoAway{}) } // All streams with IDs greater than the GoAwayId // and smaller than the previous GoAway ID should be killed. @@ -1029,11 +1002,8 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { for streamID, stream := range t.activeStreams { if streamID > id && streamID <= upperLimit { // The stream was unprocessed by the server. - stream.mu.Lock() - stream.unprocessed = true - stream.finish(statusGoAway) - stream.mu.Unlock() - close(stream.goAway) + atomic.StoreUint32(&stream.unprocessed, 1) + t.closeStream(stream, errStreamDrain, false, http2.ErrCodeNo, statusGoAway, nil, false) } } t.prevGoAwayID = id @@ -1065,15 +1035,10 @@ func (t *http2Client) GetGoAwayReason() GoAwayReason { } func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) { - id := f.Header().StreamID - incr := f.Increment - if id == 0 { - t.sendQuotaPool.add(int(incr)) - return - } - if s, ok := t.getStream(f); ok { - s.sendQuotaPool.add(int(incr)) - } + t.controlBuf.put(&incomingWindowUpdate{ + streamID: f.Header().StreamID, + increment: f.Increment, + }) } // operateHeaders takes action on the decoded headers. @@ -1082,18 +1047,10 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { if !ok { return } - s.mu.Lock() - s.bytesReceived = true - s.mu.Unlock() + atomic.StoreUint32(&s.bytesReceived, 1) var state decodeState if err := state.decodeResponseHeader(frame); err != nil { - s.mu.Lock() - if !s.headerDone { - close(s.headerChan) - s.headerDone = true - } - s.mu.Unlock() - s.write(recvMsg{err: err}) + t.closeStream(s, err, true, http2.ErrCodeProtocol, nil, nil, false) // Something wrong. Stops reading even when there is remaining. return } @@ -1117,40 +1074,25 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } } }() - - s.mu.Lock() - if !s.headerDone { + // If headers haven't been received yet. + if atomic.SwapUint32(&s.headerDone, 1) == 0 { if !endStream { // Headers frame is not actually a trailers-only frame. isHeader = true + // These values can be set without any synchronization because + // stream goroutine will read it only after seeing a closed + // headerChan which we'll close after setting this. s.recvCompress = state.encoding if len(state.mdata) > 0 { s.header = state.mdata } } close(s.headerChan) - s.headerDone = true } - if !endStream || s.state == streamDone { - s.mu.Unlock() + if !endStream { return } - if len(state.mdata) > 0 { - s.trailer = state.mdata - } - s.finish(state.status()) - s.mu.Unlock() - s.write(recvMsg{err: io.EOF}) -} - -func handleMalformedHTTP2(s *Stream, err error) { - s.mu.Lock() - if !s.headerDone { - close(s.headerChan) - s.headerDone = true - } - s.mu.Unlock() - s.write(recvMsg{err: err}) + t.closeStream(s, io.EOF, false, http2.ErrCodeNo, state.status(), state.mdata, true) } // reader runs as a separate goroutine in charge of reading data from network @@ -1160,6 +1102,7 @@ func handleMalformedHTTP2(s *Stream, err error) { // optimal. // TODO(zhaoq): Check the validity of the incoming frame sequence. func (t *http2Client) reader() { + defer close(t.readerDone) // Check the validity of server preface. frame, err := t.framer.fr.ReadFrame() if err != nil { @@ -1189,7 +1132,7 @@ func (t *http2Client) reader() { t.mu.Unlock() if s != nil { // use error detail to provide better err message - handleMalformedHTTP2(s, streamErrorf(http2ErrConvTab[se.Code], "%v", t.framer.fr.ErrorDetail())) + t.closeStream(s, streamErrorf(http2ErrConvTab[se.Code], "%v", t.framer.fr.ErrorDetail()), true, http2.ErrCodeProtocol, nil, nil, false) } continue } else { @@ -1219,109 +1162,6 @@ func (t *http2Client) reader() { } } -func (t *http2Client) applySettings(ss []http2.Setting) { - for _, s := range ss { - switch s.ID { - case http2.SettingMaxConcurrentStreams: - // TODO(zhaoq): This is a hack to avoid significant refactoring of the - // code to deal with the unrealistic int32 overflow. Probably will try - // to find a better way to handle this later. - if s.Val > math.MaxInt32 { - s.Val = math.MaxInt32 - } - ms := t.maxStreams - t.maxStreams = int(s.Val) - t.streamsQuota.add(int(s.Val) - ms) - case http2.SettingInitialWindowSize: - t.mu.Lock() - for _, stream := range t.activeStreams { - // Adjust the sending quota for each stream. - stream.sendQuotaPool.addAndUpdate(int(s.Val) - int(t.streamSendQuota)) - } - t.streamSendQuota = s.Val - t.mu.Unlock() - } - } -} - -// TODO(mmukhi): A lot of this code(and code in other places in the tranpsort layer) -// is duplicated between the client and the server. -// The transport layer needs to be refactored to take care of this. -func (t *http2Client) itemHandler(i item) (err error) { - defer func() { - if err != nil { - errorf(" error in itemHandler: %v", err) - } - }() - switch i := i.(type) { - case *dataFrame: - if err := t.framer.fr.WriteData(i.streamID, i.endStream, i.d); err != nil { - return err - } - i.f() - return nil - case *headerFrame: - t.hBuf.Reset() - for _, f := range i.hf { - t.hEnc.WriteField(f) - } - endHeaders := false - first := true - for !endHeaders { - size := t.hBuf.Len() - if size > http2MaxFrameLen { - size = http2MaxFrameLen - } else { - endHeaders = true - } - if first { - first = false - err = t.framer.fr.WriteHeaders(http2.HeadersFrameParam{ - StreamID: i.streamID, - BlockFragment: t.hBuf.Next(size), - EndStream: i.endStream, - EndHeaders: endHeaders, - }) - } else { - err = t.framer.fr.WriteContinuation( - i.streamID, - endHeaders, - t.hBuf.Next(size), - ) - } - if err != nil { - return err - } - } - return nil - case *windowUpdate: - return t.framer.fr.WriteWindowUpdate(i.streamID, i.increment) - case *settings: - return t.framer.fr.WriteSettings(i.ss...) - case *settingsAck: - return t.framer.fr.WriteSettingsAck() - case *resetStream: - // If the server needs to be to intimated about stream closing, - // then we need to make sure the RST_STREAM frame is written to - // the wire before the headers of the next stream waiting on - // streamQuota. We ensure this by adding to the streamsQuota pool - // only after having acquired the writableChan to send RST_STREAM. - err := t.framer.fr.WriteRSTStream(i.streamID, i.code) - t.streamsQuota.add(1) - return err - case *flushIO: - return t.framer.writer.Flush() - case *ping: - if !i.ack { - t.bdpEst.timesnap(i.data) - } - return t.framer.fr.WritePing(i.ack, i.data) - default: - errorf("transport: http2Client.controller got unexpected item type %v", i) - return fmt.Errorf("transport: http2Client.controller got unexpected item type %v", i) - } -} - // keepalive running in a separate goroutune makes sure the connection is alive by sending pings. func (t *http2Client) keepalive() { p := &ping{data: [8]byte{}} @@ -1348,6 +1188,11 @@ func (t *http2Client) keepalive() { } } else { t.mu.Unlock() + if channelz.IsOn() { + t.czmu.Lock() + t.kpCount++ + t.czmu.Unlock() + } // Send ping. t.controlBuf.put(p) } @@ -1384,3 +1229,56 @@ func (t *http2Client) Error() <-chan struct{} { func (t *http2Client) GoAway() <-chan struct{} { return t.goAway } + +func (t *http2Client) ChannelzMetric() *channelz.SocketInternalMetric { + t.czmu.RLock() + s := channelz.SocketInternalMetric{ + StreamsStarted: t.streamsStarted, + StreamsSucceeded: t.streamsSucceeded, + StreamsFailed: t.streamsFailed, + MessagesSent: t.msgSent, + MessagesReceived: t.msgRecv, + KeepAlivesSent: t.kpCount, + LastLocalStreamCreatedTimestamp: t.lastStreamCreated, + LastMessageSentTimestamp: t.lastMsgSent, + LastMessageReceivedTimestamp: t.lastMsgRecv, + LocalFlowControlWindow: int64(t.fc.getSize()), + //socket options + LocalAddr: t.localAddr, + RemoteAddr: t.remoteAddr, + // Security + // RemoteName : + } + t.czmu.RUnlock() + s.RemoteFlowControlWindow = t.getOutFlowWindow() + return &s +} + +func (t *http2Client) IncrMsgSent() { + t.czmu.Lock() + t.msgSent++ + t.lastMsgSent = time.Now() + t.czmu.Unlock() +} + +func (t *http2Client) IncrMsgRecv() { + t.czmu.Lock() + t.msgRecv++ + t.lastMsgRecv = time.Now() + t.czmu.Unlock() +} + +func (t *http2Client) getOutFlowWindow() int64 { + resp := make(chan uint32, 1) + timer := time.NewTimer(time.Second) + defer timer.Stop() + t.controlBuf.put(&outFlowControlSizeRequest{resp}) + select { + case sz := <-resp: + return int64(sz) + case <-t.ctxDone: + return -1 + case <-timer.C: + return -2 + } +} diff --git a/vendor/google.golang.org/grpc/transport/http2_server.go b/vendor/google.golang.org/grpc/transport/http2_server.go index 97b214c640e..ab356189050 100644 --- a/vendor/google.golang.org/grpc/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/transport/http2_server.go @@ -35,6 +35,8 @@ import ( "golang.org/x/net/context" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" + + "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" @@ -52,28 +54,25 @@ var ErrIllegalHeaderWrite = errors.New("transport: the stream is done or WriteHe // http2Server implements the ServerTransport interface with HTTP2. type http2Server struct { ctx context.Context + ctxDone <-chan struct{} // Cache the context.Done() chan cancel context.CancelFunc conn net.Conn + loopy *loopyWriter + readerDone chan struct{} // sync point to enable testing. + 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 - hBuf *bytes.Buffer // the buffer for HPACK encoding - hEnc *hpack.Encoder // HPACK encoder // The max number of concurrent streams. maxStreams uint32 // controlBuf delivers all the control related tasks (e.g., window // updates, reset streams, and various settings) to the controller. controlBuf *controlBuffer - fc *inFlow - // sendQuotaPool provides flow control to outbound message. - sendQuotaPool *quotaPool - // localSendQuota limits the amount of data that can be scheduled - // for writing before it is actually written out. - localSendQuota *quotaPool - stats stats.Handler + fc *trInFlow + stats stats.Handler // Flag to keep track of reading activity on transport. // 1 is true and 0 is false. activity uint32 // Accessed atomically. @@ -104,13 +103,27 @@ type http2Server struct { drainChan chan struct{} state transportState activeStreams map[uint32]*Stream - // the per-stream outbound flow control window size set by the peer. - streamSendQuota uint32 // idle is the time instant when the connection went idle. // This is either the beginning of the connection or when the number of // RPCs go down to 0. // When the connection is busy, this value is set to 0. idle time.Time + + // Fields below are for channelz metric collection. + channelzID int64 // channelz unique identification number + czmu sync.RWMutex + kpCount int64 + // The number of streams that have started, including already finished ones. + streamsStarted int64 + // The number of streams that have ended successfully by sending frame with + // EoS bit set. + streamsSucceeded int64 + streamsFailed int64 + lastStreamCreated time.Time + msgSent int64 + msgRecv int64 + lastMsgSent time.Time + lastMsgRecv time.Time } // newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is @@ -185,33 +198,30 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err if kep.MinTime == 0 { kep.MinTime = defaultKeepalivePolicyMinTime } - var buf bytes.Buffer ctx, cancel := context.WithCancel(context.Background()) t := &http2Server{ ctx: ctx, cancel: cancel, + ctxDone: ctx.Done(), conn: conn, remoteAddr: conn.RemoteAddr(), localAddr: conn.LocalAddr(), authInfo: config.AuthInfo, framer: framer, - hBuf: &buf, - hEnc: hpack.NewEncoder(&buf), + readerDone: make(chan struct{}), + writerDone: make(chan struct{}), maxStreams: maxStreams, inTapHandle: config.InTapHandle, - controlBuf: newControlBuffer(), - fc: &inFlow{limit: uint32(icwz)}, - sendQuotaPool: newQuotaPool(defaultWindowSize), - localSendQuota: newQuotaPool(defaultLocalSendQuota), + fc: &trInFlow{limit: uint32(icwz)}, state: reachable, activeStreams: make(map[uint32]*Stream), - streamSendQuota: defaultWindowSize, stats: config.StatsHandler, kp: kp, idle: time.Now(), kep: kep, initialWindowSize: iwz, } + t.controlBuf = newControlBuffer(t.ctxDone) if dynamicWindow { t.bdpEst = &bdpEstimator{ bdp: initialWindowSize, @@ -226,6 +236,9 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err connBegin := &stats.ConnBegin{} t.stats.HandleConn(t.ctx, connBegin) } + if channelz.IsOn() { + t.channelzID = channelz.RegisterNormalSocket(t, config.ChannelzParentID, "") + } t.framer.writer.Flush() defer func() { @@ -258,8 +271,11 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err t.handleSettings(sf) go func() { - loopyWriter(t.ctx, t.controlBuf, t.itemHandler) + t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst) + t.loopy.ssGoAwayHandler = t.outgoingGoAwayHandler + t.loopy.run() t.conn.Close() + close(t.writerDone) }() go t.keepalive() return t, nil @@ -268,12 +284,16 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err // operateHeader takes action on the decoded headers. func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (close bool) { streamID := frame.Header().StreamID - var state decodeState for _, hf := range frame.Fields { if err := state.processHeaderField(hf); err != nil { if se, ok := err.(StreamError); ok { - t.controlBuf.put(&resetStream{streamID, statusCodeConvTab[se.Code]}) + t.controlBuf.put(&cleanupStream{ + streamID: streamID, + rst: true, + rstCode: statusCodeConvTab[se.Code], + onWrite: func() {}, + }) } return } @@ -289,7 +309,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( method: state.method, contentSubtype: state.contentSubtype, } - if frame.StreamEnded() { // s is just created by the caller. No lock needed. s.state = streamReadDone @@ -325,7 +344,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( s.ctx, err = t.inTapHandle(s.ctx, info) if err != nil { warningf("transport: http2Server.operateHeaders got an error from InTapHandle: %v", err) - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) + t.controlBuf.put(&cleanupStream{ + streamID: s.id, + rst: true, + rstCode: http2.ErrCodeRefusedStream, + onWrite: func() {}, + }) return } } @@ -336,7 +360,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } if uint32(len(t.activeStreams)) >= t.maxStreams { t.mu.Unlock() - t.controlBuf.put(&resetStream{streamID, http2.ErrCodeRefusedStream}) + t.controlBuf.put(&cleanupStream{ + streamID: streamID, + rst: true, + rstCode: http2.ErrCodeRefusedStream, + onWrite: func() {}, + }) return } if streamID%2 != 1 || streamID <= t.maxStreamID { @@ -346,12 +375,17 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( return true } t.maxStreamID = streamID - s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota)) t.activeStreams[streamID] = s if len(t.activeStreams) == 1 { t.idle = time.Time{} } t.mu.Unlock() + if channelz.IsOn() { + t.czmu.Lock() + t.streamsStarted++ + t.lastStreamCreated = time.Now() + t.czmu.Unlock() + } s.requestRead = func(n int) { t.adjustWindow(s, uint32(n)) } @@ -367,19 +401,18 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } t.stats.HandleRPC(s.ctx, inHeader) } + s.ctxDone = s.ctx.Done() + s.wq = newWriteQuota(defaultWriteQuota, s.ctxDone) s.trReader = &transportReader{ reader: &recvBufferReader{ - ctx: s.ctx, - recv: s.buf, + ctx: s.ctx, + ctxDone: s.ctxDone, + recv: s.buf, }, windowHandler: func(n int) { t.updateWindow(s, uint32(n)) }, } - s.waiters = waiters{ - ctx: s.ctx, - tctx: t.ctx, - } handle(s) return } @@ -388,18 +421,26 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( // typically run in a separate goroutine. // traceCtx attaches trace to ctx and returns the new context. func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) { + defer close(t.readerDone) for { frame, err := t.framer.fr.ReadFrame() atomic.StoreUint32(&t.activity, 1) if err != nil { if se, ok := err.(http2.StreamError); ok { + warningf("transport: http2Server.HandleStreams encountered http2.StreamError: %v", se) t.mu.Lock() s := t.activeStreams[se.StreamID] t.mu.Unlock() if s != nil { - t.closeStream(s) + t.closeStream(s, true, se.Code, nil, false) + } else { + t.controlBuf.put(&cleanupStream{ + streamID: se.StreamID, + rst: true, + rstCode: se.Code, + onWrite: func() {}, + }) } - t.controlBuf.put(&resetStream{se.StreamID, se.Code}) continue } if err == io.EOF || err == io.ErrUnexpectedEOF { @@ -453,33 +494,20 @@ func (t *http2Server) getStream(f http2.Frame) (*Stream, bool) { // of stream if the application is requesting data larger in size than // the window. func (t *http2Server) adjustWindow(s *Stream, n uint32) { - s.mu.Lock() - defer s.mu.Unlock() - if s.state == streamDone { - return - } if w := s.fc.maybeAdjust(n); w > 0 { - if cw := t.fc.resetPendingUpdate(); cw > 0 { - t.controlBuf.put(&windowUpdate{0, cw}) - } - t.controlBuf.put(&windowUpdate{s.id, w}) + t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w}) } + } // updateWindow adjusts the inbound quota for the stream and the transport. // Window updates will deliver to the controller for sending when // the cumulative quota exceeds the corresponding threshold. func (t *http2Server) updateWindow(s *Stream, n uint32) { - s.mu.Lock() - defer s.mu.Unlock() - if s.state == streamDone { - return - } if w := s.fc.onRead(n); w > 0 { - if cw := t.fc.resetPendingUpdate(); cw > 0 { - t.controlBuf.put(&windowUpdate{0, cw}) - } - t.controlBuf.put(&windowUpdate{s.id, w}) + t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, + increment: w, + }) } } @@ -493,12 +521,15 @@ func (t *http2Server) updateFlowControl(n uint32) { } t.initialWindowSize = int32(n) t.mu.Unlock() - t.controlBuf.put(&windowUpdate{0, t.fc.newLimit(n)}) - t.controlBuf.put(&settings{ + t.controlBuf.put(&outgoingWindowUpdate{ + streamID: 0, + increment: t.fc.newLimit(n), + }) + t.controlBuf.put(&outgoingSettings{ ss: []http2.Setting{ { ID: http2.SettingInitialWindowSize, - Val: uint32(n), + Val: n, }, }, }) @@ -509,7 +540,7 @@ func (t *http2Server) handleData(f *http2.DataFrame) { size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { - sendBDPPing = t.bdpEst.add(uint32(size)) + sendBDPPing = t.bdpEst.add(size) } // Decouple connection's flow control from application's read. // An update on connection's flow control should not depend on @@ -519,23 +550,22 @@ func (t *http2Server) handleData(f *http2.DataFrame) { // Decoupling the connection flow control will prevent other // active(fast) streams from starving in presence of slow or // inactive streams. - // - // Furthermore, if a bdpPing is being sent out we can piggyback - // connection's window update for the bytes we just received. + if w := t.fc.onData(size); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{ + streamID: 0, + increment: w, + }) + } if sendBDPPing { - if size != 0 { // Could be an empty frame. - t.controlBuf.put(&windowUpdate{0, uint32(size)}) + // Avoid excessive ping detection (e.g. in an L7 proxy) + // by sending a window update prior to the BDP ping. + if w := t.fc.reset(); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{ + streamID: 0, + increment: w, + }) } t.controlBuf.put(bdpPing) - } else { - if err := t.fc.onData(uint32(size)); err != nil { - errorf("transport: http2Server %v", err) - t.Close() - return - } - if w := t.fc.onRead(uint32(size)); w > 0 { - t.controlBuf.put(&windowUpdate{0, w}) - } } // Select the right stream to dispatch. s, ok := t.getStream(f) @@ -543,23 +573,15 @@ func (t *http2Server) handleData(f *http2.DataFrame) { return } if size > 0 { - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return - } - if err := s.fc.onData(uint32(size)); err != nil { - s.mu.Unlock() - t.closeStream(s) - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) + if err := s.fc.onData(size); err != nil { + t.closeStream(s, true, http2.ErrCodeFlowControl, nil, false) return } if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := s.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { - t.controlBuf.put(&windowUpdate{s.id, w}) + if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 { + t.controlBuf.put(&outgoingWindowUpdate{s.id, w}) } } - s.mu.Unlock() // TODO(bradfitz, zhaoq): A copy is required here because there is no // guarantee f.Data() is consumed before the arrival of next frame. // Can this copy be eliminated? @@ -571,11 +593,7 @@ func (t *http2Server) handleData(f *http2.DataFrame) { } if f.Header().Flags.Has(http2.FlagDataEndStream) { // Received the end of stream from the client. - s.mu.Lock() - if s.state != streamDone { - s.state = streamReadDone - } - s.mu.Unlock() + s.compareAndSwapState(streamActive, streamReadDone) s.write(recvMsg{err: io.EOF}) } } @@ -585,50 +603,21 @@ func (t *http2Server) handleRSTStream(f *http2.RSTStreamFrame) { if !ok { return } - t.closeStream(s) + t.closeStream(s, false, 0, nil, false) } func (t *http2Server) handleSettings(f *http2.SettingsFrame) { if f.IsAck() { return } - var rs []http2.Setting - var ps []http2.Setting + var ss []http2.Setting f.ForeachSetting(func(s http2.Setting) error { - if t.isRestrictive(s) { - rs = append(rs, s) - } else { - ps = append(ps, s) - } + ss = append(ss, s) return nil }) - t.applySettings(rs) - t.controlBuf.put(&settingsAck{}) - t.applySettings(ps) -} - -func (t *http2Server) isRestrictive(s http2.Setting) bool { - switch s.ID { - case http2.SettingInitialWindowSize: - // Note: we don't acquire a lock here to read streamSendQuota - // because the same goroutine updates it later. - return s.Val < t.streamSendQuota - } - return false -} - -func (t *http2Server) applySettings(ss []http2.Setting) { - for _, s := range ss { - if s.ID == http2.SettingInitialWindowSize { - t.mu.Lock() - for _, stream := range t.activeStreams { - stream.sendQuotaPool.addAndUpdate(int(s.Val) - int(t.streamSendQuota)) - } - t.streamSendQuota = s.Val - t.mu.Unlock() - } - - } + t.controlBuf.put(&incomingSettings{ + ss: ss, + }) } const ( @@ -687,33 +676,31 @@ func (t *http2Server) handlePing(f *http2.PingFrame) { } func (t *http2Server) handleWindowUpdate(f *http2.WindowUpdateFrame) { - id := f.Header().StreamID - incr := f.Increment - if id == 0 { - t.sendQuotaPool.add(int(incr)) - return - } - if s, ok := t.getStream(f); ok { - s.sendQuotaPool.add(int(incr)) + t.controlBuf.put(&incomingWindowUpdate{ + streamID: f.Header().StreamID, + increment: f.Increment, + }) +} + +func appendHeaderFieldsFromMD(headerFields []hpack.HeaderField, md metadata.MD) []hpack.HeaderField { + for k, vv := range md { + if isReservedHeader(k) { + // Clients don't tolerate reading restricted headers after some non restricted ones were sent. + continue + } + for _, v := range vv { + headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) + } } + return headerFields } // WriteHeader sends the header metedata md back to the client. func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { - select { - case <-s.ctx.Done(): - return ContextErr(s.ctx.Err()) - case <-t.ctx.Done(): - return ErrConnClosing - default: - } - - s.mu.Lock() - if s.headerOk || s.state == streamDone { - s.mu.Unlock() + if s.updateHeaderSent() || s.getState() == streamDone { return ErrIllegalHeaderWrite } - s.headerOk = true + s.hdrMu.Lock() if md.Len() > 0 { if s.header.Len() > 0 { s.header = metadata.Join(s.header, md) @@ -721,8 +708,12 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { s.header = md } } - md = s.header - s.mu.Unlock() + t.writeHeaderLocked(s) + s.hdrMu.Unlock() + return nil +} + +func (t *http2Server) writeHeaderLocked(s *Stream) { // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields // first and create a slice of that exact size. headerFields := make([]hpack.HeaderField, 0, 2) // at least :status, content-type will be there if none else. @@ -731,19 +722,15 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { if s.sendCompress != "" { headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress}) } - for k, vv := range md { - if isReservedHeader(k) { - // Clients don't tolerate reading restricted headers after some non restricted ones were sent. - continue - } - for _, v := range vv { - headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) - } - } + headerFields = appendHeaderFieldsFromMD(headerFields, s.header) t.controlBuf.put(&headerFrame{ streamID: s.id, hf: headerFields, endStream: false, + onWrite: func() { + atomic.StoreUint32(&t.resetPingStrikes, 1) + }, + wq: s.wq, }) if t.stats != nil { // Note: WireLength is not set in outHeader. @@ -751,7 +738,6 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { outHeader := &stats.OutHeader{} t.stats.HandleRPC(s.Context(), outHeader) } - return nil } // WriteStatus sends stream status to the client and terminates the stream. @@ -759,37 +745,20 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { // TODO(zhaoq): Now it indicates the end of entire stream. Revisit if early // OK is adopted. func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { - select { - case <-t.ctx.Done(): - return ErrConnClosing - default: - } - - var headersSent, hasHeader bool - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() + if s.getState() == streamDone { return nil } - if s.headerOk { - headersSent = true - } - if s.header.Len() > 0 { - hasHeader = true - } - s.mu.Unlock() - - if !headersSent && hasHeader { - t.WriteHeader(s, nil) - headersSent = true - } - + s.hdrMu.Lock() // TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields // first and create a slice of that exact size. headerFields := make([]hpack.HeaderField, 0, 2) // grpc-status and grpc-message will be there if none else. - if !headersSent { - headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) - headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) + if !s.updateHeaderSent() { // No headers have been sent. + if len(s.header) > 0 { // Send a separate header frame. + t.writeHeaderLocked(s) + } else { // Send a trailer only response. + headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) + headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) + } } headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())}) @@ -805,117 +774,68 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error { } // Attach the trailer metadata. - for k, vv := range s.trailer { - // Clients don't tolerate reading restricted headers after some non restricted ones were sent. - if isReservedHeader(k) { - continue - } - for _, v := range vv { - headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) - } - } - t.controlBuf.put(&headerFrame{ + headerFields = appendHeaderFieldsFromMD(headerFields, s.trailer) + trailingHeader := &headerFrame{ streamID: s.id, hf: headerFields, endStream: true, - }) + onWrite: func() { + atomic.StoreUint32(&t.resetPingStrikes, 1) + }, + } + s.hdrMu.Unlock() + t.closeStream(s, false, 0, trailingHeader, true) if t.stats != nil { t.stats.HandleRPC(s.Context(), &stats.OutTrailer{}) } - t.closeStream(s) return nil } // Write converts the data into HTTP2 data frame and sends it out. Non-nil error // is returns if it fails (e.g., framing error, transport error). func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { - select { - case <-s.ctx.Done(): - return ContextErr(s.ctx.Err()) - case <-t.ctx.Done(): - return ErrConnClosing - default: - } - - var writeHeaderFrame bool - s.mu.Lock() - if !s.headerOk { - writeHeaderFrame = true - } - s.mu.Unlock() - if writeHeaderFrame { - t.WriteHeader(s, nil) + if !s.isHeaderSent() { // Headers haven't been written yet. + if err := t.WriteHeader(s, nil); err != nil { + // TODO(mmukhi, dfawley): Make sure this is the right code to return. + return streamErrorf(codes.Internal, "transport: %v", err) + } + } else { + // Writing headers checks for this condition. + if s.getState() == streamDone { + // TODO(mmukhi, dfawley): Should the server write also return io.EOF? + s.cancel() + select { + case <-t.ctx.Done(): + return ErrConnClosing + default: + } + return ContextErr(s.ctx.Err()) + } } - // Add data to header frame so that we can equally distribute data across frames. + // Add some data to header frame so that we can equally distribute bytes across frames. emptyLen := http2MaxFrameLen - len(hdr) if emptyLen > len(data) { emptyLen = len(data) } hdr = append(hdr, data[:emptyLen]...) data = data[emptyLen:] - var ( - streamQuota int - streamQuotaVer uint32 - err error - ) - for _, r := range [][]byte{hdr, data} { - for len(r) > 0 { - size := http2MaxFrameLen - if size > len(r) { - size = len(r) - } - if streamQuota == 0 { // Used up all the locally cached stream quota. - // Get all the stream quota there is. - streamQuota, streamQuotaVer, err = s.sendQuotaPool.get(math.MaxInt32, s.waiters) - if err != nil { - return err - } - } - if size > streamQuota { - size = streamQuota - } - // Get size worth quota from transport. - tq, _, err := t.sendQuotaPool.get(size, s.waiters) - if err != nil { - return err - } - if tq < size { - size = tq - } - ltq, _, err := t.localSendQuota.get(size, s.waiters) - if err != nil { - // Add the acquired quota back to transport. - t.sendQuotaPool.add(tq) - return err - } - // even if ltq is smaller than size we don't adjust size since, - // ltq is only a soft limit. - streamQuota -= size - p := r[:size] - success := func() { - ltq := ltq - t.controlBuf.put(&dataFrame{streamID: s.id, endStream: false, d: p, f: func() { - t.localSendQuota.add(ltq) - }}) - r = r[size:] - } - failure := func() { // The stream quota version must have changed. - // Our streamQuota cache is invalidated now, so give it back. - s.sendQuotaPool.lockedAdd(streamQuota + size) - } - if !s.sendQuotaPool.compareAndExecute(streamQuotaVer, success, failure) { - // Couldn't send this chunk out. - t.sendQuotaPool.add(size) - t.localSendQuota.add(ltq) - streamQuota = 0 - } - } + df := &dataFrame{ + streamID: s.id, + h: hdr, + d: data, + onEachWrite: func() { + atomic.StoreUint32(&t.resetPingStrikes, 1) + }, } - if streamQuota > 0 { - // ADd the left over quota back to stream. - s.sendQuotaPool.add(streamQuota) + if err := s.wq.get(int32(len(hdr) + len(data))); err != nil { + select { + case <-t.ctx.Done(): + return ErrConnClosing + default: + } + return ContextErr(s.ctx.Err()) } - return nil + return t.controlBuf.put(df) } // keepalive running in a separate goroutine does the following: @@ -960,7 +880,7 @@ func (t *http2Server) keepalive() { // The connection has been idle for a duration of keepalive.MaxConnectionIdle or more. // Gracefully close the connection. t.drain(http2.ErrCodeNo, []byte{}) - // Reseting the timer so that the clean-up doesn't deadlock. + // Resetting the timer so that the clean-up doesn't deadlock. maxIdle.Reset(infinity) return } @@ -972,7 +892,7 @@ func (t *http2Server) keepalive() { case <-maxAge.C: // Close the connection after grace period. t.Close() - // Reseting the timer so that the clean-up doesn't deadlock. + // Resetting the timer so that the clean-up doesn't deadlock. maxAge.Reset(infinity) case <-t.ctx.Done(): } @@ -985,11 +905,16 @@ func (t *http2Server) keepalive() { } if pingSent { t.Close() - // Reseting the timer so that the clean-up doesn't deadlock. + // Resetting the timer so that the clean-up doesn't deadlock. keepalive.Reset(infinity) return } pingSent = true + if channelz.IsOn() { + t.czmu.Lock() + t.kpCount++ + t.czmu.Unlock() + } t.controlBuf.put(p) keepalive.Reset(t.kp.Timeout) case <-t.ctx.Done(): @@ -998,136 +923,6 @@ func (t *http2Server) keepalive() { } } -var goAwayPing = &ping{data: [8]byte{1, 6, 1, 8, 0, 3, 3, 9}} - -// TODO(mmukhi): A lot of this code(and code in other places in the tranpsort layer) -// is duplicated between the client and the server. -// The transport layer needs to be refactored to take care of this. -func (t *http2Server) itemHandler(i item) error { - switch i := i.(type) { - case *dataFrame: - // Reset ping strikes when sending data since this might cause - // the peer to send ping. - atomic.StoreUint32(&t.resetPingStrikes, 1) - if err := t.framer.fr.WriteData(i.streamID, i.endStream, i.d); err != nil { - return err - } - i.f() - return nil - case *headerFrame: - t.hBuf.Reset() - for _, f := range i.hf { - t.hEnc.WriteField(f) - } - first := true - endHeaders := false - for !endHeaders { - size := t.hBuf.Len() - if size > http2MaxFrameLen { - size = http2MaxFrameLen - } else { - endHeaders = true - } - var err error - if first { - first = false - err = t.framer.fr.WriteHeaders(http2.HeadersFrameParam{ - StreamID: i.streamID, - BlockFragment: t.hBuf.Next(size), - EndStream: i.endStream, - EndHeaders: endHeaders, - }) - } else { - err = t.framer.fr.WriteContinuation( - i.streamID, - endHeaders, - t.hBuf.Next(size), - ) - } - if err != nil { - return err - } - } - atomic.StoreUint32(&t.resetPingStrikes, 1) - return nil - case *windowUpdate: - return t.framer.fr.WriteWindowUpdate(i.streamID, i.increment) - case *settings: - return t.framer.fr.WriteSettings(i.ss...) - case *settingsAck: - return t.framer.fr.WriteSettingsAck() - case *resetStream: - return t.framer.fr.WriteRSTStream(i.streamID, i.code) - case *goAway: - t.mu.Lock() - if t.state == closing { - t.mu.Unlock() - // The transport is closing. - return fmt.Errorf("transport: Connection closing") - } - sid := t.maxStreamID - if !i.headsUp { - // Stop accepting more streams now. - t.state = draining - if len(t.activeStreams) == 0 { - i.closeConn = true - } - t.mu.Unlock() - if err := t.framer.fr.WriteGoAway(sid, i.code, i.debugData); err != nil { - return err - } - if i.closeConn { - // Abruptly close the connection following the GoAway (via - // loopywriter). But flush out what's inside the buffer first. - t.controlBuf.put(&flushIO{closeTr: true}) - } - return nil - } - t.mu.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 - // originated before the GoAway reaches the client. - // After getting the ack or timer expiration send out another GoAway this - // time with an ID of the max stream server intends to process. - if err := t.framer.fr.WriteGoAway(math.MaxUint32, http2.ErrCodeNo, []byte{}); err != nil { - return err - } - if err := t.framer.fr.WritePing(false, goAwayPing.data); err != nil { - return err - } - go func() { - timer := time.NewTimer(time.Minute) - defer timer.Stop() - select { - case <-t.drainChan: - case <-timer.C: - case <-t.ctx.Done(): - return - } - t.controlBuf.put(&goAway{code: i.code, debugData: i.debugData}) - }() - return nil - case *flushIO: - if err := t.framer.writer.Flush(); err != nil { - return err - } - if i.closeTr { - return ErrConnClosing - } - return nil - case *ping: - if !i.ack { - t.bdpEst.timesnap(i.data) - } - return t.framer.fr.WritePing(i.ack, i.data) - default: - err := status.Errorf(codes.Internal, "transport: http2Server.controller got unexpected item type %t", i) - errorf("%v", err) - return err - } -} - // Close starts shutting down the http2Server transport. // TODO(zhaoq): Now the destruction is not blocked on any pending streams. This // could cause some resource issue. Revisit this later. @@ -1141,8 +936,12 @@ func (t *http2Server) Close() error { streams := t.activeStreams t.activeStreams = nil t.mu.Unlock() + t.controlBuf.finish() t.cancel() err := t.conn.Close() + if channelz.IsOn() { + channelz.RemoveEntry(t.channelzID) + } // Cancel all active streams. for _, s := range streams { s.cancel() @@ -1156,27 +955,45 @@ func (t *http2Server) Close() error { // closeStream clears the footprint of a stream when the stream is not needed // any more. -func (t *http2Server) closeStream(s *Stream) { - t.mu.Lock() - delete(t.activeStreams, s.id) - if len(t.activeStreams) == 0 { - t.idle = time.Now() - } - if t.state == draining && len(t.activeStreams) == 0 { - defer t.controlBuf.put(&flushIO{closeTr: true}) +func (t *http2Server) closeStream(s *Stream, rst bool, rstCode http2.ErrCode, hdr *headerFrame, eosReceived bool) { + if s.swapState(streamDone) == streamDone { + // If the stream was already done, return. + return } - t.mu.Unlock() // In case stream sending and receiving are invoked in separate // goroutines (e.g., bi-directional streaming), cancel needs to be // called to interrupt the potential blocking on other goroutines. s.cancel() - s.mu.Lock() - if s.state == streamDone { - s.mu.Unlock() - return + cleanup := &cleanupStream{ + streamID: s.id, + rst: rst, + rstCode: rstCode, + onWrite: func() { + t.mu.Lock() + if t.activeStreams != nil { + delete(t.activeStreams, s.id) + if len(t.activeStreams) == 0 { + t.idle = time.Now() + } + } + t.mu.Unlock() + if channelz.IsOn() { + t.czmu.Lock() + if eosReceived { + t.streamsSucceeded++ + } else { + t.streamsFailed++ + } + t.czmu.Unlock() + } + }, + } + if hdr != nil { + hdr.cleanup = cleanup + t.controlBuf.put(hdr) + } else { + t.controlBuf.put(cleanup) } - s.state = streamDone - s.mu.Unlock() } func (t *http2Server) RemoteAddr() net.Addr { @@ -1197,6 +1014,116 @@ func (t *http2Server) drain(code http2.ErrCode, debugData []byte) { t.controlBuf.put(&goAway{code: code, debugData: debugData, headsUp: true}) } +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.mu.Lock() + if t.state == closing { // TODO(mmukhi): This seems unnecessary. + t.mu.Unlock() + // The transport is closing. + return false, ErrConnClosing + } + sid := t.maxStreamID + if !g.headsUp { + // Stop accepting more streams now. + t.state = draining + if len(t.activeStreams) == 0 { + g.closeConn = true + } + t.mu.Unlock() + if err := t.framer.fr.WriteGoAway(sid, g.code, g.debugData); err != nil { + return false, err + } + if g.closeConn { + // Abruptly close the connection following the GoAway (via + // loopywriter). But flush out what's inside the buffer first. + t.framer.writer.Flush() + return false, fmt.Errorf("transport: Connection closing") + } + return true, nil + } + t.mu.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 + // originated before the GoAway reaches the client. + // After getting the ack or timer expiration send out another GoAway this + // time with an ID of the max stream server intends to process. + if err := t.framer.fr.WriteGoAway(math.MaxUint32, http2.ErrCodeNo, []byte{}); err != nil { + return false, err + } + if err := t.framer.fr.WritePing(false, goAwayPing.data); err != nil { + return false, err + } + go func() { + timer := time.NewTimer(time.Minute) + defer timer.Stop() + select { + case <-t.drainChan: + case <-timer.C: + case <-t.ctx.Done(): + return + } + t.controlBuf.put(&goAway{code: g.code, debugData: g.debugData}) + }() + return false, nil +} + +func (t *http2Server) ChannelzMetric() *channelz.SocketInternalMetric { + t.czmu.RLock() + s := channelz.SocketInternalMetric{ + StreamsStarted: t.streamsStarted, + StreamsSucceeded: t.streamsSucceeded, + StreamsFailed: t.streamsFailed, + MessagesSent: t.msgSent, + MessagesReceived: t.msgRecv, + KeepAlivesSent: t.kpCount, + LastRemoteStreamCreatedTimestamp: t.lastStreamCreated, + LastMessageSentTimestamp: t.lastMsgSent, + LastMessageReceivedTimestamp: t.lastMsgRecv, + LocalFlowControlWindow: int64(t.fc.getSize()), + //socket options + LocalAddr: t.localAddr, + RemoteAddr: t.remoteAddr, + // Security + // RemoteName : + } + t.czmu.RUnlock() + s.RemoteFlowControlWindow = t.getOutFlowWindow() + return &s +} + +func (t *http2Server) IncrMsgSent() { + t.czmu.Lock() + t.msgSent++ + t.lastMsgSent = time.Now() + t.czmu.Unlock() +} + +func (t *http2Server) IncrMsgRecv() { + t.czmu.Lock() + t.msgRecv++ + t.lastMsgRecv = time.Now() + t.czmu.Unlock() +} + +func (t *http2Server) getOutFlowWindow() int64 { + resp := make(chan uint32) + timer := time.NewTimer(time.Second) + defer timer.Stop() + t.controlBuf.put(&outFlowControlSizeRequest{resp}) + select { + case sz := <-resp: + return int64(sz) + case <-t.ctxDone: + return -1 + case <-timer.C: + return -2 + } +} + var rgen = rand.New(rand.NewSource(time.Now().UnixNano())) func getJitter(v time.Duration) time.Duration { diff --git a/vendor/google.golang.org/grpc/transport/http_util.go b/vendor/google.golang.org/grpc/transport/http_util.go index de37e38ec9f..835c8126946 100644 --- a/vendor/google.golang.org/grpc/transport/http_util.go +++ b/vendor/google.golang.org/grpc/transport/http_util.go @@ -23,7 +23,6 @@ import ( "bytes" "encoding/base64" "fmt" - "io" "net" "net/http" "strconv" @@ -132,6 +131,7 @@ func isReservedHeader(hdr string) bool { } switch hdr { case "content-type", + "user-agent", "grpc-message-type", "grpc-encoding", "grpc-message", @@ -145,11 +145,11 @@ func isReservedHeader(hdr string) bool { } } -// isWhitelistedPseudoHeader checks whether hdr belongs to HTTP2 pseudoheaders -// that should be propagated into metadata visible to users. -func isWhitelistedPseudoHeader(hdr string) bool { +// isWhitelistedHeader checks whether hdr should be propagated +// into metadata visible to users. +func isWhitelistedHeader(hdr string) bool { switch hdr { - case ":authority": + case ":authority", "user-agent": return true default: return false @@ -262,9 +262,9 @@ func (d *decodeState) decodeResponseHeader(frame *http2.MetaHeadersFrame) error // gRPC status doesn't exist and http status is OK. // Set rawStatusCode to be unknown and return nil error. // So that, if the stream has ended this Unknown status - // will be propogated to the user. + // will be propagated to the user. // Otherwise, it will be ignored. In which case, status from - // a later trailer, that has StreamEnded flag set, is propogated. + // a later trailer, that has StreamEnded flag set, is propagated. code := int(codes.Unknown) d.rawStatusCode = &code return nil @@ -340,7 +340,7 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) error { d.statsTrace = v d.addMetadata(f.Name, string(v)) default: - if isReservedHeader(f.Name) && !isWhitelistedPseudoHeader(f.Name) { + if isReservedHeader(f.Name) && !isWhitelistedHeader(f.Name) { break } v, err := decodeMetadataHeader(f.Name, f.Value) @@ -348,7 +348,7 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) error { errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err) return nil } - d.addMetadata(f.Name, string(v)) + d.addMetadata(f.Name, v) } return nil } @@ -509,19 +509,67 @@ func decodeGrpcMessageUnchecked(msg string) string { return buf.String() } +type bufWriter struct { + buf []byte + offset int + batchSize int + conn net.Conn + err error + + onFlush func() +} + +func newBufWriter(conn net.Conn, batchSize int) *bufWriter { + return &bufWriter{ + buf: make([]byte, batchSize*2), + batchSize: batchSize, + conn: conn, + } +} + +func (w *bufWriter) Write(b []byte) (n int, err error) { + if w.err != nil { + return 0, w.err + } + for len(b) > 0 { + nn := copy(w.buf[w.offset:], b) + b = b[nn:] + w.offset += nn + n += nn + if w.offset >= w.batchSize { + err = w.Flush() + } + } + return n, err +} + +func (w *bufWriter) Flush() error { + if w.err != nil { + return w.err + } + if w.offset == 0 { + return nil + } + if w.onFlush != nil { + w.onFlush() + } + _, w.err = w.conn.Write(w.buf[:w.offset]) + w.offset = 0 + return w.err +} + type framer struct { - numWriters int32 - reader io.Reader - writer *bufio.Writer - fr *http2.Framer + writer *bufWriter + fr *http2.Framer } func newFramer(conn net.Conn, writeBufferSize, readBufferSize int) *framer { + r := bufio.NewReaderSize(conn, readBufferSize) + w := newBufWriter(conn, writeBufferSize) f := &framer{ - reader: bufio.NewReaderSize(conn, readBufferSize), - writer: bufio.NewWriterSize(conn, writeBufferSize), + writer: w, + fr: http2.NewFramer(w, r), } - f.fr = http2.NewFramer(f.writer, f.reader) // Opt-in to Frame reuse API on framer to reduce garbage. // Frames aren't safe to read from after a subsequent call to ReadFrame. f.fr.SetReuseFrames() diff --git a/vendor/google.golang.org/grpc/transport/transport.go b/vendor/google.golang.org/grpc/transport/transport.go index e0c1e343e7a..f51f878884d 100644 --- a/vendor/google.golang.org/grpc/transport/transport.go +++ b/vendor/google.golang.org/grpc/transport/transport.go @@ -19,16 +19,17 @@ // Package transport defines and implements message oriented communication // channel to complete various transactions (e.g., an RPC). It is meant for // grpc-internal usage and is not intended to be imported directly by users. -package transport // import "google.golang.org/grpc/transport" +package transport // externally used as import "google.golang.org/grpc/transport" import ( + "errors" "fmt" "io" "net" "sync" + "sync/atomic" "golang.org/x/net/context" - "golang.org/x/net/http2" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" @@ -57,6 +58,7 @@ type recvBuffer struct { c chan recvMsg mu sync.Mutex backlog []recvMsg + err error } func newRecvBuffer() *recvBuffer { @@ -68,6 +70,13 @@ func newRecvBuffer() *recvBuffer { func (b *recvBuffer) put(r recvMsg) { b.mu.Lock() + if b.err != nil { + b.mu.Unlock() + // An error had occurred earlier, don't accept more + // data or errors. + return + } + b.err = r.err if len(b.backlog) == 0 { select { case b.c <- r: @@ -101,14 +110,15 @@ func (b *recvBuffer) get() <-chan recvMsg { return b.c } +// // recvBufferReader implements io.Reader interface to read the data from // recvBuffer. type recvBufferReader struct { - ctx context.Context - goAway chan struct{} - recv *recvBuffer - last []byte // Stores the remaining data in the previous calls. - err error + ctx context.Context + ctxDone <-chan struct{} // cache of ctx.Done() (for performance). + recv *recvBuffer + last []byte // Stores the remaining data in the previous calls. + err error } // Read reads the next len(p) bytes from last. If last is drained, it tries to @@ -130,10 +140,8 @@ func (r *recvBufferReader) read(p []byte) (n int, err error) { return copied, nil } select { - case <-r.ctx.Done(): + case <-r.ctxDone: return 0, ContextErr(r.ctx.Err()) - case <-r.goAway: - return 0, errStreamDrain case m := <-r.recv.get(): r.recv.load() if m.err != nil { @@ -145,61 +153,7 @@ func (r *recvBufferReader) read(p []byte) (n int, err error) { } } -// All items in an out of a controlBuffer should be the same type. -type item interface { - item() -} - -// controlBuffer is an unbounded channel of item. -type controlBuffer struct { - c chan item - mu sync.Mutex - backlog []item -} - -func newControlBuffer() *controlBuffer { - b := &controlBuffer{ - c: make(chan item, 1), - } - return b -} - -func (b *controlBuffer) put(r item) { - b.mu.Lock() - if len(b.backlog) == 0 { - select { - case b.c <- r: - b.mu.Unlock() - return - default: - } - } - b.backlog = append(b.backlog, r) - b.mu.Unlock() -} - -func (b *controlBuffer) load() { - b.mu.Lock() - if len(b.backlog) > 0 { - select { - case b.c <- b.backlog[0]: - b.backlog[0] = nil - b.backlog = b.backlog[1:] - default: - } - } - b.mu.Unlock() -} - -// get returns the channel that receives an item in the buffer. -// -// Upon receipt of an item, the caller should call load to send another -// item onto the channel if there is any. -func (b *controlBuffer) get() <-chan item { - return b.c -} - -type streamState uint8 +type streamState uint32 const ( streamActive streamState = iota @@ -214,8 +168,8 @@ type Stream struct { st ServerTransport // nil for client side Stream ctx context.Context // the associated context of the stream cancel context.CancelFunc // always nil for client side Stream - done chan struct{} // closed when the final status arrives - goAway chan struct{} // closed when a GOAWAY control message is received + done chan struct{} // closed at the end of stream to unblock writers. On the client side. + ctxDone <-chan struct{} // same as done chan but for server side. Cache of ctx.Done() (for performance) method string // the associated RPC method of the stream recvCompress string sendCompress string @@ -223,47 +177,69 @@ type Stream struct { trReader io.Reader fc *inFlow recvQuota uint32 - waiters waiters + wq *writeQuota // Callback to state application's intentions to read data. This // is used to adjust flow control, if needed. requestRead func(int) - sendQuotaPool *quotaPool - headerChan chan struct{} // closed to indicate the end of header metadata. - headerDone bool // set when headerChan is closed. Used to avoid closing headerChan multiple times. - header metadata.MD // the received header metadata. - trailer metadata.MD // the key-value map of trailer metadata. + headerChan chan struct{} // closed to indicate the end of header metadata. + headerDone uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times. + + // hdrMu protects header and trailer metadata on the server-side. + hdrMu sync.Mutex + header metadata.MD // the received header metadata. + trailer metadata.MD // the key-value map of trailer metadata. - mu sync.RWMutex // guard the following - headerOk bool // becomes true from the first header is about to send - state streamState + // On the server-side, headerSent is atomically set to 1 when the headers are sent out. + headerSent uint32 - status *status.Status // the status error received from the server + state streamState - rstStream bool // indicates whether a RST_STREAM frame needs to be sent - rstError http2.ErrCode // the error that needs to be sent along with the RST_STREAM frame + // On client-side it is the status error received from the server. + // On server-side it is unused. + status *status.Status - bytesReceived bool // indicates whether any bytes have been received on this stream - unprocessed bool // set if the server sends a refused stream or GOAWAY including this stream + bytesReceived uint32 // indicates whether any bytes have been received on this stream + unprocessed uint32 // set if the server sends a refused stream or GOAWAY including this stream // contentSubtype is the content-subtype for requests. // this must be lowercase or the behavior is undefined. contentSubtype string } +// isHeaderSent is only valid on the server-side. +func (s *Stream) isHeaderSent() bool { + return atomic.LoadUint32(&s.headerSent) == 1 +} + +// updateHeaderSent updates headerSent and returns true +// if it was alreay set. It is valid only on server-side. +func (s *Stream) updateHeaderSent() bool { + return atomic.SwapUint32(&s.headerSent, 1) == 1 +} + +func (s *Stream) swapState(st streamState) streamState { + return streamState(atomic.SwapUint32((*uint32)(&s.state), uint32(st))) +} + +func (s *Stream) compareAndSwapState(oldState, newState streamState) bool { + return atomic.CompareAndSwapUint32((*uint32)(&s.state), uint32(oldState), uint32(newState)) +} + +func (s *Stream) getState() streamState { + return streamState(atomic.LoadUint32((*uint32)(&s.state))) +} + func (s *Stream) waitOnHeader() error { if s.headerChan == nil { // On the server headerChan is always nil since a stream originates // only after having received headers. return nil } - wc := s.waiters select { - case <-wc.ctx.Done(): - return ContextErr(wc.ctx.Err()) - case <-wc.goAway: - return errStreamDrain + case <-s.ctx.Done(): + return ContextErr(s.ctx.Err()) case <-s.headerChan: return nil } @@ -289,12 +265,6 @@ func (s *Stream) Done() <-chan struct{} { return s.done } -// GoAway returns a channel which is closed when the server sent GoAways signal -// before this stream was initiated. -func (s *Stream) GoAway() <-chan struct{} { - return s.goAway -} - // Header acquires the key-value pairs of header metadata once it // is available. It blocks until i) the metadata is ready or ii) there is no // header metadata or iii) the stream is canceled/expired. @@ -303,6 +273,9 @@ func (s *Stream) Header() (metadata.MD, error) { // Even if the stream is closed, header is returned if available. select { case <-s.headerChan: + if s.header == nil { + return nil, nil + } return s.header.Copy(), nil default: } @@ -312,10 +285,10 @@ func (s *Stream) Header() (metadata.MD, error) { // Trailer returns the cached trailer metedata. Note that if it is not called // after the entire stream is done, it could return an empty MD. Client // side only. +// It can be safely read only after stream has ended that is either read +// or write have returned io.EOF. func (s *Stream) Trailer() metadata.MD { - s.mu.RLock() c := s.trailer.Copy() - s.mu.RUnlock() return c } @@ -345,24 +318,25 @@ func (s *Stream) Method() string { } // Status returns the status received from the server. +// Status can be read safely only after the stream has ended, +// that is, read or write has returned io.EOF. func (s *Stream) Status() *status.Status { return s.status } // SetHeader sets the header metadata. This can be called multiple times. // Server side only. +// This should not be called in parallel to other data writes. func (s *Stream) SetHeader(md metadata.MD) error { - s.mu.Lock() - if s.headerOk || s.state == streamDone { - s.mu.Unlock() - return ErrIllegalHeaderWrite - } if md.Len() == 0 { - s.mu.Unlock() return nil } + if s.isHeaderSent() || s.getState() == streamDone { + return ErrIllegalHeaderWrite + } + s.hdrMu.Lock() s.header = metadata.Join(s.header, md) - s.mu.Unlock() + s.hdrMu.Unlock() return nil } @@ -376,13 +350,17 @@ func (s *Stream) SendHeader(md metadata.MD) error { // SetTrailer sets the trailer metadata which will be sent with the RPC status // by the server. This can be called multiple times. Server side only. +// This should not be called parallel to other data writes. func (s *Stream) SetTrailer(md metadata.MD) error { if md.Len() == 0 { return nil } - s.mu.Lock() + if s.getState() == streamDone { + return ErrIllegalHeaderWrite + } + s.hdrMu.Lock() s.trailer = metadata.Join(s.trailer, md) - s.mu.Unlock() + s.hdrMu.Unlock() return nil } @@ -422,29 +400,15 @@ func (t *transportReader) Read(p []byte) (n int, err error) { return } -// finish sets the stream's state and status, and closes the done channel. -// s.mu must be held by the caller. st must always be non-nil. -func (s *Stream) finish(st *status.Status) { - s.status = st - s.state = streamDone - close(s.done) -} - // BytesReceived indicates whether any bytes have been received on this stream. func (s *Stream) BytesReceived() bool { - s.mu.Lock() - br := s.bytesReceived - s.mu.Unlock() - return br + return atomic.LoadUint32(&s.bytesReceived) == 1 } // Unprocessed indicates whether the server did not process this stream -- // i.e. it sent a refused stream or GOAWAY including this stream ID. func (s *Stream) Unprocessed() bool { - s.mu.Lock() - br := s.unprocessed - s.mu.Unlock() - return br + return atomic.LoadUint32(&s.unprocessed) == 1 } // GoString is implemented by Stream so context.String() won't @@ -474,6 +438,7 @@ type ServerConfig struct { InitialConnWindowSize int32 WriteBufferSize int ReadBufferSize int + ChannelzParentID int64 } // NewServerTransport creates a ServerTransport with conn or non-nil error @@ -509,6 +474,8 @@ type ConnectOptions struct { WriteBufferSize int // ReadBufferSize sets the size of read buffer, which in turn determines how much data can be read at most for one read syscall. ReadBufferSize int + // ChannelzParentID sets the addrConn id which initiate the creation of this client transport. + ChannelzParentID int64 } // TargetInfo contains the information of the target such as network address and metadata. @@ -608,6 +575,12 @@ type ClientTransport interface { // GetGoAwayReason returns the reason why GoAway frame was received. GetGoAwayReason() GoAwayReason + + // IncrMsgSent increments the number of message sent through this transport. + IncrMsgSent() + + // IncrMsgRecv increments the number of message received through this transport. + IncrMsgRecv() } // ServerTransport is the common interface for all gRPC server-side transport @@ -641,6 +614,12 @@ type ServerTransport interface { // Drain notifies the client this ServerTransport stops accepting new RPCs. Drain() + + // IncrMsgSent increments the number of message sent through this transport. + IncrMsgSent() + + // IncrMsgRecv increments the number of message received through this transport. + IncrMsgRecv() } // streamErrorf creates an StreamError with the specified error code and description. @@ -694,6 +673,9 @@ var ( // connection is draining. This could be caused by goaway or balancer // removing the address. errStreamDrain = streamErrorf(codes.Unavailable, "the connection is draining") + // errStreamDone is returned from write at the client side to indiacte application + // layer of an error. + errStreamDone = errors.New("the stream is done") // StatusGoAway indicates that the server sent a GOAWAY that included this // stream's ID in unprocessed RPCs. statusGoAway = status.New(codes.Unavailable, "the stream is rejected because server is draining the connection") @@ -711,15 +693,6 @@ func (e StreamError) Error() string { return fmt.Sprintf("stream error: code = %s desc = %q", e.Code, e.Desc) } -// waiters are passed to quotaPool get methods to -// wait on in addition to waiting on quota. -type waiters struct { - ctx context.Context - tctx context.Context - done chan struct{} - goAway chan struct{} -} - // GoAwayReason contains the reason for the GoAway frame received. type GoAwayReason uint8 @@ -733,39 +706,3 @@ const ( // "too_many_pings". GoAwayTooManyPings GoAwayReason = 2 ) - -// loopyWriter is run in a separate go routine. It is the single code path that will -// write data on wire. -func loopyWriter(ctx context.Context, cbuf *controlBuffer, handler func(item) error) { - for { - select { - case i := <-cbuf.get(): - cbuf.load() - if err := handler(i); err != nil { - errorf("transport: Error while handling item. Err: %v", err) - return - } - case <-ctx.Done(): - return - } - hasData: - for { - select { - case i := <-cbuf.get(): - cbuf.load() - if err := handler(i); err != nil { - errorf("transport: Error while handling item. Err: %v", err) - return - } - case <-ctx.Done(): - return - default: - if err := handler(&flushIO{}); err != nil { - errorf("transport: Error while flushing. Err: %v", err) - return - } - break hasData - } - } - } -} From 6309e4b4cfdc184aed139250867c09a0f42dd6b9 Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 15 Jun 2018 14:02:29 -0700 Subject: [PATCH 37/39] docs: Add client architecture doc --- docs/client-architecture.rst | 147 ++++++++++++++++-- ...client-architecture-balancer-figure-01.png | Bin 0 -> 100921 bytes ...client-architecture-balancer-figure-02.png | Bin 0 -> 79779 bytes ...client-architecture-balancer-figure-03.png | Bin 0 -> 75636 bytes ...client-architecture-balancer-figure-04.png | Bin 0 -> 82098 bytes ...client-architecture-balancer-figure-05.png | Bin 0 -> 74687 bytes ...client-architecture-balancer-figure-06.png | Bin 0 -> 88602 bytes ...client-architecture-balancer-figure-07.png | Bin 0 -> 75087 bytes ...client-architecture-balancer-figure-08.png | Bin 0 -> 203437 bytes ...client-architecture-balancer-figure-09.png | Bin 0 -> 141319 bytes docs/index.rst | 6 +- 11 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 docs/img/client-architecture-balancer-figure-01.png create mode 100644 docs/img/client-architecture-balancer-figure-02.png create mode 100644 docs/img/client-architecture-balancer-figure-03.png create mode 100644 docs/img/client-architecture-balancer-figure-04.png create mode 100644 docs/img/client-architecture-balancer-figure-05.png create mode 100644 docs/img/client-architecture-balancer-figure-06.png create mode 100644 docs/img/client-architecture-balancer-figure-07.png create mode 100644 docs/img/client-architecture-balancer-figure-08.png create mode 100644 docs/img/client-architecture-balancer-figure-09.png diff --git a/docs/client-architecture.rst b/docs/client-architecture.rst index b7d61cf7a13..192fb5b3817 100644 --- a/docs/client-architecture.rst +++ b/docs/client-architecture.rst @@ -1,24 +1,151 @@ .. _client-architecture: -######################## + etcd Client Architecture ######################## -.. image:: img/etcd.png - :width: 100px +Introduction +============ + +etcd server has proven its robustness with years of failure injection testing. Most complex application logic is already handled by etcd server and its data stores (e.g. cluster membership is transparent to clients, with Raft-layer forwarding proposals to leader). Although server components are correct, its composition with client requires a different set of intricate protocols to guarantee its correctness and high availability under faulty conditions. Ideally, etcd server provides one logical cluster view of many physical machines, and client implements automatic failover between replicas. This documents client architectural decisions and its implementation details. + + +Glossary +======== + +*clientv3*: etcd Official Go client for etcd v3 API. + +*clientv3-grpc1.0*: Official client implementation, with `grpc-go v1.0.x `_, which is used in latest etcd v3.1. + +*clientv3-grpc1.7*: Official client implementation, with `grpc-go v1.7.x `_, which is used in latest etcd v3.2 and v3.3. + +*clientv3-grpc1.12*: Official client implementation, with `grpc-go v1.12.x `_, which is used in latest etcd v3.4. + +*Balancer*: etcd client load balancer that implements retry and failover mechanism. etcd client should automatically balance loads between multiple endpoints. + +*Endpoints*: A list of etcd server endpoints that clients can connect to. Typically, 3 or 5 client URLs of an etcd cluster. + +*Pinned endpoint*: When configured with multiple endpoints, <= v3.3 client balancer chooses only one endpoint to establish a TCP connection, in order to conserve total open connections to etcd cluster. In v3.4, balancer round-robins pinned endpoints for every request, thus distributing loads more evenly. + +*Client Connection*: TCP connection that has been established to an etcd server, via gRPC Dial. + +*Sub Connection*: gRPC SubConn interface. Each sub-connection contains a list of addresses. Balancer creates a SubConn from a list of resolved addresses. gRPC ClientConn can map to multiple SubConn (e.g. example.com resolves to ``10.10.10.1`` and ``10.10.10.2`` of two sub-connections). etcd v3.4 balancer employs internal resolver to establish one sub-connection for each endpoint. + +*Transient disconnect*: When gRPC server returns a status error of `code Unavailable `_. + + +Client Requirements +=================== + +*Correctness*. Requests may fail in the presence of server faults. However, it never violates consistency guarantees: global ordering properties, never write corrupted data, at-most once semantics for mutable operations, watch never observes partial events, and so on. + +*Liveness*. Servers may fail or disconnect briefly. Clients should make progress in either way. Clients should `never deadlock `_ waiting for a server to come back from offline, unless configured to do so. Ideally, clients detect unavailable servers with HTTP/2 ping and failover to other nodes with clear error messages. + +*Effectiveness*. Clients should operate effectively with minimum resources: previous TCP connections should be `gracefully closed `_ after endpoint switch. Failover mechanism should effectively predict the next replica to connect, without wastefully retrying on failed nodes. + +*Portability*. Official client should be clearly documented and its implementation be applicable to other language bindings. Error handling between different language bindings should be consistent. Since etcd is fully committed to gRPC, implementation should be closely aligned with gRPC long-term design goals (e.g. pluggable retry policy should be compatible with `gRPC retry `_). Upgrades between two client versions should be non-disruptive. + + +Client Overview +=============== + +etcd client implements the following components: + +* balancer that establishes gRPC connections to an etcd cluster, +* API client that sends RPCs to an etcd server, and +* error handler that decides whether to retry a failed request or switch endpoints. + +Languages may differ in how to establish an initial connection (e.g. configure TLS), how to encode and send Protocol Buffer messages to server, how to handle stream RPCs, and so on. However, errors returned from etcd server will be the same. So should be error handling and retry policy. + +For example, etcd server may return ``"rpc error: code = Unavailable desc = etcdserver: request timed out"``, which is transient error that expects retries. Or return `rpc error: code = InvalidArgument desc = etcdserver: key is not provided`, which means request was invalid and should not be retried. Go client can parse errors with ``google.golang.org/grpc/status.FromError``, and Java client with ``io.grpc.Status.fromThrowable``. + + +clientv3-grpc1.0: Balancer Overview +----------------------------------- + +``clientv3-grpc1.0`` maintains multiple TCP connections when configured with multiple etcd endpoints. Then pick one address and use it to send all client requests. The pinned address is maintained until the client object is closed (see *Figure 1*). When the client receives an error, it randomly picks another and retries. + +.. image:: img/client-architecture-balancer-figure-01.png + :align: center + :alt: client-architecture-balancer-figure-01 + + +clientv3-grpc1.0: Balancer Limitation +------------------------------------- + +``clientv3-grpc1.0`` opening multiple TCP connections may provide faster balancer failover but requires more resources. The balancer does not understand node’s health status or cluster membership. So, it is possible that balancer gets stuck with one failed or partitioned node. + + +clientv3-grpc1.7: Balancer Overview +------------------------------------ + +``clientv3-grpc1.7`` maintains only one TCP connection to a chosen etcd server. When given multiple cluster endpoints, a client first tries to connect to them all. As soon as one connection is up, balancer pins the address, closing others (see *Figure 2*). The pinned address is to be maintained until the client object is closed. An error, from server or client network fault, is sent to client error handler (see *Figure 3*). + +.. image:: img/client-architecture-balancer-figure-02.png + :align: center + :alt: client-architecture-balancer-figure-02 + +.. image:: img/client-architecture-balancer-figure-03.png + :align: center + :alt: client-architecture-balancer-figure-03 + +The client error handler takes an error from gRPC server, and decides whether to retry on the same endpoint, or to switch to other addresses, based on the error code and message (see *Figure 4* and *Figure 5*). + +.. image:: img/client-architecture-balancer-figure-04.png + :align: center + :alt: client-architecture-balancer-figure-04 + +.. image:: img/client-architecture-balancer-figure-05.png + :align: center + :alt: client-architecture-balancer-figure-05 + +Stream RPCs, such as Watch and KeepAlive, are often requested with no timeouts. Instead, client can send periodic HTTP/2 pings to check the status of a pinned endpoint; if the server does not respond to the ping, balancer switches to other endpoints (see *Figure 6*). + +.. image:: img/client-architecture-balancer-figure-06.png + :align: center + :alt: client-architecture-balancer-figure-06 + + +clientv3-grpc1.7: Balancer Limitation +------------------------------------- + +``clientv3-grpc1.7`` balancer sends HTTP/2 keepalives to detect disconnects from streaming requests. It is a simple gRPC server ping mechanism and does not reason about cluster membership, thus unable to detect network partitions. Since partitioned gRPC server can still respond to client pings, balancer may get stuck with a partitioned node. Ideally, keepalive ping detects partition and triggers endpoint switch, before request time-out (see `issue#8673 `_ and *Figure 7*). + +.. image:: img/client-architecture-balancer-figure-07.png + :align: center + :alt: client-architecture-balancer-figure-07 + +``clientv3-grpc1.7`` balancer maintains a list of unhealthy endpoints. Disconnected addresses are added to “unhealthy” list, and considered unavailable until after wait duration, which is hard coded as dial timeout with default value 5-second. Balancer can have false positives on which endpoints are unhealthy. For instance, endpoint A may come back right after being blacklisted, but still unusable for next 5 seconds (see *Figure 8*). + +``clientv3-grpc1.0`` suffered the same problems above. + +.. image:: img/client-architecture-balancer-figure-08.png :align: center - :height: 100px + :alt: client-architecture-balancer-figure-08 + +Upstream gRPC Go had already migrated to new balancer interface. For example, ``clientv3-grpc1.7`` underlying balancer implementation uses new gRPC balancer and tries to be consistent with old balancer behaviors. While its compatibility has been maintained reasonably well, etcd client still `suffered from subtle breaking changes `_. Furthermore, gRPC maintainer recommends to `not rely on the old balancer interface `_. In general, to get better support from upstream, it is best to be in sync with latest gRPC releases. And new features, such as retry policy, may not be backported to gRPC 1.7 branch. Thus, both etcd server and client must migrate to latest gRPC versions. + +clientv3-grpc1.12: Balancer Overview +------------------------------------ -What is etcd Client? -==================== +``clientv3-grpc1.7`` is so tightly coupled with old gRPC interface, that every single gRPC dependency upgrade broke client behavior. Majority of development and debugging efforts were devoted to fixing those client behavior changes. As a result, its implementation has become overly complicated with bad assumptions on server connectivities. + +The primary goal of ``clientv3-grpc1.12`` is to simplify balancer failover logic; rather than maintaining a list of unhealthy endpoints, which may be stale, simply roundrobin to the next endpoint whenever client gets disconnected from the current endpoint. It does not assume endpoint status. Thus, no more complicated status tracking is needed (see *Figure 8* and above). Upgrading to ``clientv3-grpc1.12`` should be no issue; all changes were internal while keeping all the backward compatibilities. + +Internally, when given multiple endpoints, ``clientv3-grpc1.12`` creates multiple sub-connections (one sub-connection per each endpoint), while ``clientv3-grpc1.7`` creates only one connection to a pinned endpoint (see *Figure 9*). For instance, in 5-node cluster, ``clientv3-grpc1.12`` balancer would require 5 TCP connections, while ``clientv3-grpc1.7`` only requires one. By preserving the pool of TCP connections, ``clientv3-grpc1.12`` may consume more resources but provide more flexible load balancer with better failover performance. The default balancing policy is round robin but can be easily extended to support other types of balancers (e.g. power of two, pick leader, etc.). ``clientv3-grpc1.12`` uses gRPC resolver group and implements balancer picker policy, in order to delegate complex balancing work to upstream gRPC. On the other hand, ``clientv3-grpc1.7`` manually handles each gRPC connection and balancer failover, which complicates the implementation. ``clientv3-grpc1.12`` implements retry in the gRPC interceptor chain that automatically handles gRPC internal errors and enables more advanced retry policies like backoff, while ``clientv3-grpc1.7`` manually interprets gRPC errors for retries. + +.. image:: img/client-architecture-balancer-figure-09.png + :align: center + :alt: client-architecture-balancer-figure-09 -Hello World! +clientv3-grpc1.12: Balancer Limitation +-------------------------------------- -etcd Client Use Case? -===================== +Improvements can be made by caching the status of each endpoint. For instance, balancer can ping each server in advance to maintain a list of healthy candidates, and use this information when doing round-robin. Or when disconnected, balancer can prioritize healthy endpoints. This may complicate the balancer implementation, thus can be addressed in later versions. -Hello World! +Client-side keepalive ping still does not reason about network partitions. Streaming request may get stuck with a partitioned node. Advanced health checking service need to be implemented to understand the cluster membership (see `issue#8673 `_ for more detail). +Currently, retry logic is handled manually as an interceptor. This may be simplified via `official gRPC retries `_. diff --git a/docs/img/client-architecture-balancer-figure-01.png b/docs/img/client-architecture-balancer-figure-01.png new file mode 100644 index 0000000000000000000000000000000000000000..b11e325a4306a051e8425a60be7d13dc2e26db29 GIT binary patch literal 100921 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

Bal+>?Zd;aA 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 H7zF%Z7YXw) literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-02.png b/docs/img/client-architecture-balancer-figure-02.png new file mode 100644 index 0000000000000000000000000000000000000000..814a224e504ee10c6e2d11d9893fba16042016c0 GIT binary patch literal 79779 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 literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-04.png b/docs/img/client-architecture-balancer-figure-04.png new file mode 100644 index 0000000000000000000000000000000000000000..2a987b2db977abb8defdbbf3c36e2323af38c777 GIT binary patch literal 82098 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
    )Y@DoT) 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%0D&3q^)ykorC69~uI{d{Oq<75O}lSBa|^|4 zS;%U((+-?9LT+J}Ph$u%0k>}+BuAqiQ`IbXHgfg;X?{qqGDvwnNi!N94=lcu;yx4o zF~w;Qiis)yy)e%dPwDZmi}iu1%T8k@OAk=x?ZqLpH1V)mdBb_I>?h76%o1(Y8vL!H&LPUflTaTvU_k z)qymeFD6ZbUmfN++vPpt_T8)K(Hdxg&s53>w$H=0J_{~27KJkU(_FZVV~O}hP5~9c zXEVJ`C|}Mi>dx~YURr2U#yGyNgmfqx%3H;cQyLNclYXv2CFFujpL)9EvJU$Gd32n3 zRG^woN>W;`XE$5Wnyl1)!(i&K0^> zZv1Rs6p_5}9zh6UxO?2we|rD>p`eO{UaUOx{pv0{{~szkQcVw}xYS3NynlE)inTOU z@gBd0atTLxO#B~YYA#6X5&o%Ep*z_W4o?vTNq&*q34GtM3MXQT(^~4{b!?O61*|@b z4Qmf0;f4z7jb%w{>O3h+M-0F;3X4J4^!g@hOGoZ^mY$IQ&WEkT5-33RkL^_|lygtC z>!C(W^AcKeiaV&89 za7BiH#Et^`+iBwC$GPvtB=H<<5G9feXTluP@-34RNL&QynCIBGJe;7?o9H-!3I}+ zxdw10MFQf#URAvAj3j4r-}sWg5viSkxx7drU{Y|ozX$0A-O%n-R#jVxTK+dZG#Hp~ zM{x$I3|OIp^-CI?ZG~3MwV{R_LqV^EB?dgY<1A7O(|o70qrEKCaLRCDRQ25!MS>>= z$$VJBCT^Dr>j=!NZ&-z^Hk;QPA!au|g}((}#FYKRn`+n=KMc5l^L5`5?kd6&9&#f5 zvuiVRuoJx2nC_hBHK-ld;<;DRCBylMj#66VIQ*{$;LPHLCeD@Y1*2_V)Gm`gcDbE3 zc5gHMryRx{Q$2Hl1O6Ai-J?SJIx%mvUFaQKD7rmuq;!E%qTM~G+S})ddnvQ^Y=5Z) z3FkNKIZeUJ3JAWMIu<87xvzSOzgFf^;{_{B5@!N? zdOJn(LWhKrK6rh}m2KJY6L)Gy@y$-JN-q2K|lw zCQ!PZBzU`xm{(6wYYW{m(t7yov)Q?2)ek2T=XU$_suuoQw&1$lSP4vWJ9wGS#?%*R zKo(H~_HKn&sAN5Fm1N}Y%L+?aH#VHL`X@f$_Y=i}`mE`MJxotc=Jy;sEWl*bIN9TS z%7C@IozMwsAsli`AGEklzk}TzC9_0D=%@cV>QUd!Ws=kx;m{nEIkf*Merq?BnD(SqP(Y`jN|5`!L$;yQy1D6 zvEHAPVqZ@YE^z&DA1}GWH|rPKN6(RvjDvX${n3S3kf9+{qW5>v|xkGDqu`Uq~L!%7O>$j(c%}zOb z+0g!x>|ZV|m}Q&6_1~=8IF1GLf0gaOuVpj;Z**Lu|2*~opyPtGjsJP&XC1+YzW)pw mXk-5o^}pZp|HWhrAJ8kn!@HjYdc+XmpRAOUWUaVy$o~LR;Jv8; literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-05.png b/docs/img/client-architecture-balancer-figure-05.png new file mode 100644 index 0000000000000000000000000000000000000000..93484c7bd1bdcc684028ca592a9a35bc5b53bed4 GIT binary patch literal 74687 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�?N`B6+f=+keO0%&8gQBC6PvUC(VBs@_GL;_N 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 a#wAw_?SfIRz?dKjF{rC(D_1F5g#RCMD2F!y literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-06.png b/docs/img/client-architecture-balancer-figure-06.png new file mode 100644 index 0000000000000000000000000000000000000000..1e387223889a4ea6f0e9355a7e6f8076c18fb623 GIT binary patch literal 88602 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 eBmb8hvT^}jqtr%I8o(L=%DAsN!F_ng20 literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-08.png b/docs/img/client-architecture-balancer-figure-08.png new file mode 100644 index 0000000000000000000000000000000000000000..f0fe778309f22c7a372dc7d1f876308dc3561cd1 GIT binary patch literal 203437 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∈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®bCpMaa^z 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�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 AYybcN literal 0 HcmV?d00001 diff --git a/docs/img/client-architecture-balancer-figure-09.png b/docs/img/client-architecture-balancer-figure-09.png new file mode 100644 index 0000000000000000000000000000000000000000..baa092e3bac9ad2b8015e68345cfdf29b2281e4c GIT binary patch literal 141319 zcmbrlV~`|W)GgdIJ=4>+ZQJf?+qP}nwr$(?wC!ozsJ7j==6OH-xj$~it;nd%I+1na z?31~<)()4G5ru)ogaiQrfe{xIQUC$@t_K1FY6}hq9PyAasRy2ZI0}d>fdfBY;6`D< z-xyB9YEFu_CQh#U4#ps+Hn!HrG>(Q2#>O^|X0}dOpuM~xAow8SLi|c@S?8OkZrF=` zUmsVKR@=!cm9&*=DKuD5I(5<3QVi(yeQ9zI3jz?t?TR^E1TeS11pMOQ!HI)(1fXyh zpjTG(qNr?k2sK+sDqYQ+yC)~_3`o=)4r?r?k2{<$&Mzm^nV_~_E;vl5#X4#L-~ssm z4jz%q3&{Vu_F4`DA^q=ic0dRO=?6dj5B?KXutj;HgE1h`DfDz%6)iGbL{Gu?r2jz!m+y%TJ&jwN4Ss zEVswsgBN~{ySav%w~W}wNS=8;C+Bk{>3K`304+cXawXrvo@qzNg zdnEkZpe%H4L(TZ%q;^`LG;e%zvrlj=0Lid17X2CK+oN#(QHGecdvC$)3|chefJ`I( z8pOl4huf;@ZcgR7k+WIiaybo5LxmO*@8)Eb7AUF{Hw(fz3WlqF7$R~>R0I8Nk-S!c zL7lnPDpD{plVd1m-8UiSmMh5m1v(~|qI9oJWU5Z;(KSYxF>`}0kmy#`*D@TigLL#1 zeUpZHh4ixVmTZ2=0LB+V>L_P%Gk@@HRx#q|fvVcTa{|j1Z5bSD=tl^LivC0Z#Ky8;9`_@EwXC*U-82E^%o=7^cBU@LPqSMt?R*OP1D z1Ryly$GLASm=WD*)sS^fI)us)clVjNEg|}EJ>>30)uBPPA=y*!rxWC?rBYMNHlGa? zkiQpY;GU=->$i*a%J1!GObKR{RcI~u{GH{iWWxKCrMbIRe`)Vm#%4a-RtpuN?!~it zpQ{)eZ?|&`{G}`mQTpx!Ba6<=^QhXqFk$)Dq}DE(;nuD~x>#_#)hX@uH?NQ_*zJsl z&30L$2$QJmIVH80=-1g+eb)eb!jnBg6o zY?)Nl?RLn+=Se-TqA+SRAsdx8$HI6?=+_;#eh>A+bqM~ni=Arb^s6<(Z7D|@+s62l zX{Nm`R7-az^WboCAMMSug>j;`cz|Df5!Zs%~i_)A+bpqlhDY%XXnWD5p=Ml-C4X z@c`lO-G~KcI*CT8>zG4TbETpvM@q}gu*HGrUDo@p0U6&+^zB9l{BEJDJbaLk#6Pnl zle79~M<`tGha54qK1?obkWz*BFO`K2%0tS$@}zcSMnt{8dPF4KudD04)}xT>wAl@> zYI5OAqULtECvjX$`KJ zo~*Y_JNbuGLL+Nwn>KHfrC{#Q)ugN(%o2jAHaUgA2jm~A#xBMi9WNNLBadKiPwo&V zJ7oS^>~%m%qsM-Q{p3F(Bi3H_v}?cuZYV{CNVnzy-9!=D=&$dA74n-!`YxDa>?fu< zs4C_6{1K9Ue=i@zeix5d8aj{{scWwJIAw>BqHtW!I>dwmZ+SdXTVEDkD6{-KdjUz2 zvXUGh9ZgvwO|VLh)6p$UI7Z@Hb{CJMq=SYNi4R^1_W%0}bkP4ZztV;(3jbploUlR! zzX}n21otBzbi%(*g1jQ%g0lilURrR`%zf)d_SY;wSxXhb$qnWYz9c#_Mj@fT9C4IG zr?Ie&Co`qO1OR87ZTweCH7Q1>cwb)F_G-W^+CN{^PeB8(Vty-Xb}wpnD{6i#Y<4^H z=|zzIyNE=s`A0=yV2Z~I#pT_L?~EuwXa$BWfrXnsPouhX>lTv}RXV9->a#|?cO z7)40deSbM);3A{zjSC4M)>H7bmm!2%yz6N>?y{$V1Cd;#lt)_{I%3w4)P zO&jk`Je61-JE+u6F^R13CO4*287A}T#|e`x6~WXGwvDdssj@0|gx4S)vVU>Nrogf@ zrWeBF^3N4ryu7w)Jk1&?xvNNQd(G0R-y#!ZY@6n!JX=y80_O}<=EM331FCNwVHM8c zmM}fw&N=ifIrPm_yD5k8Fk7SYo#Inq>GwaFR>M&nkgBa?ImA^EtwwOC3o3@6zM9hs z%vnF1?8r^_<832o!8gVKvs@8104282_3tY7+I}C-J`n&=x4`VyfHY`E&_rq@GIfld zrnn>K_#8cR2sKjby}{>fgheOJE`5uP0VTFpTL_-n2d|EfTfB+zeEY^%e6jHtw23ekN@oUpW)f%>-I(H05#j=cD~s(8V79vct{BiVYllROaK zirLxT5-=P8FI)ntzhU^9%*&eEwyKT`rR}_RO#m2Dht5jTH&v6Y?5~3sE#2e^nALRB zE?q)0k!{Y^R<6f`)kSL$$3Bvn?n1_b(`NaesvSh1fBbK4+w$=#f0~VZGp9(Vyfn`^ zjHanG#r4%fvKQuY>PNo8GG>_|v;_GSH&BeC#9H(2>3y9GLBW0L{h+Wv!mUib-uxHP zdWi}tMF&9AWlCnOcJ-!P8;63`_=~Fy!$g@nsbCKHk`_nqiQeD zF~bORu+)Oxhbp1+cHE>LlSoE0-8@x>B&O@}8uKCL+rk62T81E_Mp5qcuyQlntGP7Z zo8=Ss3Qnqpk(Vsz1~fZrDhA0Pz*SUVAK%|E@OZu|sIN~D931?2c^N5OsO`LAqV44< z6Yf;QrE`-!B4A+mq*x=jtE^<<1Zp3m%q?IJ*7srR+ju3Q+{UbA7^$yX1OGr+l}L*5 za?jTF<1ub#@ahQ(GT5$NtJ_Jazg$4elM4Jpi@xC2IX-hSIJ#%Y2}#oky#Z%76DdqR*zx6R>M}TjW(N{ zxL()%!?AcX?=N@h42EE&iO;Si*nB`m$j5^eW?8y4H#&Xf1fI_Aa(n)|Au-)*PmanO zX~}St2DTlJyC&BF+yW@g149g%(?`c~1djf2H-N#CGMU1Mm!G0Xix{b!tK)q~i2hBy zq^zxe#lNh-SfN~{(HxPKggnjn<%LSCwPLF|_`k`6Ct zJ|BX1T)?dCNyqe3f(h@c%Edf-_F*y0oGq(ypDA zN3tG$*pS0J=;enG9~P^X9#F8P)KpNtdbPIQ5G=JiJ^o%x5@e51plZ&JD6@q=KWA#B zcqs}BE0$e65R|h{VjayCV{{FGi;(Y&U? z>%F6VdqUQ0Ty?t{LbK=0*6zryQPS3)B8uhiFHY5wP*6b3mpeJZ!uh4WOhG}hyC2JE z0$8dZZJ0!oo5OjS5E?+v`r1`Guf6@8%%5!*8yX&NXOAKyBqRj2PG>W z^&tYf4Q@9EpAWUZ`R163rqhSFSUyYEZ}ZI2&bnR^b5=(I?#Bs7%nxJb5=&L;1G!)C zrU8BU?(XjIXR5ttOCQ#&4Z#^187(zpbC9Ii(cpSKCu3x}=NFV-rJG)NQ{CFZg^p=tm&25(wiUxTVVeWAV>Sl~;-}Q;sUrRMTZANg;dRZ_5!-q6#OtFVlE?Yd*3j4Q ztmzXb8R2|JDA}*$^jw%uQubgWrWj#YLylH?ypz1EqhhLXzvPsQi@qr+BEQbxXLZ!v zoj1kUe}%8dMTHPuRA0h>ALr@EyA z`YGp44bBvnfpN3HdLT;7L*m2brs`(WpZB(&h1chPD7tH)K!oCqs$2Jtq*=V3 z%V&rx&W}q!R$5=6s!*9svz=9H{~Dgo7hCTF3L%B9^6q`!HU&MNkbwae634JG3ZHesWCvB4b%rG(gm+hn) z>r>3WK`;-9`wcMY&e7g)1!|7cMJ-*r@+ZfUBH{X_>gX3iueLm0=Z$tV$}SBDF^9`3 zVCu>>2)Q;UA~oNjICC<<4#OVdZMbDre22e2KuJmI_HvjSPaJOP>w#5BDh0Tpl-JT49Dw`4i5%*`-W|Z8 z*ZUoN$p;U9UF-V+``gHQiwSVK*_IB9`5jqfs%abuzDEj1*9K>7nv>y3$ zm&-^uI+tsEb!t)8W_Q8bc@!~=k2E;d24f_b7H2r^{5kP`h!!E$&D?M})TVeo$-Aa8^Cf`@ah$#xi zyXMSZs|e>*m5Y1383VjGX~+}_yeBWz@7l(P4WzhgXhUa7Hn#}a0U;RXsekp@aVI9P?b)U0Qv2XZX>YkSt>}O}p zcJZsUM^m$J3K_Xx$31yUiim)Hy=8K>pF{xOgr2{Tj$)3cjV(Zc1#b9$eUzQmZ@!jR zs#UA_Jauj}*lu;{So(7TQ$*9tjdqZKw#BV`OQ$A&HuYaD#G~h$9J{`~Hsma8Yy2|p zfvmn)U|-(l1#t6m-Gy&J>a>|{VwmW=GR0b{uZqX(-P3noh_7vo|KAok;Qy-k(g=Jq z|GEp&eEGCuy0q?VaZL)1{eo35K!8GiH7B?9;!ZMAnYtSnbGR_9`ho6uz5-E~Y z%K~foPQd!t(rN`&{~5zxi*aduXrFqpG;_3B^58%{En(@hk`ms)Jvd&tv1$_2`Z&WY zd&Xs0=FuT5J+f3iou_Y@*)a)m+utzN8cd{goIOE$a*S1_{*TOr0 zkD1w*`srWPpt2c;#9S-=0$c-Q?d>*`@+B++(-k$7?9RN_%6X9lVpMHMsv8Jem-Cf5 zR@UlLw{9IRS~N6WFZh!Ho71L{P=7{b|h(IMoMiM4x5C-4lXQTTx@-=NVYOBjwOdB4jkLx zBNoYq+7Sss14VP#X1-mNm5_wY7Z2AH#~WT-*v3;a3!N-8Rkqdv;Y#T^|TqL0p9$ilm?fZ7o=<_4=(MpoOcjdf+jSMYy^F*3Li z6VF?50510>M7OIUki|2T9>eILakS)1|3h~KLs%_JDw8}W#ikc^ViplZP_?_@A1Bd# z^+zK^vsmjVb24MAl*soeOsk>;O+L@{++*Wl>QotZZ|X%MKp`_bTvQn666-G#8G-?P z2Dh$kDpSrjhmdG{nc#c3?D@5rd|5Eq%Q1yLWJW@H2~Esx34pRfVv=D+5Vg3OhSg9J zaj>2sGMyi0@!!h0WIH|WX5LZEsbOt4;+q+)gy&;8?6ya+RSceC&=)-8|9R7;|J6a)Ake5ZIY+q9v~ zN_SI36zR_ub5WF& zVbxkDz&TPIUf$TG@j}4+L$e5Lo6$Q0sH)bP-kB0ruJ`OHPA4+*(CFrNV|?y6I2v10 z&ZXgVmvvoEmeB!Qmin=LdCUg8JM5pBF)fDSLC~PDp}4q8)f=oM)5TmF?Z?&h;eF&i zyY^bkF3E*5H!<)MJ&ETjx#bRB>FGDpUh)-87IIfk_QFchBEEEFe+=n8x4yI1YLAKH zc@MHIYXwd%c_)HdW{4;%F3hQ(8=Gd?gW>tUGgvOy6a$@WRaMniO~UNNpimW0nXS}d z#g5q2A7ijZRas%dos-Ng!VW`16<{D(^?0|moJ_r`BHC0uV`*0reI+}#Q-v_rZO3GV zlPg-xOT8;07iMW)q`*><$vi0o3i0dXe!ibFnODr7qNx|29#O9R_ACt>(tIgpHXp#u zTs~?an&QaaR436aIb@PP5-OCpX+J~->df5tB|5R2gBTojaW%(ij zZkx_ZNy@w7*zR{R#;c>59=HpP!~&ivH@CNhub)?^J-})XBxRRZeWibLCnmWqH$A+! zNKYhiOZ2p+$8@IL7>UpA!Onm6R*e>sIOBpDCkmL5Q4E_YkMcghf`}8&Ip6ByG>8-I z?_d*55WAKY1_9{b5~L-_7DTYKhivd0wO>#}{d;`QU_%GIuKRuo5h9RI^#E<8qm<3- z*Js#3)sBO+E?_wn&qqvM9m8-q>J;tU`8ZYWATVZ4Pj|mh4pz(o*Zc5C7F`OfkFvw| zy8S!nby)Rvpx9H6#8AP-@=A!T6goo;MZE=I|m|#~cSL_R)`sfZVH<4IE5M5Qig-7cK@5F_RqKZn$oY_Is zf(+N`ZPROW*KI##1@rHOxM*&*&qi8Fc)j%L2S3vx>viw-mA}~ zGsmq9oxI3D`Y~Z8mt}P|<>Wwq%gD&2Zaiz)ZVYnGa7&B7+BIY&Y7B~cyHl{_6PtXV zLEv`!4E8-mvg`Nh$pM|skGfA`GPUN!2g}M?dU@|lxU@veK|M#kWc;c?3Ul)P5f~CG z5`Xqi42tu7moJy4+I1P}lWP*Z373{WrgX0PVBagw7KVURLr3ujfxMnHn#vvn(M^99 z0?AkM5-Cg>83%3?3W2=#oZA2xujcL$ay>g3{rRMN=xMP(gre6=$qLsGoIT~&BQ@6z zFeXPE@oinfUJud3Wvhu83CP7eh0E&R*p2$Em89<#2sq)+Zy9nd*>XaKeP|_fX!(6; z`SqK+f&c2DW-ls*Qa&Bo4C*&o4Ak+!s*<-w4MG0btWz?V&I<)=Vs_)O&gcsgDg&!4 zV}sW-Yf4KULz^Y2n%e>vHQm&46TwiGn=1)lt`DvDW(WYPaue~7hT!SH^sr6|H1X>R z1@}#7qI73-Op&iAqnGann~Ls>>exx%)`A|L`&FW!LiR_Uk9hyiDE?GA2GxIW21z0A=D$Czi;51J zjCH<;z@awQs|YHbx6LnzkMwbzs_gBf5Km3qurgmj6Dy)e!7#s`_j1LWV^xZqm}O+= zW59+KuVbWK;&Y7!P=8OI4Sv_hyRvnQeq!qq@em-rBc(oghk(LJV|0__s~x2oKDar8z%tdS<}#ep`-p2N!MVr$UT=8S5u7B{w@4$}S(sre$w zHX{MVmPxDmBti+;1vjU^7i&F&Bu0q>3nfF;t1DraXHRWrQiKiyV4!=%7MaFiQP}(y z;sS8Vjh7#?&n4fm&CS(#U`|GhVz5PPv_)*U`WjkQ%dlH&$Zayu z6%ud;GcCx~K4{*sKHSb?uXN~dB*K&eQ-{kWeo*BR<%3cpTC5ieA&W;4B8bF3uPP!K z*$Vr~7K1Q;Gon~|ENyxp3o^5Aii^u5$rGnZlov<5&=@OfIRw3uw4S(VwcaecV=RW9 zsJ@~~h@z$%O$Z>wQ)3;Kuu@NvFG?{M7l~h|!_vr^pjjW$Nj;32ur|hNf-83B8nVl? zDmTux29S%cgyvU6t0%-8Q9SAZ9K|i%d0e2aXGq#FK0dcF)fbhfp(gMSgAZdF2|cNg zWz_~(4e1E`a=?EJ3R=ZP7yZ4jSuc0xz@w`q`O1(?^XM#cXZe4*0Q}fQc&}J^wPt#k zL*d#jP_BkNl3Q-MU6aVPz3;cQRA_lUJ-B|rq7f}kYv3Q=9yP@v_rK9nPA<~V5kiE( zgqDk#nE1L7@1sm|9Ql(Zx1@=q7XRclL50*SJ)txHTB8Z&Rr z2pjW3DYEgfvDRz7cF=rlhD>cgem@0V3NL%l-j-TEoM@n!Dc6f`y8gWlLRwZQ2T&I^ z_mC1a2lJGZV7G#8Jr#fDaY`D1rwC(jGmhod&cDI#mj`I1qg9>(DTeUl#Dj2Dw zemm!q0U8(KQumROuJ#tlsU4%0bTo(IJf)Ca-kc8-U4;@3bg$u;EQZNo@%gy|{r-&I z6Q(4|k=E>*pAb#=#%C2CJ1%sH_~{Ip2F@_QKtpBh3DoN~45+YvNf~YNqdA#_5e+|b zEfq3%c9ZekJxP@gXWb>pE91c6NH54gtQ3~a-C_zU;n<1|;4R(!Wbx9+(N!D4o4*b= znCOX%;j2Q567z#0#)|V8oLMyqNMa917CmR+fK|YbI|u1hFLj1=?S2q*?zV1suHeN1 zp7!7B?S(5!3QjRIT&^9El^156nh)JO4_yEwE%I~B)%yBMD`i$(`iGbBjD=;oE7ruMyXCnAmZX2E2BrVL{g~ zs4lnojI%e2GIFZ&tAaAKv(|rB!ns>MBMh1ouO&raZdpja&9JV~F5=fWyNhzEJ-`k(fNQ+KhedIZM$g*}(S(V@sGS;!7>qipkIFRBa}V&#G$==L zae;Nz6k1qx&X%+Wb5xVmHPZq*LS!ZyN?K`J!8E!r;#-^_QZC@SeDRSNY_LwyQ`VL1 z1}qU+`eSi5-3ARY(-_m*4QY1@##G~W#|^eR>(^JxERX|t4>Y}+GT@%?D@};kH8;A= zC=m1BB>T4ikl~%1?nVcmMCjker=)N-w!hhQqeQzLB6Bke(mu2Jn&AY=rt!#Xj}-KN z_>+-!P1FwOctIJpZJHAHFS}*}{bAo=MT~|S zC>Q#|zS$3|5Am%AiYEl|pYr8Ovah`q(?dK8O=Gw+01fV_dAcEIR#vqNl};^M@3Np* z|5(i2e3`~AscvW}z4b13!#Zz=J#jf*!`VkGdHbg-^jW}%XD5{SC^~I z(R4=tZ?AXfYbbuS;Cep2cqchCLMOVfGp#8gqwJ!(gK(m)p5n3dZI)!k2(#Q%&rbB> z;n119P?dcC{_T%4U|h~lz|Ei)Fz?gh4h%uUbHy~(-76R_G?bPaR(hwX^;PCSjmiFd zD2+eF5^{&iPEm1Rkckb96u@o017#s&=ygT-h{X}bVUd7`VFQ;~qN4AAXyNYE9C1WRH+By}c-eJ9giGffCs4 zVE$9?$ygjN)K)sNGOC&!#YnZ+KF*@@r`xwq+oRvMg(3r)hg$EMt}kxObZyRqW4XBF z5l#yD1g%jD}LIb-O(N5&Sekx}F;M;J)UEsrkNdVTq3d2+*Nd&5AM=uS^G zg8VvT<~K86UNa#HL7KT%x=i64Z?oCKpbwkmaJYECu-(V8L-2M-Qd|Poab$|Ta3)l+ z(s|73!yzz?$oy)8%WgsqH`$sq)jG8Fm+s?@NMQ`z9M034#F&15*5Fair-~;C1#F51 zY>t*ywZUF3GKa3nUm9z+hvFRKx+uSpm%}M2;2DjzHvhKrY1jqDj+%@zR8qm-|21sL z;PR0rDX@&mXGVD}-Iml|c)VYS>uofr?Yb#k%q8S}ceCMIfA!>HsnaMNLehMA2`QCWF0xcKZZN|oNJ0+AU3 zX5`ZK@EsNSk@i6ULT3q9b6Xqw|GYtiQ*EAdGaFHweFN z+g4hG`xCf&d*Vj0+RN>4seFU`HV5s;IYeAsZ$^|`<#y~FsAe)L9mfnkzBha;OOH53 zdNQMW&-apThhH{FlT|e@t@pEnaPn$uqq(2=WHNjo_p}s>C%Hl(0Dc~%KS2YhSKq3b zoVVz?G{3Kw7_fC#aK?mUq}Z4a&kC6wbfPC%AO-ro)t6N?Ru_CpnHB&zg@0}%R&384 zrs{6dXa@u9ZLU{aHtpjLoyaICyDpvSY__;ErH!qXME8UUe>~#A^|}E{(0acr?}F63 zOXF)Hu~yU$NULz4kFzmN4S-vL@j^Qc=VZX>bi9I*&vjl6vjg#%Th6Mm7iC21w2cx6 z!otGB%);Uf;O$7*v>{_+0?tiv|IGjz2;d4N>`PIj=MD$lnExQ6#ERVXYVM3*^u#Q` z337K;WVy1b7&b^s=O*`WA4p8&phjo}H~bK=$g21KNiV7@ld#qv6cprx@<*#i@YOr` ziu_;EtTs%kv9=)#1X4s2Q+@1|%o)HDDX%Z$L&$WCH0P8ZAVF6Nh4oTV7vIg|)3dSp zC$$sHE)8WGGkx-%p?t(8(Y`w3L6>)2)Oc#;wr0QNUYoiGbM&X8X`I9OQ5!kT;(8f) zx|jqOzsM8o8TQgvfHn8WyJQx_l|e`}<~CO{y9k7eU~E%EN^}v#Yg?KRI59a!{eF8Xbg!e zxh#b?hoeQCiXKYQB!BrYDE90`+(yA-v-uCylg?tVZ>_Ww>agXxs?`>K<+!@uIb{Sa z^$PkM!iwr!l@;q!HJFw#Q+pJ2E9mZBp>kXn=I@pT5<@$9^ z^|@fD_q^%`es&*&5g#e?hY;0D*CoXk1w=w3TPR22H0i_+=m@>Y5p2KiHT9L!$tcOp z&G|IAsgb!?{ViTs*Dg^{P1#5aBevyKBlq)kqLrQKweSV>v>64~EH{=~*Pi^=N>|pD zaOJ9;(#&6i0lUT7~-K-^(IK%!vmq1_VScTJg_6-Yik^?fBE6tVoe~J-;q) z^cJU>t@6F>`{Eh68%5eP(tRO zaMbk@2*)fg*ip~%0%-U&8$onp0w~lSv3%d1eny)xO%-EN#Qd#4%!FIrSB3D1Ah)q% zx9gRwj*;E|Ab_*EW17{OEUnbhFWipMc;jy6`@q_wupCyr{qE~=mfE$QSFrRM%9ZoU zlgr9c$I{iuGxMGz%M&m7gkjwc&tF)7;;o{@njZ`xucjEnS)ItF`|^8RvK|6#Piofc z4sGhD;yQ_r!EawaXT26NJ@*Y#pi89OCYQR_bVNo*?zgPlfbQ(K>XsPk>ev3=w_~k- z37Fw^OYHqPg|lYrab**hfBv}gjh!Gvm6x{*bvDU;eAV@tDCzc=QaPGnGzr0$Pl5ETCrGl)+nl`Z z#T^zND5{sFkY+!v+vcT5=15dN(Dpi#d-rafS}+QkaG9@;H9VBMH8CW9n#JHq(B2@h zw8H1G+~rQ(>;rY`oW$~42b(rhoM@UrG2q8hXJowSwzq0r^|0D*Z*SiK1_~fuK-eG1&3Rhsi3Kui?tq545WpFU zN<&CRRoPled{6Mt2ulcj~XM78VxYuLiW!@;hx_Ru7EpwGNb#lP=kHafs z->ZUbmg0YtXn%i?yck@EDH=xU=wROBw1}iJ4KHh%XmLW&id%ZuM%;oDH0o3XBeJ_#8mcWPKx$^zu z0=jS>ozO>aTsSzscY0@MXJ}mRG(c@{k1BxZp69>KB-D45IX=6iB?D5+aX00n4lu`m zH%3u>Q2O|8PH?-u;I$dgGb?Cepf_(?ZFOfBbZeHG!Td=0naDq)*czFzHnXaR0yt3Q zq%oz;XiYmhz;n2o(cP<1qT5ETDuwv@6Q{N{g4i=HHz#*Q^NY(r)5LV2QS!vBO1n9A z0&ji%)&hmo8gAaK%97Z0xYvC*1TP+IV>F`xjJdt}T*6}Gp(%G=JN7xeqr`6dp3D7U zsn?-pLC9oSZtp?ctGm(9`=_pa(MV!uilEAw-g!bgfga}mhShg8>L8*o;DoePESw&Dyz;a=`LG3my=WM)xY_~cGl;_6Nx&kTem)pvy+J@ zJa1n4jD#T&*>!Gedqo!PFZ}&Gxc&9%IzB#*POk?71fbbS6WgeQXXD!-8-97Ck$y+% z2nb?%Qt@HG<+)UM!OM+#{>3)k>$mN5%|zS1pDMPJ?sO(DW*xEiAl*?;bhq=ziA&FhasbEE2sNfF0WE&QHBGIpM|$$l=*irqzm~lQ z3kwVG&%2@JH8rFZT)C94svCUg&q*j(yEnf+@%sE?A&^MzmbG0U&X}QKLj!x?59zG7 zd$RKdLmwr;EzQh!@3)oDStY|NO5w$ES z=|SCOh+_ui_bxJPn{IBA>S$~Br6j1Kr4jaRAdG1-ScANnyWj_P>hnEVaGQ&tu4pbu zkQs|o;qDEOY?1iR^*;B30NXlR9iFzBUT)c3zftPAGCUN;9Y&RY96;&&a-igpPY?RH zMPd8oT5F8YCyq(k8G!u#jbf}LCLu90C;RsEtQ%oGQ^1VpIr`GPYM-w6ivo#MYUgp? z_R6uETcu-7nHHlOuFnZ(o#|+NmoZ|f87}}-!fdOsGuGZa$Ef%!n@fxOpO)e z*w~bAK2t0L;#bhi`6*Ot(m4GZMM({nVm7*vZ9YS4BOhn0*2S^tlNrh(?l;Rt-bl5V z-EWeYUuGO-8O%<=WpbHf=MrT`%(!os%Kf$@JL~=LC$T5N!mUz&h8FvNq`m;15HRuc z9G#pr0>YP4>5PX(9g+jP^vcT0KvH8KIV6zK$c_!mFq3LG`L9DPf5QE{{MNd{R3?C` z{7KkvW$MqDsR-%aNKpiqziT6qNfE!S;~A|-?-Cxkc&WE{&jF3g8Ufs%wgiP3bYvjk z3TWkTKRj2Pt)VoRY6+RRLPPOh$Y2}i=>&(#a=rS26hMAQ$EwZ^GiFBfdEyW_JQ*OZ z5=cE8z1{ul{p*|^5&|Y6Awf<}ZS?W_0OSld&hS#1w4up_{i75+WrkJMTY2Kt)>P-U z(Si&HtSmAyrt`5rl+hUUhu^(;RjJhn!0|r&@pwKuxn23%`uh0?hT=9%t<&-H3yUM5 zkO&72~b>fZGI!mD!zEWuuBdtNk4Wph5nS*K8|_g`*y zQqa)w7&)xIzk6M_?ZbjWqt8}9pvp)C(MJkOWh}m<$^sDUFZl!H;T*P1FDxO*oWoLI z>cfW8FFaFMK${HA)fS!GS7Y|G)_%I{?x~UHx2cIOx~=2JY+kMXWBc9HRqV&gwgqQ6 z#Y_9Y{CAg!kxzVvr87g%?r09LyHN12@Y2gK1$q9*J=E4v2Ie=UHRk8(|wKSv?`>wIOl|;{YyO{v!)%pb_Dddg9D>r2(~vKGrup4ZTmSe;yLRY6?y@ru&>almef|Lo|1B&wmW+=t7brcD zbUHMf9~z>@&#v;H@Qa#!h95p~Tzp=Eui~nVL`3QGrc?MckUu0>+vX&JqxB=!g&3Rozo9-JeqvuYKIeeAUuNlF>@8FDkG4o8j&z%41|=3o8?~$T;YFZRF)pSI6{0g^uH{B%xYp<#A82tL~mA$LAEw z!sRPfRJYt{bQMX=Mq^^Zb$ejHwtMQW@=Pq3Jw~eX$8I&Z&%%uRGJPb6Adb@~OMyc3 zfp`i`TkL0*tO@hJodlh=DyDlwsTNxoIIYO4 z?W_#w$i`LI_J}R6`W}Us=Iq2GwcO=CMHmF5Gp2KY2^>MY;_mWwNf;Y5VQ{$gc;DE3 zws87;nG!kYKvHHhjn?~4M7n-OFG9SqV*xnnRIAU8HZ!It9?bpxC&%vE?WXOrZg&B4 zidy`RBCwCPx(AoN)uaU5;)X!#7hH~=7m)Q<6+hNxx4?d>Mjr%Z`zK$*`@Y!G@wpj5 z;aOJ`tR*6809#%E^|`(>t%`5WLPLizgTYA)#XB)KhZht$v~+uL@wGpgZoIX_G0}jE z9vYF-c4Rc(QHzo*^_$79-t2jIRa5Uh>duf#GZ-*t(3f$!-So7|qi#;unwI7_=`8u! zwI|dkI!}dCsDc?KkkRg)HQv=>=fj#rA~=skZalU07p(hEoiqO|99yx*tDXG-t~pDd z;{Eu@expYZTs<6>GE-pVYggy<_-2awMdSwqu&Ybj+wq8dcW?|A5fL%nZ47jSmFY5% z?E|@5J(SHCwJ;HG*}622haIYXSIWnEQSF?hRzYvlu!abWLb70YQrCV_jfVV6#0fYX+@QG~1zy9CxT#3+u0#qOxK#-OS@NEIsTVCv)2 zIXNo=xX2B8ibk z&21ka-f`m8cAd)*W5m8GqmAG9-Dqyd^$#!o%n1&neb|L}3jHamp|81p?nW<~3Er)d zS%A%MmOpKS!iU%WGZVFLXH@JH%wdP-G{FQVjz*0U$z7cD42iGZTK?gYT0D5;P5OG< z7ZjG`42F2thC9wQ4rdzkmniq=q{9;JTbj3E?S6`*LtTX9ErnnTF|bAEWoyt0j5uvo zb-%QWVh~uRd$tnAu}1}MA7eFQ0JgFP`N(MfbgzKyEU&U6w1^?&7z%DK?xR{!lO^U2 zZ!3(}{Q6q$yt-zu@ci1pTJ5P+zEY`=Gal>q!$L=khz1Rk0r>?^0P(^>Ob`_02@&`E zd;W&qz>xne7X)hwy3`05o7Oki_R)q#z@@~Rj>*wesF7yNA*zu$>-O^9NqrZW;}I8? z=To8MbkJyKzkHuotBEeVDlRLlm8^$E#Bbi-;~I~=yyfh{m1OsZ7FG}c9~Z#w(qHv^WR8-eV&Iq&ApwE^@UTe#?sdE? zCnT`jOGjt=_Hg0_$aoJEDO|a5x$Jqf8IHzUrWt|Xb4vt11$K&a8(4Z*WgI+ftF=y- zm04^u)!3_%^dSmH{oY@v9n%}_j_$zb3}9HL5A2H=u|{!^9T@-)uC`cX{iB19O-yjn zjvy3tii02tWGQL&fZO^i&#ddR?7$3xoTM5ZR@{6%Z-qHoG3tM{DJUlcsennqBplw) zet($3@kojqb;6#fX5x?#80iiU3NbP=9<=Zm%>2*VeiBjQS(zDKR&btilAe>TNNagm zkf!FO2uIvKg9Tg_s8J6Ci&2BexxO)DMteYVoe>ax0%rOHyZBsOScUTaEhnA-JuHig zl18V@OyAn7;-=|eeye5l&T^E;v7>rU`~lSGYcPcI;7^;bc)C6>Br283>WwBq;`)Z= z!-^H_>jOYkTRR(CGJ;q9dm8*67yq&|tBR+)w=vCxi`ye_PP(4{a2RwC8)3GOcxb0= z1$)*Ao^Mv`whvb5Kp&m&izFl@q(?KZ_HsI(FRx@O?cr%@E-4pRrz!wp?{`B)_P;6R zkwp$j=n3ik*HYl|tj+cYV4|6ugx!FeK}S<*qU7`aI9AG3LY&ei5?U9BMtiwl{As#`U4 zs;Oe;%;`SeXYaMwT3wzjMP}XpxTlS%<8E$lX-R?wceUYz1FC?B ziCObF8@iQ8RZeW5#;TD1DXjkz4b(V6djBKw6m}vm9cpv*&v~bk($eD6QesNVV(`e~ z$o{q6y664m++2So+u9&t4B_5FLUi{&Ws!X7(6+DTG2h3;A=2i2yNQyu08tY7WiX%3 zK+pzSK*kTCxA!1yt)&_x*tdrPKA=?2{ra#ChsRz8mPGj&^ZqSgicJx&HiJZX*A@as z2v?t6tZO30NhzhMWc0+M6v)n}y><=W@x>%NRfoM%z00jWsZ{1rzL!JUHmxWmpHK8e z0qpt71+^d**sP>x)%G4i4f=-rrWtl7)Nlp%Hi}S%Cvq>7{RkE|wrx<0drL%*xs}!K zIM=z*ypy);E;;};y8)Nr5!ch6$NMiaS})$R5nks*wc zvJN}VCoH*f;Z{^q(gQDZAoGf7dt3@DS89(V#+5GY>ly}wr+CU$E^Xpc|rql$8J ze0AvuffOseOZVW^J|M=3A^>Sil`>2~JTX4*2RWP6a9$swk&`PdHONg>YfVB+GPyaC zjCXD3lh2r??yrV;J&sGbAu~jyA-g?o-f$a}+oM?sV7a-mClv+CR;WHeNkmgqlTL*= z;0CyKA+S7420pwOtBU)g=COf8z4Li_4CjbU%^AF5kNp;Q%x0(INk(Rhu}y$t;bT#- zG;!=#Of5&`YSjo)0@qJ1t83B;0ma&v2-jY^Sg{$2_Q_a$$6g&R5H=Vsr}R`~uez_k!P7R-&j{w8B8~ zzS9wvpHBg=TGGCE+BGLlSkd z35I><|L)q18KesvNJp8%OI<*A=%`R6c3nkq;}jBSH7XT#b!geRDy=GukHh#Y9N@&< zES?_J4i5i19?S%DJ-^AGFWm;nFoA!aUh+G{1a3T0Is=e$gHzyOAepwYlCcL!3+wsj zP(o_*)@1*1i6u}A1yTb?u{<^bpb<#@)dj(vnq_M(P|6=s-MsHn-A|7#Y#mM;z1*f>AK^KmRrT2yOn0M)A8+8mqr&0q(|KWWaVVy?OU~2B ze5AJz?^A3tc#%dVtagiHobzG0ExBNNW{x+htb!$CnJ|YGDUSu|4-0U_T7A0LRqQ#9 z-a4zRd8;_-&acF_saWd-Ya;)DuCRsBI4 zNcwMm;Ri-zQT!D3Hkt%)+7wpgGElKK2g^4kjIRtOqG(i0Yv62e19>@_oj&C{=+AXu z>qOCX?ka1vM0npXbl3)KJacX|C8ZDaDLdP;H?)tKz+7Jcpd9ae<3fFS`GpT$G&5@i z!Gwd>k4a`X9-~NxAl0R3mBm!QwA9unmKY$kEe#O%TNm61UUB!YJrlClcY8L!@!-R(O+v z=!+owi+>l8yIg;l9y0g1GS*gPZI$K%fVZr%Af6d0^dcIF*H!lk} z^ZJi-oQs7}bU|AB(fA0JjLFKS8_YYJxyu9vk8@ML3@khU{WgUeGCpq)YN4f{)Z#hx zX&+_0c&8kHnL>QaugOv0{*I8`R^In;)26(3k&2aN^R1OfD3BWA_g;dIndY%c2SlMJ;{;*X3yHkzxA6@nI_lDa?L`2OsBG z185z0KqEYAe7(k@w66So2)@2KVSjy|$9!BKyLNS%Kn-#!SbOpUf1>x<*)fDdBv_%b zg1)pGZ2K07$Mg9PHV{`< zVPR8_0^88Q<$L2C*ml_%_FfBL^z4H;=@N$C&>RJCONt#SMhy+;T0aGg2xNInkD|We z3O()%4NeQSk`zLulM#bhy*;QJ`pZu#qw5CxKOZ28gVnJQenDgu93fGcviuf;-M^i7 zImD-vCbg5IuKOhrp(O%KERP;iY)J)Q&0?sl8ygur1fxzrECLpg|JHqEJV;%`kaPV@ zQ))M@ubg| zlflMezI9t9sa_FdEtxDn%%F7Q-*<4wMea^=oG*k`V>x*!2*TB^wwqm4nYe|MXEU!YW}*(6IAeUL-JLmse|xAYTHw)wUomf;+LA7Q{#PkmL!I1 z6d?+Obaf20wbL3_^rN}zOe#_hOs+0?^N2HQs!Q#x2}EndeW)i=9J5`YEFvVl3rkV) z$%0I(zcYK^v=cAUdt)UThuLiy(x6x@L*1x`dO{Lln$h&Wr68!k+-^Ov)Wg-xcI z$%hU_WWwt%H#8U+9r0}nqu|m6cefvDe#k3Pq2RA%n0;el%FLQ=xY~fVwwFhriScD> zPwM;(xz*?)iF@CMO+<>;!^fV9yM=BZ^+zZ?ftozvA&Y}xjs*9skhG>}Q~c%j=?~3~ z%`+>J2oWal_TH{e%ZI02R()c8sl&Rr-EBecf5xynTr4Hstk$XKpYCH3%3^+733#uM z5SL^u7lJ%#pIF)G4^Lbg#%y*~c15mEH)_=%ZvzqRn-5iNY8OXsysmN2nvZD#y9ilv zacBUWU7dDe`|FhPUS$A;nrChI+E5613(KU?)k&J4DFY4+9G|t(RfLE*hQyvTAE!!0 z1jB<^CWWRxba0v@CvfuRlj9zG5a}`($)8VGvqu^(CA6Iedr%SdiGfzU+kXUN$eCRn zEpW}}nK8RF->?%OHr!DY^AMoV>XD3<;I($>5V0(FRm?$P!~$t2*L0!m`K((2d(0@10)D+L z2ZX@y{ep+^1+Y%o26DQCSvLSx=?2anD)e0{i#Nk;y5QEFN5E0o4Y-vB9K-RsKo^6= z(YDuPYzFn}(XBmp7h`Pe7eKZGcqcF!k8aFq*d#c02xXDH$I?RJ#p~Fqg@uKe(j7v6 z5|m9K(*jrv4u^k|7%k?0z(HXjAbZBYM^#9xGV@NY+H}&=(nVh?`s~u#Gi2Id|5*US z+Ibysp0KGNHeSpvEHMB{_-eNTh_eI*A-&(8*0GsQkqic+`iyIxju|3Eh*MHh*fYkh ztyDs`-*a+-knJOYUbMLxObE3^egZI869JEfYd&?RA80Vupi^}qPsa7SE!BxlO$96^ z;zqAg$nn%dl)e)6+BuDL?Ca8F0iG5g(patZ7i)}uXf#CR=aXDrT_w}&easV$<~8m3 z`R%8H0WnbNa_=xVHxKDsI-gb3(2xWiI%Kn=kX8l$>$jWaurL^mAF7?exB!BeRB2yKKk-AuilksS+hL2NH`?kAGjzPfs%drv$uWHXU9AE)?dg_ ziboyI8EO*~#y#E_*fg%aOJ9@nfx&z8mxeS+j%r1tdg88UcLB`U{@GN z`0uy3cT>n*uiOpTdOKdYvRa!ipM(@)wWECm1hVl;-N)=0-{%5vG@!-oru7ip@QtO; zQM{_+LDo1_Q9@AF|Cs^YC#(CB*2XI)kOPeWbUghTk2b7E;`*4NWa4DezYx_g3UNp$TP_&_zt4Lsv#{@yHd>b#31Yu-aQq^X%Ek>iB*95~*uYv2)!#UMAzno09S{pEyN;0{6C%$~^ zotHe@A*=RN`1VX6JTTTS*NRoIk+pwlvlPNa7R!_Wss2#-%gCuRrMPR2#aZHW9z|-=~^ff@!iQxi{SyBOp`g>628z z-u%0Dv1mBRTlvHts$cDDrt(z7t@pyb>RUT5oN(@6omI>zv^wWwJwy;wIuE)GhG2f| zj;wNeNy@y?~Mc~ZV_Y*C=CUzbm*I9GXGH($ayk6MG z4Ul0+AaAJC`-=nj85tLQQm1BT5@}45c>PK%{*imPJ;2yU)5ra3@R$?kvgp$74GH2a zl4E}*vL@t9r8LnN@$C{j8|1S;cioS5({(k6o`2bb)o#6#KLWQ-u*Gg_dfvuA-a1Jz0|$EQ_l*;`;00HA-0N>9?LS_l9Isz*$|E=DO#6 z?yzqfX@skSJo*BCula=Loyz`c{m%Ig9usF`Lv)^J2+~Bhcc#IQ#VOWbL!saPv|jci zsdl^^0HaZilEw6nMMwK%1Z~F+`J3 z&j(;EyQ!&3cv4NTxxn62w?wwm*|z8>0qwu5mTGTQHa6DZEjqF6Phl3I7`}5sOEbky zd&8xO-Xd(0u(c7A{Y0$a6wRwY(ppo$WvJL;!pg*urN@IH&2(u&_9u>QtoW$nEQ)|zqym##F)S~ikRwCJR# ziy*!zTOYCst8P691L}9INkr?hRFSYTpCQApkSMPUvM0PlmkcZi6NaaT-B!1*U!V$) z0B{LcV2A2&ED@;;w;Y6uZ3>;`7a@w(XO3HQ78@S6-;!%;0`xVBe>Bo2A!>&{ng<+g zj?0ChCd2W=*u_cWUBOn2zWEe3FlTdxeL!8rh73_qr^LCZcaD-6&`^Cjnz-m+-oQsE48L@aH zS(7_kp|8|q+KS#XImq>l*u9XIJ1SI{F`}l-24%I6;P`}%Mh5PHKi(YkXmj8y$Vh{x z;@HV|yuB;K`+ne*p40Wo#ST0-X*$7va>+UEzw4pY3^5<2Rg}M3bt0(&WkLmA);Bx% zD9lk6CD_qG)ZdId z(zm)m_C;(c`$M&)n2OSqL#Yr#5H(wLRqf4fX!>n1zvj&i-@?wL&mt@a673_rv!*Bc zdh}B&=DKTUhB{-OL_f;>4PJ-WgUFg=AA;wPCpa&+*Ut+Q-ebGYh#O@c+I=HRa&bW~ z$>EIZkB;MTUM)2!1_Om)()*9&uvjO};B>Et|Ij=CB%f-6mVfxcEa*4b@O@*cZJml;n7VI~=!a5YfACXwx$?48-w1uQb2I&_Ai>iW3v#F81!@Yf9u9v=6z~EgFN42{s`j~ z!7dDPr_Mk8V)=Yrd}o%Qe?z?H^01AcL66JZ=%X=0yECNoy!X}j0IBkn;l<;pm0MUE zp?hHWG4I~pR^wZ?G0{I>AejguKwo{q^m*mze}0@kntx2mT)= z6O-YNnzwq1)(F)loI&UG;af$S-zI3_r{{KA0bn=^L}gHif0oI?^X(w4YH`}1`q>mm zsUEpuPH1s5e~V}IG#S8G{8x-5zfMXzf-0|U-;{|9@7Xq%336y?GtGvA8&@60j0EfZ zh4E~e1ZnyU&+D&(=6fGJAU98msNh1Mzunt{%g?Mi&$Nh!r!6Bwzc@REBw!E=Mn8H* zY1KQU?IXinj;=tY>yy!z$j_D8ZW@VerccIK`U2&*_gwS_LCav8Hqy?7+`(Y2Jv;kz zuE(xCR)R@)nZ@s#>O85xCk}fLu<8Xzkdo0fWk1s&z|1Ty>CN1^Wbpw&j~IGFb)Kl4m@y;1jt8>+Ak3=$_a4 zyf|fcBKQBrS_!SaX_X0g6p{pj7`kh(Tg#$5_pY!41y8>O(m9*qdaL2|#F}Q?Sy3Z; z;zMS)&y09pzbL9kEF3+rfa&3yhvO#HU0jsl+;h95NNKoR6uxK~Jc`n^uI0z%2D(~i zW}TY5FW>(NL#f66)kD?g6U6p0b?sG2=g2^&X>RQ-n5K2k@LNy8)fpF!$Hv^GMmP7h zF|`c~rD1Yj>s3K?{iJ98`TAPUgi!~RG7D7+aoIZZPB;{C-gP8Od%!`@dqvAFw0lE5 z^qGQYc)GH&(LgWcAMxlaBgs-DU?eqE$iXpzt!GvrL=bH>s$KU?OQ5l=kF8XluF-?ZW@g2ZhiSL(*p4rnU z8o}0taPox|`x1ZXIjn?N9wJ4KY@Xe!{hIp-VybmZgc?)H0Fks%%VT~Zx%h|lKIhjA z7#TK4g$UXhVkd94wbwwl9~HR9jM!As(;>n>6eoQpop@TE7}Ijl#cI&W>?HbO(UpBg<**MG2+{ryz!Q^C zWn@tK6ajfYa$RVX7so$U|8#ZOFqJF)5dr+Zj+7x6#px}5QidZxfIu?1P{d6`oU;{o z*(eTuw&Z8HFA6nJ8azC3t)ZGC8 zR=pye$x~);BLl%MH)9y>Aalh)s}<(iuV=(~Dh!V1bWw!(eNCqANVPf<`m;{@AXqj{ z(=Lw_lOF*GM1YSpPoruifY;@kH>$IIgo0EP5AyWopEWg|tRhPl9vv3Pv-P<)>U1&R0vI5$Jx$D+Cjx%NBb;A8X1M z4;**s<7X~kGCR&)X~)dE^y7{@0%C~70O(7wTQ0%eIRr)}M#4En0ywI~P^rY|-VCxe zF;moi$!2E)^zt&L(6w@1`iynn*zm~_Fe3hb{2zqn=t0lLho^0Zo5>>VCCFiBLb>?f z=Ps5L_pwDH-*R6?ed$=nr9^TQ%7m!Zpp_Z9+9#yyViHVA$EiFaw|2PnMv{@yWA)k~md}Vyo_?!BUSR;EfD0R1IS$`AZhMr3ONyi~MI4kI3QnV^;9(=d zns#jM!lLn|DEFnHaqozp#IYoVu{wQdAxG-l4aajEt3{A0wX>lZk*to*GB5Nl$#_=e z3XQVywkl-9B3`2<0?N(rPwYv-B|kaV`G*DhZKMBq7Q7#0U)*CNU~VL>5%JwSI0aPX z*#e^1Fs2*1*P%zy*$J=ZTygm`WS*|B2IHhw#Pky)Pn zToTkk-{E408a<9Dpk4AZ$o17O{rw>svQ1KO_As2jN~^FPtk9sfaSa?LIdIup#DK%fJ=+j)dN^8L@!Gn5;YkM zG+*dcpE~^*c~|}ll}wM;vPFHHb-jW0|2moRA0vUy{7x94!E^&BD_flPqpPb798ud=Lfg7@mBcNlm;#LRlsDsF=7RsN1r}%`wCZPzUrq z^icAi$aR@Uf;|DXcLtSn%#=EzhWT=^i!&<}LaK!^80)LZ-8nz5yTo_IS6w9IZD;Vj z+zrC}_}E1N!z>AId8`@jS<)(h8r!Z(hD)F0^R>gzp=J}_!@dprrD$Z4krg>>RaSX( zB`r)nDL6`##``0Zt3xMX${&;NE7AL{7Ujo=WpIeVmStmC4KSiI_(rZ3tRe_wMjyIK zME1x%3ioYqg_|pwj(4Rki!br*(=22sHVbO6+^eWAwaDi;VTgK(Tm%^st(>o2i^Ex3 z)aAJP>)>G0m172f$Dsk2`JHQMZX_YBN3FfbVd3KQ#Zef})E12t=CF?2Ys<_PiZ-_Pyk8GSE z4wPPsA`k>rygz!g_g|d?A8!UmRG>`Z0B4oYw-&09v56$X5L$~IPkP?W6kcH$LGBcI zLCXV2pgoVmX`0)%_&MDBWAL2ByRohC7+|WEg3bt=MNXl$`Y7|$ zNsfg+`|}Tvj4W0gd@(XII$5ZOOd1hp#@T)M`6cIj9O8fa^zj2gF2xuby2QbwK!UrT zO|+joEz;MhPa9Uw_^HUxqK<8{Iwa`*yvqL+(4)sJ-D8~;PTdP87K{PT3ui;oABIoR zzHp?kb=Z_fMn24!3o>B+DpQ6-_yQH| z&$jNFG`~M)@U9cJ?fMLd&A5}_ByT$uN6LNC2`Q8}>7U8;ak3h9@z-fjHifDb45;=) zOW&Xhb>j8l8}}f$)1-!g?F*R*J0PM?G=v7FxFmn=P%-j;HLp6>a47hLPZ)o=a z5Wn%15@M9XhxdgQfD!fjzXSU>K)qruQ{(=1Qf>F(7)hcd=i$k~W-_(~NT%_>f6puz zlF58y{6rbRe%R=NoM`QE-KUIsOtpMBe!a-l%Kj(6t&p1Lbq7`pqPYOs!kTPpC4vAB zprow*^a%jsIP21erW9F~O5;rxoyjHSQGglNxSObf#Ral-PV!ngY3h@!r3>pYU{Kc4 zpdJtC1wJslZG3tqT&8|Sc~$@gv9-PZUSXRqkmfRTgYLV0e=6C@7hMLZHvC5s1h}8~ zyw)qVhP@k#WboXsmD*($6??^nnOiD+PuMb*m^^4lb9V+M!>Lp9+HWUJp!r!nz~B6| zeqKz9`wn7S0bo|ia$kPJ)N$izsC#|rJ_4w^P^ksg4##8|v@ZTNQ;`tdFE`U@7#N^q zyWS!3_xV9iB0ayTzHhJyLIehLaTRanDIMUjR?vr1`pYM){uXF`+3_lU0^GV?c49>V zs&B7w-t*Bgb;}DDSzhdPFucLZMptW#BPA#e-km-qGv+V3yT@F)#sJU#ivPsEfwL12 z+uNqEHD3_mQoj*{j0R6IuA<|`uiyzkw}YoBJz#p^UKx6zk^iOa3lH$)+O)czm!#QN zXO>Cv+QfLePUU{|T(O(FRoYOmIbm##|JX5|*O+JHlbme{+*!8+v0*ONerJdT$a`uH zt36lv)|%~}s73e~!~k!O0U$L`hsSj=v8iSPrO;ZF38;-S1=-zxSi0xvAe=V^s6y0b zpAQrT?Wp!D6`Diu`XXkWKQ0Fp6^(irlx8VBAdgaUy`Rna;Va1ecp~eMgv0?0)U0o; z4eK)k9n$BqnYC|ejoK|ACp^gKm|&{$cAr|->OP#8WFnB@Ov>tz)9;w0AaVyM-nwLZ zza$*Ix(3*$n#kmB^(d=&$aRX$NNly$wNwDWX|95y` zs{Td!jfyqKl8Z;JQr4xRW0bPpE4p+y~9+I(gF zwi>j0wfZ3Z6ORj6=WaNp>)PdHp6^HNMoYh)CMw(^emFD`YwZjh%0||>M>vxm{>u~; zZ#(3=5YOrTw+_X_o7QT1bisjh%oAE=GlL7UgA08iFe%krp^%FAPxfnWORJHu8jK;dnm z$MuUYL+Waw-Capz|AOUsN-~8(PoQ!(H&`H`!Nx?%lLsR$G8C2kP6A&t@yBNS)0yDB zQ^9Cs0Ic^#o;EH)q#37?G{1hus^ae?yCKq~8wvc}^SD6`9!OvB4nN03SUb= zb0oB?Je6td5l-rG?!()oB#ug8&MxGUHwcx5&Ps5;#P)5IuFh4l+rj#e<=*<&{`-Nd zJ!{a~-Q+9FBin}7T0pfo#$=(1{G~pNxdRP@PMxQjzl0?`w|AjiZ|%c2CTc)_@dMCE5vgbl!MlP=4&w(Yn z-pvUN{`f_(Nz*IzF=c4DNMxF85l;3W+dDMHtqvjZ4mCopm&D6#VS@g6=R8mRQI(Y@ zSDc1f=;V&+%~}<5^x%ZaPDnzfPWQNIu;WnO`i|GiY_blhKw&~09+2ijARWI{T*iQBH*7P;qdd<~f(mWttwy4F{CEiKACLmk;Sl}dj1 zEf|T3cK^A5o(_3`HGZ{0Cr>T;N5{qa$$ePe7I7+=^{2frwEx(Wc0djx zH25`k207}kL*qyA+TAB43+ug`-0u~f`{`@6Ms$Fg$t=1`3=-x*v(vH5aXHxS=3xzR zcv#V=Z8_Wb{O)mj*+yMKOii8z(1bGGj*0<7{|ylk*B%JolvhrpypTf9mm3T=JHe?v zlaCU<;_5V(mcu8D=!lMMgbz`>ru92(J!LW)>kXNaYVLhz*5?ADO#d9h4w&tTjQR3S zpr{T*Qk*DgQo)wF)xXpc;<6AEm2XXOgFwHwiYjH%9n*Vah!O zQznBveb*U32G_oPJu3Lzm^A13Ne;Ck7EVKbla{>zqv^QMMT7jJXk zht^M+qg@))yqTqNoGT5UX&5lb#U)3ljLpB%2q)+&8$bG(WO+6W1btVME}UB3^^Vgv zVbw@}yk6I5PW|7cnWNJbk=>%Ng za8-HIdXEdYNVN%1n*U7IG+i4?0+4u9t(#*q&i8%!e1^XEdPa|~x5@MwB~2!-3KZQ^ zT@j3aNP{%li!Ns66Y=fw+f2_1FtH&y^`8FKeBy1PgQ9O@;A7>a-GH__9&}|>^pqv# zIB>qqwty1QC>4<^5j)l$V7HjQ#OF)I0hNomCf(=F7u=^FZu9I9ho#rWebFK2{z!hp zes1)8NqLLFBm8c+Mc{#;Ei~*DjC}uSr5Xt}szT6Oo|hG0M%W?MS9fizW+6AgH|WD7 z2+kp8UU4MeT<-tnu?+D$g1TizdHofwrP4j<=Pknbe(J^MEzYa;XC6DxgAB%{zYv}- zx7nA1LAL!Dn;uPe#Mk>1`-On2#g4CF1atMgJ=0UVqxEBf*EtFXZSp{?>opdurK(L5 z$v+4+AnY3A1dPlA)W4iin86zbXo-wM%5=^2eN81WC7CMHr4?JRB$vFOM>eC^ryXT4 zv9|SQ7ZG-dxhjCrIDWP)zpkzY@>Wmu4?toUclIt8;a{|a8)l8&chVcC?M7LDwD*9o zZ%;2u4P_zsS`Tm@ldWro;&JGFUH*a-5~RRD?kpkjpB(T*c@6>%OPc41629AVyF|1I zwB^b|AtY93Ll}&;_ZBn|KR@1gyhV-~b6F1n`ZXFSUtau9h$VHN73XEyNtQ?zZP&ne zr*~TMn;PC5_lyHVNyT?G{xL8#MsD z%jH>)N|T=|)&B(a83=;2b2%E<{R%T~W$P8<@CP<5$w?Pw!Wi)vM!%G~|IV0G+Qq63 zL(n#yLkeyxde|7lPf5lXtvw}L`V5z{_$6RW&}JDE2!I_-c?tKUafIV)Z4i?ZZ_Xz+ z{5~=|I9xux4AA%h(Wd)vYr5^3V$8V2SWnHUNi7tVcx)aol*#NQ+eRCNq_9lV4<|y| zB){lu0^lSE&JUV{aNsvBtO^>M3C^oEYyPlP;PLrGFT!lrOt@H0(&`WVF`ZN~YnBnT zNCwcURq3v%PIO%`bqL+eZQIqTNFV?Y0ct9Vnsm7FC<9$(JUeAq*hA=8u>fFif%o() z1@g3rAj6`>4s@qD`CE#hHpr$6?k&Eof>+|7xqhCj_HhFM@_W}%ksYP<0 zN&b;EbA^Z)dyCt)N=)>6zUtq$?84t43=(y)1I(#Q-GJQ8)D(7lq51qNDC(S;p?t)1 zyG-O?A)vsSdsT`#%JxppC=)7OS+2lLNJ>IQ2p$?6{Ki+yWmBA2V=y_=3Roe}vPl`( zvUz<6c<;=Wm|9A55v%JH2e)p;Wo5mFj7+AzV4W~Q3+?WeeZD;Y&uE|X7G*s8e+Ef$ zCr!hkw(N$n^cfyH+}XBQ6Lqu&O#a)IsH?qU*_C{6z?i$^lu7fU_}x6o)bK>SKins0*_ z1z${|%3hfoVYJJ|yBbQ})Ai+i_Q#4Aj9$$Qly-oD(q;5~()f0GhTeL=lc#u#%Ws|Q zhjF=Cu_mm*o-uu%Jh1M~8h7UA^br{dzCSKNi;yeo3?C)jnyQ*6zX}#$Br|ZNFQA7U z=!(-A-xTTFPsfKu*X9bfGAmgc-JVV-v%Q)eHWu zPjebcto`G<{-uM)0&BY;j*O4yLn#XB!d|ZNF;1e5H3?L593ZZ;CV{MpufooKNG)Ax z2BG7cL!QpBa6sAaEeeX?o$5Y9Md~T^%m2nPIR4`BSkrL4mWCQxatOdX>W~Zo9;ohL z9aTwT<%?wnK#W}!R_+9dLYGhmcxDyV{D3qCHYHS<5p}795d}aV0Z?=BuO1=FwF^Bd zIUy*GM=y_^uR|jmf9-bLsZlPQfi0aNT*dn9=8o^T#wuESQBt-*Y+PJ?gr zJjodj`5)`Wmh8C69iMUSbMS;ch%FKi>N{2O&8sJq@Ohl`CTg7DP>Qs1F7n^6jam;} zyuS_lNv>puz7uT?EFTTT{ub-tFJv0S(p}qcVONCWy7GeQZ``?~c8)WNF=^0^D5ij7 z8jtWzXPA|pPO~tb>WX0uJ7nsQGm`sTk0m!Ipeeyx+<-;(?0t8}&0F$s?d{<>eUY3g zMM}Bmqc74o%i)qARM`}{bLN$^%FD~IWngd{sHtQ5=~&6VzP^pt%Er5)qqMKQZOz=G ztC^aLBHI$ChK`5?sV3Gak)(Da{rZWR2npqG2r6rTJ65zi#i4!QolI|!XZ>2MQ?5H9 zyi(UNOrWehw7)8&EFH!8heXfAo`t3L)Gs*7Mj$E2LZ~1xCT5xS$*p%_b?)M|m>`V` z^NaczuvRLdgbpV$kRJ;r%&*j?uL#8T?BybOti&>R7AllFVezt6dN<*88>OM2Qa_9 z-UGr5_KHTo-`Gs_1-F{sHPk+&^`C!?E;;k%gJz_k&AQ0i|4qRtH%#pHlbHMw`u3Mx zZu`JNUU6k4JJQhZx7}_l!*j<(swuR&uI%_sO4mW)C6RnOA+)Ywn8Z>(?xRjT=UcJp zrONq6{8(@c=afa{vg)Hchp#cAoEE9U(%6cJ8PwggfIXa&V%E;vi&^yXFbqt|qL0mp zMQiP1xceSvqnLTk;XVH*047!B^aY8MER-QK>c-u18C5N%p_2pW2u z6B}Xc6$6VLmqA5S0=0-Cqn7~kGB5Yw=pTjC2o-!nD8g$ZF$cQlsDMl|cVyqIQpJH0|HbV>YaV(DIE5=nts{f+PiS##3 zZJDxa1e3*d>s=890+P*3T|T{(R8uW{bq(CPsVVE_Z5kTWu5Wv&Ya6Jirw5Z1{du(; z&QrN^f0<`^)3rDBb40)Qrlnod=tS8(+Lsv&#Mv(zEzmW6nw_$nE^TVU_Gukm6ZxH) z*9oPqeVlVL_gS<4@Ayn%)_x`7Q`6tulY@JTZ+G5rEPy3gf0Qe|<6*?vF6ne2}g1G6{;uDoNN%RqpC$z;T& zZg;HG;6=LUjB>rROcFNn@4;MCOCp3cYzyOk6WRFB*Oa)z<6G?Ip^)xrAs@$Xr31+S z7z{)#!coZ7FrE}0^d8;U&QBx5FGtTzA!M@;j@dbJuMu>|D2Nede}Ozutul1JF$ z05PUId-M))d6K6f-0Tz-%*6gxa?Gxt9VQZ7kL>5n?|LTbMHhg!BGt-IATfL}f_tRah!+G0k1Gqv{e5dn6jZE@%xz!lX zcO@l5uTu-2%wSU|mP4Iad+@2RbXo`jZ^TogMzhU&Yl^BBjTzh8f|a}N2uAMhZN8Sb z=l!V$7{$W0)9JK+6>PICax#dlIj=eG`4olU8;wWgwAo!@6}Grym37hX8E}90XZLZR z0Ud3leHTN2GKwG*L*Z&EuJu^Cs#~X#*Senm_sZcdDeUFm);OlLKc?22uPIAdK!P4f)knMk$C z$t}6u)XN)GDJm1^^ol@er?rt8ROsftEk%evzrGc@c~kZohcMW_ps}UuT1p$LaG8gT z5^U+@D86cf!r67NxlLHz-?Q_#4!(=MFKjxYetsp6%1Uy-&^X&U!PAe)i$(ejCCQO6 zlZ`Up;<9P*&Yvaz;)G3N@HUjQpygGwNC5j>YOqJqEnI$PtwJz)3F3qpg$m>bCp_1Q z@91!4_|d?lHI}30FAvJF#Dhj?@;x_Uc`Eumf!6I6)ZXhBrhC9=pE^=Wu~Var&Y9B6 ze!E2@o(3);^LhGB2{eqyy49y@qa$D9B{HDDx!mCjmp)xR`x3$V-N45= z=vFm})e2-dd{h`{WI+A%TI=lcc1>Kgon-rT(yD-vqwFw##V);_&-U?_{qmEK-=wKf z;Mvm=gT5U^hV7{`>u+b9#i=}Pk5xpm;rw;VAgn(<4X zQ!(kjnA9bM7D{SZK*?{(w!be(=Q}K~8S$sFaR9X3Xh(0Y8GU*;6t8!+L$Iu1Jo?S3 zKZ0O6!(qRBrHQAZa6Aj~8W_nl^}4SZG)L7uRzoPNHTeUed(3u-w$Do%Hhrs2#+#Q@ zHZAbhE#><_Xiffdx9n~?@i45iL9KJx^rq4-;AXRY-k4{?G4SR~4RgS0ZcY`YRx6gm z0_)(#o5ty{$sOBi$o-aNRlZA{=!x^Qx^2SwTs!#VK@QZ{N3~aed~a`2etEK@$X-sU zy<}O_UCE%l@W$Tlp>)52pO=w*+d5I8Zt`JY@}!H|w+Gq@>8F5mE&JDbWUe!g*b67S zcnH?s|Mp10vgC-M85jC|;}W8aHiPyB^!cwwG1r2aDqGw+?+Xg`zME#{jWTyTmRrzg z1rRSs0hPIdkA8pb$J5htW^lkHlU1@6Onb=-GpNlAhan9`NKfnstOH{)D=P5XzH)q|WXxh`($Tp;+o9 zKJb)O6`^bwk)Z=tQ`;{0r<=BLbq&fNIg=9{CFrt(X_EUdkSWv8URRq4=3B6W?+LQ4 zcG%@J4Afj|)B0KQHQdPd{Le41$er-k+Jg7CB!}!}x0Z0V6lcMCY50x7yGW~dD#|Bq zwn>L#)mYd}ql3wMt6!dun`I0%ey`S_o|IV`I9uU99YNyP&VU14+}#IGS<=*uG&B97 zA7%RYxhO{*(v^W+$k?$xKnmJeh7ju6VR96!|VWkt>?)qs;!OxGa=&6 z=J5_nB|AciZz0ll3;DXQ-7d(dJU(OMaRZga)f(G@*BJLQkp8jKR|x^9?Z za(g%xkY~$OPAh_9R*PGdEhhb!D(`^$%3jv~^8cI|F^*<5qFw8Ko2LVh`nD*HUo%!b zl^8>F7L%n9K*IgXQ$`aT@ZfZ1vrrG9*SpKpO zzpR(LUrJ#|TUsMtBVr;(P8&(7=ZDuf``EeEW#cQ3h%2xb=v97TEfldK9l{!%ot9Yc zh?>V!L#i(U0ouvpHWg=$`06N)EhEc>FGT}$5X!?CYnD^iCkIAF6Spep27d^fx6x18 zDJA8lQaNU!oKK3x{d|r}^vPLEvnmNcGNtpW{^7K=0zyZFwxM)l0mv?Ra&@r zSC|tawt}h#g5>yWq`hdng*t?qmJ{YdACmGv(@oBoV;?sYqm&X-y!C~6?ml7MoGw6? zIGNksNp2WLAP(@%%<_Utq>l!E@03D`G`A?XGF##vSpDFYCL1Tf``5g20pRXeU#J?S&qQg-Ck2GMRlRGz*_Iwy0(zlcQe4|r( ztNz{mL8vkUas{oZ`FWPfIX3EkO7t3$X5`NrPC9z({++ z`2ilzUOpWKSzwD1`s6g8p=V%{<4c=G$+f%}@vo;C5bwvvpj=qW1<7HfsQ@a5jG-*T zU$=1bPAoA5D4i1Q?4DjxRE4gh;Qy!!VN(%5c}=#y!#2sP3?#uM@^&G;NESS^wxxDs z?Pw+H-LLOGW{F-ZW23UBRGIN(dnD&pBRHX5#@?RXv~7=s3lph*0b2XU*b=a&rewHt zDH4)!b*<0c;=9o}Be(3_2T|A(Wx}Y1LeGX_@IcbI5lTfOC~EvEZ_VQ)715&X_UgW# zbpGt&-&&q-TdkkTsYlD8>iX{-nis5EBsg;KbO6tFNQ zB{K24`dyC_?KVer4C$n)EBZKBB8&11<0o18x??;DRgF^ZD`9iQS&NY2h(yAdK&z%r zV-9HWn6N4SLnQ}#rN=FzX;YIy6y6%`0VU9H2ry)rW)~1)ATU^6)jHB2EG*P+Kq07y zd#AvWIt9_8b>2k3EOy3%%*V}HWK(SnNha;mxkq5fmB{VD&1^>hE1n)0NjomKfQ^ka zBNZ2xg=Z_B`_0D(itl-gxPpMAv;TM{mW;{k%5u8gcacbYkSEQ%2XLHv<9qvhq=gj~ z0cNlZKxQhgpeoeqC9e*;(@-+co>fx5ensxkc}qKWb2;2#7@-yBm~!BsS#IIKH^yDOK1j_cx=$Kx-uk368p8u@T-+-`Kkc?# zq!=wUGJL2p9{djVdRU*9^X6|r5S2<4TL|=1TYKzagS_rFZA>5E+^#f9lOVxg6&89$ zCW`(81lR!J38MM8-uD=^e_q7YI$+{zo#PRFiWn(|2dU7i8ocb-aeO(|KSd*1M@!bI zJ^r;|=9SPf%mhvS$l8MFCP~uhPGvSPDy}qs=}?zstdjX*8|fW3y&Hz z+5viKNr{O3-J(9qe@CXRl_Lg=rbPq zQ345jZ(6~5%j$1jPlsSQC5z%pN@$clWxRVtm=q0O%b1KS@hC@FE=x zJ1&-%mL33$Eu!O=!K`&igY0^=_L+<8Zc*B#tdaoWt9`<^(!G)qBw@KfE0c^R7aZ#q z8?LAOIp&ogo9ibqclPg7TqVeDhD|EH@=rLf`h|1r>L#OUkM4jOe}7((NT%}*#&d&8 zv$O%Xv8LMVkM zHSXMe|{-hagjWFIcdfC;FR&PiE68dS@2@{j0%gj+T^|sK*xVr5d9GYi&;1 z!@vOSRYrw`FlMG>RZiL)jMhlKp5wgrcayJ1o42t@B|C89j#dTq31hPVR{5T;dH^}7 z4L9L1Sv9Xd%ughnXo(C{*UAdEtE)cu-Fp-do2nF}vsUJW9<<(@9^g!;(r!-|%q4;{ zZq%SkXNiRF>q9x%+hX(oN!V&>iBnW=Xzt2hT5pj<$C|Xk^-LaATT6h+pymDMm`J1f zn+*q|GjUsIc~6<%s2c?)D7yQI!!sMUzCl)9bI7-ibE(#e6ZCK4` z|M?UdQtEiBC*J#`EnI>mA4qkEQ9B1`hrQc3Xg$xNle_D6_lql_VO>^PdEu-|8*pOB zqoCch?RNn6R{oBTz@YjT*4NLf0L=0yfe*gfOUsxD1Gd_1j$+;8MiQ`^H*-4;G7sF@smywmjlluFcN&$pScdy9WrFq1t_u1fUi&Vw3-#GL1wKU*dW^j#9jhb+eqPCrx<&rLroW6Ku8z2#M3=O+uKQ;i$YpP(FOM z(XI|5NkX-oPTJOb;1+8#mWM=g8R6B{v}94%phpXC4MuQFt*9}o3_jrHkx-O}@;Oyt zbRMJGY!*`m?ziLuQ;W6cggWzuYVQxPl9CdaC+~O2@QOU4ylhik+y}*kmG(EwWe)!& zzz7b+k`b7ULEzueWaQ)+I+bHJ0H9WxT3wju%e}0uY}Zc5Gjk7&FjAzsiQ7QAhLEhw zjqA$lDS;3|5(7G`1^nT$4Al?V@bK>P!!2+{g#ku+Q7)k z8VCK&NZtahJ9w$JDPw!2f9t&Gp$zEw<#O4;3bWScpN~p%NvOO^! z9nu>Ruk2p%>OnBNXW8ZA6QF%r=k#!--5bweKyoYXX;UqG?fWp4{2^s&10VfyaE3C> z5GW8-a8kB@^7&9>UtC4(wTt?u$53qkAxeyd0o(R5;0o{$Y&bFiD%zj4G|nG4u5e!^ zXF%NA)`5zKKQ3HS05}3*8eqXyp z;dQw+oVPSDG?|isfunb7zs!2QdfNk_11qLfAB-KIRhd)YXh~)oe2u-H>7?KGP}d(D zQ*`AE|5(faXpSsAf5)rDIrEPGUE4ip!zx>-UP ze7Wzq?qfjT(xiM;cO=kG)m*Gu6$HDW;U1PNGa!Xp@>^USlV0yD!1G2|909NeWNqsJ-=D#3mK{YoRaPGt*2Yja`O!eaSIXb%7I zYAeb6uFo@rPP;X1TOt3tjXb;15UU>F9fj^SOuO(`cKdpIRY}CtoJQy;r^{Ahhk>RV zG!5W+*dsl5Uuvj&umi)=A@+XOOVnVmV3f(ohX?(Z!fr|i9i>+!gBl3F!}!#*7Wed! z_M3e8-}Gkm<>E?$$6j%HYh8Ja3qSUu84Q77RxAihd@)Vfz^)$CKAo8a2ZWG__+wE? ziQ$3x!%c?u#t1DBj1IwKlQ*fnbz59uU!fY3K$n5Uzn8#|P1K(9)elQ{@Ye@!1oTBP--a2U zcGl!wHv6Yg3e<}qL|ksm!94CB)Lq@(^%q+8fWqLMm;^prrV;=h>QJ!$iou(@!?o4* zL-B-lT8kk%G==PBKquQq0W|D@;{5?PPOR7~w5>pIpA$wF28B!c&H6k!o&QeUogQ`K zqD@19AkXd8og}cSEq-MQnpdSRGxCgNG%Y!F%fJz%*}oF-!YBYpQ3HB!4^m2J)|=pH zbD-4Gb$1M`^-_#lJ1uUayP7J(&Rh=47Bq%O$6qc!3_*D_%Z7WV8t#{$K;@g?i`NTF zi#bBI{o4Qzo#yahqT?)Vmh}C*;v?B;-~E%@?MTI5Nv_lS>zaJoqX#E8&#RArv3+u^ zvCpAV=GzoeS^dPpF~fmaDle+V)6&-suP|d}%T0f0O3a%(@6F>aE0}8k*0%r21(I|7y^T^QMxfr#Leo{%NfBZm{F3 z7n`%V_^0s**$PyGMppo=DQ3&}qOk6wQxvOa2aG@~N?^?}YtsdSLe-{H%;34f5Gu}N zp;=ZjDBG=A*+1wdMOn{o__pE{DXLZ$8E{&<*59gTVbcq-2lE0iXIb-_ZU$?yqgGCH z+Esfpl7?*4+^HKDJf-DmbZM!p6d0P(CtupPbH6V*@GQ>t;?K{^9GoOi*8Nx;UNA!6ZX>!tZ|c6O$qpdceJFF=gcWx`}PBgqQqP#170{~5vft7*)*S{oyKzcvOvkN-%F zGuszuv{PkZhinn*ev+A4ULFoZrCe-tX9e6zKn&&V&c(@@w()lETvQU)rPHZbrNfW{ z_4UhVAr}Di4E`x!Fjyo=pa0qK2Mc`_&<}k5Urjx!nKPsI4CJUFteS~~)e8?|k8sk% z`NA_lNx6U@%b!mmqz@pGsAxIoK%-U@ z1l;Bw^V%T>pNJ9+xljFr7p?cY%D{b(BFhKS)xlcy2h z&4BSBgUFl2g5$c|2{jP33@=vcKkc)2EE(%IR#n9$X}N`#lu%@PT#Erdf_1V3M8c)d zI&2z@@*IpxOZM*S39L&9JrD!-u~iQ4BIHyqK3Vf1q*CdFtQ}91 zqxkSvj@eP;nIU88>f(|v zFWDSJCvz(u2@-Us%Qp1qC>0bx*{25m5twgyL2`0(G$nn#eUqP|lWo_uLq;T|$E&M8 zZWg{7_%B7B{URnb>EZorBv#TU?i7?zLB9b!_lN{aWj!XWr}>8Vz5r-sh%z34P6S~5 zQ`6D_Y%<xR^#8xTZp3)ZRNTSNXYu>rB&}|} zs>?HL?=*V-eprVDmy5QCM}S@HvY%oM*d=fdkD;a_J#K+(MEPxx%k>yY-)eWk)ZU`Q z1}sspIcy_5mYdiURtISlR{BqccaQ#6z7@xR06|iY43v;8gyiEiQ{?rv-SWzc>uJ@% zB$H)!DX}2y^cT{AMsgri;VR(M(7<~AUSYJ{0}T>U(hVdjfJ-)VvrVOPb%1ctEUd3j z^oK-%FIn;Q>c|4T_Q}6~bn?FM@Lu=Pdf9&dGGN=w9}=!bTyZ7V6b$^bE7QM~=2BG= zD9FI$Jw-WHO0^x?KcGs>wM{EEH5H;6t8Ka2krH74_8Bl-&B{v;F`?>B{)g`wFf|Lm zJdYw>Uz!(;WL>S}9xw8nstZD}>|Jp~BUC_)4=qOcB*QYR_KnYts1)T^EkYpYHxmSCE={{z-tD!B<# z_3j)Ag2B(izebkCamRFAijbgkkW0MCr<&k1tpH1Vy8R}Jz)aizNI1d$fCKogwhPj% zunv#Yl_VwMfQO=n_uHLu?PbGvPs#Z`0B}?je^75XQmbK=k|@rWxOpQ`Zo#$*B!{9Q zyaS>~+B`JQ!8u61a1$&qpbVa*hOcpVTgnBziqqk+SwQIQb_YQ#hEjf#lY{&~3e{;? zfox>q7ySlYH*sXe32+zzWG;cF2fM{=g>4c;kaJ6sWzOhxq^iDg)m&sd*EoyRU<(v8 zXi+;{;f@SDS8S}SHedh^SNr`sNeq?=(QLkBxVmA!s-<6icAK-%9k62))EvZtI!GDpb>S%j%wj4(VRXer%Vw@m6s(5!U zI9k{yW^7w<3Sj~HiY#WV^wbOg8fB-1-#e-=AMm*Y;N=t78@Ls<$9WGw?K8s-qAx^sZ>?tBsu8%YG~$OOyD zMq}p5Mbz?u^xp&dh$%Dh;-g19RhzMMXR{UW2F5T8y)QXBK(y2Y3q*FHeo?yT|nMTUAY zHmM_IUekC6gSLC76yav{xF;T!?P>Z} zV?W9k872@!R7`CA%l_1wxr%Zn!=5>-%<90!Z$LZq16Z!OK3;EtHjw+Xp#U~;;E@AG zFNz6A5s1kG!)U>cH{*ya{X@8|k|=_?R`?Qf=h5HRqYL9*rflaxCPGG`2RUR5I(>zI zhvQ#OL1YHAW&ToR<4WpRBIii*9?d|2pt#P&%+V^ z-B|eZmebhXhR%>(a@voc(CKQFxL%!?Kj#HR1Az9%wq;F>y(7m2^oHy;aijHqqt$ys zUn6ObmAeZbpC_WgGy;bmDXrYNgM;At6KL0)jcguXRZc23X`}_krW07DHvJB zc6t{8IL;OpGot~0LeSQhUPwqtb4E6uwTJxt@g29-b^^cYBaOoGVSOl9Jp7*s*R0Zc zE`cMSva|&E#SB0v2c-j2gEutdAvd2rOX*+Rt^T(bfWC-0^~>vS0$mCT+H^Of69n!m zG}y6_q4P1ll+X}+)DVc=w6T& z&O?kFnw={=_F$)dK@|5+>z4Mug7kXISa3N#1S^T7#CN0o2}%V#kL%PhS*=!GlSq1> zV(WmGC;yNrI-;;u`7RLlG%CEMw`20bP?LEMS|W8ee!dpuA#2 zA-Q#|$N0Cmm?1eOKIa!#*EqNCeURdBVfB<#rAdBzCl{1ypzW4fV9SKECaMD!UHbCL zjJLzO?s8Aer~=23a30HWY#rl_1)1^p*e+gH-tAY)L{}B=)v*bqbmGp;Lcm$2rmh~R zUWB$g=C`5h5Kd8*m zQT64-sRCRr1j1i`h2KnxfYu9f;DxgWxO4~+tbE@~F|@P@@2(hT>ocs!+Zw4UOvoya z)ORjs^4dR`qP7LWy05*uGzW!Gfm1mF<7#F2;^xPU|L9_&kCP^@`+nQo@a{5U~4XfW>}Q6+fs zOjOINO2h=Vga&h!bvOE-S{|LXA}71wG`jPrO&UD*zvX(8dbT>v>V7{W`TPC%ZPcT% zxwKZsSbFnK^7g2TbZnProi3%JWF4!ew=VG)iXt~RXIduOtj%(1ku{zf#~`V}p~5oW zxv2QJx#4PY92ha>WR+rKc;)cT$y$U?KsJSbZQSpQ#R?}+W}}Rcyc=?kVkh{gil#6O zKO&Fi)T4uOgspwXD%VoKq8aP)^a}a76l!mYD#e(p*ZTV`ii)vV3Eypz&U& z;6NQ4^(xayKWfr)ieaccE}PR?$=YDA1*%9v3Z=M2tzy7pT3WXP*urd_W;cAs$z27!T}qHn%wQv z)s1X`l`BP8d3T&RI$r_!?pJ!^VTYve**STEXXvq@fDUmPY_=ItWNwL?GO)(SWVv zoZInK2LJ6RRwjh+H;H#LqP&C%BRNJM7AS5AF`L&B|5#Qw3n)-l2=oY;(&@&B9^>-C z?q@yu3<- z8LZ}ylQuq|gI`sJC=gb6-A!OyYL;5DUGpou|4ONKQ$-Q8`DbJtjGdwahs=55$#vHi z;RzEa^xyE_!e6^FDo*qD>noeTzq5sm*ZBvQl&37V#A~{_-qC5Znic)%iL~4Mx<*r2 zl-iH>d_5sxA&`0B{4!!S9w!vbd&yRswi|b>Q!w{gM?5WTO>m-Ap5_xEF4NC@VN9EZ zZyS~$6}$VRDxF-GA@LjMaGuk z!qp>E(Rk)wIvjQCrf6E!+2gYb)|eI?ci*qXI(>uug|r{Ktba^p-SF^b25m~yC6g{~ zen^EE#9kutVl7G{N5xaZFApsyH%fsb_LAMa54C(vicLL240t;z2GT;3RmC02I&{>uqJ~4N-v7FeWy( z+b~J9Kb_rvaD1HS6Jl@nPYy!LZq59Oy!;+@xB zgIPB`;()`5iZj4*3if>qGNHVQH3gKU}x)ct4C8i7#^1p;JDFy8_4P$$&L@k z5CKyU{SA%k&(c}npU-fJK(HfRkPwKHfKv@VRaHzPk*HI*)5Dc%U=E%xdk`?W-IxH@ zFYvt&n9380%gCU9Ywi8>U)&0yTC;{QH2*{Fdi^hAH|u{9yT+eS;m?^$`EP3ZL;gSa z5?ee6H7vriHB=*g$#kc;U3bz}f=o^9oVo>12eo5PK$bXDa;NAyAyGs!Omp#*hIEN3 zvd2;Oe!hQkBR-pY%DWJa9~O4!m+~(tdW9pL)x*65cQ_3ejlXSNzsXF@`G;?&-o8)4W$~67StY<4P+g4@*$aq;D2O*K}EAk6McI zyBgczZqZ41-~_~w%IQHh2>U$q)3WJg`i(JGm}qctY7Bk* z@a}kiB2vw~YKADwfRxkmfs=bk)iXtMTyw+RZ)#4pSA{+a!55-A+3IPnQZrk0_zPzF z@HKSq0f>|wmXY*ka1`(Uh$ZxN`hE#pMv?w=&=F3N<*2rU|LD9U5IKRMuEG=X{;MKs zk}7!>C9dH@z>-W}PU9%m@QjP0q)&ld_M(JoMc`4;_;4@s=_<03waQ@IVwPF@V?N*)yG2x6gO7%y5LqC!w;c@0K zz{8WZ2>_C_v4#y7;RI5r37qnruj}Ws_ts$R34+wJ;d`R!sORrgxd?wp_QG33g3Q*H#zyEs+x)ZSQ^(WULrIiPrgGyO zJ;2gm)r4|id37}kit7+5y?%FfKpN;k-Hkl*C7z69*vm#SqEV6rQS3X6S*;M?3}-tK z6y7BLF3($a_INE8BUwq^yi@jUJ)4^HM9kDWKS&@Aw2+TLPi(kRJylt2Npik6KXU4W zjuc>)tv^G!nnDCiLct{VfVKtw&*voqv$Nm)sAd%Lm$vqDCqWlWYQ|aIQMJjmAktMH zm=RXuMC547;DSKX2$&8eR}Wlv{{4cpr}5d34@W+4wV?`)M}YIyqy?R0&dF0W`b z*~+-f1)DWWw#a(^2g;VeS;AEhN*N6icn(xny@^LNV}E0llvSq1PQgzlx!@Gcm|PhJ z3q#>?TiWm(sM2GJRgQ`N*3h4;in%p*(jt)5bEaDbsw@?av*T3a>BpT@-%HYEGVO-V zkD9kl{3e_ORq8l@=go;${2id-5BV1I8FV%=Vx=$-^jWuWZN~<&yt`{pd`)Dlplvae zwE-cgfDf1Yk-J&#U#VHR67yg9H8+1H-B$uwB`L}K3UWcnbq6AJwy|LS zzl^f7SQNm(tuj&*qq32#Lw9U(TL_#BG-u$Xz?y^uWs~x4UT`2ACh8gc40TjW#r49!1ms7Q~2JQe;X3;Q+w7b3i~ufdP*Yfufh9d5 z)Er%%q6jl%iIT5Ab8Z+u}C}+a$Gr6i)ecxyxlSL zKxUCV@*N7U91-gk5$i2Rex1KAMInsA0(PQSHr>$J0LTkdaV`}b0&J~cz|6Q_MfB8Ol=srE#PhlkSM!3+iO*x3}`OT-{xZkgz6)PMxC*(k;<^qRf6 zD~_7L_03FTP9A$A!Z!r4%_8MClLUMaQ=2%|_yXByeS3L~tOEm}q`8$zyfWuh`9kS> zIgtqX?@w#WBQ`33DQht>K7$}aXu_(mD+mpa5H$FxkzH2Ng-ekVG{4WFpqhS0VDA5U zmogqgkC0tM+Lo~o0h8n_N%d1;fpCX2DrOeH z_pVm@by>7pbChBKvtQZ5Gie_D0*XyJ)tl;-8=P7^jabMfD1IA04>A=*JIJP6UK~Es z#Qp|66TTQP*#4*G=ElyN*g)0p6=X{=Lk%B6pI)bX_x;lj!{ZS8?_Fo2=Zw$kViN#T z&(fu@WlIOTJ)pGh$m^pb#w(m(l&iu+!KIE4xqku0RX|-ckVri(GBsS;Vxf>WiQ!sL zDmb`a3+xH@z~`i|{f~nt4<)#5QFK|=%;*rPueP8M+?z8Z&B-j&eYG*Bjq(y;D)P1z z5APE+^-2wyw^!B^_~U0a*UweL+j?Ye2!Q11Yr9T_^u4=kO+z3spx!$DEct=md^&3j z4*TEJm??&FN^t}_H+&yId(b=MO5tO+=xoOV%DhSv8W;ZyO0%n&yO-?{%e)w?}C6@f#Rzm&UW|iuX)_c z=>Jj_G8=0mrgfu0i^?05Kc_>32{=%zrXM`^1iY0Ak^>w@X^|9+;WLf7Oc^7>Ew;-v zW!PVVQ$}E)b$_uTEhGf~@^CS*bM-w^ShdxK9@se;N9*TKkpqcD#kNbpqk+MV2^5jR ztx%f%th|w1D?y4fHwg=Ex`)AgUT}BEK?nS6im3j45Go-z_PTA|z$OiPJ9Rv&r@V~% z;nKD#3F7l~tYxY56p8ib_e_dx`n>hX<88xdSGsYgYmiCpfx$nJoMkmXPA8w-Y_;+t zC(e?wer;g)Vp!SQ-5vPtb6B6dhlA9I3}8!iwWuGB2=&#Cs{o{{cn0Wo7W_|7ixv(Z z-q#JV6Z$K??kM-i(gwEs!c*vU{cgcf4n93c+kVJlq>BniJ3Dr?ClsxAKpNEHr zx-3}qYnFfm56u@yf86rFz^OO^Qs^b%-Maz)eyZGoA%9*i;C=;jRn-fLQU3!(J^bSC z@s6lkJTwoNstH+(wsE;=`RXa0q!~cLBWBh`<0jeKtYb#NfIwL>H1+piGfZl)Ue&9{ z8%?jjEU7OnmwS&Vc*#(XOt`5iD7Ldcp0a*YQi?mxy+l-;0PwX}FK^{iWpVLufD*@8 zQwgxH0MZ(LK!Q_SR|onhCZ-E$$R7dBHF0XzKz+t%c|j~tUZCyibBC%muk5n4F!`o5 z{m2DrFV0yIj*)4Kovn7C1sqsrdfuy^KfQDe?BC?v*_oet1mwxiTW*Yxlq$(?O#Aie zZS?`7g*`ixJFcPG_4V-?Y2L9|67hd4XKm|NT=>8pip%Nrsq2NuJ;pF?ayXI;3lEQT z!?V?m{hSp7U}d{8O~?X9!Gd$S1=rh*$B2zWgS(|Sp6ozFYa8c^7^(64xD)MqJ0$`g z$X`@m?(s0M9_IyLx-|M~I+M)k%HXY87lzV;>&^rO8UED>vzgrPj}P7-&HykBC{)-9 z=6%(jp3tSkNMdTGf_EGE$Xs@0tjM-k@=!JMQvv$Mo0jVteERLT1-)Kk3 zR*O}walFzm1S%sEusxjRpL?nHhCoKT`uNjB2b`j>TQI5*EHl{dY}(jb@4-_UFZ=9C}Y z%@w*Y$TYN~>hixDe*YR3iOu@y*F_5NOXl(9a9(p%?vTbr3>7S@s3^$J2B~@4HVs8J zHs1Vb0FbDY0UwnKY?X{#>P{h}7m=rUU#bhXn%Dhg=N>;!*PN3T=DG&1E;82ZAJ!K& z6W7BXzE}OYCl}r@$rX-k6^r;;S=GC!ucvqF*OmiCK~DIab~?X76L@i9lKTd)6JMg{ zSA+VYC$0&u)66FO^OPmS1J;9d_bBI!LMD%dU?leVPRauvSRVRArX7{4pRsJ9V;q5|mw9kaeFH|p!`5&8i?;{o)>+fIsOaHM;EAfSp+#I&6NyxBSYWDJ9F114o93t#VBm=uF*p zFx2Nfr@-G2@p5E*>2lcxdG+KWk;#>UZPl2w5cH)@ixyx(HfqoSU+AXC?Qr+MxcnO=5XIm*(XtdtQz~2u zgYLA#?+)}8ap))GAAiCIl(v6*Cggs&;E*}Nnu;6VwZt4WOA-4u`A=(r!T&RRJ0&7do+c|q=3?`04EWqQ3sRKxOYiW9K1g7_0zvWUkB3wIAaua{20oM&hip4-D(8U!A3X9>>hc1cJ-+KI-ulS?^Uez}pPXa6av%xtmM{ zR6Fl5t~R<5mBh;0k0gUGrTk# z-c#wvyhuf!iR#ya5G*dpo2vq&oaI|6a9-^oBZaS-*Y$U0lLg2QFSr2_(Z9aL`}tKw zGY{m1AHH~_<>lo)jY+cF&efV~Fxcu@vqJg=42nYh&7WLd9qo9(qjht0tC}*|Rql93 z-RY0qDJiWuMn;E79y1b-#0p$E*}EFR=iTFP?0zb~`1p$$a1&bT(;w}$qiRdGlxLmY zPMlSMa6dVxo`Q12OWekZ|I}yB&TylZTDp62u8?|J2ayYHoo4tWFeo?im#IakF?6A2 zNfeBuC`fxVslyEEkBepgkp*y!{k5>ZI^V=PbsM;pAXS7PIq-0nrRwFvv3u~ASC);@ zL^WO__sYSGvL=P34FT%f?4#Xs*rm#bH}CZ@@CoWMhJm+GL|x3*_S&*2=;cqzPc>ge zu`{|3O`tD2y!axY5%SxwkR7nNW3|AGCdb;-7Sn?2KDuK6IPjDqY|LJ%#Nip9^q_E; zk{{EHz4@#z4vzBqH||Y~ySMxtC|02G`iRTY zVZm5z_vEBhDm$FI;lzPQLPl-^fT3<}2Pc9H8uoCAh=_o2?Fv{R9-Qn0c=9h^6Jh*( zV&6a|GdKRUJJb5#pBqjFb>wI^L6w%4<|uS={BV2cjE2BhxYZLIx`5=LK$psYmw zbG5&}lfV~-9kQP2UxU~gc_RwmJgT5TRH$3>9{)^a`c-1|hmHr4WFZZ!PbHH2z!G%-ks|jx7eG9kV;h&@^Gy;rDzSlE>V4xOJM3y}IZN#DBeQYX^*dij zTgo!$eryLJsD%@gS*HiItoN(Wc4=h+NddI+%^fipCL{!MVv+jK86NR!v0$@#9gPXx zAG^geVP3mSM&dIa=Dj)~|8-a5FiQAK$jDKm1nNt%($w^u4b2 zm+yO=^gQ?B!=l&yUDvn|=O}6y9C_wZ%#U){@6o55VvaezqE0gD4OZiVP?i+2naD-7 za}M_d7f0-DODl=oPsWI8-jMfsZj+|v6~W2we%+JE2O${uWOAQ!*#+#4B-xKpH#oeo zr=_JuTVv0nlaP@y0$#;W13eI-Hh#RnxVMP$k>C{kJ|>ISh5LC!Ze+n*qq+KQlf8)5?^EL`Awwi}JJI2Imq6b?H4C;^H zE-lRMnp%NF8uz6z1L-f4-=Iw)oFZ%Rv~+$dGO?4>xX7+u7Z>wSh`m#cXJNBxZ1ECE z@@f1I7McSQ+aoyurmRtv6aIz*=^*P5ZK)=#Of7=Toqa=CL&3NVs)=JA#&6CMr!Or_ zn;jO$ZpwByJ8&;_(ec3(Hr5}gs;Uy<>2ChF>*vq>{(dApTuET3vbD9fW|yg0suYuy zGys$XL&Kl9hzEfL1OjuIfL2~NTUS>X-8gGRACF(#%PW`@k_=Bo{Wq1G=+!#w8!J98)>VcyD&BUY!#Xt& zYHIT8h*>yg618$ApcYHEbPg7m<2L;y?xf-BT|KQh7F3KMu)CwgjBY~9V<_tyGKKo?`U(jy6iEgJEQ1A5MECA;vV1DDUbP@gd-A(U4dF_yIGMo{j^8 zgoHF{Ht_})Fx{9Yt>vB&dq{=kM{E!s`Ft&BrlBkGzC+pSa`NN#?ilc(B6fa7?0iNC zH=aXuvMA*j7P%ARHFT>+iyZQk{nGHI`5&xs)Zh;u!eo#dyrH$~RAr)1-{{Hcvg3Gc1eySJJz`uX9e1Ta`h!+S4(-Nr(Sh;#2P zaW2^vLC|JHV|FIn_K1Z;_h(=0lv9$ttel-z{%eqYX8B9+y061u)?~3n%V@By0ukUk z^sYz0p^*Oc)u6DfKd*M|`lqE$HhJxuO#6$P?6Q&wB$pQELRFebzvuRY7G^`?n|7t$ zYjXXI$H2&1W>mB(cleLFqk~*oM3c$yCez;yW*smq2){Ud4YirGekz-vfruv(S*~Ck z6P36%akhY!^+ny zbZf$A(;2fA*)X_WLg>*XYGgTsT3pre_9n2p5VSlX;v6%y8Ny+z-7{+9ku!pV3LHE8 zU-OSSYy)#q}IL{v@S2~{w{`WVt)y!lo>G|iNde2K*J zi%|O#f%OH>(wCyMh~RSZY?r|wqOHC|0ukM-C>31`Bnx3=cPLpnTG;X&$2aQ1=ZV>e zed%_OV|b=wvrUz=(R7MpUWJT)VnTJuxM2ntY;}O&^!M-Ith4Lmv0Y_i#auNlnH<5e zq#v+WfqEvk2a}07#`7eM z&51+IkU&OR;GPwd+^Tj)&D$wy1DlxEC?TzCm^Ah;MSb6iNlSgDW#HYcFfAerKIitB z<%HyPc5FmG%djAuAv{@Oj(i%O$tXrG+V(Lvl;ff)1_NT7aG+Evf29(ljAB4lF}&+3 z_+cG|gBhw@IgF$&&vJnqf1&i*B#p5a-g>GT6}rY`6&EkQH6L{%G%`ng%k|CG{DM9n zYlXh6uCDr1GCl6_+TPw{Y8pK})@;tIyn47rO%8AILg&J?->F>=_t7xb(vjfR6Gz|| zp+vq|!OApzaw)RRFc_?`a8s39sF@JNWHO0PHWxa%msCsuwhYqybTiDp!h&XPrgK?W zlWdz4I`wU1GjD8cv}Mton^97UGR^t)2ODz&yZ!!Sxz^)3b7YYu?!G}ev2ANd{j^lq=-WK3mfpippCeERmJdSMEm0(M zsjwfrHi-sTKDVa=O8kvpme2A*9k(bCNfZR71r8kSdD6x&pRiq>SH0l8(~Sk+;@FP6 zWCqi!LbtIgDBv1K?wITJ_sZzFvW)j0^KLKH`G>2hnz4ZIHIR-3XDIucr+05dR3 zKc3$Fn%cEmwEj~^M72{LZ8LV^jKJ^uY>1#!$q1*N^#{KSL5wz(_S+nu)BQK&j|nB)<2mp=%E$ zzJq4F0d$n-ONje!Ow=8%cGS)!f!=vrgTVj6-dlKO(Z12bC|%Os4bt5p-JR0i-3^k` z-QC?%lG5GXA=2^C%{RQibJqDMJ{K%?7R=0Z$8}$|_x8L_)!!W-Avt#MK3Se@vUc3x zM{%58wXMeE-2F;N{$eh8a>LUqBMsB}_Vi`HIh)rJ;Ky{@PbL^WYj@wPsUm-97~@H~ zSPLtyh{#Lq4czPb#K+DKF`2PY|1zE5e1*fxlS0HD*wUO5P_Dw8y0nDGsx1Fx*h zf>6mXgLP|ub+orwMoL=x${URLF5|)YZOOgk)awz`?%~_#+{8gES`sw7y>9pSKm@@~ z?XMBNK&lrwaY+NR4@ALEG|;ua_>UymstsolvzU*YN=uV}N0U(2X$WUo5nfG_NlU;9 zlvj@Hv|DfZcLOggZGO)s=C*5rL>@6U1UiyKB|e%tSzSGL-RBdt;R9`=UcZLd!y!3*EjJ zK~@;@YKLcR!pqTu;r(noL`;hZ&9)7Cv~XjaKDCI97+}teNpK)|t?Kh^`U<;;&Ht&= zPa&A1MhNTuj(<1Z-IB#;MM6CGIKwYZip(2~1VfN_l$jFelt$YzIyhv)&e7Vl- ziBdX`LX{F9p^3)9Cue;b_|3bVSTxUy3WxoD*RSpv7&}}Jc`9Je|Na_mb$0{*z~>6^ zz1WohG~Q)Tbm#lb1IYwTuw^8MKfdQVKMJE)QSe0gl*U!Dfg#Jm#LKB zop$f%&t;_4hD;)f3)BM8h$$PV%MF3W-}kWnP9#6SIzZjR)`k~Q7hpZ|QGV9+-?uIK z)d5Q}5VeXeTKDPe=TIgdG6({@uid&MOWqYqh#6xqQuSNjuuWf&>4>PtvYdz&LA$q~ zBrVu*Ri{Ck13^RZdE0bbW6gyG-p%OlWO4*V0fKi{Ow^xD2A<5b0aSs5XZG@jy3M&d zz8#+1lsn19zvfwk%Hl;Wsp_T#%$DSo64KGk2%d#vCCjzX=@g+q1DXRcagb#Sw8NC{ zH|n;^DVZz5KPy5*xYTgcRgDD{MMj4(q%coYQMu!$Kroq@84Zu0s@mc5p#N^Y+2S*k zE#2e-8!M91q?9{WCFC-O%0k!6qyLtJ9|#tBFd5S3%$pph?m)|EamRq!-Ry6-V z6tL)!BL+QucKH8b(WXmF8aiK_E}wjcL?+tePL>)?N)bmM*t^?a@yNe9s{SZUFRr9* zy&d%Erc*qz($C)P?o_pnvEAL>aws+GG4bGg6%pDRH)mgeB|@}B zQ5+IVi6osyXlsJ6v?@HxA5he11XbA-^4p6_x)mi`si2%GHDhnX1lL82$nJ1%trxhp zkad3^JF1nyYZiWHWipR72K(8MDqxyytudMwrK@VpzG_RC+t8fdv>x_&DEC4opr!~( zKYS|y-{ZYWf#p9?J<*iPsPT?aD_!=ISQwZEAFZc%QT-Yv5@wXng|Y`IbWsIk2K@=x zup9d4fb24CeV|N-FVvGm{ z4)#UD{rq1(P@$kUO6D9HaSR~(yj*|hHe^kD!}vcpCRA^7O5xN)v4ia&e*QeTlQldC zq>D0jI+pB!1rjCA%J|XXh2P=c?|=Dhyt#`>bf3YYoOaYm=6`*~7U`S8@EzEt$4DX+ z5Y|1WCKV2K1a%O@(N~}O%o5#vUz2aKj3ln(c{)nweb93x}dJa3&@5><=T^-j7Yan;*rz2n~0JhLsbpf-HE8wlnYX{Z~ZC7cP0 zCGIcJ-r)Gan!;8RQ+wxci0SHa5L^}FX`txkMzLeBy)W$6ReXz8$;n8^zd9taSs_gT6-iD37V>@k|GkK+I2^=X#-;}k-b3{ zHa9w)c#)Zk0Zb7=%*{b@9Z1LnEqHIe-{Ck!6jxV63XUSBua(tw{+Cv7=I>%YT@SIc z0z(jPYiJ6JYxDhqknoa#!p1B3WWv~fL2K(WqwC|9-d`$>m(-x4cc}6i=%F~*KWgiq zQv~Bb1@CtxK8X5LPETzJeJ+Jca$Mp=9sE!}RYBj%f}zP5cSF}DdioLq)-~onfuNfE zTV&(W!z)iyPSqg_LNwk`TpBY3cK_(wYx=3%9&l*2WI7I_iy5Bf@S}6%zdS8<3;5mU zc25Br4herRytNj4qJ@Qp5ALNp28JY*Ng`m|5u@~YEydCYWXr0XNaI~`E7m8zV({|l zx~8M1j7+yWB199&$QlGf@188B8bBm`(0Lm51{` zerynSfdR1I5BaM4`ueCNBgEfbqLm9q=SH;(J1^v)(}=U65wyt2cBxoM{YV>53(Iz> zpGTV;3q8Qu7vWNfJW{8=bV#V*z)%*A8)>e4F#qUA(rU3Iuy#AmD{o7Ow(D$CxYx02 zca9xII|Z658M|K+4i7EFqp*LJL}p_@Ma0F$3B0nRd8Ir7P554AEqIPyfG~@OJ?Bmb zhu6*Frk!vuvD8;#SrikoSOf9Feey8V?L>an7Ij7AY6FE8PMhJ^Kyhz^AE5@H5qISZ zYbB+ncY#VyUQtCvT^{l5?ygt6RCd++@8q~N>_8P2@4xqy%*^rZ){Ba2Y9T{=M~P#+ zKF?<@WvX9USXlbsDR#JksgHv(=#+t|n^aAR7!CktMc(W1tT z>74d@EjB_fEKOQ+W@eQ7zPA`a6}hOm7=@5<^X!0M1%SSGT=v4-thdGkDq_gkhp_|{ zAw=N+?~N`>=MJS{#|%aTB5^P|zeG!mR5wKcZATIncXbT+&bX&dqEP(?Gm#YZUNQiS zCr}IgyMK_Vt1~`6Q~25 zhP@CjrORI`s66=y^*nYUfz<(!@^Pf(L9ECs{$V+d%MP`MI*LIPtL0wx-0jv;!1)@l!=0>Gl zHHv^Y#dfV(Nl#DS$A{0clTBiB$KmqjdMYt9Q^vu8=|cc5K0dy47Bl|>^&|i`&_P`* zd%|X1dn9xnV|l$AEI5NSEuA|yoCeEi+UEDhWhxK@pP7q4zY1uI+}Px{3zJC-I>$NVc*+v80U9Z~@}jz&ib`Ry%j z>AT;j{D+6f{{j`Y3upK!4o*%L6_vR{ZHyHg4q$9i=rG{{9w@=^YEi1cx@hPI3?y#!exA6PCnHMQr;)!ff21 zsmci)(g&K&HY=cuJyOzEi*+uj(vk#pjF5ALn!(0)9dtF-Ix5c81ztB~Jc0>9pygdo z5=NOw^KRFM{(D2PhQ2QbP()Ce-`+N0Xx;wY(9rNRGP2Kr>lN1>7$AV}!O7WqrNI;) z_|FFk2QV_uaL&Dt|4sGp=!J#ee=llPyieTf8mwU9B$hfHCIUgm;*rFQ(Eut%v(1Tu zR->jUKYz!gO}couT>m}e=HWp;_IraHy_lNX-_%qU$61KG;9v+nAJBjR@4N>ku*n|) z4irrQXasO(5u86iPWf-{>m_7z+JEdgfKCjc4&xc8f`^CS1H>6AyCRpAk>w>I>I4QP z7HSw*ln=Kbki;7J6;rx}mo?nG8tG z$EHser8$I-XNXpyIHWXKrzX#be-C!qg9)g$f<)SAiJyOT4(F=-sqlz1wayQ95)syF zZ~XQ8K-|G#ru4GWyw%%BLxcaZ?09#1i_-S6U=!r>T|~q7K^J!ot3z%fz)>`nVQ=De zKgE1Bl$-k3*q|=nLnHTrEmJ&5#PSU+6TVq+cz)( z%tTH>aT~ty*Bjuw5wBKv$0<>-(FtEWGwTWXR8&%OcS86r-yQ|N{*LA=-HfG)8Rcw) zF~RIW_d#i5BJ50{_uAO{YwUneyX~G9$6<%M zH?tXZ+!uRw6-IQu@{gxLB9?fP`iD0WBtB}m#&uR>^i6)h!*V2)NqyipjljdD6n14 zr19X(n$#N_J-~qj6V)h!cpaZ2P?K}kf~~Z}emIV#sI(LfkgC+XJuO=}R;gLf&B>YU z`2ijt6@^Z_0`Ir*Zl;`5>VkIucjbL^0LC8e3(mJcYW_b*gL&JStvHL*yUlVaVqes$ zo-sQ`7D8e{qrDm)4)Y|NdV0(1;gm7;sgtH&>eKfx+rFym&B%!s|9W_lu50ZBrI4(O zYMKH^Q?OAdpgj(D{6TUKJVMc~y1++2-CfS8IcXXR2yLxTWpVey6sowe43ZPr9K1r@ zQWP{n0H2ISIuzvbivFYF3vDtvO)>iEr3| z!VIe#aybsO1)EZlX>dUbx`{qoNXS0Ej*DP-*GUUs*Jdc8_cLfGs_V{-LH4z)(wF=f zE>YN)XJG8YlGA%`2}yw4`b&3scTCkc=HXJ{~oHB0J;C{We$kqtFFz}u#Ao|?(>z(cyO@U`sN zznGutkCbmR1_CBnP`dOy67z{FIb?fVQC$BP-9N|2iLYLz)s=VgrW}EP|F557xaR-u z1#o4DwFF5bvOerZ_c5o^k!oj-%F29J?hx&Z5_H;gV5Nv`m?@b&{jKNv*)lsOTV7Ze z>$X{;v<}P8w4FV>T%=Daw*pZ-J(Pg)`cMMb&gari96EdoNe~lwRqpRgOtwA6Ra387 z3ulqP1=`<)phOXN8%VB4e&VmLO`t;vUE(oAS1C9pw%B1zh%gw-7U5nhb(}h$d!NI9 zNyTz>I5E3wDwHgL4{az)7ySox{tTl>c{dlxcBJ8is|)zQnW~?yOf|b!(!gbn|I}n} zbqB5H=9&aqQZ_ZPT6;Dr_~*}8JOn(OHl6rSD)jh4qFhF5@ewVSKAlQ$*y zR>G(W8lc2QD`n+7eo3#qGA>oJ#zAIIqXB0SDqU1r)%+%B4Y?M%7_aRd$K*_IXjWfd zPYm;#+X{CzG`b;cYKo@kOT2?Zg1yEX(O6HYV1WIpbd0VcD0GzPZ=7bY+%Z}NURhNl zY&haU@Gis2*+)S=vN7IEf!L6M_V7t_r(?%qO*8eG+uAv0?{XPTG8Vsgo#YyLh1OJU zs)xw6vl4&>7B~{KEpvIO1u(Z%eI^K4Tcr|XcRR<<{+0#2QEoklIk$W*ctBZz2Lrwbq;XgURG;-t}5FnWTN9om~HSK`><%m9`r|*%J`r{q$+q z2|xe+qktalH?-4SU%YL-Nbnj(DZvl+>4~`HBR#!3kaoL^*v9DOWpZpwh-*Wb;{U)Q zM3Uc6QH+uq7q#i|1Sx+~LA%mn2HD=d60ord7oc9b-!R6o2k?Rp*l@Vts~|&k|L+lF zRNgCU4IvBMxr&>r5CQ7ddx7&S+8*DNq_Lac@{=umQFF=dhru} zIo}+-i`Gx{_7I$IX50g)kcR3E`(B*B2p!sy0^aTP1xq*fzN1s1wWXiWRzsu zaMd_15SJY8W9ImONbv#~T7|{99Ub>)qQe^ZjEDqb0k3PZuQKqSJNqF;6jUx#;ShhO zKE`-QqPU~{Cp-7p^7jsu@0KTyQt(9mA>g$plBd|rhTWMo5xwmfU1Qqzosi$O{vq2v z_kmluKH2e>(mhi%GLSQyg3Sm41?^u(fnC!F;b4?7y|K!PojJE{w985F`yg5>GWz;) zTo1^-gp$HJ7ky$bec+BjKH}l9Fcvu1Y5l!;vBo=!hWZqLN{I-U5hlYJ3KLO&cz}Xd z^09@!y>CvqXSUC&S~ovrW!vJyX&QgRMV;(pzHtpVi~}g_F+edo2Jo^<-8cI#>l3}I z`F}vMkL&=%7g{iIaK}wB*MOUv)>q&tq?N7kM*{LETl?_?^q0f>}MTqi|y&=Cx?c8`YfajHpgx{KVB{aKx+A6`h0AL|ia|=x(8+c6e%PZ*l9b zNg1|*SiQu(s;>y7IV!M*E4d^I{3L@Wy{TQX;g`PVqEHfi6* zYnK`g{(Rou?ava-w?_uH3d9aX1F7?`CopaJrMeayTD=wU0P=EdWaghJj%e6~f;q@4 zqKqxS>@P>*!f0)VeIK&_Zlva#UEIm(xXg&X0Iux$**}=i#2=H|OS7_}?v`q2sBpxI zU(Fp*<NtNjI_!daPdco9f0B4y`` z{B=nOI4}wLDWJkE-aCM=88k{;%XCojIG*F~3k?4+Y4~f4n##XVV~jxJ=FZI-;**>` z=Qm`C{O%dc8-!TAsmoRn(3d3U%nVKI?pS7o1$n#jcY*NOLCrLnA~dN^R=F3*=MqWU zD56>=|HjJw)3yeL9$-rK;hpOVtch_f0l`V1T1_xW{a}569DYIr&N{O8HEFvEA-H2_ z>WZoSR+8F22MWNtkkPPpicrI|LEI}xu6f-|npsokPJM_!I<)D$ysoq`LqAl8O-T); z+&`K;dNs(LTpsp?%VG}?%h>H!gulUhF$?h&e0KFKmqDHk`9Dueob0`PfJKPS1^ZRq zqra&tYv?P#R?QH`qpen|*Py6EK4N$z3QdVjfGuJ24PWV#BY%VWegc23(uUR`Buyhu zy2BsKsqdPaKgae-=}1@ocg8e&kD+T0^6wjFiWE$D4`t~#t*#b3`}Hokx*?g`*A7y> zAa-;S1ChFw~T_cTAD)j0Uo^stl5CasAVuboDmUv$GMu z8EJ*;Pa+AxZM7zLck4?Z*9z5y7kCh5aB6|gV#KbWCbUn!Z z)64Zh{t-Ta?zzl{rnrbC_ju8%LVL8#j=mHqYZP|I0Qs<$1#jL3;~D7YuzKp=iFksqYn_8*?0^^WyFyWv^R>W-Lrto(^6y&E_WkCVw(-4_A@B&NXn zsgN#*#y-W!`CJZidWiG&Y@KtH_{`Iqe^@{$~$bcLv0G2oz zsAN8v$80!_%WM&)%7*3s*c$QADCN{MTH&rs3`j2mSx#Z~8%ZE>0QN5cteek&>W`XD zr6TJMmLN%S=2mi!=OJMQt+ZyV0|^KO+VEx(dLaBbt%z@wtdc{^?%E9cqfZ=itA^Ql zam!lNRP3wsC)MRD8(}Nk_dS>zo8C{ZcpN>mb-y^~z~=7m8|=5>SG}Ylt8jH0@F)DX z9YOTJOu|$t6lHSzM*da~n(xp<`e@c_w`U}zeR=rbYC$R|#{kxXu|p>Mfu#ENy`Lg?DM(?q zaLN6>E|0Ed;?uRp8sttG>VGCsLNY_pX&m4D9 zjR}y_%|uD3ji>d~^g~hkUV!FG6{z@lV)9yLyR-DOU1;V)`6uCJ^i4LZ;nN9L`1^wP zDMB86^ni|J=5*N&z?5&hk zp4a%zy`Gl`KOKirOHzkb`pR{@fnN3t;mgs^K2nE(QzU;J*RMU?!%f+0MFw4mJIBqJW2qu3sGuy=8AGQXIRk!<+l>fop$tU!LfBtfN() zA+^(|v+9TU5*N4Dh2atdt-kurO^r1iV#t>+Tea0L2Iq$Ag}5KD}R*i5B7S;mCW(GVrDATEM8kv)O(7CJkFE5nMRue&FymsP07&G z*$vS-U#B*FoQ4I3YMYvHlBFp=rvV31Co!bZp$Iyz8@r=^VY)fvJi!**_h#>9HAZGq zp&<2m8tO|U*+rD^#`{Po6n+9)q%BIM&Urubg%rDM!@a*8dE7v;f~b7QZ+^9|q@De- znrC59k%*GdZyFS7(-L!zk3^l1!ZRY4qEb=a6){d&$vg;Q;%8-LO9Sf=__E*1Wu(FE zQA(+0Z#m{ zmkB(}%QQYC>Qqw<^~zNm#+J?3e_41jSX2ot_h(EwB3!Kyk~s)<_dX8{4KB#>HEl#P zOOm=9qt*VS%^S9PYzRlPq7XL40)E?ZzI585C?ssr>k?P(K}P`JEnS^+SbDAc_-8T_ z{NH9T4GxfAb%bP4?0zl>NGvFD_OH>-teHSf^3QoM5A*V5dw!~48}up{aiFi;i3+be zG1nf;D#fOQ)90}IU?`tf=ZIXIRn16QhgjW?LuAvqCTn+Sd-5o!ZP0gQ4$=OLU=(Hs zwI5E{5W_S$uPX1gA4`s&d5_eDplYz~O^z+#o$2g2l1DUkkqwUd_!ct`hzqovD(kgk?PDx`ePx ztU`TP`{Y%VW?-^PjfQrkCvCIE@188xH!X>g;sC6JrmbfkFq7CQfX8DV5$6ayQ9fGS zO+{O6#+M3IID8Hh6?h5$VSI&&$WRguccxl`ulz>{^)|M5|Kfb>LSspnW;Fa1r>c1a z$B`-;2b)nfD&<^^UiAgCu-+H-H@V@=2+%xx&W@)hXQ-5&G>)r(*q~E3vhl2=bkCW3xv63g3qlhCmBU+GhN4sSo41{6`Ks6C@4MG2 z$Zy3eDAZ%~?Z53)MgXGu){7RwgICD+{MsoqW(zgVn~eo5Y8Z2c|DsoF496MVUxbj~ z>GK`OmeG6m3s>(pl$;u4ors8^sjbyf&=5pseX;(n5QR~r+gb0X^ZjJ{W}DcXw1O#2Iv?fMa`|^ij}#+h5v}ABTUJ1w z-rA^oO2RQN9J)XiLJZQuTZ~nqYv!9yTJTi)ZlKKPHoA#%_AqS^m-0--QY>wCuoei( zq+B^PSFf7P?Z3H191X0ZAsdA|q1JR!MD`{V2hn_Sh2qtVSC`3@v&P$k*4md}IGN9a zhxA!DM_6Stmu=%|Tya{Bf8*lHyBmdXV{WqS4rSJz=qVWjP+^}m8{DMFfJ4qNI#~;@r?`JP88r7wIfRA_z9!ryc-fg z7#91rGH|kha+I2&cFXd1hPphQ`Tdd*W#z2!x7r^MBX5)R-{Lp}Rg}HcZl)&2g-E$l zat~6^_`ppTIKu0YG0YojV&?u8>#3t)p$nQm)p|Tc0Y#qJP#m+ z4rgWfSCM&scso&3IV$4;8hNhM)_&Y)^dE6u(p{=NLkUg{=l*axd%0m@V739>5wXIV zI@gWVs)xffJzItEN`#C8Gnic;;B0oFh3MlqYW|C5h_f0E`$9oLe2q#-I5~gR=v7S+ zB7t;0D@fR5XKniO0OGru7U|r?qk_>z5s3pH5x4~qZLw+5i@C~cWtFarR(MaIIe`xO zTMS#8KDf2cJMZ;=Y1QAQPILa}X%RnU7f{bPdzINZ&Z%6mP-aIERHiDaeV^QL--_0v zUwlP<0?LyRJ3f2mC3y|_VU!6;%O0`;l@wte>fqtbQV>69a0RcFZqBwmC^+M({%wK4 z2`?z@pIzu&_qiF#UQOD##h|1jB!xzodIw57RG8#j7ZY-e8A{Lgg+z zi80j5N_8fR+0&EzPCKxK#fTPP*)u6iA>$QMjTZn#1m?fgH}JbHs>qY9U4yY6r^U2w zXKmUZh0Bl+WvjR5=U%VeckclxWE!B8m%Yl`vP1=cn<-}}hwDx{J{`FWwkZkc1xgOn zi|0SF_7%&+C8yx??hj>jDr%QBx&H0x4XnOKIU96u50)HFS&y_k)w<&%@9CpcdhhhH zz7BZquLo9oG;X-lKnu zQ;_a$b+{5EuQ1J!Nxht0UHYEx3}L=aJnORJ)3)Q=yxNP6zGJ*~7g9y-hJv;_elT{i z|JotV$G};VCM0UTwpe(@US$Ys7NM@#@cCs%pVRNy?|pYY@N}Tr;Z4w%W5H5DHocSQ z@GjHQ^#~6~5fbsLuV_Rl7EvA?jkom|&zmiXln(vWH%0-@G+`H&Y5CsuyTVL9WJlKy z_~JfMiBPKSuUnCvHBZsW9Lw5;#3xe}>VNj}YXvkTFHh zgMmA3e8u!_O!=3GLVo1@V0Gqws-irCP;-zuaS?ZxxYB*gEx?@sztbnlV%d}3mvQ^P z-xuWRp|f&Y1$%J7%J6(1Mx0t_dfkpMHt~c0eIIX2QarSctK&p$bG;#{I{8blF$>Jx zsH#c#K&mTBQRbIArvvp2`XC(SqM;KjNEynYM3p-8?#ZEjyT1i3p3ZAl5t1adqz~^%egZPsO@ry{g-#mw>wBd@dTQqpt=%!ytD-H#3|?eL32sQ%PF|c(=n^lm(!AtRzJw#;=+(He9lEahk> zsBZEeaTl+4G;$3VtYHv3f2z8Fl1Y6_I=~xp5-W+th_o+>WAvnckt2NB7Q;0%XDxdn zY*zP3BLq2{g<{ru&NbSM;f}Be6%23Oe?ZZF(1-rKgQoOqd9uAANmLItZ}irCu=h4) zqV~8(61gT6uAF!K-Hdo0U$`8lcDd}*`ZoqK)~T7rZYX6@U@+!Sq)O**+c&m|P{kx& z)Y9%xD1!gFnIB*}-gmC7q<7a3gM3S(WegUy2~Hk;uTbA^7WExW)+hU?<=vJhNeldN zp!p4ZYmB6E34&vOF^HPJS$F&@#A)P7=r>#mKG8d9spXS59H)p<#k|(sWbnD~9$$aL z=;(A}pHuGcb2-sduky+nO;2qw$V6}1Cj>z&r`w~b)A$Pn?xGbHV3C*ALp--~A( zl@BflE=IT-pzmElhtPy#iSM|PUD=|GN#C)r_4oGazo06c#~;6iPF+RRzg@Z3SZX^c zyV)D(>@3v(SKDevDoS-djxaBo(V6P|y&sx=o;f}bhu9&?pKDGT^ySvYmPGy$l6 z04PbP>j} zFfu7nc?Tn`|B<58`I$ug6h-9NmyhS+yw58;#rIN!S7NrM+zbuHWWP-AWI}}puintr zKU|pl{-Y8O^C}jpC*cvAFfC3V|DJ`)4&_-y12m`Y-{12^!#}WSN^exqtb~kPZ&yN$ zFkw({Fzp7p2K+>`+nzN!ybYbUu=V1fzf8)&JTb7P2M9?y!;fq ze+~FFOS1X0MA<=uba_Z1Nc^%@+2*`IRAt*0>H>C)-sOA{DZADgS1GvKlTrbe7+Wic zJ4N8$(Nhe4Zu|xvlEfKGT;Lkxn|#Y+H|ZID|J6swYvFnwL)R~GZ)038-Zp>YAdkC> zv=?^+)R9e*EIk0g`#u;m8B!wy13v@0f7q;M2hNIhng7-s0L>^lI_twCn06d8|3y5W7Fz&Ll<#XiL-yT zap@|E$u-cv$e6X{M=2}3*j;Mj0KKHvUZBnX632f;;)hvDx3btn8v z#j9ssr<86~XKC_<+Ztx()*e&6G8qv5U@{v|E1VKa(3(kgWj;%-}8| zz|(IHSP6Ya;{5WE<$qQ!#BBNr7j+NHI3>$O)*~W}A!0yHEm%-qrth4}!!S0*QG{i= z>0e6+?+g9;PnAL?aQfSMp2~10Dg(hky`i|?nBccxbzoaSnRr;pXB$KL(*^}_s57I_ z%Z}S%WZ=BzNhB!WSn}qmTaWKmw3ddOkCCy3HZ1wmhv`GuhSvLt{Jm%xU(dWYN48|= z!~jg^uV1Q4CsF-&k%QBoCzA5xq{1sbi@uLhs!lZOq}W)wQR$Y zY5WvNtscel;ulGVa@cWDSF(=cJpzGE%Nu%$Ls;MOO?6)xX(HDa zbTva+|Jz4k&begLt>|mR2)_P#*oP4epZ+z`+FW0x3iY2uSh#Ed)Z+{_ne|rs=;;=M z;U5`)bN9W27uzG{mleUir53(^wSQ+FZy;;*Q8$KB!q~QRHNUHQemYB-><7)kwsqbM zX@qCL-rH`x@l(Og@$Rue(HPC)KrX7Wt?V&Ij+6p{mz+wRi2<_4xCp|7$%8jxV^yn@ z;yU>g5E-ZMXyR0?;a%U-bqZPfcVlxj3BEET_mcD{)|ztQ(7w)(fA zu{P%st0=NJB6p;wJejtH$UTZ?#`diC6wWwQ*9v&2nei+yJj+dmhu22;YdNkXHq5nV zrq@}0=XyU3f1f+2oOOGn#r5a(5_?}D6G7oWqT|WtCH1@$D>TqRm;djOm)V+dw{2}1kUP*kzYxGg=;la36RXl<- zjKux zx|8z8d=xVa#+s!bC(LtYIjm@$0ekiv)kbr(krdhFvVh6R58Mhqs9l8v{M<6RO{-CD zlY)YTx(WrqjzZyITu4eMb4D$t220^nXMxig|D`ylU{d%7&)zM(1T_9{F8~k6N9s2# zw;%hXNQ~e*{`u|T2}QIh>!~1{7)IEi(yIqT2pRbDN^4DHg!u?Tru+U&kOCJE4gX6V z16OaD07LhGe(t{r>LUX$`w_YWSAz-vs}cg2gptrbQnbL;{ILJUC;$KL{?FKPLBk;~ z_${7;>dW~=Bb|2X`=a&qbWcj#0IovZiq&Hp#2Gqw4R3|m7LuNv!9>3F8p^RGHK^fQ z7-dDQL5%Y@5%|DqUbfB+Fkl{OLlWeo`QmHMuD)T=Lw{z=tkyA0<=I-tGSg@q|9o>c zwvRj}=zP^B)7Ss$>kG6GzVb>d%|67f3p}JmeQW_X_+HgE^?kiksadP;2IOt_;PvIB z$0I)y!mQDkP;N~|=J(%{H8Ce6&cKu^d}Q5&8w-a|8B)?6>a)F-W1%2d&`h+0`o+p| ze?`bDDZvh|@aK_{G%5@Y#<@i6Q4c~kHso(8nrD2*0&N_4Y(0is6%jC33*@iht)`#5_7)c72* z%o7s!zMi7x!e}HO@S*uqbDgaD5ob#U(|N~@WR$2`>Qmj7p4|eb>piwCTL^TBe0#K1 zAdk$&n-K8_gu2$^x#B(EH002ks=)~b0v{&ndK{F?>AtO3Y)ainM0Gp{tM%C?Wd!q% z5H3REbc4~4@txpn<_I1sDR8Sy6tqYBq}Vw%u$SA)U8G`M=5+laEl6#+SY@$WE=Ll@ z5gy1c*P&b%K$`e$igqzCB|74l^WHP@!@+tZwgwKBr2^);&x4<(25eaRMB}y#bCW(n z`{cDCobyDrAE(#n$q_A=7d?qmkde{x$}n3ri(}Y7SS4yhc?AK0Qw&GdWB$|p zTt%O12?|Fd8GLmbDe9hzUpG$Y4A8_aAq zNsEX4j;eOc;_+HVt;S3z)N3#ow|m=IEMzuzPApQY-RAZyzLJpzIX;hUf{vMZqbK;m zSP!$)PhHKb%`h|`hiqa!xxCIv{{)OaS#4?|F=`y&*eF3jwEWotCOM{3f#=AX`zi|D z(Crk|U}Ybm6%;D!8zu)K#1;ErQ^CrdMH<`0VB`JraJd`k;*|hR^C7MN^lN7@-{(@h0-#AK>BDGd@pgv(Q9^wq&P$nwFj== zpn?MRmuX)fBRrnTs4ivJo*$hslS9=?`4ny+v08{?9=X?$*GuT!C9I^f+6t8&HOP5< zooAxAT18IBL?sb9Ur~KY;HEslp*8U}h-7q?r)m_pw_mJMp%O*nn0LVWD5_9QygFX~ z*%K~s`u(TS1Mn&Y+{4gBY>klYDGq5?13mZV<6(pI;X{$Ct$65WI#%4*9LG27 zTq8s8oomCG?QjZ;ee_G(XWoVhJz!EA*Miks^ zn{WajMWdGmQ648&T1>d;*uns2Y@Ti8&=!|8_Bo9Tx>vQ0WU)rvUDk6NujIFwkhv0? zz0Juppd2V?sJa+)n+3~_TryZ}z}L**AnRKgWu%T~ZPCfvP|qy8Ttq3(=(@f|k7BFQ zWPLqCprMb&j|H@aApO5X5ybt`lfeh5e$jU^$!!ud=>PmCNrIgK8;Svlhh7+d7Xm0) z9P?}|5Fn?^)ehOe-mDgGA>v~2RKxYfv#|3t1(S!PSOq#T{CDCWLcfrtkBSrRe<5m` z@XP2R$>0+Ldbut(iRE&T&xQki@SZ^W}xG`1k*Cbx8AocV<8PCGl1}H}MiBdtD5T#Pm*`fif z>Aa<~=?aQpRv22*!SdC@)d}A|W1uELgz+{88bbOj4iY7Rp!=$~L|D4=WQjd)F7g{? zJt(s?T44xaIru>+Uc0tW&D|$ITaLn;54F}PVX(md^A)^)R8c3fKP+qPlXlNebd6%d zS*=qcaDIhFB||J)MkE(K4nHj_rI{_F=q)?C%PFa=+C~~_ea$E9vKwY6_6$7x7km<>6}a4NfJu~3Hd8{yJ~|wLO$J^Nm3E`s$nlMuu23m*lPll-f8}TJ zW8F`8s;$c!E5d$i@Q-SCd_Nq*&UQ&%ibQcFqgfKH08ew{iJw_{t5yW);c4|DvC05f z<$ay!dYxx`j(zkF5O=>Ua_!f2LHNlWhiPl1SQUT6#TsN3rHN1~m{Br<_V>Lrb4rC1 z>5myInlDV}f3SdHyKThamG7(LDZ7--=)Byj7iV>HRcb?VqVOV#THOt3AU^Mfrkad8BEgUy>jA=_uK71Cxs6KaP}Enc(X; zv1by^=?R7hAiRc|pN{6jjQ=Zi)2XpPE^D2d$kZp=QpoFcO~((B8%P#}I0o575t$f! zL}1tob_FkM08ax}FeJO(Ri)Ol_Db*DSI#%ZXIH6eC`8KaioOYiYl$JynCFp^7GnO; zf3xL~(c945vd^%1?q0WJK(-&<c8d$kL*%OcdE1+CF zb+3CfAhUa)X~#$;y&WHqG1E%P&L2@{_A4V=);;I3eE9ruk~$#z-9>gP;y7O&7K>*F z9O#w-16C9VI$MKRcaWTz97GI`ND`s7k+>T=Yh$5gENi%4jUPZK>-Un&@1uRrQ+<}( z?Td|t_{-I~65_v)!C-($|7nrHq0X#3=!|004K3u0Tmus;Z^Y4xQjs{0cWpdSiU=`n zPT?wdc%ZhvQQEmw+B__*7+E?>ki!hhz&ghj(uyiSz@ybs{ID!LfyxVijnDj`EAM$% zZzIacTP?mpur>g2C0nY+ii-htH-0gtvfA3xyu9sUKz)fA8OGtLIMwNcplX>4|4X5I zZtk^7NH5ERyx{W~-Y{)hUS31^t{FytT~t9&@nX=+6PDm{ojw=7c(HQ0p5jhcIP=GO zw}dX1pEuYC%mnaZ6g5w{uhPURm<2Wl$4@OvOjZ@RJk9-E0M1oFTQ+#^==8?%AYa7e z?p!Q^oIL1L(@5~B#ot#z#{*DL1zPD?YFc;UcK7zYTlY82*B0qNgm32;7u|dmm1mgD z_$VkS0EDx8rC9BfF%$N3vn}3~;5%QV`IOm5S`C-2>;R833+unp=s<%T`P{MmY1-y zI*zYxZRG+mKA4dq-|}wn2QTjDW@kV50PK|?S}ujL0w3Q-hX8~JxP=Tu8$`CeY!z*m zfFrE(M~mYck&$zJqARr4!J!rRzsUjW#uUr%O0r`OtY&%DYgm=%wh;x$ZalCH3n+GW zlyqZB3-e4&Bs)t6DWjg6jwQmCYY!bMH#G<}C2qnw;v zt4?)l&e+%(KrbO&%=5YhEv7_GOH0PdnM$wSynWp?37MLd)Ia5S#|mi9&e7XqO$z?k zw_=~n?y=%f7k?WP{WrN;|H<3{V8s6JR$?y3Og<8L%tzF?=fd+J>+_Z$?`kNN$Xyx3vy*mrgeMDqBDG7bo>wM=u|!i>`(x#mm3?+C_bbk%H}nP2RnVP4OLON& zF#IaeAiT6D$L0IOsslRkP`qf?ut-iupeCDVd~TL|1Dg%zN z9|soq$H>tS$N@NvgM$X_d0E+56Y0=w*rWL6$vL4LDgaqGW*o~ePK|E#cl*!F;nRs! zRLuF^9^n@pt9AlkE!^4vkEyeYs-uarFo6Ie1b2tv?ry=|-QC?Cg1fuByE_DT3-0dj z4%7K(&06z#U+%r#Rozu}&fed~&>TFq1~h71K-eyjh!_Km5su3mAK<%f#(TVCr^k%^ zXLmWZv*&+A0+b-rkbyjIzrh}vV;f!>_2XB&YsnL_OyBzbyu~@%W<94APMo(_=4Ndm z7<%?Ew=kTcn|8AW6mTPHyFyn}f8SqRBBfecuX`VQAa^E7L|TJ#l1LkSX)!8EeK^0u zhA}e`_YD^OVePLN2BRT(sVk>Tkyf-hly0#qL4736-ompbe7KIi+xLz@p;W#CGII&W z7;cND_uFp|vu9k39PCnRP)S-{~$L`wR&R2%r8FYn0V#I{Q(;Ql+c6dv}^gJS=c;NMGjOI36Eaw7rG-VwW} z{$zH9p)JO+gTn@!o|&%6og%m1)PsBZV1KWZx7{zn)}l-$Z>vf5V<4xJJ!B`+0(VIDN(SKGyfXTcqTbo6R$g)pU@$>{RX$ybD6L43i%yRmT?cS&$eM6i_w%? ziIwYj!-WtBPM)JAR@6F6-W_EQ-3O(`S)f}%%k`}W zlNs9WXT;47Ta;wF-9!HB7YByKP}5gd+^})G*V{d2^oz_9a$^gL6Dza3>&>rd2(_ z$fMVM&+EeJpLh!GoL154Kg~yj3T?0U1;~Eem)u$v-j$0zA34O)L>j3s;uG?`& zkTrG}8kfn;)l-8g@vyL`2?4lkfEGTx=6U0U*6t60Tti8-@X>!T7>y#M(r7N7CWEA> zrTq(We!9*6lvGnwt8NlH@&Hz?03IQdnmsVT`l#E+*Wb~{)*#(y}Rxrpg+1pD@QBS;{$t9dVQaAHXOdX5Aoy?Ip zA_oE%sYn~PU=>@(pqOm_tkZoNll=$M{WKw3bAzts?ZmwA;XYs0&lEcYTQraq{lhjc zt?Pqvl976(|I@7NNSw#P<7w07!ERrN^|mj$X{IE~u8JF$4nNzJk61 z@nS!bIeI>gw*BT_yL0{Uu~BI9uHVnUCcpoFt8Jc^RTrJ*_qACId+UWc zYkyi~E02bi7BTyI7WNBimSgl+SQtOG@82VQ%TT`jFS5B!8c>U@x#{dsZW=TbW8rjE zsv!g2X$MNJPa@;}6_7EqB0`M|mCA%8_U%mpD!lz45bh@UrlGR4Cm?F%`DO?{s<}7gWy=J72nB0jv zzsu&fpB)0zx9l8Tg8y%z*>fMxT$zuZqXt0J?ZYE;)0TQ`53eaUlf_s`LXsy&xV{Yz z#hq{J%9eK7v3mWod~E0~4g2Y^?$o)SKsaa4%-g+>`hx=paEIy*grRV|duC6GC@Z7# zzHP(30@#|fgLIog>t=_oKVLq8IeYj2X}X`VQayO_tq!Dp&KHJDCnD3{*DTZ@lTFIG zBdzg!a4PX>?+A_AEZAP6nZIe4LqBt%W1vuw5##v8as4(r@pK4(foyh(J1!rW*c|7pr_o-@I-2K(u z)zvACjzJv6)!RGCaK-K7h==p$3TpQD!WcQIHT1a>fq<#XGpffmkR}S6Nu|3H`SCU( z$uzLJwGi2BXvVGW`Y4ZWLAl~rQgmzOAe^5pjQDzDQzMKv3(#okiz`7jU6Z@Q0n(>? z71N&K>VuU%UC8G&pb-%+M$FG|b?E^G$@2MlMxybWn>SP|0vyRAQBFk>!i4`E{vbA; z_o#t^ft{=Pxl{1|SKzr*-?qID6$-#V2d5u+6~tR%Jx;JO7u{>L&CQz|JFcUVKfEE! z`sqHjmIc>scn{>TnzGl#*1c}YHIu$u!(lS(cKJPhdIbwCYKb`LBk>$g6!4@1sgg-g zfe9Abq!R@KP9wH&KCekf-GT~>XT=rd5kp!N9@#HsOUeU&3@}sF8(uBMDLW!`&tpSR z-ifK-oQRqTpt!tV4dpSV=Jt66_F{rXTl1{!M?#)iF zgoiG&0OlXN!(rhMvHz!=to7E2YajgU9BW+nwloCQGciEW700Nk3c9)m1AC2<7>YEo z(#CZI2dC0#ideO7uUw5@UR#r-LqB`i&8Qq7mjoJ+J!#Aqsz6d;_se`Wp!xzJx(ELt zy14%!x(8o4rR*p8-eAld{2_CX_r9m?loHEQ=1PTIEyfqNlz*EmnJF5|1?}*qGlIo4 zg3%Vo(S~JqCAYYeKe*b49>qCP5{p4K_(|C#sqB|J?#g&9&#bQW^TqI#9&CyGEA*J$ z4n%%EE+@V^iKdny9rmWT$FfvS2`KSL%Q-jm*Y_FI^C6;K>1J-e&fl5}f!LW#&d>LI zs}D-zY!N1lG%=hX>377OaM)Y1Dmug*9BBZ<6)Cdl6|o|ZEFmG`?id6@R!3*54~{oN zIDh-Rc@uc{vfW9yI~Ia@7NNKWB8h1b+Hrd?ASiG)HmqZ8VHK}2(066tS`5PkvKL-l z)Z|L6U>gD{$*fFs9Sv}dV0cb6eUTRQ7|lf^S>jL)xDZ<)hSH`jqPzR3^5Q$COd(C2 z7VPJsFmOmyl~QMn9qT&DaVw7-Ss3s~ZTTi? zHWz_BcvM>L2*CEe740p!@cT>WdO%*9+K)_-M_{k9urgY;`_om73@B>SZNb{kdnL&cNkM zH?aXPl_hKO#~!@I>OtYu96;)RQyHpDZ~PI$K!@MvH7p*&lp}W^9BzwNw6mkXX3csn zC#Qwo)r(y5!fJzDXgju^Oy2;#i9khyw0&TVtag!P6b6KcfP;ers1|#B`^i?GRy{%+ zYHg9mAEJm9m0vV0G5bg-bbIkFlGkdDnJ|H1lCS=Jj$R#T3P;}mNLt^!R{XI`9&+i2 z=xt@OjU5;a?Eenyc*h{=2qLt*r{~aCv@x=Itr~lnYQSUoO+k@_69;>OG~vh>}+YnbzEajB#=y7 zO(A?R76%mU$iCMthPPqSz~Qplg8T+1Gz}D^_^vaL`#p*{Qc#?Ee2l*}CdFvUy$IvE z&*uO%bnuLl$c|?A>HfWskLP2Fwf`<}}@zYG&$8B+$Z8px=-3>W5p&!V{K6 zD~!42{|K$;a$9aR1g}UJGnhJYyrx3P?Cxwka|Na^P0$rZNE5Dn+XzB*t{OA4d7rV! z4GKVzL}yL-LXU7%h`(_FL2=U@(_WkepHpi>XWeTGK`N{-3vO0QOXzb-;;iJZUW`cx zW4toZSKEvn(=K7$KYnc}7(D#>#=W4D^#QUM^AkZ?k?dDIXGjHEC1WmSPEJD*Rv_Vi zPmCWg(JtJ`S6CmoQJm4Yo?UMqlXk+7l@Pr zJK7OyVvgwkq}Fa3TzWAWtlwHt^5+_=L|M^3A#BiY_ z^#_B*S1qTx7s>uX69zqLqO`*7no4P5`Se;+qL|--V~7!gf+ewgsFQaT%PcNG!MjXB zcwDVd)v_+D^rp)~=uAWCn!Fo>D5w!2CSrdZF`I|bVZXP}=kSsG&AU}Tv>wJda!@H0 z>0)!Y&C)ryKvK0!fTct?>U<=J^{Ct)5U029o>!|bGbDm*ap9z$IlEW|TERlrSmVeT zjqJpg9&@#K`np&DD5`qyKDvEwE5Ob5K>`rpatQ3^Uml~(U*({-sNR#-tc*k;{*E=e zRlMziT)dEghJ_v3E!v-_m`aY6_Lql~bMqa}kUw%VoW}%+7Z;hml6uxwBE0rnw3sU* zmkkTXRUkS|P{akn5lWNhK2a{+EvuZ_|0d?a%^&mi3v1a1GNY7pnJ$e6f=|idQij{! zAdHI!=q&p|Zay@6xBJ8>pb^^CZx(&Va)sflf*9N1wFyU#3g}Wd%n=2sE1F=;JWwlr z4Bae5Uo8mP{!a_=ja>~bcD=1roWEQycE6l}U~PQkX&c>%LXv|SsFM0p&o8}C%$Y>+ z7c1p1Hh2rMDC%0;A!5e*OB3cUmZ;-ZQ}k__k(lp1IrEL`$n!64|6wwgY=3x>kbq)k z(Z{4~({jtHh>%r4D%wS5hgDRBx3=tO(-&;N(Y2zkTaEl^VJ(iC4c)oTjkNJOEjYW+ z3+OAMKHv08v_&T>)fl|`R%sX=yw&6X1e5^(9@9l)1smNM-eF*fOnLx`-5{k}>^bY5 zv5yNJ=-z$A<6U$8OUY>09~`962hL6I&&zghT$b+-8kYO}lG!|+V<&<6heQMfxe}*V zLmK@jFat*-Yh$V!i*I<}g8M48JJXhyR1bio8L`v%_jp&Oa~0%0S&;5c8M=xbQFW@? z(jO7c*o?t)@HR`mzX=FiO(kJd>8)I9%a?T2rjguq@b?z1>F84@%iJ z-EDz!?zFHj@R3A%EA^RMaW0&9`mL;%IY8}_qhT>KU7~Mi0B2OoV)5_Q%PG#7N_CO< zOdjn11f{a<>tnYInG21E@V$bJ6PeB15g`ShTY*69?z#*4=RPfGQuiY+3LN)bZB{gI z(Mc#N2kv7yZHe2%a@4tIV_2K?!H`3xhKK;^SW|-~kT=x%2r!qMtqYH5WjGT&ZfI$_| z9VMfa5dk%Rjc;1_Ti}h(d)UjOHleIi;O6Fv%X;Fh3{%x-San9KCOapL*N$Zs=C;ZF zVtMItFZm#5hIE$KE)1&64X0NXY<7GVjs%MSYPie4KnB~f$VEDnNw{Z<3+*hAgLXqF zIhR1HF)Hnj6cf(pPkc>PK{(a!yy=cd37#d@$coxnZV#SW$N_psd)Q#{MR^(xbguXB zMJB5=VwHN-_!iRKJq3=&ujF-bHmi-2#MnF>qMD#Y-0oTy8elv&L*z6>gu()+gDVBK z<6`EYH5D^?+z)@mLyS!%iY*ATLU-oA?i6i|5W0hff!0;PD)nQ&ie(6Z9SBR_bh)-Q^=&WE`sGSqSY-6TI6N3w6_#(AhQC|*=jy#FbC zooE$?NCZQ)7E275H9WwKk+XqOe}k){-q?w(S<4Q5Ar+^&c_K5sF+M9WB%F4T%UUzo zer&E3Js9>})*6=;_Wh49&ZtzraBgJexoj047kXmXZj7OoCs$bW?wUPCK0@OBO0Fgp zL@Z}3HWI+?%DjGO>nWq~e0>B46`*3RH--;48QH?#UTj3N(!#CUal>R~a$cXUiS-Yl zHPdc3!AwnN;!HMrjG=P5xv3UOTj_L}DOfxgh@FqITKC5%2fOtKJnUn!G$cjiZEmw; z&#%tT^E*Ey)KnGDt-tKJ3(3nmAlAB>l+za}>y)IqB$9G&hui zBqSF@WcPR-DE_tPgJQ3sA`i+a2BUFCxZn##B_Vpck%-9evPzvAPZ5|hz1vnN^S@&4 z97@+FE!k8Ky>KJqA+qSd`15CS{2o>*`1}Y!z;yW#i0sx_JdW)A_NH<`ftQeqj$1}~d4E~K4WF5neg^j-c8BJO;! z$<`R3c~sn8Rk)nT{ejt;(~-e0pM>H&ENz_E4=aAq6=|A@BS_c)X}=@_O6WQc>EGtq z^m%>zQXscWwRFywzzFY+(7D+M+n-jbWkQazJJd|1&gOF6l$CFlm2)JlR(GPlHZD4o zMYkQ3v>X?~?$0!D!ZvSi*u84l5T6{Rb1wMIN-b4)cNsjxu)@1dFyo9>w^ub*4mE$S zSk!8{84cZ7RQ0gfXb0aA5|uLlHe*V$x2Kay`EB=P%c5-u4w7s8BwE6bX5Yu^Su+r6 zs5u$k-HZz*+cCf8RA(qe3~eKE90z3%X4NB6e*V(B2TQwyc=6XLRnbuMSpt`X>svMm zq)t~lR+e{2@}TGVXB5xlM$QA~9gIJU8eXR4|kSnh=8~1EOdQO#&sG zR!UVmP;4sNly`oRy_m8U^l$2zAg5^m@FYG8BTC@Xd z9_`7tkTV+u^($KoRXC0aSU-V=_*00&W&&;!3UDA%WO^a9xs#hY%J1FIub2@r%8w?I z2_cbzZl_dVIWJa{FeFaF$g;G0t%G;L972+gE>IRAWUL;bouAEEIj6ItjX0Rx>o_J<^+L)oc|NxROW zU&21x4klB=w^MkU&IRd8i5_$m|EfKUjj_rC%S)b<*&%ofyn!`vmauYlSx3|}sOzsc z8;9j@@#D!eQzc4>p|KEv5+H)}!k8L3lsbTy*gma*H|%i00mp6+3iTz08BoP9@CGyV zTm!vVM3j9B6~Ij!m_s`JvYx}2LMqkErN?g(ZA=RB1O0b?Q<8A<=^B`7iyxfI89(#E zAs5i66l1@kBvNQ^2+9~zx$)l4d~1S+;6_nV2CY25GzZ#tib_h`*RE|H7x{*o0|Rs9 z%ECe}Qo0nhHaJDoIER8qIso7?85x;9WKm6K*%!xhT}cc+$C{QC7FJ)l2u4C4Rp9OZ z?EL^s5y_Z-&UI&HuYT=P&<`bDwN`s<+|EBVOp=Hh5qVjrl60B_Qf1vasal@VVAM7) zACjJ_=$?I~$M}ZZ%OiHXsvwSNhw!dnCo3_lue0{e6JB#niKFZd~<~ZFJP} zrwxnP`AvIR%W<+@=raP(2Sv7yQRJ59s|RdC;upGxc)Q@k6BFK744}Kx*49Q-p_I$> z*-fQW1->j}a1F8H^WOdR_j0FgMC;b4yrAIgQ5MkhqK(UQ9QcN{?btClvaYQq6G^~; z*)Eq`m>U&eQM)Geni()EYA8trFm|WZqWiqwerduSl$cryGmB3HykICB)~XW6O|M5M zo&9x5Wn^)z4iV7htnX+8PB@V5#RKT6Nf-kOlAHqZR3!s9{D+Y-eZJi-Pns5bL%f%| z53gXZ*L}q;#5Yev8c%oIr!5wX`vBz5C)GI$>a2_RGZiz~A5F(IHX5IZ4+?!p zg9TWcvQuB_wgq1tKu#lo)~k-uSgdT4%0dLIZ3E+K%x7{yX@$UGX|S%}T)I`1bcA{QU0CkEJDt}!2XEKO#!BtVju8USaoTW^tV zoqhzU-~PZn(>l}u;ne=$K4zPr@B$i>{5cm#l5}~}Fovmtc~K^L>cecC@2?{bU};Fs zj$W%Y$ZAU>;lT7tra_sLQ&K{qQ5oWlc%|^~!5{R20vH+Gz&f?z)CE~%08onTGLzRp zAzQ&C&nhi_l8uBjLQ7Mfjo~5*xU0fZTaenc7Bz#;*ave3WL%3)RgOo;Vz>$D=@BeQ z9d@A5aOuD4z)uRq5yj$~{N$eO&s0#bZTPyRy42qW3aJs6fn9NLTeHx&v-Ta(xQt`r z3>Eh2*I*7v@arfaKHh%R8I89c{e%EIb386zug1v_7_td)*b#@0BX8n-3xAINy^x)6T(Chnm|UVz?k1+=Lu>?FTD0a zw;#z9cG^j`sWo-z*2vaLutVr5h~Dmv>acwV5CiJ~4Fjx_QYizvNR^cf^q^6m8wVx*e>$w7XLG*Dvne-;B3kg3>*P zQPSQ2d8+OMN>#wtAzc+UD&gA}N9$7`1frHSWt-Vua zuvW>^18#!I*`X8YTIA{rc2f9VLMQ+l8{ zn2?mjz|5Ul(>Jp_RBgxRx|gU@Vs47h%$)f7+WkpLLQ*ueI#VUZ%naoCG8hd0!0xn_ z2Fi;6V7KH{RQ<;F*Fbxo-t}q=*CzPiYYG5&so6dAdp6|+!?C=MRv_yl&BH%fI;_BI}R{JxQ&#Im?AbSx#}WO)^TZT>nGpdX0|9w_j9%w)v_T z!MXJck0aphcKgOPgf6}PT$Lgm95|79)_ZdUIMU1d`u5j%Z{Yr2J$nLT2**>w67|X? z09opMzA^ytE5$@bkD3gi-n0MdaUG$AZ{^0a>G zJp>R`|FKb1Q&SJuJ`BLL6cv#Hs4utot?#tYznjQCD&pnDGyer==DJUG4y*?=tn6fZ z2$pc!)w{zLZLY=5M1i>cVGhaKBM>7VwTF9F0V8om0j!}f!O|Omo%*M-Bn3 z^$T@1?&!hfDJsKR3T2u7m@&_4hRjV1Cpy}U*QYUo%9mFh>Z6|wBMsT!zr9|Li=S`C zXrtdUQqxBFkx1x%a%Tq2{(#!HO{rT^cSc3NnlF;-7ZSNG7Qy91LJ2H5_DN3ObU&rm z_Bd}Oato}O=J6FIbF3{Fl?KyAFndYuE9Ha@Sbn;{VqXY-si|3ZoA z_>RPG_)xoT*=dvbUdG{6nQVTXwE0}Un`j$MXSQ-N-Hp>MKErhm*1sw|T@)rG%|g~| zEN>*cnpTFE9Z0@efjtZrEw`Asej(?i1 z{v5qC*w7K!pSq1_?Ka+v#JlOcn@tHLY{`7&d8G|a{>V5k##!pHRAq?Qu`cC{{~)v3 zY>(+@d}5`cpwI`%!WF8upv|jZ1#2a<*Zpwb)5q#@V++-XvQEr_z`LQWiUj>TjvEyT zNsv%Ds@|CZQg>D z_Yp4>a>|nB_Obg@E8-S$QvCB3-9Z~#q4QkAKD-lKY@ke4cq&t@2+Hrd`g%8btmQgU zq@_V)v)xeryUOkIs8`E5`*2mYCl6^X3i{XmqPuR|pAt8dGD0Hcro5SY{>#rOmJp&3h`VFP`I2>oHBd>0_LB-8A z?iSSS4i_eMchow2T|=Gu!1q}9#B;4`@osYF7rVGOjiH|_7&?9_) z2vI#o>lWCc=p??1VKl|02N&i-pPB5hur8fR|D>KCk;u>#x4tlOg_P{=eC5Xc>odZ= zd}5F_>xe3;^=n~!Qz|iAU^Uy&t1t~=w`&NcG*+}|dmO!-aHhLXjy-eb}!d#QAe_my~L+j!5G*wboxl)gvc1Vw`*~ ziHfxOnR5-^?FEX8n>!N-$jrXHWV#FDMscCGR#2sy+pC2 zQ6HRoD4?;wUl3qow*UdwsZ3__+1e)8jzmO6wo}}Xwu@RWwRXG0%9ZK^zF?3*V!1J} zviAe|l|LY2jWGUID?)*V-S%p$htNq-MCA5!x&88qZ1l}{H#_w_ecjR>&KDU!V7lB< zQC5!azDBqD@EFhuZ5)prlPA@`S-P;Cp(x5KRv;ham=SydB?!1{6KlF0fBO*q4FV-3 z0OGY#t)fj;Uq9t(V(jwJK6N{O3+yrI?Ds^4qp(Alsb1K=cSnf+k*1H^_&sw(>~c}8 z2-lLKckS*tv5x#BbL!xL6HAP{N6Rds&gOY)H(adq&2xvPd#YO}(e0z^3zPRN9FkQ?en|OZP^=_3yd{2dSIRMLMNe-9OXW2(LADh`1T}|@`P3tsTVwkkb~=iSzs|< zw&0rCV2LQ!Wc&c};R&QPUbF9oxvOaio`-{Hw>1?Mv=}G$2vEH*hogaxWOcrWG%PSY#$){W$*2WFSwfGP%5= z=^|)h>>liC`RU|d^JPnObe^yuK4NEFWvfJ8Ea2g<5<&YN0WP#TauN zoKMtHm1n~3&*->`vH_~~21J>DcwtA3-mA$_HkWZowP0A3WD#PgE1wn{SrdH*xM6{0znAr49a>e;{&k&}&U@7{vqWL^4uXDVm;1XlKx=O6R zp02@Vvi|fVzMsHa%7 zRS5`Y*9o?Gyp5jtnR{5<`kC_sjm( zMl!fm;1Lpr>&A1$q$BorKV?GF0@5aVz{DkQFXLF>u?sz`Db=cmgr@lIRcT$M`YZSa%iP#{?G6v%{(2Ge65jrgV66$wPy!I&u zPNT+uKR@d#3pbBy#}qFqCzxV%=T6AM#557f2~tNKA*&WVp%?mvL4+fHY7%*+F59PKAae{!=vtbS-VKsivUK#9Z+hKT5c z1=q`)>fbiu9F4MEihtYP`u#)5Rx8w;Qq|y=^u_4BPrPal@mxfR&fbN`>^~k0PF$|l zzA~37zB9Ul)t3A3q#YSf>;7vNPYImH=o+*t3=MTvrIGgO@;xOVK6_>PgZN++N@E{m z3ecRu);q{n{Zkm&-_Q5G)Lp&emHzD&6O!?i#3YD|zvGj+p5mEdO1S7nGvK#+O8fMF z1q*7Zb)@l(G)ay;96&%ga{6U}f-=d#FR=5?hLB)B*oAn2XcnF|5RycO=x@A)9v#?n zO)$Dv%$pXkT+9whv9Pc0F9w4-3noP?0~sUg(@{U&FOFi4p|E^6h789$CTy!0MBRUb zS6b3vN>1J0ChJ|_3&T@mo739#4)K_I^2Mem3^?q>vgy|ze6kgCs!Rr80}E-uy;2f` z#4GRF9^zr8YxdX>fnZVMa#f{`oQ>vz?i!?PwfKn%X zsSSOl9ro?qtnXMD{8Kxdf?O=%@Y$d4(4}Xl4TJV00?T zxwhLriniN5b5xt;OGe_KPT#P*Fbu)P<6!ofT7D1w;27{RxVrl%x zV8TXjg4K9mOxlKfYum!YglXs60j+{d&v#VoD)TBRb3%x4@re_a)Q8e6^3S%=kEq%A z^YZ-Lt!}!>+zXqky2lQH1FWXXV1RCbdo`xB zJ{5)F?wBPeW|XycxAZhs3CGeyA<3cTx&tc3;c{!I)GWg7O@4#&hCpM==qASzlVxK- z$;L}GbS(qW2{W>^Q~s?o)4cDkpA#mURXjY{68&#B+j@M zG;7X+*GN3=gTZ(=%|U*epztG}KmgBM$}TiCZ}JeSG(Mk4pziJ|km@}5i^QdaHr_^( zkCQ5@Nsz&O37Ny=;Og?QdY_coH(w=g1>J7pJ%X8f>-uo$dXI%Nf2uWed*YVlwD)Xp zf+N^N1r=%Y(S0xw&!$))b(+TD$ZBI?{Ft!SX26=?eQl(2SvkpvC92$*y7%wxrDl3- zHETikxdxiNC3^o8BZ-}H^)0)_ACwKS=cLtwy43T^Vq$ zSIdP*29b*GZ4SHLHhQ8hYv!$43k%GLP3lz<*@;t{Bl#YrjPJ`ML`N$jxtp*bmu11K z)?x$;KYfu$F?_NHm6p3ZuU+0Q06vl*+fVs#ygL`l|r%T2W;+%&NWeWY2wHh z453Y4E;i&X83V47MxIZbV^0*@Lf#!ao_}Nnte_$XzwQXYN*Q?0EuzZh2O;5MghE}l zKgh1Z;}dl-(vz%z)J2o>3@8k42v4UHKHEFd*DogS!oAGQQ9u+FI-D*kGU!M1w6*V0 z=C9$2XRN!Qbu-MnQqwWbN}knX?>6b*B-A#`0a;65+F>8?u=2$0 z18|9Jk-u&?3lr&fD|wR^7CL>E$`up}uD^Wk*U*r{XR3RK-uk8p$6TmXvJEAu=0uIU zD63wGm<2BsK2VvodeUZ<5Z;`0@{24M5^egWtv3`wvk!VoYjp7ysV2UP54AmMy z0v4K<^^5nZq5l4xZq^OHB%&X60n7M=V~9ZZ5Rl7EXSqa2|5R$bg7oiaiX+Q}dGRXA zj3;00K_^00qrYM$!-(k$E`D>Y6YZCVY+R2~DprJn=8KaLj z3ztE5vg)t?+C;Xk7%U+0tu8j9`SsGHQ|tPbRx@R`!=%sr3)}^Y3)K=PKV|ph@Y8 zBMTA~`TKrGy}aDS{Y3RmXC>w5jC~@WehoevYZG=b}6&b=j3 zwRW0NXtZ{*@>(+`&1BSKo znuo=cUICm=(f*6O#(ibP6`FTo9xj((@qqv}pSN8+Lu2EnF_y2}g0tLjfL1p94F4-1 z$aD#1%E+0Yp!Lfb)yGPyU*Kt14~7qz2`1Xj^$j>1j+ZFC)ow62UY(o-abLz1sl&x# z{+m1~5AVVgZa+p^+BVaod6#-?TEO)t!~0*(R{f_|$Huf7Nad?*6-GG@n89unG`V-$ zg5c#}#Z>}VhvSA4Z;BgjN0y-NCJY|QS$usYv$OUAMcj>1m19LW*#i=#vY6NDZTht7 zJuXW0hN>*|F8k4EMbN1;dPlN$s9uAw&>K6eR;{iA9x=wOxpU)(@hIun78a?UTV6@Z zzwG5PA#aLMhYl88p7R@0*m-SwXDq&}IM>@5k28docjXOsrn}qxOb;u2GJIC3pwSc) z%SW0>5^8IUkWoI!899A+{NNJj#!^eu5EQj1ky~Loc)!Dy!2CHJ7RfIX(@R}VZ{b0B zEk^EoyScl2Uv3w8PoSpNmoZRJUT26bR<8}cX zav~JgDCCr*+&9R4dF$}B-f@2ze7t-GXFiLpY911E}6VFs@qVl0A4wHQ)>LXXXs0nN8==Ek!XdN{9n zYt0~BpiK>>Jh8j`9FCsl2}5|Mk=nc^BrY6jl=4{4a_GM%lH_<~$jy-n#VwL%P-8|X zCl(hIl@C$W*b0jTqRC5V@lM9kn#43U`vi;jq@I0h?>G^AsIVsUSFbL>fN>aKtrndK zsAaf5RTeQ!MPv=3d6bJ@GNo9pewSlTluBQaJ6sd@oVrYX?pu649Lhkv+^KgvEfMEP z_K?iJdWpt^cj+md00SLEw?MnPcpM8oTSuD>GkG!%7BAZOdRAp7MQC3`1hTy<&!{803vJXUx z{flJ)sc}&Gf?Ex@3w2VQxI!J?{`rpGetUbBW>9K*g=EASg2fo1uwhe>rvdLR&>+81 z?Uuf*(VQQBAtYWt3-)DLu!&)Frw8=y?OaXLwSg8%`xTEf@gdvb>Z%nsKiydXw|`!x zbI`yb{m`u9zL9;==oWeET?Xf#Xk3gwnwaYh?Q&~0-uRkU0dIC)-_2`bk zQO;q6TyrYvFI&+4(Rfzp>BxFLhRYK?gz%*Ws`WXS=&{V~mW>t2=mcDJfo z|I*1s&&D?|)a29SgoLPfrb#R&sa=0Ko~3NhOothiwT~?YQmW?_5gWRgtklRdKD*59 z9S+_+mq^>Pe+x{8`JT3>(=5O7`aQnZ7|+&yVmhoh5Bk=U$KRu4o*OCc)_ukgmyvxU z3mn#=9K$e@n;bXHXr@KtA(rgCj0vOoPkvf|IHAKo_5sg~Nc@J5aiNq|M?;N$@wVv1 zpsGvptHblvZ{v`se=$~=lWyMBJb@*!Xbvi(0jGuNBtLj|b=y#6!)5Qeje&|1snnAG z_Avo|!3cdlN?n22pjHU!Cl=kweNH2fKmpD1REOgf15ZlKz+~bSgNF@eyjL~^la#^B z-Eqh5BqxZb{kjg@so#i|w7u6)C1wb+itAi6Ox)<=zGm8QLp!Xp%hcbx>C@u5IeiSh z`w+tvL%+qNjOg};#OK|BKC~pqR`ZjAwB?UClOZ4`NmbKGNxAv1+ca_y=}xI*f?}3c z!QQfNQCr&Tdu@Ux&E9Jw*Q=@u>mk;$g;H*5_yl#zVG#$1T(5#n z_5W#It2M>cd2mm*vq~tfwhS9VD%t7vmV2WoNgKI5lC?4(&EV1gEXDTonrZJo@W}N2 zh#$0{?u2-;gm8x1Pd^W*7uJzb4ojqQ`ZOrCEIOS2NEtD!y&*WvZS6f&P3vKrqFLDY zmb#AuMb4{n?#Ia>{d2Zl{|vOlVvJJBSvQ5UdkCe?xtuQWCD7jfux3wfH7;uRS@Tw! zM4@>53&Q-H1an+GpLZmx2U>4Nq7Z`xP!W-ds=O}PDxa!S@`AU?4W4JW)VOX@ zJ(o{Avslj+;(X+uK^_NHL^ZrgK#b=)*3-~ip{xDJ$iB>aFIpot79?EuX$^Jdq?6kk z>!8{(<4bkK9QAIcO_yo@c%K2oDRD*Ic*u39>rLE6#0jeJBpqV*-az@CZr={S_FLC) zE2GGX2^D4c!h3KN_ND^w{$=<0zkl~Fxia{_sauG@iV(#~vtw&?Uf0`W6w{G>-u zPHF>)ciBoVUf2%(-}b-M+_yWlcs&w#INVMU;oaPx122MA=Q9T_-1dc0E_F$#`_t#g zPZ~X`t7_4VGGPPo!i?d4n*t2Lnw)@r1j3GZd263^W} z?`a3esobuPY|?Rr^je&(r4d1p5=r?u{S zCJZ|9_pGn=!*eJrd1O)=J0@ZyB=oypmir+N(`U){ZeGm6Us+x{HD78vzk~ODcmAo@ zsGiE>#Q{VMlX4RAf&BLymkILiVS#~8{e=;ECM|BC_CB&kZr;?J))l(@lN=9P_>u0Z ze5J6$&f!}Tceec0=Za1)X$p#wmBcg&3U}#(vrHRq$71&0+5-VlNG*WJQQBG54w*sgEnb ztp)Yy>H^MwHteW)oH+ddzGYigaqd;Mtutw0wR~AtHF{-dG! zj^bEAp4+~`X*r#-j5*$1=5wyg%nEgG8wk{WfqB4r1u|vb&KnoeeP&Q1*dKf2aVF(Z z&V~!y8bT;(IMBie?Cbgv0yftpIRNNfw|dFIANFPXjCdRYS-EBTwc*h>b>)i+1C_#b zBTjR*rhHV>li_S!EPmy@%nCs)wNU4sKFQ*DMRti~8e;Sxh^Poobpq@pS_}-waj~Hk zSkYZ|_>LfQ|41IwQKP3`6|3h&CN^N~#F5B~E$nwcv5emyDqFA&ef$BA6s{1XpBaZ^Kh)h4OefDvUvenkK*m;?s zKP48uU9>kKUS{qeq^}`=NfznwK5V-~n;xXa>w9ZqjKbQVg&pCOwH|--5kn~X z{7OTTr6X*kLWP1<=~z^K9FjfQ5^|62p>@TD*VJj!pjkfkuW2GCbNtgW@T1QX7R<@m3*V^Y*rk(g(cYr8R`GS|mFBcQ}oP>RG zv2MGSo@ENN=maP;h7u0^Njlp_Vo^DSRF9C&NXUC7JNhI&Q$j`4 z5i-|!MpAjFneGN}-qd>qegI=K5wJn;7-rd7tq6GeK{Dmro5mjaa<|Sko*qSn3N~KY zr{1lYVqYn~D$(NlysjIa0K-oO?q~Zm^uKuwCKe|s9}rmZ4SKgO|I5`AN>gI%lJ?4Q z7ACDn8TVP~gY542n)2R>Dgi`BkW4CsLplUJlaYAb`yy`JL&??h&Yj6`ss>1_V}a<{w4hoxro_B3xH-3kGp!X`|>Eb z9c7^cuPV#Izft*~PbiT&dAW<38(vodv)TW9!x+NlM6#uG?~Wa>u;U{)@P#Iz)btvI z<2Yywop{7gJLX#b|6zBk?s01^{iQj`2O1qa5C-4#nNZU3rwspRfwJ&dB z!b_r^&t)J)go2dkr}+$NitJ zU-;HTJ|oEZ5PmSV9$@Oxom3+^x3P;@7nikmt(iVYz%khw8zUQd2mvjMfIZAxeO@V% z2mTbVB0L6;o+=C3L>rP%i2kolneC zg0ST@D{LwHR!++8mO9yS;^8kCZdmpZ(dRzBU8(*(pKd2GKM4p5#?&?8|5W|=crw3H zVr~(D4LZjB9m9(9e*J`Sk%aL* zMMEhB2?m&{^cr(@=w#wM5(A;HzwHPl^Vg*owor{~BA(^%{;ju$-%D>VCQ#Fc`=kSL zo5Dq$O0z3jFh$z4#)x@x$?T#nWgD}#>ijA$V9z{|m6qhubC&23FsyP!6UTB5&Ndxn zC@JKV4RBScsi-2V)|rMWI6Pe=b%Yi(hwnFjvn_Uw5vhzMu3ei<;7)pBW>mitv;Dm+;^KYEl04OPWqX<>xROo9P8K%NEbTXf!`SRJ zKX<-}>o3ge(9lQ5#2Y2katJx9WIJo825_8Ux6BFpO3R(1{5HzVv3P-ENCxdkwO_uX z*f%}?b{f5zV2smC`V=(+jP^`94qIf9tfb>v7$j+3fSqezjLjeUAo3f9D`9!b|BN+@-8@`zGQhK-51P56R8C>xE#t#YH^s53HBhN+>Bjs~u<_da{R zhMeT5DEJ{<`K>uzdwIqnk%dZGL^zHHIknAqhCsy3g@lGwqRm4$Zo~@y3erwfe`FcX zkLOh{g-|bUxv0*cz2{CSzr8|K?YB_K=TbNOnK@`8V>+cuYDs0BgGlRLc5r5? zqx5gbF4eX~vpJjRlu|!eMPno(B9z6T@j29LO_vN;?r(v9_D97SDE?zt&iueXpZ+#t zkCoOcX+?Rz%E?=i7{);5`6!IDIcI9>#9K6J&)BX&I0*meRV}IlrXh5_TH#b4(mbcc z>U`AO?-O4N26}>*emtG8X~xz86%HLoV5)Th2KyCtFf`6w)o{vq0TC&q6tPY(1wZkJ z=ad4pI4drCp~-QFy7g;`->#ErRY3}lF)2(FeO-6dB`crgT^aM!FKeC-w+ezyL2XY6 zl{;34Jq*_1mQLz2GfFufMVN_Io!_NpBgDFii>^LWPQz0IxU=O>-Vz)I{`$9kFjj^@1&& zf(G8!ibUm>Yz$_hLGrH^&WVf&~n-hTNUF9ug(4yKmai${?9P=iMQKVTFzG>-ljQzVMTaiuYib|#U%`rLSp$6 z8!(x*cp}15&9Rt=+$7DI-k7RVk7rJiKxW7ncDbQu!AC^SHMluGkE9r$j9<$i6GJM` z%5hV|l!;gw#r9H0$?(t^rYRkDO0V)@4&-mK{(iQBs7WKyJS!3-NeJxZuu0r4O0QqQ zant!^m@idQrf|w-*D2LxvpFH?vfdw3&F9kFG_5V=4%z-FG!F{Zd--q5lsv7UohR5g zxGZ7nSy>bFk0QAXj;f>+B{G$7SUsx zVo1oxkR)|`6X9>3S_~M3zHVXfzM6`Dk@dN*W273 z=6@xSL@aZ^@%sGT#!y!w_K^>qLq6dQ@C@3yedrVW+b2qjA&k9vP#Z|uz^=8%CtB|92i2%o9_Qh zpq-VaHkxjW;qrab2yGRed#U%O+9It@Iz-0gPaL1>aA?Rr>KxLEm61}!q7L`c=ym;( zs*NJWRa&cC1FU&;lU`VjE6>UrSe7#N8e7|~7e`IoSZJ+t)3}}eGc8Ug^^_^2-O%F? z#MQ&%=6UnoU@YrD186#b>s_=7NGh?m;-*g(UAZ2O7r+xyPPezf&qHDrsaUt}rPn|y zfYFQe&F9FSra-{W>TNEX7nCF}mZoC6_rg9NmOPD~Ec+q+Z5qMKx{ax>=?8!9Q7ETjmf(D~+*y1m zbP8PfqiIJK{(PBKo)+(**R-iE<=j_d!d}Ov)kl-&A~UH>&ZHzNpt?j8p>gK%RgIS= z{i$i+^6#w)u0XO;md6$b4AHcpXj4+11o9j}>;=Q661 zhx48L**_J#=@wO6W#v_zrI!%Bxk7j!4%S;LkZ^GO=RWUT@x+(2&Dqzl`*@jit|Q?Q zcVs%t%ga(}jJpMcL=cr9sd)(hi*guj_E^BH8 zS`5z}I_G-nG%+OvO&F=zha2{Kize+DDix@*Hl~-A455?2gBLGu`#q^9%q@Tthy~Cx z5cSJzxeO4!wf|Hx{HM;8r1LvAWZk^}98fhQ)$$V&Wf5X5!=0)ClizT zbroNI$+BMHK)`R#YhK|4?}$HkW?yp!zkd0gOs6veXu-z;r@juK$Npgz+da?}csbZN z&Zq6ZL+VVn`x<+|Kmrif41Jbsvixy!239pFoF%q0!U3z-JwqGO)Q&?CS)XmN9 zFMyqKJb-6J5|C4ZQJ!5=p{e;@CYo&2=6ERJ?#>Bt8vv~RY^4U}-v+e_SSTWkL<-xQIt zBY-&)k2+x!)4N{*Z++ic^w~NN_*?x1e(!U|su4hwQa*?3+0R9lEbrDkk(N?c2V=Q> z&BWo+tg!T_sFSsj9qqbj-{s=Z5FEWnO!2(a+~E9}+6At~v9f<5-sRut`wQ{7?o2}X zef_&kxrcbhY9f8#5i2YVLfq&B@KHDGtw^%a;^@ z-n53;W{1w(OKVs^`L9)xx<#vIq2(f)XQwlTM~vN&XOPWhHlBCRkt$qZTS{xTR=@On zMt>AtW}i{at;tBSj>26zTtSS}x3iu3Ml3t56IH3;1vZ`L`13{2-t;p3GO7?|4YV5R z7XkRd*Bde}z!%;7GsV{oJv1HK)N#D(*1xf2%0Gqc9}G_=MhXqVVA`_m;y^u{9eI-& z_8ruIpl7J~^t%_bTc=bp@AxVoxA*FKb;z#dg8%d0N@~$6{QZ>d2TTmTvy*UR< zWIQ@Alf9fIhEAsT7J9ks_6hbB9gc0tOKZl+3nl;6KY1h*IoxA15G2Ta+V~Y(bNW(q z;epvMwFPI!@Mp!y_Q%!jO||(~O8WD1NOuZU0~5bXSpn?fb~iWo)^#X9qpgzPTqKT&7ndbPJ^{(j(ZW_*Gu89 zvXe0RGQE=;$pb6rrn=5PAH`#Zw?V9w7^3&payR`x>;>tczrW6QnFJ^ykLg=aOd@+k z5<9iWOL{mB*e|uV0743BBk-ezy1Ej?@wt$L3vI_3|8W1bkR-hZV}`o!IM;UApH|&` z;IM7c^*iQjZGRn{Lu7R&s;k82`4}BuS@r8}j3asOFYYBQhWMC{&^%+mZvFkIXc}Dr z$Pmwmk5HJXeo{w8^!EwnjS%zC=cMW$6vNd2;(8^d(g51$t3kqjrc5m(-^&6~;~SaW?#H zT|4QB@p+XS_(ebjiCk&m(#+$S3QEE;UjZ8M0!ZVsuMO@eBRAjvhy{E#FYIjHW@u z)r^p-+sJ9v|AmAU=MA26c24sV<(uTO+&pDCWwPl$DAeN~vub8)4yuvbxx?~eRZiI$@M1QjFuL?k{FC#Z6?*QhYT=~xFVZjqyX zI#>{aXQE(;O%#oqJrh-K{Tk*%sw1|%(E4&RxmP!;y?ZHHP1M((iWj}&Sekq6`7K8F z5l^K!<6{1#h`S}E@sVo^v3f^GrE6^Sskp%|cMf83eekAxd^g)mIoVn}i0lvyx9f@p z7jq$C@KmR!pv&#Jjj3g?OvO$Ag#PB1N>$O_wKqo`r1fef#m#ZdHnNBCZsAOl&4`f6 z#?UhomgDHlS;vdENN^&yn+HwFe=b(#T;~%J9Ai$%N)yo%!e*!o={fzma6rgi?e^BJ z_GqDB_O;|nz@qE&%X(TEHO-tEbc14SScDKLOR24^3r*r`(mP`G+cLK}Z*2_0dw+uz z~AP$zT3{ zuZ=2|DM%vQQ^jtsQhY~jQoD4MI>X>3LvrPM5(FetaHcJuRj+pcT?NSg*#bcn2_u3g z9DdnXz8YLwaDJ@?1Ye^UBFSlq+F5;)g5-ZEIc%L~8BSLl&^*%L_ZT;-X(>;Z?Z(s+hc)MbkxTcIi{{T-ByTDlQMjBhMvDn*1AE-3%>8 z$)^M`;4?yVquB4_3Iu1Uw74PlNax~}D=5z;1-Gn~;$u7s9VrTi0H7+Zf!X1wxlFDp zYLi&Z4acx~PmYSsR-D%fY;Fa@Foa4i_d-@go=-ULI8&;^LG_VSu|zi2B;)lFPOUnM zo%x@F$SVdxFeGctiB{La>L$HHhi5h)-BElctdvjn_VpzMG|Rh}H0bhatg^{EN#bhP zvAG+bh$4YIg^CF=zpC+G0?f8Q3ZmDHAIQM>9!gF8>1FW2S65t8In+IOLuIlfal>psZ_mXIu$F4u@JJGv7Gmc^!r$fVuZv>q{zXob7j z*4#E<_SU#sT+J~JaJv)l!@mUHc0`9U$aw#BgLSE13#j6VpeL2qwXEoWYTaph8ZwW+ zV=-t{j}EDedjFGdw$5>#v`7xi6>{Asn%|7;;Yq-A_IJUOYLpLaNVy(W@9@#3YP_Kl zM>K;FgT)dmlgHuV{(A8$A>Q|ry!sOs(L`HzZ4oH<+Y5Wvnz7LhM>A7LYkfQIn9N3n zLyL|4DCX#k+l{uJB)Lci>G{Qs@ARILfFV%|lv0j@yLnzq-`3`LJLJ5B%jC{)M3fNaDhgC#c`4ZXN zZ3vO?Py}F zU<}pJg=7^sHQZ$(1lCP; zcu#&5Za=~W0dS(Bb(24cj3s=IO`c zbP25$t%+>xQ{R5UnOv4RA4ek&dv8pcg3>lwHT$n$rJH0gDGXZGia?NE8^^n=P)c=ggHr@ z6I;Tm@6kAn=K&*=l>~%1`23-T5~`b;dIch;iN2}{58z}+`33pd@MpW%3I9+|ma#%4 zW&1t(K_w;DRRFx1zNHG!58oVt30AyT>$D9uQpO4e#bw}t*UAYwA|qwx(mI!fjo9$# z#hP7_K%B;VzM?e!bsxSSF)YY9#G%b~|6*Ac2jTFnrUg2DXdEnr2f_R2rkQ-Q-xs|! z$%`Y^lEHpDP|p33rD>viZ(R#&f9N2FcQOx%sDps~E3gNRHJs5WS*X6QX%t?~SJ>Se zPx|cn$vigO+rD;s@kaB1mv}7$GN#ic{1vci;f*qH^}G<@)vQ5g_gcuAkLXj{Qdu{_ z(|fnwoVXh)Lk9?I4eW4iPUgNVs@-YfFj0~aD&f(2@9DSCDBbma`|_2BF|I0WLZL{4U(fZJYFO;>&po@1 zopj)*wRs9Hi<5&mxZaeD)6EGo!~ir1>YwZ#S(wls1o)y5sO#&i`C0aP;R(4{vtdY@ zcmPf0iH$+W_+)%1WfX6X2Aq}otZFC8_d$599VQx0#*sjW2$%QzxWgZ`6RZv!-;owp z_6$YhF_@=D(s-z)PAKKo3;l(=`V2v9|C>lyoz4Pz{H5GipK zvK;%pmb-_%gR&jCsh5t5dn?E`9G578EU~~p_4ncYMJB_RfccR=kN?jJ)Ie4pGb>}rly(Y* zVdC0qj}&UQdFRF>^5?quB~|Dby^er(QjV5-huCJ7hYLI&Az;Bo{HI)Wm3C7gEcWol zS4QAm#m>xEHh0ZQmu0?GuHsW!uXeGNC;E*PQ;kV|MkY-?jGCxI%izXQkr0V0PQjpE z4*!{Id(rU;=k_h4nU`RTXG_#*h}z-K6areAypo&k3>irH%*Zhae|S1oMR*yCKB3bP za{t>^bRAt~shQIBVWGi>EUP#lo z2lSg_!X$&xF=(K4Wh)MS5zd{`GcvD;BwCn|CXvbVY!B`igB+l87;Mdr#L_pqT;nV{ zjki;_h~yr1eqq3rmcrfMJxpt?fK0IiztMEAjp_-Y8GCO_E?os|)xdBc1Ck+SXtKML|^LB=h2MjpVRz32?;F;Sz3{;lsIPC$ZUKK-zK+bb5<3 zk|r6P8_FmOQzRo=RnlQSrbu3~7=IOxZr`=UJLn1wMsn0uDXB~yg+@rUq4jX$_li2` zKpD{pG0vNNijZN8r`(vRvzXgy1068cF#o5d(mDA@Q8LFjZ`%>v?iuBUAEi5Q8_xV8 zfbz4j@cj2#W~D~6l5f%=#Po-|MSKAr@3pl91_}HkbdqB62YRFs-NFCXoRQLtGoz&(Fft%p}q#=I_z2%HV+oN#|z(WFC(k_ zzQZYd-97#ehfCs+@TcZ04M(q(L_rWBkg(jlGi!h>OBcHd1eUXpR2~kXo1r{<{a0Hd zCSwP(I;t5$LW&5FXPl?#usuyNBTx*%lu1Yv7=kV{`lgazv~@+H#QH$t@I>zNL=OBc z=euZWW3cUKBou%y5KYT;COJ7Qy)9sF;+OetifE<<`KAWjfuG5=n+ z9Cuk?7EqZV=M@IhehFoReM7~vVRf-dx*f(`Ll3g-rINoA4fOx zz%{NZD}9`=Zg_J-F<^_wG$_>Qh{A=G_OT*Ssis@&1E&fH)B=|;iMV+>>9#Zfs()}w z(+BSu(WqP&YRye(G$OFz%w-zV9B;%LW|^i%VuZNTAe(l|dLBC!SfcEUV|sxyBNfj{ znPYwV%=npvOL>@@Kx$Zp5ZAJ}b6vuzV_1{IEc&0Acdh1bxK%(!kK5UT#W{^l@$>7y z74jtDLi{){GnlX91b+1$!k+oakV)Q1G%>NWMD~>GIfm6{)pV(Ki-woUD;$#mG7@WX z5DiJpVJfFBI9%@PlD<5${(}61374rW@mKv;56_g@IyDvc&VMu@TkK}(BHE=9yN`_( zD{7M3rN=q@1_Dvg;@?bc)zM!Su~nt>(#nZIQIfQhaP7mfmO7gKqCAdDEloYluQEhP z9Oi2ZU?b(=|G5!urS3?y_;!EP(^sc6?)#-9cT!K2kt7n7>Oo6U@$NUmt$2ciG>PR|dx$>UOzfNf_=z`l6>p_zasDN~j=QA=&uj z*}SpRs&ane$=qvRy;A*D|9E2nN2M$jKit>!du-;{I)u9OgfYWAkMn0ye0W6GG)r6F z$po^kvCjRU(8FVN`&k|`k;;>a{1doGNw!T$L?3fpR8Bd`i>0z+d6soXdG`pt%@{7m z=s+tL)K(k0xJuT8a@{+A$eL@s41-XD!$4{Sj3WRCbYxW2k0y8n z1ELY(+uJP)-j_QW?(G@9 z7*b<^W@M|v&a}Y7%6oNp;A*fhmue-?;I5`=G{RsaX9~2&eplk7Gz6MBrmrEs84`3uwFZ0AjfDmS(^KKj4zeTg%(ZA5drXw>AaVQcuoIj=+jEdB-Jy0 zD=#9vo2zy*DSdec6H}@aIJl!rkxW#MIC;SW=>Gh`K~0%88j_MRk+URKBqGP_>(d|H z60>KLdeo{lzIHoRVpJUX;UUY>y|46df+NoZkT*IHZ1$jCpZ;p0@0N~Qkt2mO``7?p5l zx=NzRWVL(+%6=H4Ka`x4+SS^d{Td&ma29;+)o;fz5&M4X)kfmLI}KWoP`9rK9v#JM z(e`f9gBlqb78mEwX1%PZ_sE4-KHt@v&yqHstgrGaBqk*E0p{c~fD?<0Q@6&0KjUo{ zyz~ZrI26hAT`a3Dy?f_Y!EtP#o&?m@Qp*{Vv*jBi$6C|D8Q{^fu9chd8t3=vUTgv6?bo2 zmapd37b;)qq+uFAeXykrjf{jFaJI&XKZt9u$wPRg&A$iGo-v1M?6+V>PGd+!vC^2B zlb8GS(UbML@>3$0Iz8Av?1gS`Z#5YWz}_8(N!AyB)DQJZ;(3QMai-(F-V`4^nSP?8 zquYObJ~sib8Z;hLG~R}{PIDs)#KYs?S2oj%>aFJcHOUI;&1$-7C>}w;c<5%$Ve-}) zZNeO|f_JcS;ScpqPyVEHV(~Y^OBy&Ud%wL>nRdp;uxD6jk}^#>s5ZVfTQ2tkFd|^N z(6v>^Y^G5YFt)dEZZ0Q>0&KRlG_ivtBYAH%0h*KTex}Q|eQRixto|^Zx71xI!}`Wq z?tZ`ZOgx4KCy}Un&~F4LFwk5&s2@A4Xub55nKDey+>b5I7&Q~W1svk%OWSpFPP)|$ z*Lc`^aZbJUgpG6>L!!V%+n~%lPGiJ>Ek%+W8*`=c67N-Lw43z>!+cPR0O$3+XuL^T zx^C=Kim^*h98Bp|S)Gos2Y9iyi!I-NzAVMj`87XMZK`hE$4>LD zPrR$C+=m@#pkWA%qOTIs6wr0}>i}VzuJxYoMf(w;H3S0q8qqIGSJU=?Mxb|wd)YVgHh+^$P zC&H$0<{2VCEXSq<;d05L&}1_P-!<*UJw6?n**h@3^$OgD^qDrO2gBm5P`&;J_)=Bv zXTAH^41^|xi?0t8is`y|;$5)<@|AcQGg88jpGjr2)~H_LpJ+cuI9ZziSgC)4c4*hB zC9yUM?hO6;63JH6Qm9xS!p0+eP&udsGC!=%k$3NaXwx_2l14{jRGVjSuJ3d3cZ{<_ zo7olg2&1iCA()%(S68-l4hMR*wTB-*!HnC86%_C*Hwb?HkE!Zi__|{FH-{k)7|}az z!l{7KoUmDM^HFz$WwijNEAPZ8Sq{y6V)`>KVGDZ81tE4m+*vKzdGXuR`4;9i);3sG zvHdU(w(1?(9ZH4n>vQD^R!!hS4%k9UlS($Cm7!-*z@eZ%diLQjxi>=LZ<~WJ8F+fC z@1KNSOfl|mATVP}n$3rC#B{g?Qxp@UkNR>A@?l>x<25!#U{UaOSikM}cLC&bzz+JO zsY)I>XmpY%<#=!M{8rKzJUqPR_^{YcNc8%oep0^?m+QqDJbQ8oXu1fU+$S=bfv)O0 zL^JhpQwtSQVY=V&;qtbj`hdiVi;H)z;N{vN807m;4V5U5no?#8_pYPmb#y2Cdd__o zwo!O|MhP6z)_uR%5fmT&u8}F4^Z5JlCsa?0_xH=}@Tnp&Q}4I#K;-YGMVCdAr7IXg z1VxD}RMkaQ^g_pc-V&#SpIUhHo{BO`AMUl43L^(MdBr3Xr2dSrQgrOudER|C?dnR& zm`r)A1qm&S-_hTO9Y8AVRAo%hGc6Rg>} zm(a*s3oq9dC}2-EwEcLUmXt6{){FLCUkV!F?Ep}=>zi5Gd)H^ugs^k!)^L77(c7>K zP~D~#u~F>E$^D%;z*Q2Vj*D@7YsK~uE%Gm2Khgtlr4*kuCf5yo`?**(YzQtBhBuhL^nTm!k@a~O+^(QWZUHs@`$#LwLhKf- z>9=qSFW+$o?Jggv(mx{|)Z48&GUX0mT2W=9p&?DA&p)bKP=Z;+{-*r_?^ZU{mo!_H zE0ehboLgV*;+?zls_hh?$8Y67D)mPaDaO)S%7<3&Lr{XHe~!jN?%$>D3&j&BYrY%Y zp`95mR%uadwIu=uc5Th=BsU29fPVT3u{E`*xr?CT1@&r3N4U?AP}9b;uv{al@b5X( zpQLj6x30jBR+m}LN^EaACxZ6;sewjNHVRU(%CF{%M0GULO+BAs)QfRiH-hx;kP~UJ zOdku-XY;UXKo@4NmchQ{8ym2E5i>Q=UpZPZk3%jL)cM55rX?%O;jwy(G(uGROJa>_Bc*wo)?aN1EY;SUW9qDrTh7A5Yby5*?|))x6WGBW}KsO-SKj zCe&62q$B59cgHL-BsckBK@agZ#V86Ae0jbHxk<~h6BE%VWTRI7^eaJ@H*EjU(bKxL zys`w=my(;#{eJV9?J~R#uwQ)dkFv1X>{FvWh)+=eJXqwfH-Di#Q7o@EG3c+did4Mf zJfT$YKsq`#fUR>Z#yy>>&xCY|p88)`ehY`7!Y_pFTWUk|LHZ8w zqfHW=t7bZp7FD?Cg?R<0m(BV0`7fy^-Khwl#h#rGaR-0eDLAI9Q&P%4-8+uUO3_7t zU(B^hd;hh?x~0_b+-baC>lp?MQ)s?!`~)8@?Ow&|*M#@ZeTAx@TLe#3XvuamQU>*U zH?rs#tIGhHt9YIsETXa!$89l$xvH8SUC2ob&tpP0a>jI9=Y{5O36UEMCUufS^|Q5S z2BM+Ie`o>ly29G?P7!07Ph5xYta4Y>*;}66bs7Lzq6jQ@C;&_OU_i|nC$L5G75seo zrOoJg!%jNRjVCPc3hX7^+qner2x$4C1hNqZ?+1P2P+!4kdp4ygtQaP49+@le_4zhu zwoFOx^}^q5sL~D7ensc@ukCSf%Y#Q}Yl*Ipf1^;(g00kcRR86U5Z3J;Ff$NgChojt z(-nUss?cNDlk5tz`_vt4#*o8dL+samMtmk~CkAd1qocf}xJBAcoGDFYP~vWlRe0Og zBwD!%Ishk~qu5(OrEX73Mt>1OR%1EMa5AFtyX9pn({8iJq3>JbM22iYEuz~M5j3X3 z_v}}T?YC<|sJ(aa(mIS|qI3O)&kvW}PE3W*XTRF^U?bIGo~StSEW~T$9Q%Bt%kKXm z9qNdVkFz1R7<>^tLk~=}6;2r{d(JX?cbeRxp~Elf^na-%v~jxVbelpu(6&WNAFI@x&r@1=Tn_Sf2KY`f z^$S$+l-N-BmklJxM$SUmTYw}*b#0u<#c&{@=_{K z^@$~~s0-JKMDSAO&O)S&V&ye#h?)h&yxwj!RUdoJ;nrqv6R1PhAyPw2HZk8^S?V+Zo%N1>pAr-lWX$-6Hgd?J_`F~2B3ZGzeIE1>k&xO8 zGpoCv!s|xN!6GxmrkCy)eLc2h;pB9*CeF{=Qvg4wduNwGZPG8Qa8C=LrNRjvPUH}w z!8Zw&Kso*n&1%XTxv>=A3}qXdjP+8b#Zx1YYmS#^Cr!D~84Cdh2EV#!l$A@Jz?XH- z^KaySo11=9AMdL2A#A`5i&RAtOtAbIFZJ?Bju4edX6o?Nx}n-$I8{|nVPTM{b!XC6 zT3Y9NA(FYJ838R1&h?+3^@TyJ=5-OEg=Mqzk`duZRpOo-v=qK7T0lfi-oI=qST_lm+(ho5OkyjR!Pal|kNWYT=6Pl-=X)Lv#RER6W# zrnkl?kjs&&-n={Y*a&I#Rx#nE1>faxH7Mbh6bi)fT+`RpfcdRhl$<6GF)K;m9M6{?s}NyPBHRU zP1~nnmy2$A3EA0jzox{_ThUkgy5HZRWpTuM6PO~%NQE9`JbQ*m(`uHfBoJk~p*X!99D-sIF@x4DgrMtlNoU5hgX?q7Ze&^+2^^D* zreF@DoR_FphG;~otgH#DOb?iM^`{XqC#8IzG;@i};o?Za2?%IG@=#kAW;ulK7OW># zB+v&x1JdZtr~~5j8O#J+k4ArQcEsT%Z8V9mC-aY`av>HM`@(P`i;QRe{aFRJS%YLioe}S7?C)t!{B%}dw!6!VU9`) z9UB1kCnPH&l3$QewLI&xxq5qe6mH{Ud%36EyT!Nr?X=qRVdL9aBli1A!Y&LYyN7fB zr(8HhfN2vywHz<*X!;q{#sH)e9Gt6n#dHRo6amF@V0|r|WQBjErgUu9-OWQ9a%e=C zj8|5`!l}usaH}KMvGcQ)s>sYiEr}tMl?;{BxufJ#P{g$NTF(UU{j7@VCV(L^A1;p_+40OX%N{OiAm$sBD zzo1?iewPIXhJW>}X#|TB5g_st;RG5J^ybWJG90b&jA@(`VDn`h*5ZO|@(`gZzV=$- zy~RoaoD^t^hZu6WK$I^8pgLkj16XJ9b+Dl2s5YTU&_A5=lJK{5Qzarf(W{~#I0;5V z2bP-2%)eQgzrUC;%gZknN%b&$x~Axz$St9#$0772zgE1v!k`3n^_A~iZLchx$M!8BSCXaCf#wgH?Y?{vD}~)7o5Wd-rjMr{Q^w z5#E{h{_xG?kApkc=}D?R3%M6Mb7j-6idu9=CDLk z?@BcO%-fza zcOqaw(Fb4r9rB2fy8haxR(CH;CCyIdbAdv5#Q>bE@Nh&}gm`6UP7uyYTn$f75X5^0 z`E3q#D1nE+u8hHj225D=12~?7^FZ0i<>ml+Lh*=vq?!m~PGx=z4=Df?1Pla!IV`3z zho3}A`MbA)2J_9F(x1<(j^`Cf z$Cq88^HyiTr&2wrqbk-5VBg7Is&!;*3IdnTeYyo($=Fi<_<{6$-nZk7Xf#_AD9Q8R z4wcDVD(IHe zl4`4nv~an@m3+Plr0MjAhYwY?)x+1;B8|pUVRw5#z+*Mk>0>bGwMj961(6CD?o5_% z;PtTh01=4+AP2s7Twgq|-Ze1e*qt1jKiQK|Wz;`jwx*#cNNX}4VZ7Ct)3BgX6EXhW)0d#}-rP#~uAahJ%*oT&mfOS&tMrct0H7 zZ$w!ug3ZkCJj)S^?G}#iseC#VnIrPb2l_<% z%e#b1inFEMN@`&~=NmG+e!T*oTrSQ~x?KR-$9i>v!q=_)5$wFlD)Wo#F`%GYZ>m)M zWHCNI&~Bwsygzrno8sDRbB_FrTgTJS<@rwL^H{yNm&T((j1w>qH9}mToo8Yw&m~%l zz+9wZvS6@M_iQhW931|lGApO6)?QJm*H!PvOVOAns5zdIzn!0#Kje55yeGh?eJ$c^ zIZ$uG`f;|1-BGVl9`)DI87BHOM zYA(G^kyT8NeJzlKNlFbOcHqd#JawtcB18_Ivt7L;a@G}1q+wnIsWOBV+ArZWU4)M& zJI)!zq%YeFqpWOZ3Ip(Bit~nL-4s{%!$tUvq9}229zps+~?g{%L zQnZ2|hdDs*63hO)H4W>fPxwSj%ZF3G7Mm5F8v(gm)Dw^!;gmK;B)~8{RpNFXn$A|O=ypSr@{1(JkE7h3ztx~0L(6jHJ^LihdDY*_sU-vu{4P&PKthx+dd z-%DZ&spX&j{hx0ThU|0B_3_%(Xj{kBWM5SIfXNMHxhpy1^wjK3Ke3$jVSSC74;KD1 zhe~MjwGVBSFQoz^nmoaEeanKVOpZQOW0JUCa zh3{6+ctf1f?rHcpkIKXCgyM*HtRTS4fGLlYyQqwEe8bj*oQw0P+Y2Mt==uq@3y772 z2I2{rNYOGzn!bThStfz_!kuK2fQeRTnz*}H3ZEpV;$li=!w)J)yP>QFYf+1H?zdbg zcFyr)kTzVJN|v)ldYSA9R&!g~1o_jUo99Dl5W463LRWL-i9T)d zw>{8%w5ei0JZ3Ou^6Da!%6W4yMzGK^{l4zOHNYfcMa69%yvSvK_HjAe)5A9yK~|V6 zruc0W1o5@zdroEd`sGiE*NBgYBG}M@WQ*Mmg0OjNQKwsC(09A$sWN~5aJ_>DwbagO z=-&-lwotRc8M7c5xr(rb!*pG>-68}h zQN~pYfH6@Z>^ZfmWqfBpHo8%aCq}TEN3xn{qO82eY8}NQ>>lYmg8~ZAYbT`_a>uX= z8H>NtJDQ}MoG`;q3Yz5W;!2%?$u1s=XyJV8^7Pkm%wSMAlN&=&LLz0Tps-M0S)RXh z)amK7RMnsLd9JC*_RQk54*!NbscN}rJ(zv&8wwi~t)X{Vj@Rap_yDBc*_j!c_Nsg! zn6mo=WWAO8&kQYPmLS+ zlz_)#A6d3p8jBTiX)Oo_?&2K}MK0=9r^y1Q4jE9XG#SAsQv_>_`bl0UVb)pA6M0%@ zTi?AQs$Ce;ApvLOv(DO0vrtB6@*SC}&LEYU9J5{gMdF_AlB`yEfhdOCzqS>luviW_KqNaT&G(9; zd?q$Qr)au#&5*6;l=IaV#cRTY&Ra&S^8E7emiEorY<#I z%RWw-70lTN6i9maaY6>A95tCyGsC-Evj!LPb;zAm4fO3&8%qf2szlI+mV)UCpPsd2 zaeY#O!F*^8XkF^tXQGCDT;C#o_%mS0OW~auKEe+E)}N879B=rzHcWg~gA|Gtl)lHCtZsZ6jx_ zEB&{of8ssQ2*7#^;d>d!8Iy<^!zrQTy@hvMWplV+cX7~0yZ#zf z9|GWVN=&|_+ETyr_~J+_16e^&OKSVt+?+5RBbtzNF;F15O-nvCbZ<%kW@Upu-jKI6 zHd8XbuG@vCpvs{7 zWZ;X$KDazZpf&l84+2(t6*XZ|!nEojA)-UTKD!9k{F?z%J+^Apwt|sQei>y~2e*=r ziwtKBb>(ar#j(;EFRv5HEpNKltBH|Yy>{PJ0mP{p@r}i{vMT%pyu2~bwa5UIjS@Fd zqW4J4p!=5oke3rjsp6Z|ex%x34x1^W4-6qr!M46uBqKg*L#*F>z72C@fY|*lc+yDwaBcKe(FUP&F;eZ~WcA zU6atBtTYq~j{-G7O6B54&1lJ}lXXA6U3BxAr=+y7c-);=0kTHX(joDqT*`)_>~-QjLab1Agk8l$^ckRHm)|++TEXRoXMoH0{B9e{6l-cl{0#uH_yEM zd=5FyUu{XF?gitD$~7k7Sw-&KmfKhPwyAeOwYR&wnai$esm@5!#$~I5^{vF~Wx^RC zDjm=>8i+wll9DDMSoEHzl$u^CYVI5D0y>(Lk^!vP;mCSjMj=x?fh~>t)zj2*V^*7u zu_pg7#xYnSg>aQ6UFW|(pZOe;ELg1$0tNlH-HD<6!&`SoMn*uZH%UIWIdH*%6dHck zvGd)=j%DsLM)Z8Qe3H-j?nF&USUB^6c&h?m1Q0j>vYfKtI{5l#e=4@oa*JJ+9Veon z=$D?1PghB2GQ8Pz(I2iqg84LQxX(0il%h5GaJggW(fQ(bsDNB(L#=)C?W^l{6dQo{ z0YEmo9H%r1G8=&DqiTaGLOAvHQN1iY#Dho0>5e>d+G6%QleZ34zDN||E;<(ugb5kL zmxX|S2mr1_q)Rqry})4Y8$g1TfRSKbJb+i!&@;Jl^kZXPL7HEh3ep(_QXi{8n?M|K zXE4fOcO(hBq%5ziH4U;aB>!;W{hJN`uk-g?x^OBg40c<5AhAlf0%3{i{XxkF2nuv1 zW#s$cr+GzUvi)TiJyKo_)&3iE|Ja?^_qC71N!v7TY}+iYyx z|DDhG`aQVc!M(>hGDgm1%)R#7bFDRb9L7gsK1L6X(x3bYLqt)!tYKJM{=EPV6F*@1 z0a;g>2x9SkcIh!JjLDW>1IPg7(3AGls)wuTNKRIiw(1ne{J~_8yYUZ8!x}Z9W&zz| zv9ch$JY3%Id(2Y5$R_vSvpf5bFuGVB4GAeJ7&zWndWMcEvd4;`Tle;rd{nHD6^X7)Wz35+4XTx{^D%aZ{Bbn#QXXOfk@=mmDj&h7NBu~ zmr0=`uhSRBv9T`AA3*`>ww|$DeiTBDk|i8KP4T+%j{`QTzcUIwu&~@K=Nn(aKU1*c zl9D^X?TC1IIz?A`ae+Xr8KnF=Vn~FQY-U+a=HNvOY~FM?K)*He?{5ba6xpp8u*>*f zRt$%ZJbOj7XKIRT7l4~;TH3!CyLNJMq>QoG>`BN%wf*`Ahs_5Ya0%|046}~r&tl9J z2>Mm6#u#?CNOthZ(OFZoD31kSaRIp-PXXfFD|*CwlX!8lEY^M)1CEK5SDlp0TM2+a; z|K9q?h;PTrJ^aIO=~kQF?DHYv<-svMsn?bI2f554N34KS5SZZYz?37x;JfI}kN1ai z^}52hO^)T|&$|-U)dn{%%aj7#VkIsuOB!8O zR5x6)h$d6!j?%eTI}6M6e_zk&Q%Z!H+Tq}Cv7p2Jd^xQHnzJ9oJ_48kqtq)@>Mk*A zS#CS;jaL|HatYcb&gzfcyX5O_Cu61SmP`6(#f1)3W^RM$r5SF;ghO(dE#;9h-<4eb z{a2oGi+;BWPgEJ{gJin?|56jOIdGk|zf{UMBq~TVO3-xrme}nUwR8^dgWN0>0qa$H z3$_ug<|^ffb$Y_YA+rbF!}-|a%uLVme3^QaH4adt_W0(#;eJF?u0dn|2^Nt}o;__l zf7Z0aL6{6|qzr}t+%VV0Pvh~_fEu`pRv%7)(+t=P_Ua~i3^qHxn%Ax0FCP{2;jjTh z=j_t#VoFT$?JbXs>}Fk?mlor?l)rx9Odj`^6TBaumSh~c=hWci&UO=5kQ z#wRQgQk99DBU1iAnrsy2adfG+UvOb=i@nXRUb9kr;_acF=;Sz{@4_?Vp(V4(I_tN| z)7x4dZCDxFK;6yV!`5?AvzNUp9S`HY7WPXWs6|_`3!~P;lro+-PjJNKThQ^}I5A4i zfv#(l%cb5;0&&A?@4sizb+;#!8=Qo}(%6Up03{7aTT$md-6K04bi?y#?{Aj(Y&xUmTUazL4G63Q%>H zYrBAbf7U?po5T}Mr%FP@bqzMz@Jxd1EG9Ga{83^I>AUK-Uzl~%X^rX-xODhzq+ICU z?@%t&lAuF}8`9gAmT-ZC@3n>R+|isGujw#(z~0=w?{d$iyJZ+F403`N z?hd>2on6gQ<_l*2}^kI6`8lL~Q=fxiGug3^8yZ4Gj%- z8`ouAZ9%VOBBhNxk0rc_M&XVFC+p|g1#pI$^aFI8FbF#@kiGGmkE2;OBxI*OMIgoB zFr5_>yrCC~y#<%2vZ{wv#9~$_?-z9npIQ0N1g!5j`5JdK2V9+mR9*g$zC^tF%uFIjiFWA5)J&fjg`46zrbCHu=4t4s1z zG~YYIxHg9jWDDAw+Q2BRS^9HSK zYts(|HtJK^2k2TNZ|WkOenk3flhqyU@m^LsLw<~#wue4t&bI^wRT;lwRUH${nDxr6wI0sYT3B(1=B}F93G+%=-i8*r`kFGyi&NH z7x}qdhwe2te4;S}UO>Q-Ia*bT5q8&ovU^gOY#}Ks5oh}ILG>5Y!pNV+n`x8rvsP^@ zPCrf@uAtjp+GB2Z$W6^_6fExLF-)AGdo?e5DrRfvIg!a0Y)HOo17Gc`LACdb5DMv8Us7M)L{OonX9J*c#(LF3wX1GoaNQm{n zrH*QYS`Mo4YKruE+d7|p3E$YivwDg@{ca&f_7u4(PDyU^4&b~Km>9pj+}h04+grfd ztyCX>FUHu-HzjBeK)Z1FZ%Awz!!qL?xGeJLU;({AW{kA@f3*Ojlb72jE#>dc$ppO3 z0d8N@^GRHv4pY#cs9jj_(BQVfo5qO7shuiS8RJ%tmY|1lO>-)~^^6`!Wn z-$splm4;Yc3ql!7J7SGUIl~98DlM+(Stz)cumCq)I|fu{_GWT>kW*9VXJ`L@ety0mL_$P#1?;RCV#o}@ z#i;^he)$9PS;uocSJKztf4A)dfXM%6VLb2QK;Z-8T0?!t5PkVYZM6Rw$^8EEd3kXIcM{sB z89Z~2OG_IC_ymh9D!}jo=sKN`=S3Jy=EM3EH2+Ne|7vbib4vjy{Ayr8qA{@%Bs)R( zJea*QJ-sKIm_e_L?R2drjx5vFS3Xa~ZrLb_VAQ^E6)2$4YID8BsvZHeOo&L7Enq0W14Zu;FYD zd%MUJcnCrKWTdAqNiLgSNnIZR>&93bTT}ovnsdvrlS^nw2mvdr$~WsA+5g@TQ{pP# zzF!4P`<)XCY-S_m3xf(bT11G+-{fyI2IC!Ty-cGPRUHG8bRFNifvE?o&Cekx4tahA z`2oC^0y%O`k)4n|P5&vXNqcnp!dY1>93ncux3Fy{QWRYoiIW*zG!$iiCy<8DEDc{ns(d-Nv- zGVmq;A>XGrF(CnjXjWdo^Q_1Z-hZVz+XJ`^fM-4EnZ6?gadXof<3oKSk-LYhb zj`alRgoK2j*F!`=$?$pChqv)q^7QAjbpAA@Z5PT+uWzm*HB?T^^BPH(w7n;OlkZ1` znrJk}nMzK|5MRgw{RM!4j`axI3iZ0crVV%Kw)F{MRD70}mNqWp@F1Vc3%^29I>Ppn z_GBuGb8?LZ0a)cxi1egHq3VMA$sEjq#6fDicksgU2iL@z#h&56e`iAHJ%GAb{o7|-{B#q8@ zVtVN)0}Bhlobr$&MY3tX`p%kG(bPr^@%do*>A|Z&wRv@j(+lRZ+HQ}J?KRJDpmjn* zg<0`CR|@^qpEv5|Fh&5m7Up|NSs4*B)Sa`7%;BWZJCn!5`JO26GlJ?bJK@haDFgxl zYO*(qs0& za_88<|JSyHuzeM@tg+Hl3B~3?xwfm^fCxi{6UlJQ3?o^?0O)eC5*Lx8W#5v!OM$bp zvaH!EuCRRb?TjPtkuLYFrX-)?%xh9#5wZ|7zZBcQ`jyG)m@%jT zyi5@KC)j8(e46;+SdNv8#20^~fLuDxg5sx11z$jHd0d&lP}Xg!Bf z%02(7oO{}{qOx8l5$oA=7_Tfk}j)B zA%avcz4_u zJ=^Odqg)nZPz|=XYaoBIb7n3XgpYmJjzp)VVC3KqA$NGPJy2H|UP@2iJm^Rf$#`56 zko=}YZW#>(RL(2lvtn|!6zCQI-;J{9E)5a%elmeaV{;S*dHO`zffW+Rl8R|lnq zbB+M?O8$E+j@2J(juo-kUa@fH|0Sxost*}XSje2r7|O9TLlia+50+WLUEgoMO5vRD z0>hHRI6bAesmRU-3DyO*O@?rDE{rXe-tmY+-PPixvN=tH6eSyBD`sMEXQIQA0kPQ1$uUvnE04|*H zE(gT9`e2eWrhR3Xf4Q}{>NCI#FQcf41h9mY1D?zJYBD}H5GV%b$t#+_NKdyt;LlaY z1tZKbR0GZ5dp?ytD7kgS>}9OnWvqccix+GNsyPo|;Gxf&GjwLevH`c|bJ> z?$bb&KV}WeC9M39@=`2S#fPd;$KpwSZu*?2Pa0!i+eByz9KWdfVxxsA^a(74!DcDZ zq5uQ9@i(C7CsgdE^{?OX9JR@0WQYGj#8D#~sd}QBnVIH$uXU4)B9*VL{r|S6(c#cg z^(s9e0|3Yv?fWy0?l{=l&Th5B?8u4p75qoI*vm@zO)}SU)z5}gUeD_{UVC}Zhp}&E z<>kc4P%-K0)L)XPPCjMjOhUE@TGGMo%$s4PSCaM5-Ff1)3=eiWn>FhP#uUp06*DV= zEzcI~SD&C~77~@3EU=u#RuPERh)jx^z)f;PjUvYf7DGt%4V+*g6m1=r+%%UjB`GAOCol1IULf4c?n!sQ-vihPo)?u!Ong z$$mtR3clg+cs`mfl0Xzmex7~L$Ssu#FB^AANmj%wc)Cf815pFVoB`iKfZX8-3aZ_l zCoaht86NXZqUPW(4D25-ZwZzkooNekTcQOueX@4xA&}0d)DLkMQ;aiDXBXH?KS3Af z50hEMJQQ4@M__p}U8@oTiHY(oDO)^x4^cHRyyvJsit82eU}uh2&VA#Jp5iCZLcNZ) z0DpxF?&m8V?#D3WyXzrnLn>Md%8AV6xrrR+Nr+^LMrC6(37h8R*zNw zcak9Qgy+H{$?f20ofmx8MN*7xM@CCrZf#64(G35^GSHpQ?=C>Yzpxi{ znS3XOX^#Ii6mX)Pl3>GD>-;+ooD7GkJF^hj#P3=D5{g_YDAp&%YQTj~$OtUReIHCC zQfMeG1v<%qP(N@OB9th?cUEX)N237;Y^p!Ed{AZI=!kua;KS1J9gp@HECPsJkLSA^ z)#_KPsDLsr&l&ufY}o6bRqIA@Jr}#=@|b;@tultH0Pw}28g}M6YwP`Kn;sCQgBCYa zb*h5zG=-%qxX;WWrDLu0wC~Lf;mZW_)f4^D!j#2OSUk>GA{j#Ai2vwpnrdaO%U<3m z1^mY`cYlXZmi_zDNMl-BESGVxHLhP!v4KLXT`?Q28~b$eLd#>WB(DXU=~I;Aazc!F zOQ7vN*t<_;&8XxH1ApC z$)n#bW>Cn56f2WOd&E;C{>doHPta=3$j-ImS~Jz)Qwr97IJTShWsy`D9Boh@=jwn_ z(KI#s>EMDAnq&gAWajA}H=;3WOhS3(s^4_o$6ql;h{cn!IcGdUJFVp&-Bp9Pn9hE& zE{wU@O9_!+4An~*8m1)2yY@*|&~=+6xF?P=GFj9oT$q%3rY<^T$QrO+XHXa$N7d2y zqwn*bY+Yy>Ofzuu`_W8vr(1HL3*bEst5o7xY`2@S7P`KbTmAHGM^rGK7h}J5#Hw2k z*RC2o5a9aH3o;;%4O(hG0cUt6W$J^r_=??O+~O%jI*U$X2@?Aq z0N-W7->UvSnrFZJ8$Q%`l@7tlmQR?`jQ#~q)sV#^dL>oVqgZW5m)EsjC00%eWx42| zic;!vPy9leVK^hxO!3sVjm3kIVOFSWm01@-w{fiLUE`e+qr?*2zDPu&)o!^3C2m&e zvZt*;2eBR#dHrLgt2PW~E^;Fit1Z+`{L3Hr2M z1@M6AhIkN9ey%BrBY%Jj3dGP{JuZ)gSz<{vsBmGsx}ZICIA;Dqw+x-I=y`YEbnLzo zg^ejR@m3&xL}V}8fsC_z=RzfkIvK+uHkImRwcVW~G{+Abad}E3Cmg}7@9gf8t-#+o zkPA=${nJ$UlM*Pu&*#jEAx17Z>t0f$c^{z11>ulN5tUqtQT^uhowP9E$gaaL(y`67q1HfiGncZ)ic zKqz?>2=k9|Kdj9j?5ld+PKzojE877~;J41I-*Ud%q2&XXNw`FrW+4Ij)yHXui<*+? zVz*D*==qE=;J(zj$e-4T%#km#+?$rW8O#x58k~l9WHT^~o8~fLwhf?zd4zybDNw8N z@$MD|3b=r`f+xg<_p5-6nws1db%Ss8zPTK=H5{ds38{;XW0=az25HXe8G znk#qzgfF5u^=w2tJ5GnxDDeuT+HYR5FhQ}`*qmtoq~fDU9oQyV9Qaej$H7Sy=8$0H&@FumFRct~SBx=xjEX%34}dQ>m2W)6zz!vRE*BK3+DJ zM)~^_(~FBC!_M&N0wNI+ZD63Gp>et0`+x`# zXt6>8RE1_!ArN#aRw}gYjuBQ;LVkaLcXf5uH8K(|Q7B+kZG&1Z^cS)ts`$T|lh-#e zSZ;S?5{<;(+8eFEiKV8dE>VPR4n)RpN$hI&h+%OkdP2OF0PEJDfz?2 zCb;f$tr4^NT=CK~0fBtH7!o}saE0gtLXy(i?REer!Z>voaKbkpI==sOM9of2O{D%fZW5HseRfYtBD1EiCoOlY-DSQBVGG&*zHg8Y$bMAF+F*|cd&or^vo_E$&CWRS*xP?!Y72pok6$)26lPcB{-)H3Qd$k%I*MynFZ3D{c3F=R;JeCI>-upcTUCDaya9 z@G&2ir1jD%x6LoYl<_9qHS=j1b4y-;M#EzhWrZ;M>UzU%%j4hgrH>c2t7M-G^V)8G`lF-k*G*;Ggh|bnI9LzS<2+`Gqu#f=m__E1hobyWQMmj1KDS;= z>q?#Ai>!lgy{!rx5_Bb6zo{E{eN$4fOFYETBM}l*ntATY8c|QYEt~sTXW>~mnfR~t zhsf#%3#vpgR6_bJ8+h(Hj+@GAeT!6x>As z+PO6o+CJ_0;prBC(!zXA+}@2alPW1*7%7h{N`@rzpMULQ(q0|j6qF3^Cg{@$Y|Zvt zhy*!BDe83TG-a#~Cpy&-`kb7VbY4*+Z=EFN!`PjN*_4J%i|WtXM9*GWB5@o#y44tA zwMy0uzOa9`)>`aCESh8)>fXPy(mnp63b2)!2?3K@Hy!||LYrFdtQOpaSsWe=j)XIw z$AWFw7cd^o#u0~z8L6}7WreBRx%GuhuBVzfBn`){>o?|-OqEg}KTxKtHC(arM>8*x z77n@~oDMPEwtnwNW3jHguY1B4qcqnF3*DY;f_P2FaM3^{qO0}JMkz{?F&UReip_0- z9_RGR=RJDL_IwpL?8?Mz)J9R52YBm`%)VoT zt*Y|!qn)>9W)I>H&X(DhO)~0DAGX0)ek6C03&^imjy6$kCO!ibtKBWdY%V#Josh`` zcpoRel-Abq%=pS44s29tAAKogs6&5tL2O!^aQ`- zVUe>Ol@tblJsAJ*A%;PCe!HgyVBlJE4QW1jqd57apKxhQ10Wd#E~QdO*dR9DCM zncPtBM02WBD%RYC>pJuf{mLlgmq!<~`BIb$jfPyO0}gDq+noc@a=gtLjmX4e8cF$% zb#j%gFv+a7D?GP((zD(o>n!DvMpjb|&er_5PJHcY){AT}Z0No>7W;M-U{bw=G9S03*A0U} zng7rj*ns|nt-X+Co>+KGfEAhwqtTcI{U=C$p$qeSS`62$!!!2tV!gymQv#J1mr{U7 zQ;WrMY6nj$&ZaeC?Gt-pds11q0$%@u>>9><9o}FPY_7PQ=2%6B8KWl-e@fsqQis>i zi^4&zvW1Kk1NzJBx(v|z9Lu+QXsb49pSK$ee_oA3YYp+9zn@Lt|OEQy%#hcWYdlQR4Q9 z)_Z%YF<*ZupVG)_a>^C#fm3t6lZ!^0U2sJCFd8TiRyg&VeY{J&HslX#fs2&i>>TEH zGTYV)SSbSGyF~|XQBN{kvoo@G)h23IbCg7*m)du4?R&cNP7Gc)qOB!?XExfXiWZg$ z>A*T#2YB=?(Y%-VKVQmfrP0}|aSFr%yIpHDUa}l_E%F%!r&^(eo=!c6^bOUvFTdF@ zUa|~ht&#@?r(CXNj6%Hz@(mT6AP>W9PKD=)-q*=nRDG@qR7SFP2OY-_AS8^4h9Il}##DnXYa-zE0 z!XnTThKT7)>;%JPZ^l*IcF9^Rw{__a_0f7{PS>XFi_DCC=jlvZfwu%^bK8xKGD(y3 z+3uQRoRl@9u}$Ssx~a`G((M|IhdQw+5Mts@Fwkkk8K+9QS>ye^TFVJzr|8EMc%~i3 zp<}`MenP4!2s%t6b93`oat`kjjOf?+RDcyJy@GUyNTney?jhce&mE7k6V@~z>kW>qs7?npq%0T&WMR1C<2y&fum9WT&HzVLzxco}TJ(Ekdd%>%K0x+NLTjuKINW2h1}b@Q-> zA(i$>lp!mV)4EUa*W{tr=O4e)ohZXY5?$~nN0K^{h+IJlUL964sJs3 z?U=4$E41JgE>8ya;CWsnj7E!&?ydP0g;9?gFwC@11D!&I4hxC56krQl-_mmG}7=PcrbV&$UY2>|4xDPDpT7i~yd*P@MegHG?j;Rhou+ezK@yct&DpTRj)h<5 z(HF`tPmv*^IR`qGJs{~=;d8IeRLK#>Bqc_m(;Izb8A%cn!rsQBqYIQN=^c;oqIX`G zChqL3odzikFXn%W^$ix>{I|yvPvK-;TO$kU|EU=7yHyWfH5N>NjIc%!uWw)yrBlUU ztlPvDoBr8pT_)4`=G~5!@{t$oCX$^pdlV9HTG;G87)(ua;R)5#AL~oyR4~aR&HUl%9ztSd zD7RR-`0}9${HH=Z%2uGES2OhmEj2ts+5#cKjffb?@L42>M!od&;}qsHoB?2=VY0p= z<%Hb1mox`%OY;OOopX~wK+cgW5U6xs1Gh{)s3gvN36RlC9UU^SdHWx{cY85JB z9VF(3szlW|^!xsNe-T;jo9O99Zc|2p>4#PGP_c^~g+;Ku2jT2VNNoS`WGSR_g!r~2 z>t5{pAyxTJIj9HeJLVR3yz)Sp!I2Zv%Ic^v7PQB-hk>I?hJyL;y+gH9l8`q#WVPeXW8WP%o^HyQV7`ZvSY(kKv&cnNsYnX707E4u4vFBN52yqk zH8;*XEMb-Vn)qUYb`ZM1u#nr|6K<#XbC`@5MOm4pAI$_tY$_1yf|W9-oKeYglrem1 zttnrQJy7%bX*3U4lBkT@Ma5F_g9T`VsOTLW!+eA?Br+`U(R9+1*Rb`eT?o zqF;YL6_iygu_D}58I|oz-XrPEGmMskhOOeeYfuT#f-O4Cy_)@a6IGH^LEQOVL09?) zHds03FELBiygR8MO^;@3PI`sjvaiTA{;suxFwTrfkriYiFf!7N&0@#?)9! zVFD^YF#cpVL!&jPAP$a}WSA!8B)U#`MWB{dzwmK@7&eX{EfiQj6aC_7Y*ZOiP!wZ$ zp3a87fV~hckmh>Yc|=_CNJgr)0!KUZsO?ZaDi`fFRz`1i#~a^?b@hC~dsllyIa}r3 zt%{R$UHpz8Fni;{G|onFV)wfiA&1>9>~=gw$uvNVO@I z*tF3SzF{LhPD%u;HHG0TnnTl*ex$eFvw7hCG?BhtzU6XzVxZwty=ooi>P!*=;!DAORnsqGVLu^TgMhr^l!?bAyL|at=lo==L zp@jiFq~(!GKPpHH_9)cF_3(g`lufShE+6eZ{at zcs!WmG>6;}ydrwLy!~9NCW8G;)YeuIrx2aoxyu$`Y)PmS`;ciSFZ%IM>+@5ukrlA| zkM7FUFB}4@mxL7RCtJ{ns%HyxgX+CUEX_75pw*98^hqR1NOlC|OP*=NEwW93%zL(XA7>qN=KGVd33qimG}lYq$0QxeCMk@Ajt6NsE#p_vblRq94X z+D1Jubf#yeGF4y-XKdy^}~j%KMvr?zGd*+CuZu2_>*>xUZ>HrjO?u3pkmhZC+!b9QQ1m zQ=AkORQxH*Iq96EG9=s{(;N208#rZkP1bIX55AC1ry~zpU6PR}R+Sn`Ob4Wn&N44; zTZ8Rph$z>$gF1AvQxF)etc@S39rKP4F4r{&qS&^{LzPxp1?&0Ap}!(wXi7>3K7>HX zcge%cFIZNbuMy`?6kLDN){}cgdH?4fEyEwMGz!*JhMb^aio+uNtK- zW6k;nYP#6Y*N{21CXUr<5qu*@Cf#j@*;P8{%s|xZj#gm&|RHAbTx zs7>eHvqW*Gn_jS!n76sAWiHjqP`UL4Yhuj81B#JS=CThglcrN;XcZeZ!W6kvfot9I z#!!*tzJujr^b1kTF*zz_(ILpOad(CZB%g|V-Ew8pbz4`{TCnIc{|oG3`BLU# zVbXSYc?D$qBhI;$r4-7&5+u52^qvKstf6Z$EoOr%R3dp&p$s~+kjEXx**ahFh)SgV zWiHJI4u(#|KE>7NX64Cc=?;}JmEQ~TW%AasmDhL8B;Q!m$4dB(BAV4kDfSd_HQ&@} zwcP&({|KnqU=@(*zg`-_RW#}TiOR-=%ud=qPmn|;Dr&ARylH%g&RP#fg=)1|rAT|) zsvN}M)gkEZ@GV8)4-CtGN+e63%!fMldDbFb{kN*8%sdp{t;H&HL=42RF*DZ8x3<~t zoa%lav%{U&7EH>b<9`|;Ly|3W8!D5_l1Z)%xqe&Qu8J$N&AkPha(Ut`r%()ev)pT= zkJi1*%KBfp9%%a3b8Ke?m=3PlLuUO0%yI$|SZ4>fd{~g(=KszyGD@P`aDmBmELKad zhsn%(Tsp!bvx(YYmFYw4pgpUO|o_+yvqOmI3g z{JYVZwwWFJ{u|h3{4;yL&+GP#u&>jEW0{s?n=qCM7Z?jK`b3*#=Wdi$=dVpCoFwnf zZEK^;(d@8)@4d+7lx=bYzegj&q`~GzWDE}3GM?;?tOu-*qw>~Z(9lf!fMVxNfj8=2 zTL!ftho+U^FaZKTTI0)6u-*);HzHZgHmTiG7}XV#-sp#2%V3L7a)=sC84 z;FhJ;*{W>E6g2O8k}QB{9HXHIfh}q}C!RAy29JE>6P5gpEaTmnRv{{FD(8?|!6j&i z>Ga!x*HJ30BtFiw(SxdxSyA_Mi@h^fe1pIrED=#Y?YFDckp(NdEt9Z^XgWU`MvuN` zgte<6UqZ1(fi;@Fn?=GymTE3X6+EHjKlis-w+~)VYL5QaVR;P?iBHFWhO1LkqpXft z7NIx!&gHqMQUuS&0HoE~o>tP{zwy@Pdgrx%$Lrv}=^C+8w_2M~tyHMQ+n=f^`pJHU zntImerj&$Dd zZ<4>1f1jiBK2(Z@m?N>r4r1x3;C!Dh`}GZr2WM3F!~aStPHZfL>GZlI@qHj9ll-Ii z@%ize$1^J=VM6jhBA_(ho*AE$#_lNZlJ_($DvGYih74c90D@=RFX}@6OYW@ia;=w7 zYZByEkzL&5o9DL>4IGLPhP5{53<#u?fpN-KITfFPGmA(&UWx!T*>k%c?fY>)6eV0{ z7?(4>M4yH@aGF3y>K6qs31ouB-Vl!*;j$2-+cA+ssn>w&(yve1XH<$OKc^5d5QMMe zG=KT%M@XKz&)GNrd+HTKl%XIr40W4j#9KQ zP!{85me-$qJ{sL2h^fu9C6NbmUKHGRZV?JW=eq!&NjK5BMZP>RW@$nNd4u z^Gt34RR-#>@rI0`%){S#pHt$n%UR>jKl2ubeSNj{uSG5vgXNxM1ON@c5*e= z{t5px0|iXjap2V|F-Y-om!LtL=|m>fh=C>ozxkZ>m!7O#ahQX-;~Lke;!AXvXo*l} zFX(V-BFm%z5?C_pt%sTdo5l#OY%NvGA6`h7$zR zuKVncQ(^E$)Oy>qKHoCZPFPcwG#)R^xa%gg9iM9=O7Y&E-{YPPPcFGwFa|=B4R+2H z0;|JRw+8-2U}Q;=J55|p=n#v(f^<2oH7<7})BddrEjgAJs1llI;) zGhdA7&S!%LD13HU-pO8bW2b)p(7i#6d*W^d?G>s2w#(kE)J=Ls-R8WpWwyrREOp-7 z*Ad0Aw`bC_)7IPu`L5m;-irX~kEb)x?Y(JB)CE=|aFiA(7O`#2#||le<)8T)DZ7s& zbjA@1N(&W!(@_LUIp1^QXk@R14DC1c z4p!zM7_WHVATO`WEADZCkjh+&Uewd`k{gq+9yeM&dO-@_1u-_bvlXezJ5L?UTz`g| zo)sFmF5sF-_x&8iJ<(%mL70l;K?s~8ioIbhjW@6Ej(2gSF#=Lj>Fson__kC!V~ZDy zYfRUgV@&1p!mFdxLP8CGjaw61>DO}IS zF#}5!PZP3{RImIH#h0Wbl}SQ!@a#O)=$9C(>w1hm|JeAi|S7Skb4t%a1uTp)sZe0G(?Rl|4_yH3rcc zpgKhH^xm%#rh zw8=DUV@3kT(`sYmEc6b@TL%IAt!livf2v0hqsFA-&0k}d9GQr`BWfh4muP2^f(An+ zpi-($cWA5+)G5kQTvQv-7M!*ESHQnZwB3Y_A7BB%?GlcUw2T(N`Yuq2MyQ0B3Ah+x zh$^e_2r!o@W?o2;j0-TsqFC3*Mqon@`d$})n6=0h!rzbcprxTYBP4Q!Ue5RE9pc6$ z1ito{e?P`{<9hF-zLFVc=3sOZ-fA&SQk$A!R7H3{IW9wz_wghzT}d^15;rFJ#W{&0 z$_V}k!J9c(b~)_Q$eu}0zBbqP2Aj_NCstgEkNv-}#j%p&BT$}QU(*B-7O!c}Nq z@ih0YXMHS)ly&?V)R|$zOHJ9W^7w)SZLVqCM}3AZoG__XGr!`W_q;o_6fGu4+%>T_%9&hs4k6Olo|=jMp_Shs69q=)(CX|Q`qlHN=!+RDJd=0 zwMvjJYZ^)yHyM{cer>C$C+g8)xlvn_3^W*C4iG+P54lGBP#;L%M;d=*wP{IihrZ1m zug8V7*acX~b4tvc3%%I~xT?it_eM}H2G13fD;MD2{gP-OU^HNatJ2;w3oxFeIIKzL zi3$D(a{d!xsbwz58s&veF}})U;tlwUM@B?XFHG>sT*-wZ=yO4Wn?aCkau!i4|OH&zMWFq94*lIV8UWgZ*@- z8(uK66N7ADnUcPwO$StDd!6+wskwwsj`D67NTXmfmkUAqozKLfBS>{;kU?;v9nV$x zdJ&}O1J-6geDQLU|3a>yWZm;&PN1V~!CDf5? zN@PRvmp*>T?ncg;t3}lUKnh_86a8gELzMp(@-dwQU?{ZKXlTE450r`+94E0b_$236 zk)}ptxrBr+>T8woz?Q{RcXp)O=KyjpudOoGmHWrX!O{et#AFqO@RMwKdrM2PtZ7-R zu9nLFEm50}p)N!{=R%-cFKA2G1jhTQ)P zO;S+0P~_cs(#gsbo^7G`Q(RuY)cosl+fsXsXjDpjON$nPYoqvWjeHobg}#F~t_xpy zi8%$PN6PziN8blXs^B*7UP6!AzeG!_|BtVC46-e3qBP64ZQHhOo40J+wr<(BZS$6G z+jez*-*nGRN5{;+^J~8m@7^aPGoO_!nY0j_?V`)_n~=NNM~+`EWMH zRb6RCg`_rYEn{|ArM{{sz8kb+=ql6FV9%hTKWK?8^CE2Umyys|degx1P^vxs>ZxRQ zZDg-u3u%Ngy?8f#v-_zkr&U^JZ(pZHpMRdQPpGF2R)$tqWfb;%(OsC|;cG_GZt@p7 zy8X|OY<4X;*zJZDD>fXDpPeY$U?KYwj~m$@T=KYcVKap{CMPX)Uxz^=j6^Pa69W!> zPv&C`3Z$G*Hgp#(jO-Z|Mz?as?(5}6|KMrFhWR*HQ4tav()`qj$M-$tRlEB&;`VmR zOZ?SVoG7NF?SMHP?1^#kF0=|j`qc(Rbk*hd%Ih8o3Q<9AG%VVl39c-s!0_oYh9Bdd z$cGy3J=4{+yPf^~h$u0P)+FcYWm4N<+{r3Y5$7)y&t_?zpejUOSbQadj=-?zX{9iFnHr?@WwYc z&SRuJol}V0U-mtt-n%SVUg+&_cTMp!8tBW1wDXtC)XfK1`;@=T^#qN1Em!m6?f04!t0-t|%xw7t~!x7b1r7Mo)f8Kjn}b^9f7d?MD3hWH}tagH2Cs0`8ZR zC5z0ajnO2rRo{uIAHom(aEGdf)(~+=hsmB~mg%~Wh`t+$Vjis{$sXz7FQG9G@3|3? zLhCI6kBk92Q7ok{H`mD#&;%4hvXgpS3+xcz`hBr{*aH!~oS;cU?PFKf1!ArAX=}ZB zP#&Z_6x?pd)4|(O#zwdohI;v zie?%Q!v;KCTX#4d3HWj+eA*Y3f*U6fb050)H?t}INor0sJO}YCv#GpO-HrnQ-)N zZr-?h$f0wkQQX&fUl^V4_--tm8#&AeUa_O6F&CL^i3|Kb{aw5XL;4ysRprX*Z&CUk zZC*I~C&R&pKBxQFvEse;G}~_AvVR%aX2!oI#H4qR+iBL2g%;CUy%|@L-Q48CGu#2)+-aa?E{!D+SJgniL^|p<@r!#AMJzM;Rb4slv zCy8XX>M^J*<-X1VPJbP=aEwYGHzmpXr%&(G#h>{nu1KBSjqBwj_~_#%@n2V0n_HHP z9x}#qDfIO<-XnHrn$H?;{NUW%Dx1E~z;Vj~1XXrAa`}A---8YxU>}Lx{(6|UT3GzX6WYlN7FlZEj ztcq*ISaaG(lj(r=9^d!1i$8mdd*18O+jVD3H#K2Au}I^8X#sXL0-H242BUA{lF%l^ zhSd0;mxJ;8yaoh#wl)r~*K+G0*zD=fU9OK}qFrPuz`Mv9W*4%buX7&px#M6@3TBV6 zu|Z!a!+8v%YoV@<2r^IT4r*slHdnb)0JGN@JdXRPetfZ-vfuE#<!y<#KV4ZRRPzCqQ>;IrjEW`FRe5cCwfiV2yM?haR1qAxZeUt+s4dHvYegjWtf z-zyX!dgXXvjXpl(+YQHHNN1X zOl1Pj_a87PHt_gS#0k)mjyZ?CxKY0aYq96^V6PKj?*?yvP}eiqhh=gDO7`al$7vKW z_gJHg1*shZAMOUanaj8v3Y`7mwJ)8FUyD&NhL>NSoY*sb%Yyf#9)LR-hy|~pIbA7XL`rvMk ztlP8FiU%h+WiWTg%c|we7aMqGt%X-_E!e^Q+^9>N!gnxO$?&`Xzr->6t}BQ=PJmI* zynP+6R$MLRGJ$dEH*T5Jl)&UHCuSR;PYDNkaHbmt7T#_=+Jo;9uCzJR>}CfHZsyaY z+Y6`KSbE9kBjVI03$@XE`N__8Z{9#y@g?3|}TQnk-Htr)Dw(vPbGznGP~Rql>3B z-ZPG{XA;pSKu6Vbp5?eEQguBW7v&{^-Q2dzD-M@yva`G_XLZDkWU9y=9w+G@_0Gc@ z0=TblR75?j(79)Lf4dJ*kLm#)eH{9zo5R?@w~x3(`w8z;f=k}6!+NelmsC=ii>|vK zYv5DHE4#U&sO}INqN~kpVmv}zQGnAsGP=DI)nmNp_s($T&U93*!bD$j*z9KM#$2D6?I<%8_Hsj_l+YM`bGuYvJK zJZ0p#_tP$&V_S2H2I&jO7L{dU%?EtM?&`Ak<~Kk^;AZj0?}u}>iqn=5i_6RA1--jFvf%#+8s57y6x492V-KCCoicb*%=b~# zJOIouN45ukD?+-$m#E)%3u|dF0&9wE!B0z7lD$jEW!O4dIBt*>r0JFg=f<=vK_jCN z@_6ac$x?E`{wi@hT-$O)*1l`FU~zK6brUq^fCxW!v3bj`XQ*?1b8bvLC3a+bDpPSp zW|THW@w#r6ulcUPHqTtd*bNs%N=M@>CE3QPgH=eF7U{a*2SFwrQ@%BavPnc2C0eTj zRP%==^5cBO2Y(m2^%YkX{!Q^kc@#>ob1U*h$HGtuOzDL)QK=+YgwjNZk=8r9f(EI{x$XW+FpOK#u=c^)cY1v_?1!FngDO<(n!0EO zgDXw(8pVNveWCHBf4B0NSp--`~632skLcCdaYWOGA^hBxzpfv&OlDg7T-g^0*n z9GqN3e&Bk;=w1;(k3I=~R0&TViV1im?Z!CWE#CT$!sa^b^lG0w85~@7U5ae+NS~Qt ziupS$rAtbYH$iPtIhU9?_Vk8?w5wdqcB0D?_UMqdUoa$$4A!K@d|StlF~Fx>f&Yv(&>($tb;&cfR=Yp2-eAe=r#OAcvAAsm^4^>9T zOCY=U0Nmd^Q{q>~qFbsosFo*SdzS6yEXr%}LT?c1Q0R_ieGkt1n$)i~%i{5lO`WFG z(BTe`i~H^Q*2ZhiBRZTgsqfGGk);xohqToZ(B*Irhlq30NbIjUE!Y{}FO5#c5CUT( zboyb3fXc!Jo2+sx3_Alrux`s6ljd z0U75`x}?Tzrs}yqUsc{M%P4lw%8`j?Vzp9WX0Cgzyyot%q--%(&V`9MH8MxnoS<`h zjk(^V77vZ9_sdF*R~&Pq~_rr2T)}*TnH%kQ@tI0i*slsD!C4{j?nEbX0 zlaQ{2Uu%8gnRxy>wJ5t?P1c|%fXm6|nQBj+Zqv{?$oqX0eBkYgT1es!VUz0j*K?W2 zI}S|+Q-2dl8{y43nJ^&;0dN@==SU`T9ULdVaY0Qdk^|Euh-9bFTUOUht)$7sLrK|| ze+6!Y;E$N#pytABtWW$hnqHBkYIG{Dt^#;pAo&!#1OeV3f7#b(b!EP0P?mE%qf?|! zvv3bZ_z_WPYDhST$KwgzolbDa>S#3+vxrO&(b8)5Ixz6WEM~XAgCg)R$L_eXJI_y9 zESfd*J4^0!DXlo4R-sZSra8t6J%~`i@LAEzTzXuY3YT{Un=~52ZTRjU&wc`XU*7W_ ze-iLKcr00h>5d5%v1nU=(j=yB*9_Ek!^j>g5_@4?;tD+ysY;#K8rFIW^O_0=D_LvX z4`i<;EWknEZ%28SFBG~*G^kXp*IxEEvM1lH2ZTb|^}^Ik6J)aaqu?{Uz8N{+E{x@K z?c5qZ8zE_Mw$ohnvN1Sa_!{}F1AXuUWA0P=es~NLe-lx{SUEY)#3nkj$WxQwK93yd zzNlgeOmR3NlM*->{_!Dg;i=IK;;+_VJtMi(l+^KE`C0=DJS69KG;Z!7IqW@TvX*mp z7y&gqdUGx!%knRw?7uN@TO6n{Kd~b=PDd|zw5dCTliO~JcUz+#i$;3n4+a037))@m z(fDX2&h?Z7jdj2fzO2!>r(Kn({vfS7lQ}l;{g2t7;hoDT<-KoXc$i|<10!BxXF#X$ zFh%pG);HsQ@WabQ+$9&gT7=7;dZ53yG=pQmR=xsciF4)+JK$#?=~*yTPE)eiAyai- zJT9}yca!d7zjG3m__cQw_h^9V^0zMZ|ZPtExS0_o4j*p1F48Du4 z!OiapR&hk_sP+lS`Y!_*NoiH^d1WmAgjY(OT?YVhh0paXGg)$9okkP&J50@-mg$LV zQGpRLlWVo&AN=`7Ep7j1LYbPeQe@&2v$Bjy1xi^Taq36V)Gn>#ku`lZV-QB6HK7#NP!T6~RXtII~WE-q%h zllGF(I;+0Dwj;bfp*GXjH^mbsvL32DkXl$~oOtC=vHxHuYyiw|{n;2vDq7zt7BRSl~{TTX|D${o_ESDZyqk=*RqVtbUd1(|G1t+%Z zFSW~p-Kz-`=jl@kYA2Q`Revgwhhhuz;Jex&lE6=NomU)m^LrvPriG>10vBtVbO1fY zh6RRCrDh@-U9?wR)WxbCr)3IMy8Cz(x~6Aw=%zQb*U4D6eYG~6!vPZd{i*~f>nl&12;09K;GUq$YS@8a$p6eB#f3B(7yH}d4I+dGau`OqRj5NBQ zRhV77GHk^<^tDc}oaJ%pc<^S&k7d=xKs#pQ)zi+QK%mfLa+zKf6x6|}(%#tU_OEW_ z`VJKvzP6zAlgaCOhq8n><}kTzE2x$Y?~ke*XX?x4N$hl84d_SZ?tlNX3z`4r(h!hs z8CS4|ymx~A!IPGITI!ODu{$dlaTa$a(7lq=v#>-W`hn-oR4Ds*Hrri}+3~6Q`BtJL z5&)b_Fhm?WVzetAWYv_%ec_v6TDHFYIh?*r>Gv-?-kL}Fe`GbJmvN@TdDMb^D7?4l z5q+71Y0$}#F;D_~L|iOYB_8s+bnru+mFyjF=v?t{2OFIH?xiY6hC38_2gaM}j)q|A zj;?}R2mJa$U{q+unF{WdU8i#6?ApK{BZ4o}`X>shxM1)FTtNMS$V-*RjDp(SxiOt7 zi%>K})fH-VEW+ETBB<|9T_ANfm{4yD>Gk}8O6&*9lcQJjqKeA1fg&A?BDnig+<7~K zt*q0}|IMKfyV-WQTxqq1Q(wL=#gTJKL~xBaylp`E5f_{M|FJp>_1O6#`?n#~{UE12hM2hrHMI<5! zyARYGGnsWEZEu}0r<$>0+UWK7o5;>Zx2KCtT|X?h9o-VU{;lXA*WCcRI4A0~Cek99 zo{x5Mq$9D#LPmYt>wQ~Q8*VA$SpFylHDHeN#&T4Z}@E9i4xvuSSggaY+-`Hk{ zQW#aG#bV8;7H}WZZ?I#*cq0h8)9UXjD&Ph&aATqf89*wy+Jg*;y3KzZw4KmIX&+h%CMxL2nN}lFH1%xXfyfDoX)-z!kkCAa%Q&CEahp z2PZlC`@ywu1s+ahwK(Ey2aRcU&$jEbk%}Hw5FZMsU8E8myD=+0UR?s>n{TQ#n@ED` zcwgg=`M_KJr;%@Ks@`rR8FXJsJgk~&nZsDF$bDFU6gg#S-nm%R$HC$Rceo)B1F3TxRN^ zRC!5}H1Yd1cd%r6TX+IwQU&S`8+|y;J^5ioV1o;`;-uo!1pA}>%>llBsz|0Ki>(6U zMM0L4<#%m2GrZOL`uFz>2?-#GsEBEq0u+bh@Y!;^Ucs0GHQK0UGhBDex@Gk?71gv$ znbHPVt4l@ebflKlTyCQsq6$d52#Tm8pr9Bj8R59+4}=6zx`2Sy%ZbM;@m^NVs zNjP~MEhYJVDIVFgX7c)<4#v#hQ&v^~oDMzM5mG+^kOFWwxt#r_le9=&R4K2?NA}d} zS$V#fAge3dgd6-oE=RY8?Tt|Wut^T5EDC9KxQLmdTjBOLMaLDdD{gH^4#1EUn-J*J z1!rfXbG2!xzpLcL3{`^G`Hdpeo_DmUqYx2Of?~coLR@X`F7<;hYx$hU!3KRZk-}r# zrx1ZodOG)$&3v`JT_9+zDDg_FA?+{R_q*MSq_+H1+zN8x@QoSDft=64uv0Q2*A=wP zC*9<73ypI5(jlJZmX~OVN68KK?g8fMY!c0eh3=EEWu~e>^QeMzDO24}{6l;uMFmB> zHQU|@UD5u#0Q7f0K$P#hZMhoy9Jd?fmh8z!W8=!J$W7Px{16UzcLzYMuZw^Qk zPC!gPG{YTeE}#X)tE{*1Pq!L+J8~9(?|IYUX_0!I6X!5(hIn-s{Lt zD?U*tIQ_z0&3wa(z4a6iq~;bvz`@b^X2-{#IquO&s?fnn=fwz!`l+=QG5M5@-~Wsx z=;Ak~Usb5$obd9xO*qlMz4wI=>hdz!eEy)nLM zi4LQ;m@svU^TjFwP%heEX$FtqaDq*YBy~^34VA23-IC89YmCRi4XJE})^8!HrELiQ zTE$3E463%Dnl18Bra*o^=$zGes2_v+XCsOlWB4-?{=QalW+(RV>izxUd{rbg_zN3R z@j#&Z%Ht^jsJypkIAAkJBS{5jJjps(*7B(##B(@-n3@wyQQfYvP7G9o_G|jyFvdI& z=n{{q5nyzBYmW*fSWV4Zv-h{!t7Cp(oSyOhFqOW~P>NK(=pAuGbTfE|jt?>CHAfQ? z>w@LGkp=N)#U}1rym6)DZN}60Ll5Alzq6UB1#7OqxR|CkNt##I_<3>*FSE#7e(v%V z4DdRS0NGkXhOAl@F#^E`xtba}cxb$HmEHDGknU~t)?hyG22e)R8I8~tN7OG6GMr)y z1_|3O?WL-@Mj<}yW0Ju;26y;*VemZm%o<-@J&{TX--=Q{c=;6vbkJiE`*iJLI||w9 zR}8p+w%8MezJ~8{ds8ioY^w2FmB#2sn?w}d% zBjAVogc+Y_Ky3PcM+X2!bfOzS-MHP7xZnqhzxQpp@wwuE06w7K2qrE+>rk0wexiGhai(ZcnGQ%ct}b7|8C5g~S>TWsztkr=(~t8yg%jVa6hW9aOU zvG1?=YPq2AUi20vmJxfKl51F2ivbKYYW$A#bk)!v^-LhSW57^m=YkT^S?~UI)Am4u z`VlBTFuH+GNG>I7EBzNSVksBY(7?|x`qL43n!hV3d^ThiQ`@$Pd~OvBDex+l1s_de z=NtO%sNk;;^nu^tK!orv@$>L)xfMeGQaXy;H_UL8A|QfS6e5EW)Zgo9u@)L=Kb!QB zV(|tAcMoth5O87>?P%W+*aCrNeQgG-Gei?qy^&bZeh9d=Pz;||1QX0~oiEHlu;;L! zF^ktd89WZ$$-1Z{ID@rA!&~H+QLjQXqpx%s^P5jK=`ELUBGu8y3Sx_~*muZAF>nDI zxW2Mlw!K<$RbM)E^1Z%>ag1Yfv7Z+)S)=&bI57KJ0N)mNf0+K^?9h#c> zW+NWnp^&EqhD~*tT*9O}l1ng>2_2o!x87HC1;6kwal_*o#<3NqAjS==jO9964!p2} zs8E|4_S`eUTpJWkKf}qYVva=B@JVho0uBz44Wk^O_}%NZY7HhEoWRc^gb(=9h%4WU59`vv`3ms|nI7n&Bj^iT&uUxcVH zM(+u!;8Jxhxr8?CD0N&xEmsuZ3|1^aBSs)GpAcC;%#EG-@E_TKOYwxnj;wspSmEE_ z#Ee&A%wv^_FgcTHPd{}QBM`5V;JZQce+e(emcYxSr}7mD9A1c~cHGh0D+Z!_^O5O*PyR`Wiod8ROzMGXflC&T|0hB27RVeaE04PMK zkodc}4Z*KhU4z3hNj3 zqv_$$2b$y9xuEF{m%zUmp8a0(4mb8RH_2#pp0Sl!BY%arKXuM~XhV`$ed3e_sqey{ z!-gmw&jD^`gml&K0AC~D@Gr>#1mfrQH}yt?j^0=-=w_y|_I5vLo7L9_KauUL_G-rT z+)F+b`oN5RKws z2J<~eU2BurTUe!rCy#;{-FtsLGDBVupz&dL(gIqDOLqBtmfJe6xIewG8EG+ zkYdiXaXro%y?c#-|N3DCx0{*TJKMpp{3S|SS31HQJ^>CjIMd`@f4+)Yo-cf**#(}` zsRIj3T9lM|VuAZu4&Lu${pSN{D#3vf$?8>W+2%?4Ijp~HpHKbn?iPAGPvt#pNAs<| zt$B7}B{ysQ`eOi*T?yZ1cJFRgGtRrpjpof^+dp`eGHUF<(2-rX z0dLqEJw6-~TfTXnGXFtmO7^b+b5;F=H^5FkaMxFX`wnvoy;2}SgUJT*KqnDBsqcxU z|Ka0rXAE+S6g9UX>z^40PuPLse@?moTrGGSU^N?=6C4Dg+la27`*)+mh$jXknw|*7 z{KZzkEj6p}UNMM~Wencf^>9Dt`fGNW2vNHfZsc%_Gv%An$m{Bs!5Vq#OP}$KKh)Zm zAoq-^N^he~Au-2)LmsjSPKAQXv5pBjtVc887F^AI#SGxR1C7veoB<}o4bcDlK?bWd z)Vo;OmzeRdA8hG#QGo()f-5{+b#)MTpdYe z+o9!`OBdbPZY2yj(dp0;OYSwpScB@EYcLUg<1|ffYoms|5}W@PB0B1pWvQ!&eY5S6 zp15agZ0VL~1Cr11YL5<=ve>b=+JCj14g0{Gce0qE``s{4_@8shQRg?c<&C7R?tdf? z+D=AC7l7yqIsIG{i#k*&x5NjlK_92tS6KO+NKR3N>QTulY-cC%fRG|qD*5?&1q!W$ z7oAkG%A1qs+RtoBtog%?Oi=&9kX!Y0KmKzOZI2##Tq0e!Z#aH^90~i7Sil|GP*OOL z#3N}qm84dtTK}u~Sx`fAD8gdyrPw~nrr?(!?%jH5`KTq(`x+&2+Fet?yv|a`xb|vW zBK4aDz1V;pa=tL7C}IxN-xB3Nm^mw@2AHCohj#jBm1fP`_yMEX;ENCm!fSaH5`~&L z8-{X2W|huT0>@A)sVl%IfIcmTyJ&tAol_l3{2tkk7q-gEk+TqyH&M@>t>mKfz6l zFF#-_O;JyT$-6CzziL0^_7+SOInkT+E!wyaG4^NZ`~Ep*O%I&Q#wg=o%uyrOT4=Zl z`k(mDzVs3mNR{Fi=F{CUSCZQ-t6LuBk#a_lCPv1QQ%u~CHc-g*f4(^@&g`WmR|p^R zmn`=gTyc5kwA+Nt4VGBp5)K`9m=CWlpgDx>D10&-yjO1Pt2RimR6BNRNq4D2(Z?4aki z{Qx$t-q{WlA2=|-VC3k~BsdWx#>OPF!l{K9j)Na79Vq-Uv$#D6*e)3UPHhB%EWqg0 zqUc8p6-*!l22mU9u5RN4v!u}Tg()*Kxr2ce$R7C%+z2pUI?e35S5<90pL9mApRS@y`RsktQJCzy{JirJT6dTlC<3iN?&`smSh17IjzV8K$0w026N^~HFK&Lw8 zH33LT=KzQuvO8Tv$Zyc#=rO^2AwlWwu9Rcye8381M&kNZ?*v*wGqZS>^1riE{>bgZ zwW{Heh#ndDmt9$f8fIe8m@TnX6x+C4nh_A6K^4&Hh{djBvMwHr9+e+p9NhH<;ecXF zwjsFyraC3p2~bMqm`n7#g{;;BQz=(~Z#O9ApWYd7*<@V$5w~}|5)~k+-ojw$a88F@ z?eX_@zmIHst3P6|e!q<@c%J~gdN{QCGD5|vvZf1H8gqP~2V|^kqdzgaWhA;?B&0T> zCLp!1#St8^hIM!US-qQUD8`M?45>O9Ihx(v-mc4=(BYZ-E977W-}8Q&x7_<{ahsQ1 z;g8mX!BxjN5{f@qz59GC!5zt;m^t9qL!TiKkqO^OsJc0jQ_D9Xcm=kbpA4jM!#Bg) z8rJ=n;g31@yYQn%GtdVyG$M;oAb;$z0L6B6KdlRDObdjT8-!I)R_M_|r_luxz6^|B zC@8*LQ1v!elJG%^Y%>@|+<||jEDTm}6d-SihUc!C4opT&jj353^(PtD%PW>{#TF1K z0I6fld8K^vaoq}&Kco$1?w+DAraLXpZeBh}uc#Btf`^w1|HNk4*k3Aps~PoK*Jf60 zp5*unfRj8Ij;3R28Erm1-B!AQnIQuj?OVo zCcKP|4Xd~God(j&_^2ux(qy@(Lhh9|$_z_zbdGqguH^PHUlQQj_TM>gOX;#OOSKp% z!r|;eh;L^$-}d-0F}fOi%aOrCZ=hbDWE!D$?K90LoR;wRiu;5;ShKT?}ZA`r%G>=`o^u4>pD6xX}tl z<({_Lnfrb&gJ4e0OgD)4lkVnwyLk>U__P`9!p$lgs$zOhP39wERLt-mRHVUVGx?k%c_4Jv=Dy z1&V~9PCzhVL|+c1gzEud)nJ!Ff0ZQoVATfG77tq7NSPnzp&qPH(1Z?%{4@r2Jg#|$ zeoSyk#Z1<)0v6>vlbEqYA5#wR6WL>Jj|0sZE*J48KON7(X6 z;WtJT)%LEiACEBGZm0423^oIUG37g##z4nEkSq@;IA84_LaozBd)UV%hnf}&vZmn( z0whO*b{HmEH)_-o^|11y_;G4*N^+*KaOAv~NE05q*=HBIAr+1XfZL4xm*^dY%pCtZ zkFC4YmGM)4bJTt8{qhM*dxVJKT?x4KmU{(riWyCtib;HK;ZGBS-gswJiLnI5^}^PAL|bzq-E2`k>g|>lP#IP6sH~I@NTgiu%nZF z%^S>DS~grr!-v_gx!SZ%KLoWiRXNadnVN@oo-Iz%9}MR4UqAA^f3o9dOITI)HrI9^ zKuOD{c`wH(hbCJh11V0gzTw?~)EIda9MuUtE*QoY9~-yBMJ{8f-I$b|nr-R!peH@} zKp&P;cLvs4b4c}I-|WeioCy#@u>`5(dD&1drUOi4rWtH0d+9Qu5TXcSo_V*K88N}cQM(s; zGu`xMcCs7$YlY33v=m=lHL0q;w9=gsURa7V$dH7G5Hlo+6a5N*L$R^wQ}))}f=h`B zS)Uu`*drb?ng(|{JMHhoUiv#dK@iWRRCLx9%FmCLxJzxP`pzlwF10)R|IRz;YI|%h zD&(v*Aaijc&m+}XR5>f>Gf-lt>u5VaqAUhnH~X(3Wpi-_%_HUUswBl{jpy5l%a?^! zK)TXOi&+97Gr@D(nc=ud4RB@vD{KE(W6vsR> zBnhe@a0~YHW5M-|)t*GHk&K8pnU{7hYhVBza6A6`0&6!5YoiNo_FsjMs{`FzXX$std@!qKWbY+GX5StvK=f=nfUY5d9*VvsAhhFx z)A54l2^g)a1?yAqTubG|SVg!Ivby;f;)35^8QP8RIn1MOCIU*e<0zQ!wJO@>MU0k0 z(9f3^Ln)QR1T#sPqV}r2)wQ+)zi>dRFUAPg5F@7j{MM|v_(nUC&Eaa)anO~*4l2qX zhV(L7v#7)1sfKHJG7|X%3r#_3z0WMcU!%rF;6KAIwWvam861GRQSe4<4F?MFODs59 zy=bK&UyQy*Vej+Zbe(D;HxQ#}0@q7U;nXUEn#qCP{rMcUIjgi?@1DZLPJJh2kdyPMUpe=Y_-cQ7zd!^1w*3~ZXX@*5$P84jM-*r9UJHHp;Z(_B=M6z4yWG2J77)f7N zMmH^_wyWjOUod|hovQ*#h!VHfrE^^xi2?Nm(2jxjA+yVNvg|z->c?b^Me~HzJGI6r ze14qIuLwxs2mpreXvR#HFi+{KIMeUOs+LS5X{zUi-hDH+Hv)pQ-o5R48tl_W z3`0bg)RR<{6Xj$*l3u;ZGf?hSdei2UShlvZIHsegur(9oM~SK92-XqwwPek{*xbbo zjjbhTJq+JQwNcpY0NUC7lDM4Dq<)u`<-=Y78_zNm)kW7MqEyufwb0o)KeZe@Y)Otg zJ^bWb6zWSz9}5&glVh>}K3SC4XopIa5X9^pRlE?xaR72^K!_ac_agLRZ)eq1sR~wf z6Z;nrVzv|cD};Af#Y15(%#)(_Snou^3CY2P zoUUA$LmiG%-qa8l&Oyz;Uf|ShgLs91AwVRzg->oS_Kj^Kt)dI6Y05KRaN27FV#0q1@A<5=DBk3e7at7FmTvB) zd)g3j#>P&Xt7X79R0>s>4`NH^he_-C= zmR*rA^z62apAn!p6g+? zkpauNQqQmq-T0fGR2@0teoiuDr!-`}1dXV{{=V2wEryglimH+zWXocH<@?Z@kif|a z@NOzY@kP^5;jdUE6Ia-|Ca49=xAWWA8Y9-uKy*eNpU68J`1wRw$=XKnrk(Y5Rb<3B z1Le&!GLB+5ud5jmk_qWBF>Nk9_za!-hK~6sityNjJuw|7JPFWhyNdvLDjC_2CzTz5 z2m5~frvOkuHa3^Dy&!TDmcs5tm)vP%DgAd_Jtp6AC>*Vbm=YAz{Sm@?dw*pRbd{B^ z`6MFlCgxXMS5#dT_TEa>e!(Bc@3cp@&=KG0DD_@J_3lax%St<&b*21AU!r|m)WP)w z{Zj#cV}Egmsumuh*Wd~1(EBcyp<1;_Q-l8<@KTR8B0Dtq;oZ2mlo@sbRP1|&%eTdp{$jlIBN%Gl zl@!(Dmjnzq=;39&SM^dg&!KN4DP_s9>5YHsO0^pJ>>?iUWBU3Wc$5cd)D z1$^AlLoZny5-=k>VJ>V@L_N^+2D0UE4)s!}=g__buz*W6t|gwigmrhZg4Ndb5L)qI z;FF7)D1jG3)l<+9mQalrZ59=-1C*42u`+hHAe@}2qW65brJ@Ea;S z7&1OCoUa#0esj8&E+EiVtAV70*Ct~EIvDOKD=SadT{Y>DRjl1`%wv9HZfhEt6fH6YBD2!IU@PA-w*&8YO>5444uA^hh9UxdG8ByBAB6o51V<_X}(@ z97!Gc8_!q62l+$N?$CV~!4UD27DMqzU=y63&}?~dqrt^%*sd8<28`%l%0RwG2a+k=FO54@vpS$l(1atB{mVO& zydwx}7+wDeSiP&;x%Y=44&xVMHhjKlS~C)VQD%--{76`I{(gnpj9%jMegw|_s~ld# zwZIU4X-HoN*$@<1r|GRnUCY5%`3ak@!{P)CPt~=M))qpPkf5Bb`J#`Jh z5rZy*`AZX#Y|fx4N=}Bl2xZRyhvJ6BMt79{@vU0Oak#mM%xZhD6ZA4=z@SH0`@N)Q zvHug^j_7?*4!@xay8Dgjne=@64Cn(u8VQLL*&iG7u;ew6kf9I*o9)--2kCJ&-x=c; zObjQi1tGS*{+##N{7>X-sJ8h_%dx5ej2SGWh){cLc>OnXtm0K@`jajhWGtWLF_j!m zNX3cI&uviD7O5IQE_W~h|7{5$YL17;c0_K3)Y=0+bA2f0gdCny=ydC93?iFKto9-z za57T_7kg@#&x(uGettgal0umb>clT{6}PP0Yw~GczL_ zM^PHm^3|g&JEFU>BW$E>g4_vjuRMQxzo2TJeX_sIk%P0-;~adSH(0rAA5oV5zHo`I zPdA-LhR7;Lg?(~H4Sp@0K`{=Ng#L>`_axSZbu{>42YrTbF_|Ex2W$ljriO=ef1={R zp2AlBQ2u=51btGWt401Fq;9M^V<%>~(r+}IynGd%2Z?XH2ZB@ao8@l>!d)-z&=4UX z!Ux>wA}eBB^@$;YX}?R>std{;DHY0ywXlRegztMLf`VjJH$b^SCy)WHk(~cQcxr} z`jP*T!gs$+2IFA*Az;2f^2q`F!gFD>VM&fUYo^e*h`FEcxwQ6g7^dbH>g4_D-25~) zDgYz9Uv4JTKc77}rl$7T2=Tz4BU&RBP}SRFuslyZM%5LK3mu4v(*VmTzx1rf;zvAv zT#+Zk9ewoBJN!$8Q`wTw#Op1;rEuEISp@Zkna)NOWKLfS;i8Aah#c6_YPkZNE-5}) ztXP1Cj6kB_p)$WK8fJ27fDOP`)IxE}NW!+!sbjx@HXXe36rbG`|qLyK7Da$z95nfCCt zw3tGA?dAsLzFsZJY{TbyQ`Gp@!;fA|x$8n~8SJ)%)Cu{tDzztTMt}k+QWE;5RFVM6=^T&$po019 zggW_vXIGjb5~M>7XS<+1QRs%xYQJv#TNx4DBM7h0!216Ua-it)l_!fCm-k}mc{*}NC%i^DPI7w$gf`ixbSfk+wG4c+L< ztU7?VYz>{BjEgVczs#CV)tt2YO3C}d_QLmwM)yG*Imov`+V0=xo1^gGoOo6J2fIul z6_Ia_C=>Yu6bk`{2tiiVy=3UNrOEGZ@Ct91glIf#Q&G{|QpA7u7HhPT!YOh0DEh!o z77ccQ84vj5yS$ETc#kQ^8D15vVPG;qenjqw(BIgaXSE#IG4Oa&{KrPyK%QRuI6guO>vi+A(w}B*8wQB!z zNB7iq5mkHjHcbekA? z9G1j7TFTD1DW36ROMPQg5D;TC=JuegF zmTWo#{`o4BLTUW!Nsr2=h9ob@|ETTG!=c*$IDp%SF^r6vY-25<5U!oWG9y>Ever*D6B4dL?uc^fc7MNnf6x8>`8$7op7X~!&vX7b z&-c9E@AG^mcnPu9--_Dr-bp@Fy)?*or)%x@AF0oFl=n{irnnyx;j)XB)UV=6eax3_ zzr&M-==c_)^@LM-z;)jIqUA^BCPnw<41mN*KX4{`8&@2sz2v4ESd#rjb^8=$(udR2?BzhuSCDfwxshB`yN99 zW;UoVAvBGmR7{Vu7!R^UH^ArK9Lj5YsicSI;CN+g-=2Nnhh12u>=||6%t$xnZkjRB z(#S+YX_MvsU}2fYBg%Cb{d*NlwD7nN1_86nJhv8%evOT*wW8sTbrnFp0a(cQEHAOvF1<8H>8PL#+=DO$Uu?J+{To9fID6DJBo9HA0 z`_;o_*_1{YSnLHkCp($NTSoe^l*X(c&@f!b^T93t3yW*|2KugV$&Cr`&Wbvm`kGPh zx3a^hmLaBH!@h+9!(HfbhHHo$Y_vZ&|b4$S<@S3#sS`1exVc$syC|`T0d*|yG)$?&oMsu@938Eh8n~R2 z4E@>4LZ`w*Zy~`4gxg{*^ZoY9s-NMhsC$rOF{`j#Yl*dGQAEH>nrD}V`FN|8(uA}W z$QDgMGVscU9%eG55sO|*hv}{~r8y3bCMoX2eCN{ZCjDLM)@s6nYRK9vkP;^ZWDQrT zO2ZLD_zyk@8HNB9=o#2lN6uDpP50`FD4KB2BnC=2-T^uBdbey!oFHu4|nwz0M@rRAfOYlfxHKkogCboUr<2W`(4-7Ttr zfFK5QzXLa_iD`8EPYNl=q#55a;9tBI8}EMVH*%>REh|K#AWfb94nc2|!h~!KmN+{x z)4d^=V_2zC^P)t*k||z7Tz!}~4s>RO_fRD-KPuaV(Gn zoo_?Py^yijH~FLL84Jp8a{1Icn)8=Q(R$FsqmO+}Pq3H<5BL=F%HT$u-xK7cssSZ2 z;uckvG+cWo(%rnbJ$-Jv2=2o(#=yY#(#%NzQZ~B&0S7U7JrdMNC_W=w0}8g-du`gG z#Q&N7bT>bNwIi>1bPSu77ii(#Uj$4rdoe(rEi13;9-V3Ub(zETyz%OX;}N>i0@>aW z@t}I#H(>M0>TTW#cO|MxyG2#7OI&7d);u<-IZ!$_on_vVpEl|nNoTp;LMLX(D zc`*3BSsJ~erwHH0x$kA<#AEyXbM=bfnHC$!X!cN{w%^cE$qRBaa6=#ox4z<9X?M6W)PpJ= z&06v?ump}rSO;xnyNh(3%eE$Z5FPQINR(jMOW@3duYnLTGF!Tl^)vdpc zXi%MNjF=HhAAQC9m9^jxIh8ZjNgMo`AWp;cEplfxQ2w{rdLYioeu3aF^afpKlIE0O zNcs>N^z4I2`bGVpi9c!W)KCQNJJXbn>UdW z)EQ!4_;K41fcosmcCVhp91>0>n-POAut|T&`h@+heh-X}eWkwNrO7l^xa(aVR=#<_ z|Ee`nsnf&U*H(?j_*WbqKJ-~(7YBH1d1mcpO@;No*}lIy*71nJwVIKWj^|2F< z7(g-bsEarf&C41Lbe^m@=u{28HKjN^^N=|ZVrM1mZi~KB(9MvFoT`EH`VvHPODaCD zFFp^t>N7Wx9j`6o7{M}@NFN8*{E3v7Un@XC+MK~yK9lx-+g63ub17XrVN#qZ4J>E4 z&t$#U%P*2?vd2qbEdLX!Nw+@DwX_U76_O#Tyk+7eLp?S!h`y`({9=u~PBH@1n3Jf< zk*o~CM3k2_N!a1Yb5=55*$^yksCt5mkUePu`si;i1ux)%R36f*l8C70t!S8u_YKz9 z5evyvXOd+X#9_$ZdIY7S^7aw62~lsUXKK1BNyr->|~ra&<*l97G~+OW Date: Fri, 15 Jun 2018 15:32:55 -0700 Subject: [PATCH 38/39] clientv3: Enable balancer logging if ETCD_CLIENT_DEBUG environment variable is set --- clientv3/client.go | 14 ++++++++++---- clientv3/config.go | 2 +- clientv3/doc.go | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clientv3/client.go b/clientv3/client.go index 4d1270e8aef..eae8491e4ea 100644 --- a/clientv3/client.go +++ b/clientv3/client.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "net/url" + "os" "strconv" "strings" "sync" @@ -49,13 +50,18 @@ var ( ) func init() { + lg := zap.NewNop() + if os.Getenv("ETCD_CLIENT_DEBUG") != "" { + var err error + lg, err = zap.NewProductionConfig().Build() // info level logging + if err != nil { + panic(err) + } + } balancer.RegisterBuilder(balancer.Config{ Policy: picker.RoundrobinBalanced, Name: roundRobinBalancerName, - - // TODO: configure from clientv3.Config - Logger: zap.NewNop(), - // Logger: zap.NewExample(), + Logger: lg, }) } diff --git a/clientv3/config.go b/clientv3/config.go index c81d56466fc..4cea369076e 100644 --- a/clientv3/config.go +++ b/clientv3/config.go @@ -76,7 +76,7 @@ type Config struct { // LogConfig configures client-side logger. // If nil, use the default logger. - // TODO: configure balancer and gRPC logger + // TODO: configure gRPC logger LogConfig *zap.Config } diff --git a/clientv3/doc.go b/clientv3/doc.go index 0bb68618965..cd6ef01d213 100644 --- a/clientv3/doc.go +++ b/clientv3/doc.go @@ -99,4 +99,8 @@ // } // } // +// The grpc load balancer is registered statically and is shared across etcd clients. +// To enable detailed load balancer logging, set the ETCD_CLIENT_DEBUG environment +// variable. E.g. "ETCD_CLIENT_DEBUG=1". +// package clientv3 From cb6e9d2a7e25756b879a55833aa6fb4c8046cd8b Mon Sep 17 00:00:00 2001 From: Joe Betz Date: Fri, 15 Jun 2018 16:43:30 -0700 Subject: [PATCH 39/39] CHANGELOG: Add PR and issue links for new client balancer --- CHANGELOG-3.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index 477cc33e103..1b5069deeb3 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -9,7 +9,7 @@ See [code changes](https://github.com/coreos/etcd/compare/v3.3.0...v3.4.0) and [ ### Improved -- Rewrite [client balancer](TODO) with [new gRPC balancer interface](TODO). +- Rewrite [client balancer](https://github.com/coreos/etcd/pull/9860) with [new gRPC balancer interface](https://github.com/coreos/etcd/issues/9106). - Add [backoff on watch retries on transient errors](https://github.com/coreos/etcd/pull/9840). - Add [jitter to watch progress notify](https://github.com/coreos/etcd/pull/9278) to prevent [spikes in `etcd_network_client_grpc_sent_bytes_total`](https://github.com/coreos/etcd/issues/9246). - Improve [slow request apply warning log](https://github.com/coreos/etcd/pull/9288).