From a83f796f30ad75d5fd2af4c442fd606564da276b Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Mon, 11 Dec 2017 20:09:48 -0500 Subject: [PATCH 1/2] bump(google.golang.org/grpc):v1.2.1 --- Godeps/Godeps.json | 55 +- .../vendor/google.golang.org/grpc/README.md | 25 + .../vendor/google.golang.org/grpc/call.go | 79 +- .../google.golang.org/grpc/clientconn.go | 160 ++- .../vendor/google.golang.org/grpc/codegen.sh | 2 +- .../google.golang.org/grpc/codes/codes.go | 2 +- .../vendor/google.golang.org/grpc/coverage.sh | 3 +- .../grpc/credentials/credentials.go | 6 +- .../grpc/credentials/credentials_util_go17.go | 3 +- .../credentials/credentials_util_pre_go17.go | 2 - .../etcd/vendor/google.golang.org/grpc/doc.go | 2 +- .../google.golang.org/grpc/grpclog/logger.go | 2 +- .../grpc/metadata/metadata.go | 7 +- .../vendor/google.golang.org/grpc/rpc_util.go | 87 +- .../vendor/google.golang.org/grpc/server.go | 217 +++- .../vendor/google.golang.org/grpc/stream.go | 154 ++- .../vendor/google.golang.org/grpc/tap/tap.go | 54 + .../grpc/transport/control.go | 44 +- .../grpc/transport/handler_server.go | 2 +- .../grpc/transport/http2_client.go | 290 ++++-- .../grpc/transport/http2_server.go | 110 +- .../grpc/transport/http_util.go | 2 - .../grpc/transport/transport.go | 53 +- .../grpc/keepalive/keepalive.go | 52 + .../grpc/stats/grpc_testing/test.pb.go | 225 ++++ .../grpc/stats/grpc_testing/test.proto | 23 + .../google.golang.org/grpc/stats/handlers.go | 76 ++ vendor/google.golang.org/grpc/stats/stats.go | 223 ++++ .../grpc/stats/stats_test.go | 981 ++++++++++++++++++ 29 files changed, 2675 insertions(+), 266 deletions(-) create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go create mode 100644 vendor/google.golang.org/grpc/keepalive/keepalive.go create mode 100644 vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go create mode 100644 vendor/google.golang.org/grpc/stats/grpc_testing/test.proto create mode 100644 vendor/google.golang.org/grpc/stats/handlers.go create mode 100644 vendor/google.golang.org/grpc/stats/stats.go create mode 100644 vendor/google.golang.org/grpc/stats/stats_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 22c21d85bc96..e8dce9b0334e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -3928,53 +3928,68 @@ }, { "ImportPath": "google.golang.org/grpc", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/codes", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/credentials", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/credentials/oauth", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/grpclog", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/internal", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" + }, + { + "ImportPath": "google.golang.org/grpc/keepalive", + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/metadata", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/naming", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/peer", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" + }, + { + "ImportPath": "google.golang.org/grpc/stats", + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" + }, + { + "ImportPath": "google.golang.org/grpc/tap", + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "google.golang.org/grpc/transport", - "Comment": "v1.0.4", - "Rev": "777daa17ff9b5daef1cfdf915088a2ada3332bf0" + "Comment": "v1.2.1", + "Rev": "8050b9cbc271307e5a716a9d782803d09b0d6f2d" }, { "ImportPath": "gopkg.in/asn1-ber.v1", diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md index 110a8cf42531..39120c2031c8 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md @@ -18,6 +18,22 @@ Prerequisites This requires Go 1.5 or later. +A note on the version used: significant performance improvements in benchmarks +of grpc-go have been seen by upgrading the go version from 1.5 to the latest +1.7.1. + +From https://golang.org/doc/install, one way to install the latest version of go is: +``` +$ GO_VERSION=1.7.1 +$ OS=linux +$ ARCH=amd64 +$ curl -O https://storage.googleapis.com/golang/go${GO_VERSION}.${OS}-${ARCH}.tar.gz +$ sudo tar -C /usr/local -xzf go$GO_VERSION.$OS-$ARCH.tar.gz +$ # Put go on the PATH, keep the usual installation dir +$ sudo ln -s /usr/local/go/bin/go /usr/bin/go +$ rm go$GO_VERSION.$OS-$ARCH.tar.gz +``` + Constraints ----------- The grpc package should only depend on standard Go packages and a small number of exceptions. If your contribution introduces new dependencies which are NOT in the [list](http://godoc.org/google.golang.org/grpc?imports), you need a discussion with gRPC-Go authors and consultants. @@ -30,3 +46,12 @@ Status ------ GA +FAQ +--- + +#### Compiling error, undefined: grpc.SupportPackageIsVersion + +Please update proto package, gRPC package and rebuild the proto files: + - `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}` + - `go get -u google.golang.org/grpc` + - `protoc --go_out=plugins=grpc:. *.proto` diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go index 772c817edd3d..c1588c6375c7 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go @@ -36,12 +36,13 @@ package grpc import ( "bytes" "io" - "math" "time" "golang.org/x/net/context" "golang.org/x/net/trace" "google.golang.org/grpc/codes" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -49,7 +50,8 @@ import ( // On error, it returns the error and indicates whether the call should be retried. // // TODO(zhaoq): Check whether the received message sequence is valid. -func recvResponse(dopts dialOptions, t transport.ClientTransport, c *callInfo, stream *transport.Stream, reply interface{}) (err error) { +// 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 { @@ -63,20 +65,34 @@ func recvResponse(dopts dialOptions, t transport.ClientTransport, c *callInfo, s return } p := &parser{r: stream} + var inPayload *stats.InPayload + if dopts.copts.StatsHandler != nil { + inPayload = &stats.InPayload{ + Client: true, + } + } for { - if err = recv(p, dopts.codec, stream, dopts.dc, reply, math.MaxInt32); err != nil { + if err = recv(p, dopts.codec, stream, dopts.dc, reply, dopts.maxMsgSize, inPayload); err != nil { if err == io.EOF { break } return } } + if inPayload != nil && err == io.EOF && stream.StatusCode() == 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() + if peer, ok := peer.FromContext(stream.Context()); ok { + c.peer = peer + } return nil } // sendRequest writes out various information of an RPC such as Context and Message. -func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHdr *transport.CallHdr, t transport.ClientTransport, args interface{}, opts *transport.Options) (_ *transport.Stream, err error) { +func sendRequest(ctx context.Context, dopts dialOptions, compressor Compressor, callHdr *transport.CallHdr, t transport.ClientTransport, args interface{}, opts *transport.Options) (_ *transport.Stream, err error) { stream, err := t.NewStream(ctx, callHdr) if err != nil { return nil, err @@ -89,15 +105,27 @@ func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHd } } }() - var cbuf *bytes.Buffer + var ( + cbuf *bytes.Buffer + outPayload *stats.OutPayload + ) if compressor != nil { cbuf = new(bytes.Buffer) } - outBuf, err := encode(codec, args, compressor, cbuf) + if dopts.copts.StatsHandler != nil { + outPayload = &stats.OutPayload{ + Client: true, + } + } + outBuf, err := encode(dopts.codec, args, compressor, cbuf, outPayload) if err != nil { return nil, Errorf(codes.Internal, "grpc: %v", err) } err = t.Write(stream, outBuf, 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. @@ -118,8 +146,16 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli return invoke(ctx, method, args, reply, cc, opts...) } -func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) { +func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (e error) { c := defaultCallInfo + if mc, ok := cc.getMethodConfig(method); ok { + c.failFast = !mc.WaitForReady + if mc.Timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, mc.Timeout) + defer cancel() + } + } for _, o := range opts { if err := o.before(&c); err != nil { return toRPCErr(err) @@ -140,12 +176,32 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli c.traceInfo.tr.LazyLog(&c.traceInfo.firstLine, false) // TODO(dsymonds): Arrange for c.traceInfo.firstLine.remoteAddr to be set. defer func() { - if err != nil { - c.traceInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) + if e != nil { + c.traceInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{e}}, true) c.traceInfo.tr.SetError() } }() } + sh := cc.dopts.copts.StatsHandler + if sh != nil { + ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method}) + begin := &stats.Begin{ + Client: true, + BeginTime: time.Now(), + FailFast: c.failFast, + } + sh.HandleRPC(ctx, begin) + } + defer func() { + if sh != nil { + end := &stats.End{ + Client: true, + EndTime: time.Now(), + Error: e, + } + sh.HandleRPC(ctx, end) + } + }() topts := &transport.Options{ Last: true, Delay: false, @@ -167,6 +223,7 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli if cc.dopts.cp != nil { callHdr.SendCompress = cc.dopts.cp.Type() } + gopts := BalancerGetOptions{ BlockingWait: !c.failFast, } @@ -188,7 +245,7 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli if c.traceInfo.tr != nil { c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true) } - stream, err = sendRequest(ctx, cc.dopts.codec, cc.dopts.cp, callHdr, t, args, topts) + stream, err = sendRequest(ctx, cc.dopts, cc.dopts.cp, callHdr, t, args, topts) if err != nil { if put != nil { put() @@ -205,7 +262,7 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli } return toRPCErr(err) } - err = recvResponse(cc.dopts, t, &c, stream, reply) + err = recvResponse(ctx, cc.dopts, t, &c, stream, reply) if err != nil { if put != nil { put() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go index 61674729a77b..9858fe574d79 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go @@ -36,6 +36,7 @@ package grpc import ( "errors" "fmt" + "math" "net" "strings" "sync" @@ -45,6 +46,8 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -54,6 +57,8 @@ var ( 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. This error will be + // removed in Q1 2017. ErrClientConnTimeout = errors.New("grpc: timed out when dialing") // errNoTransportSecurity indicates that there is no transport security @@ -83,22 +88,33 @@ var ( // dialOptions configure a Dial call. dialOptions are set by the DialOption // values passed to Dial. type dialOptions struct { - unaryInt UnaryClientInterceptor - streamInt StreamClientInterceptor - codec Codec - cp Compressor - dc Decompressor - bs backoffStrategy - balancer Balancer - block bool - insecure bool - timeout time.Duration - copts transport.ConnectOptions -} + unaryInt UnaryClientInterceptor + streamInt StreamClientInterceptor + codec Codec + cp Compressor + dc Decompressor + bs backoffStrategy + balancer Balancer + block bool + insecure bool + timeout time.Duration + scChan <-chan ServiceConfig + copts transport.ConnectOptions + maxMsgSize int +} + +const defaultClientMaxMsgSize = math.MaxInt32 // DialOption configures how we set up the connection. type DialOption func(*dialOptions) +// WithMaxMsgSize returns a DialOption which sets the maximum message size the client can receive. +func WithMaxMsgSize(s int) DialOption { + return func(o *dialOptions) { + o.maxMsgSize = s + } +} + // WithCodec returns a DialOption which sets a codec for message marshaling and unmarshaling. func WithCodec(c Codec) DialOption { return func(o *dialOptions) { @@ -129,6 +145,13 @@ func WithBalancer(b Balancer) DialOption { } } +// WithServiceConfig returns a DialOption which has a channel to read the service configuration. +func WithServiceConfig(c <-chan ServiceConfig) DialOption { + return func(o *dialOptions) { + o.scChan = c + } +} + // WithBackoffMaxDelay configures the dialer to use the provided maximum delay // when backing off after failed connection attempts. func WithBackoffMaxDelay(md time.Duration) DialOption { @@ -199,6 +222,8 @@ func WithTimeout(d time.Duration) DialOption { } // 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) { @@ -210,6 +235,25 @@ func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption { } } +// WithStatsHandler returns a DialOption that specifies the stats handler +// for all the RPCs and underlying network connections in this ClientConn. +func WithStatsHandler(h stats.Handler) DialOption { + return func(o *dialOptions) { + o.copts.StatsHandler = h + } +} + +// FailOnNonTempDialError returns a DialOption that specified if gRPC fails on non-temporary dial errors. +// If f is true, and dialer returns a non-temporary error, gRPC will fail the connection to the network +// address and won't try to reconnect. +// The default value of FailOnNonTempDialError is false. +// This is an EXPERIMENTAL API. +func FailOnNonTempDialError(f bool) DialOption { + return func(o *dialOptions) { + o.copts.FailOnNonTempDialError = f + } +} + // WithUserAgent returns a DialOption that specifies a user agent string for all the RPCs. func WithUserAgent(s string) DialOption { return func(o *dialOptions) { @@ -217,6 +261,13 @@ func WithUserAgent(s string) DialOption { } } +// WithKeepaliveParams returns a DialOption that specifies keepalive paramaters for the client transport. +func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption { + return func(o *dialOptions) { + o.copts.KeepaliveParams = kp + } +} + // WithUnaryInterceptor returns a DialOption that specifies the interceptor for unary RPCs. func WithUnaryInterceptor(f UnaryClientInterceptor) DialOption { return func(o *dialOptions) { @@ -231,6 +282,15 @@ func WithStreamInterceptor(f StreamClientInterceptor) DialOption { } } +// WithAuthority returns a DialOption that specifies the value to be used as +// the :authority pseudo-header. This value only works with WithInsecure and +// has no effect if TransportCredentials are present. +func WithAuthority(a string) DialOption { + return func(o *dialOptions) { + o.copts.Authority = a + } +} + // Dial creates a client connection to the given target. func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) @@ -247,6 +307,24 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * conns: make(map[Address]*addrConn), } cc.ctx, cc.cancel = context.WithCancel(context.Background()) + cc.dopts.maxMsgSize = defaultClientMaxMsgSize + for _, opt := range opts { + opt(&cc.dopts) + } + + grpcUA := "grpc-go/" + Version + if cc.dopts.copts.UserAgent != "" { + cc.dopts.copts.UserAgent += " " + grpcUA + } else { + cc.dopts.copts.UserAgent = grpcUA + } + + if cc.dopts.timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout) + defer cancel() + } + defer func() { select { case <-ctx.Done(): @@ -259,10 +337,17 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } }() - for _, opt := range opts { - opt(&cc.dopts) + if cc.dopts.scChan != nil { + // Wait for the initial service config. + select { + case sc, ok := <-cc.dopts.scChan: + if ok { + cc.sc = sc + } + case <-ctx.Done(): + return nil, ctx.Err() + } } - // Set defaults. if cc.dopts.codec == nil { cc.dopts.codec = protoCodec{} @@ -273,6 +358,8 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * 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 { colonPos := strings.LastIndex(target, ":") if colonPos == -1 { @@ -284,6 +371,9 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * waitC := make(chan error, 1) go func() { var addrs []Address + if cc.dopts.balancer == nil && cc.sc.LB != nil { + cc.dopts.balancer = cc.sc.LB + } if cc.dopts.balancer == nil { // Connect to target directly if balancer is nil. addrs = append(addrs, Address{Addr: target}) @@ -319,10 +409,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } close(waitC) }() - var timeoutCh <-chan time.Time - if cc.dopts.timeout > 0 { - timeoutCh = time.After(cc.dopts.timeout) - } select { case <-ctx.Done(): return nil, ctx.Err() @@ -330,14 +416,17 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if err != nil { return nil, err } - case <-timeoutCh: - return nil, ErrClientConnTimeout } + // If balancer is nil or balancer.Notify() is nil, ok will be false here. // The lbWatcher goroutine will not be created. if ok { go cc.lbWatcher() } + + if cc.dopts.scChan != nil { + go cc.scWatcher() + } return cc, nil } @@ -384,6 +473,7 @@ type ClientConn struct { dopts dialOptions mu sync.RWMutex + sc ServiceConfig conns map[Address]*addrConn } @@ -422,6 +512,24 @@ func (cc *ClientConn) lbWatcher() { } } +func (cc *ClientConn) scWatcher() { + for { + select { + case sc, ok := <-cc.dopts.scChan: + if !ok { + return + } + cc.mu.Lock() + // TODO: load balance policy runtime change is ignored. + // We may revist this decision in the future. + cc.sc = sc + cc.mu.Unlock() + case <-cc.ctx.Done(): + return + } + } +} + // resetAddrConn creates an addrConn for addr and adds it to cc.conns. // If there is an old addrConn for addr, it will be torn down, using tearDownErr as the reason. // If tearDownErr is nil, errConnDrain will be used instead. @@ -509,6 +617,14 @@ func (cc *ClientConn) resetAddrConn(addr Address, skipWait bool, tearDownErr err return nil } +// TODO: Avoid the locking here. +func (cc *ClientConn) getMethodConfig(method string) (m MethodConfig, ok bool) { + cc.mu.RLock() + defer cc.mu.RUnlock() + m, ok = cc.sc.Methods[method] + return +} + func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) { var ( ac *addrConn @@ -689,6 +805,8 @@ func (ac *addrConn) resetTransport(closeTransport bool) error { Metadata: ac.addr.Metadata, } newTransport, err := transport.NewClientTransport(ctx, sinfo, ac.dopts.copts) + // Don't call cancel in success path due to a race in Go 1.6: + // https://github.com/golang/go/issues/15078. if err != nil { cancel() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh index b00948884295..4cdc6ba7c090 100755 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # This script serves as an example to demonstrate how to generate the gRPC-Go # interface and the related messages from .proto file. diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go index 37c5b860bd6d..e14b464acfd4 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go @@ -33,7 +33,7 @@ // Package codes defines the canonical error codes used by gRPC. It is // consistent across various languages. -package codes +package codes // import "google.golang.org/grpc/codes" // A Code is an unsigned 32-bit error code as defined in the gRPC spec. type Code uint32 diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh index 120235374a42..b85f9181dee9 100755 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + set -e diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go index 4154f1eeba9a..4d45c3e3c7f2 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go @@ -35,7 +35,7 @@ // which encapsulate all the state needed by a client to authenticate with a // server and make various assertions, e.g., about the client's identity, role, // or whether it is authorized to make a particular call. -package credentials +package credentials // import "google.golang.org/grpc/credentials" import ( "crypto/tls" @@ -165,9 +165,7 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, addr string, rawConn net case <-ctx.Done(): return nil, nil, ctx.Err() } - // TODO(zhaoq): Omit the auth info for client now. It is more for - // information than anything else. - return conn, nil, nil + return conn, TLSInfo{conn.ConnectionState()}, nil } func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) { diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go index 9647b9ec83ae..7597b09e358b 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go @@ -1,4 +1,5 @@ // +build go1.7 +// +build !go1.8 /* * @@ -44,8 +45,6 @@ import ( // contains a mutex and must not be copied. // // If cfg is nil, a new zero tls.Config is returned. -// -// TODO replace this function with official clone function. func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { return &tls.Config{} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go index 09b8d12c79b7..cfd40dfa34ab 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go @@ -44,8 +44,6 @@ import ( // contains a mutex and must not be copied. // // If cfg is nil, a new zero tls.Config is returned. -// -// TODO replace this function with official clone function. func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { return &tls.Config{} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go index b4c0e740e9c7..a35f218852bd 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go @@ -3,4 +3,4 @@ Package grpc implements an RPC system called gRPC. See www.grpc.io for more information about gRPC. */ -package grpc +package grpc // import "google.golang.org/grpc" diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go index 2cc09be48945..3b2933079e9c 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go @@ -34,7 +34,7 @@ /* Package grpclog defines logging for grpc. */ -package grpclog +package grpclog // import "google.golang.org/grpc/grpclog" import ( "log" diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go index cb29bf945793..733239502865 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go @@ -32,7 +32,8 @@ */ // Package metadata define the structure of the metadata supported by gRPC library. -package metadata +// Please refer to http://www.grpc.io/docs/guides/wire.html for more information about custom-metadata. +package metadata // import "google.golang.org/grpc/metadata" import ( "encoding/base64" @@ -82,6 +83,7 @@ func DecodeKeyValue(k, v string) (string, string, error) { type MD map[string][]string // New creates a MD from given key-value map. +// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be applied Base64 encoding. func New(m map[string]string) MD { md := MD{} for k, v := range m { @@ -93,6 +95,7 @@ func New(m map[string]string) MD { // Pairs returns an MD formed by the mapping of key, value ... // Pairs panics if len(kv) is odd. +// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be appplied Base64 encoding. func Pairs(kv ...string) MD { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv))) @@ -141,6 +144,8 @@ func NewContext(ctx context.Context, md MD) context.Context { } // FromContext returns the MD in ctx if it exists. +// The returned md should be immutable, writing to it may cause races. +// Modification should be made to the copies of the returned md. func FromContext(ctx context.Context) (md MD, ok bool) { md, ok = ctx.Value(mdKey{}).(MD) return diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go index a25eaa8a2874..28aab0a3f986 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go @@ -42,11 +42,14 @@ import ( "io/ioutil" "math" "os" + "time" "github.com/golang/protobuf/proto" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -138,6 +141,7 @@ type callInfo struct { failFast bool headerMD metadata.MD trailerMD metadata.MD + peer *peer.Peer traceInfo traceInfo // in trace.go } @@ -181,12 +185,20 @@ func Trailer(md *metadata.MD) CallOption { }) } +// Peer returns a CallOption that retrieves peer information for a +// unary RPC. +func Peer(peer *peer.Peer) CallOption { + return afterCall(func(c *callInfo) { + *peer = *c.peer + }) +} + // 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 // 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 -// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md +// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md. Note: failFast is default to true. func FailFast(failFast bool) CallOption { return beforeCall(func(c *callInfo) error { c.failFast = failFast @@ -255,9 +267,11 @@ func (p *parser) recvMsg(maxMsgSize int) (pf payloadFormat, msg []byte, err erro // encode serializes msg and prepends the message header. If msg is nil, it // generates the message header of 0 message length. -func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer) ([]byte, error) { - var b []byte - var length uint +func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayload *stats.OutPayload) ([]byte, error) { + var ( + b []byte + length uint + ) if msg != nil { var err error // TODO(zhaoq): optimize to reduce memory alloc and copying. @@ -265,6 +279,12 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer) ([]byte if err != nil { return nil, err } + if outPayload != nil { + outPayload.Payload = msg + // TODO truncate large payload. + outPayload.Data = b + outPayload.Length = len(b) + } if cp != nil { if err := cp.Do(cbuf, b); err != nil { return nil, err @@ -295,6 +315,10 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer) ([]byte // Copy encoded msg to buf copy(buf[5:], b) + if outPayload != nil { + outPayload.WireLength = len(buf) + } + return buf, nil } @@ -311,11 +335,14 @@ func checkRecvPayload(pf payloadFormat, recvCompress string, dc Decompressor) er return nil } -func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxMsgSize int) error { +func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxMsgSize int, inPayload *stats.InPayload) error { pf, d, err := p.recvMsg(maxMsgSize) if err != nil { return err } + if inPayload != nil { + inPayload.WireLength = len(d) + } if err := checkRecvPayload(pf, s.RecvCompress(), dc); err != nil { return err } @@ -333,6 +360,13 @@ func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{ if err := c.Unmarshal(d, m); err != nil { return Errorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err) } + if inPayload != nil { + inPayload.RecvTime = time.Now() + inPayload.Payload = m + // TODO truncate large payload. + inPayload.Data = d + inPayload.Length = len(d) + } return nil } @@ -343,7 +377,7 @@ type rpcError struct { } func (e *rpcError) Error() string { - return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) + return fmt.Sprintf("rpc error: code = %s desc = %s", e.code, e.desc) } // Code returns the error code for err if it was produced by the rpc system. @@ -448,6 +482,44 @@ func convertCode(err error) codes.Code { return codes.Unknown } +// 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 minumum 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. + // TODO: support this. + MaxReqSize uint32 + // MaxRespSize is the maximum allowed payload size for an individual response in a + // stream (server->client) in bytes. + // TODO: support this. + MaxRespSize uint32 +} + +// 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. + Methods map[string]MethodConfig +} + // SupportPackageIsVersion4 is referenced from generated protocol buffer files // to assert that that code is compatible with this version of the grpc package. // @@ -455,3 +527,6 @@ func convertCode(err error) codes.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 + +// Version is the current grpc version. +const Version = "1.2.1" diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go index e0bb187ef975..157f35eea502 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go @@ -54,6 +54,8 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" + "google.golang.org/grpc/tap" "google.golang.org/grpc/transport" ) @@ -110,8 +112,11 @@ type options struct { maxMsgSize int unaryInt UnaryServerInterceptor streamInt StreamServerInterceptor + inTapHandle tap.ServerInHandle + statsHandler stats.Handler maxConcurrentStreams uint32 useHandlerImpl bool // use http.Handler-based server + unknownStreamDesc *StreamDesc } var defaultMaxMsgSize = 1024 * 1024 * 4 // use 4MB as the default message size limit @@ -186,6 +191,42 @@ func StreamInterceptor(i StreamServerInterceptor) ServerOption { } } +// InTapHandle returns a ServerOption that sets the tap handle for all the server +// transport to be created. Only one can be installed. +func InTapHandle(h tap.ServerInHandle) ServerOption { + return func(o *options) { + if o.inTapHandle != nil { + panic("The tap handle has been set.") + } + o.inTapHandle = h + } +} + +// StatsHandler returns a ServerOption that sets the stats handler for the server. +func StatsHandler(h stats.Handler) ServerOption { + return func(o *options) { + o.statsHandler = h + } +} + +// UnknownServiceHandler returns a ServerOption that allows for adding a custom +// unknown service handler. The provided method is a bidi-streaming RPC service +// handler that will be invoked instead of returning the the "unimplemented" gRPC +// error whenever a request is received for an unregistered service or method. +// The handling function has full access to the Context of the request and the +// stream, and the invocation passes through interceptors. +func UnknownServiceHandler(streamHandler StreamHandler) ServerOption { + return func(o *options) { + o.unknownStreamDesc = &StreamDesc{ + StreamName: "unknown_service_handler", + Handler: streamHandler, + // We need to assume that the users of the streamHandler will want to use both. + ClientStreams: true, + ServerStreams: true, + } + } +} + // NewServer creates a gRPC server which has no service registered and has not // started to accept requests yet. func NewServer(opt ...ServerOption) *Server { @@ -329,6 +370,7 @@ 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. func (s *Server) Serve(lis net.Listener) error { s.mu.Lock() s.printf("serving") @@ -412,17 +454,23 @@ func (s *Server) handleRawConn(rawConn net.Conn) { if s.opts.useHandlerImpl { s.serveUsingHandler(conn) } else { - s.serveNewHTTP2Transport(conn, authInfo) + s.serveHTTP2Transport(conn, authInfo) } } -// serveNewHTTP2Transport sets up a new http/2 transport (using the +// serveHTTP2Transport sets up a http/2 transport (using the // gRPC http2 server transport in transport/http2_server.go) and // serves streams on it. // This is run in its own goroutine (it does network I/O in // transport.NewServerTransport). -func (s *Server) serveNewHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) { - st, err := transport.NewServerTransport("http2", c, s.opts.maxConcurrentStreams, authInfo) +func (s *Server) serveHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) { + config := &transport.ServerConfig{ + MaxStreams: s.opts.maxConcurrentStreams, + AuthInfo: authInfo, + InTapHandle: s.opts.inTapHandle, + StatsHandler: s.opts.statsHandler, + } + st, err := transport.NewServerTransport("http2", c, config) if err != nil { s.mu.Lock() s.errorf("NewServerTransport(%q) failed: %v", c.RemoteAddr(), err) @@ -448,6 +496,12 @@ func (s *Server) serveStreams(st transport.ServerTransport) { defer wg.Done() s.handleStream(st, stream, s.traceInfo(st, stream)) }() + }, func(ctx context.Context, method string) context.Context { + if !EnableTracing { + return ctx + } + tr := trace.New("grpc.Recv."+methodFamily(method), method) + return trace.NewContext(ctx, tr) }) wg.Wait() } @@ -497,15 +551,17 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // traceInfo returns a traceInfo and associates it with stream, if tracing is enabled. // If tracing is not enabled, it returns nil. func (s *Server) traceInfo(st transport.ServerTransport, stream *transport.Stream) (trInfo *traceInfo) { - if !EnableTracing { + tr, ok := trace.FromContext(stream.Context()) + if !ok { return nil } + trInfo = &traceInfo{ - tr: trace.New("grpc.Recv."+methodFamily(stream.Method()), stream.Method()), + tr: tr, } trInfo.firstLine.client = false trInfo.firstLine.remoteAddr = st.RemoteAddr() - stream.TraceContext(trInfo.tr) + if dl, ok := stream.Context().Deadline(); ok { trInfo.firstLine.deadline = dl.Sub(time.Now()) } @@ -532,11 +588,17 @@ 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 { - var cbuf *bytes.Buffer + var ( + cbuf *bytes.Buffer + outPayload *stats.OutPayload + ) if cp != nil { cbuf = new(bytes.Buffer) } - p, err := encode(s.opts.codec, msg, cp, cbuf) + if s.opts.statsHandler != nil { + outPayload = &stats.OutPayload{} + } + p, err := encode(s.opts.codec, msg, cp, cbuf, outPayload) if err != nil { // This typically indicates a fatal issue (e.g., memory // corruption or hardware faults) the application program @@ -547,10 +609,33 @@ func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Str // the optimal option. grpclog.Fatalf("grpc: Server failed to encode response %v", err) } - return t.Write(stream, p, opts) + err = t.Write(stream, p, opts) + if err == nil && outPayload != nil { + outPayload.SentTime = time.Now() + s.opts.statsHandler.HandleRPC(stream.Context(), outPayload) + } + return err } 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 { + begin := &stats.Begin{ + BeginTime: time.Now(), + } + sh.HandleRPC(stream.Context(), begin) + } + defer func() { + if sh != nil { + end := &stats.End{ + EndTime: time.Now(), + } + if err != nil && err != io.EOF { + end.Error = toRPCErr(err) + } + sh.HandleRPC(stream.Context(), end) + } + }() if trInfo != nil { defer trInfo.tr.Finish() trInfo.firstLine.client = false @@ -579,14 +664,14 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err != nil { switch err := err.(type) { case *rpcError: - if err := t.WriteStatus(stream, err.code, err.desc); err != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) + if e := t.WriteStatus(stream, err.code, err.desc); e != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) } case transport.ConnectionError: // Nothing to do here. case transport.StreamError: - if err := t.WriteStatus(stream, err.Code, err.Desc); err != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) + if e := t.WriteStatus(stream, err.Code, err.Desc); e != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) } default: panic(fmt.Sprintf("grpc: Unexpected error (%T) from recvMsg: %v", err, err)) @@ -597,20 +682,29 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := checkRecvPayload(pf, stream.RecvCompress(), s.opts.dc); err != nil { switch err := err.(type) { case *rpcError: - if err := t.WriteStatus(stream, err.code, err.desc); err != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) + if e := t.WriteStatus(stream, err.code, err.desc); e != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) } + return err default: - if err := t.WriteStatus(stream, codes.Internal, err.Error()); err != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) + if e := t.WriteStatus(stream, codes.Internal, err.Error()); e != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) } - + // TODO checkRecvPayload always return RPC error. Add a return here if necessary. + } + } + var inPayload *stats.InPayload + if sh != nil { + inPayload = &stats.InPayload{ + RecvTime: time.Now(), } - return err } statusCode := codes.OK statusDesc := "" df := func(v interface{}) error { + if inPayload != nil { + inPayload.WireLength = len(req) + } if pf == compressionMade { var err error req, err = s.opts.dc.Do(bytes.NewReader(req)) @@ -618,7 +712,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := t.WriteStatus(stream, codes.Internal, err.Error()); err != nil { grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } - return err + return Errorf(codes.Internal, err.Error()) } } if len(req) > s.opts.maxMsgSize { @@ -630,6 +724,12 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := s.opts.codec.Unmarshal(req, v); err != nil { return err } + if inPayload != nil { + inPayload.Payload = v + inPayload.Data = req + inPayload.Length = len(req) + sh.HandleRPC(stream.Context(), inPayload) + } if trInfo != nil { trInfo.tr.LazyLog(&payload{sent: false, msg: v}, true) } @@ -650,9 +750,8 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } if err := t.WriteStatus(stream, statusCode, statusDesc); err != nil { grpclog.Printf("grpc: Server.processUnaryRPC failed to write status: %v", err) - return err } - return nil + return Errorf(statusCode, statusDesc) } if trInfo != nil { trInfo.tr.LazyLog(stringer("OK"), false) @@ -677,23 +776,46 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if trInfo != nil { trInfo.tr.LazyLog(&payload{sent: true, msg: reply}, true) } - return t.WriteStatus(stream, statusCode, statusDesc) + errWrite := t.WriteStatus(stream, statusCode, statusDesc) + if statusCode != codes.OK { + return Errorf(statusCode, statusDesc) + } + return errWrite } } 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 { + begin := &stats.Begin{ + BeginTime: time.Now(), + } + sh.HandleRPC(stream.Context(), begin) + } + defer func() { + if sh != nil { + end := &stats.End{ + EndTime: time.Now(), + } + if err != nil && err != io.EOF { + end.Error = toRPCErr(err) + } + sh.HandleRPC(stream.Context(), end) + } + }() if s.opts.cp != nil { stream.SetSendCompress(s.opts.cp.Type()) } ss := &serverStream{ - t: t, - s: stream, - p: &parser{r: stream}, - codec: s.opts.codec, - cp: s.opts.cp, - dc: s.opts.dc, - maxMsgSize: s.opts.maxMsgSize, - trInfo: trInfo, + t: t, + s: stream, + p: &parser{r: stream}, + codec: s.opts.codec, + cp: s.opts.cp, + dc: s.opts.dc, + maxMsgSize: s.opts.maxMsgSize, + trInfo: trInfo, + statsHandler: sh, } if ss.cp != nil { ss.cbuf = new(bytes.Buffer) @@ -712,15 +834,19 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp }() } var appErr error + var server interface{} + if srv != nil { + server = srv.server + } if s.opts.streamInt == nil { - appErr = sd.Handler(srv.server, ss) + appErr = sd.Handler(server, ss) } else { info := &StreamServerInfo{ FullMethod: stream.Method(), IsClientStream: sd.ClientStreams, IsServerStream: sd.ServerStreams, } - appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler) + appErr = s.opts.streamInt(server, ss, info, sd.Handler) } if appErr != nil { if err, ok := appErr.(*rpcError); ok { @@ -744,7 +870,11 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp } ss.mu.Unlock() } - return t.WriteStatus(ss.s, ss.statusCode, ss.statusDesc) + errWrite := t.WriteStatus(ss.s, ss.statusCode, ss.statusDesc) + if ss.statusCode != codes.OK { + return Errorf(ss.statusCode, ss.statusDesc) + } + return errWrite } @@ -759,7 +889,8 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str trInfo.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{sm}}, true) trInfo.tr.SetError() } - if err := t.WriteStatus(stream, codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", stream.Method())); err != nil { + errDesc := fmt.Sprintf("malformed method name: %q", stream.Method()) + if err := t.WriteStatus(stream, codes.InvalidArgument, errDesc); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() @@ -775,11 +906,16 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str method := sm[pos+1:] srv, ok := s.m[service] if !ok { + if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil { + s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo) + return + } if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"Unknown service %v", []interface{}{service}}, true) trInfo.tr.SetError() } - if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown service %v", service)); err != nil { + errDesc := fmt.Sprintf("unknown service %v", service) + if err := t.WriteStatus(stream, codes.Unimplemented, errDesc); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() @@ -804,7 +940,12 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str trInfo.tr.LazyLog(&fmtStringer{"Unknown method %v", []interface{}{method}}, true) trInfo.tr.SetError() } - if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown method %v", method)); err != nil { + if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil { + s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo) + return + } + errDesc := fmt.Sprintf("unknown method %v", method) + if err := t.WriteStatus(stream, codes.Unimplemented, errDesc); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go index 46810544f19f..0ef2077ce254 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go @@ -37,7 +37,6 @@ import ( "bytes" "errors" "io" - "math" "sync" "time" @@ -45,6 +44,7 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -97,7 +97,7 @@ type ClientStream interface { // 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, error) { +func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) { if cc.dopts.streamInt != nil { return cc.dopts.streamInt(ctx, desc, cc, method, newClientStream, opts...) } @@ -106,11 +106,18 @@ 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) { var ( - t transport.ClientTransport - s *transport.Stream - put func() + t transport.ClientTransport + s *transport.Stream + put func() + cancel context.CancelFunc ) c := defaultCallInfo + if mc, ok := cc.getMethodConfig(method); ok { + c.failFast = !mc.WaitForReady + if mc.Timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, mc.Timeout) + } + } for _, o := range opts { if err := o.before(&c); err != nil { return nil, toRPCErr(err) @@ -143,6 +150,26 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } }() } + sh := cc.dopts.copts.StatsHandler + if sh != nil { + ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method}) + begin := &stats.Begin{ + Client: true, + BeginTime: time.Now(), + FailFast: c.failFast, + } + sh.HandleRPC(ctx, begin) + } + defer func() { + if err != nil && sh != nil { + // Only handle end stats if err != nil. + end := &stats.End{ + Client: true, + Error: err, + } + sh.HandleRPC(ctx, end) + } + }() gopts := BalancerGetOptions{ BlockingWait: !c.failFast, } @@ -180,12 +207,14 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth break } cs := &clientStream{ - opts: opts, - c: c, - desc: desc, - codec: cc.dopts.codec, - cp: cc.dopts.cp, - dc: cc.dopts.dc, + opts: opts, + c: c, + desc: desc, + codec: cc.dopts.codec, + cp: cc.dopts.cp, + dc: cc.dopts.dc, + maxMsgSize: cc.dopts.maxMsgSize, + cancel: cancel, put: put, t: t, @@ -194,6 +223,9 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth tracing: EnableTracing, trInfo: trInfo, + + statsCtx: ctx, + statsHandler: cc.dopts.copts.StatsHandler, } if cc.dopts.cp != nil { cs.cbuf = new(bytes.Buffer) @@ -227,16 +259,18 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth // 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 - cbuf *bytes.Buffer - dc Decompressor + opts []CallOption + c callInfo + t transport.ClientTransport + s *transport.Stream + p *parser + desc *StreamDesc + codec Codec + cp Compressor + cbuf *bytes.Buffer + dc Decompressor + maxMsgSize int + cancel context.CancelFunc tracing bool // set to EnableTracing when the clientStream is created. @@ -246,6 +280,12 @@ type clientStream struct { // 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. 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 } func (cs *clientStream) Context() context.Context { @@ -274,6 +314,8 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { } cs.mu.Unlock() } + // TODO Investigate how to signal the stats handling party. + // generate error stats if err != nil && err != io.EOF? defer func() { if err != nil { cs.finish(err) @@ -296,7 +338,13 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { } err = toRPCErr(err) }() - out, err := encode(cs.codec, m, cs.cp, cs.cbuf) + var outPayload *stats.OutPayload + if cs.statsHandler != nil { + outPayload = &stats.OutPayload{ + Client: true, + } + } + out, err := encode(cs.codec, m, cs.cp, cs.cbuf, outPayload) defer func() { if cs.cbuf != nil { cs.cbuf.Reset() @@ -305,11 +353,37 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { if err != nil { return Errorf(codes.Internal, "grpc: %v", err) } - return cs.t.Write(cs.s, out, &transport.Options{Last: false}) + err = cs.t.Write(cs.s, out, &transport.Options{Last: false}) + if err == nil && outPayload != nil { + outPayload.SentTime = time.Now() + cs.statsHandler.HandleRPC(cs.statsCtx, outPayload) + } + return err } func (cs *clientStream) RecvMsg(m interface{}) (err error) { - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32) + defer func() { + if err != nil && cs.statsHandler != nil { + // Only generate End if err != nil. + // If err == nil, it's not the last RecvMsg. + // The last RecvMsg gets either an RPC error or io.EOF. + end := &stats.End{ + Client: true, + EndTime: time.Now(), + } + if err != io.EOF { + end.Error = toRPCErr(err) + } + cs.statsHandler.HandleRPC(cs.statsCtx, end) + } + }() + var inPayload *stats.InPayload + if cs.statsHandler != nil { + inPayload = &stats.InPayload{ + Client: true, + } + } + err = recv(cs.p, cs.codec, cs.s, cs.dc, m, cs.maxMsgSize, inPayload) defer func() { // err != nil indicates the termination of the stream. if err != nil { @@ -324,11 +398,15 @@ func (cs *clientStream) RecvMsg(m interface{}) (err error) { } 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. - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32) + // This recv expects EOF or errors, so we don't collect inPayload. + err = recv(cs.p, cs.codec, cs.s, cs.dc, m, cs.maxMsgSize, nil) cs.closeTransportStream(err) if err == nil { return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) @@ -384,6 +462,11 @@ func (cs *clientStream) closeTransportStream(err error) { } func (cs *clientStream) finish(err error) { + defer func() { + if cs.cancel != nil { + cs.cancel() + } + }() cs.mu.Lock() defer cs.mu.Unlock() for _, o := range cs.opts { @@ -441,6 +524,8 @@ type serverStream struct { statusDesc string trInfo *traceInfo + statsHandler stats.Handler + mu sync.Mutex // protects trInfo.tr after the service handler runs. } @@ -482,7 +567,11 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { ss.mu.Unlock() } }() - out, err := encode(ss.codec, m, ss.cp, ss.cbuf) + var outPayload *stats.OutPayload + if ss.statsHandler != nil { + outPayload = &stats.OutPayload{} + } + out, err := encode(ss.codec, m, ss.cp, ss.cbuf, outPayload) defer func() { if ss.cbuf != nil { ss.cbuf.Reset() @@ -495,6 +584,10 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { if err := ss.t.Write(ss.s, out, &transport.Options{Last: false}); err != nil { return toRPCErr(err) } + if outPayload != nil { + outPayload.SentTime = time.Now() + ss.statsHandler.HandleRPC(ss.s.Context(), outPayload) + } return nil } @@ -513,7 +606,11 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { ss.mu.Unlock() } }() - if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxMsgSize); err != nil { + var inPayload *stats.InPayload + if ss.statsHandler != nil { + inPayload = &stats.InPayload{} + } + if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxMsgSize, inPayload); err != nil { if err == io.EOF { return err } @@ -522,5 +619,8 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { } return toRPCErr(err) } + if inPayload != nil { + ss.statsHandler.HandleRPC(ss.s.Context(), inPayload) + } return nil } diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go new file mode 100644 index 000000000000..0f3664767423 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go @@ -0,0 +1,54 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package tap defines the function handles which are executed on the transport +// layer of gRPC-Go and related information. Everything here is EXPERIMENTAL. +package tap + +import ( + "golang.org/x/net/context" +) + +// Info defines the relevant information needed by the handles. +type Info struct { + // FullMethodName is the string of grpc method (in the format of + // /package.service/method). + FullMethodName string + // TODO: More to be added. +} + +// ServerInHandle defines the function which runs when a new stream is created +// on the server side. Note that it is executed in the per-connection I/O goroutine(s) instead +// of per-RPC goroutine. Therefore, users should NOT have any blocking/time-consuming +// work in this handle. Otherwise all the RPCs would slow down. +type ServerInHandle func(ctx context.Context, info *Info) (context.Context, error) diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go index 4ef0830b56ca..0edbe53a700d 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go @@ -35,7 +35,9 @@ package transport import ( "fmt" + "math" "sync" + "time" "golang.org/x/net/http2" ) @@ -44,8 +46,12 @@ const ( // The default value of flow control window size in HTTP2 spec. defaultWindowSize = 65535 // The initial window size for flow control. - initialWindowSize = defaultWindowSize // for an RPC - initialConnWindowSize = defaultWindowSize * 16 // for a connection + initialWindowSize = defaultWindowSize // for an RPC + initialConnWindowSize = defaultWindowSize * 16 // for a connection + infinity = time.Duration(math.MaxInt64) + defaultKeepaliveTime = infinity + defaultKeepaliveTimeout = time.Duration(20 * time.Second) + defaultMaxStreamsClient = 100 ) // The following defines various control items which could flow through @@ -111,35 +117,9 @@ func newQuotaPool(q int) *quotaPool { return qb } -// add adds n to the available quota and tries to send it on acquire. -func (qb *quotaPool) add(n int) { - qb.mu.Lock() - defer qb.mu.Unlock() - qb.quota += n - if qb.quota <= 0 { - return - } - select { - case qb.c <- qb.quota: - qb.quota = 0 - default: - } -} - -// cancel cancels the pending quota sent on acquire, if any. -func (qb *quotaPool) cancel() { - qb.mu.Lock() - defer qb.mu.Unlock() - select { - case n := <-qb.c: - qb.quota += n - default: - } -} - -// reset cancels the pending quota sent on acquired, incremented by v and sends +// add cancels the pending quota sent on acquired, incremented by v and sends // it back on acquire. -func (qb *quotaPool) reset(v int) { +func (qb *quotaPool) add(v int) { qb.mu.Lock() defer qb.mu.Unlock() select { @@ -151,6 +131,10 @@ func (qb *quotaPool) reset(v int) { if qb.quota <= 0 { return } + // 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 diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go index 114e34906a14..10b6dc0b19d7 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go @@ -268,7 +268,7 @@ func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { }) } -func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream)) { +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 diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go index 2b0f68016af5..5f579b8b01b9 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go @@ -41,6 +41,7 @@ import ( "net" "strings" "sync" + "sync/atomic" "time" "golang.org/x/net/context" @@ -49,18 +50,23 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + "google.golang.org/grpc/stats" ) // http2Client implements the ClientTransport interface with HTTP2. type http2Client struct { - target string // server name/addr - userAgent string - md interface{} - conn net.Conn // underlying communication channel - authInfo credentials.AuthInfo // auth info about the connection - nextID uint32 // the next stream ID to be used + ctx context.Context + target string // server name/addr + userAgent string + md interface{} + conn net.Conn // underlying communication channel + remoteAddr net.Addr + localAddr net.Addr + authInfo credentials.AuthInfo // auth info about the connection + nextID uint32 // the next stream ID to be used // writableChan synchronizes write access to the transport. // A writer acquires the write lock by sending a value on writableChan @@ -76,6 +82,8 @@ type http2Client struct { // goAway is closed to notify the upper layer (i.e., addrConn.transportMonitor) // that the server sent GoAway on this transport. goAway chan struct{} + // awakenKeepalive is used to wake up keepalive when after it has gone dormant. + awakenKeepalive chan struct{} framer *framer hBuf *bytes.Buffer // the buffer for HPACK encoding @@ -95,6 +103,13 @@ type http2Client struct { creds []credentials.PerRPCCredentials + // Boolean to keep track of reading activity on transport. + // 1 is true and 0 is false. + activity uint32 // Accessed atomically. + kp keepalive.ClientParameters + + statsHandler stats.Handler + mu sync.Mutex // guard the following variables state transportState // the state of underlying connection activeStreams map[uint32]*Stream @@ -150,6 +165,9 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( scheme := "http" conn, err := dial(ctx, opts.Dialer, addr.Addr) if err != nil { + if opts.FailOnNonTempDialError { + return nil, connectionErrorf(isTemporary(err), err, "transport: %v", err) + } return nil, connectionErrorf(true, err, "transport: %v", err) } // Any further errors will close the underlying connection @@ -169,23 +187,31 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( return nil, connectionErrorf(temp, err, "transport: %v", err) } } - ua := primaryUA - if opts.UserAgent != "" { - ua = opts.UserAgent + " " + ua + kp := opts.KeepaliveParams + // Validate keepalive parameters. + if kp.Time == 0 { + kp.Time = defaultKeepaliveTime + } + if kp.Timeout == 0 { + kp.Timeout = defaultKeepaliveTimeout } var buf bytes.Buffer t := &http2Client{ - target: addr.Addr, - userAgent: ua, - md: addr.Metadata, - conn: conn, - authInfo: authInfo, + ctx: ctx, + target: addr.Addr, + 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, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), errorChan: make(chan struct{}), goAway: make(chan struct{}), + awakenKeepalive: make(chan struct{}, 1), framer: newFramer(conn), hBuf: &buf, hEnc: hpack.NewEncoder(&buf), @@ -196,8 +222,24 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( state: reachable, activeStreams: make(map[uint32]*Stream), creds: opts.PerRPCCredentials, - maxStreams: math.MaxInt32, + maxStreams: defaultMaxStreamsClient, + streamsQuota: newQuotaPool(defaultMaxStreamsClient), streamSendQuota: defaultWindowSize, + kp: kp, + statsHandler: opts.StatsHandler, + } + // Make sure awakenKeepalive can't be written upon. + // keepalive routine will make it writable, if need be. + t.awakenKeepalive <- struct{}{} + if t.statsHandler != nil { + t.ctx = t.statsHandler.TagConn(t.ctx, &stats.ConnTagInfo{ + RemoteAddr: t.remoteAddr, + LocalAddr: t.localAddr, + }) + connBegin := &stats.ConnBegin{ + Client: true, + } + t.statsHandler.HandleConn(t.ctx, connBegin) } // Start the reader goroutine for incoming message. Each transport has // a dedicated goroutine which reads HTTP2 frame from network. Then it @@ -233,6 +275,9 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( } } go t.controller() + if t.kp.Time != infinity { + go t.keepalive() + } t.writableChan <- 0 return t, nil } @@ -270,12 +315,13 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { // streams. func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) { pr := &peer.Peer{ - Addr: t.conn.RemoteAddr(), + Addr: t.remoteAddr, } // Attach Auth info if there is any. if t.authInfo != nil { pr.AuthInfo = t.authInfo } + userCtx := ctx ctx = peer.NewContext(ctx, pr) authData := make(map[string]string) for _, c := range t.creds { @@ -313,21 +359,18 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea t.mu.Unlock() return nil, ErrConnClosing } - checkStreamsQuota := t.streamsQuota != nil t.mu.Unlock() - if checkStreamsQuota { - sq, err := wait(ctx, nil, nil, t.shutdownChan, t.streamsQuota.acquire()) - if err != nil { - return nil, err - } - // Returns the quota balance back. - if sq > 1 { - t.streamsQuota.add(sq - 1) - } + sq, err := wait(ctx, nil, nil, t.shutdownChan, t.streamsQuota.acquire()) + if err != nil { + return nil, err + } + // Returns the quota balance back. + if sq > 1 { + t.streamsQuota.add(sq - 1) } if _, err := wait(ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil { // Return the quota back now because there is no stream returned to the caller. - if _, ok := err.(StreamError); ok && checkStreamsQuota { + if _, ok := err.(StreamError); ok { t.streamsQuota.add(1) } return nil, err @@ -335,9 +378,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea t.mu.Lock() if t.state == draining { t.mu.Unlock() - if checkStreamsQuota { - t.streamsQuota.add(1) - } + t.streamsQuota.add(1) // Need to make t writable again so that the rpc in flight can still proceed. t.writableChan <- 0 return nil, ErrStreamDrain @@ -347,18 +388,19 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea return nil, ErrConnClosing } s := t.newStream(ctx, callHdr) + s.clientStatsCtx = userCtx t.activeStreams[s.id] = s - - // This stream is not counted when applySetings(...) initialize t.streamsQuota. - // Reset t.streamsQuota to the right value. - var reset bool - if !checkStreamsQuota && t.streamsQuota != nil { - reset = true + // 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.framer.writePing(false, false, [8]byte{}) + default: + } } + t.mu.Unlock() - if reset { - t.streamsQuota.reset(-1) - } // HPACK encodes various headers. Note that once WriteField(...) is // called, the corresponding headers/continuation frame has to be sent @@ -413,6 +455,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea } } first := true + bufLen := t.hBuf.Len() // Sends the headers in a single batch even when they span multiple frames. for !endHeaders { size := t.hBuf.Len() @@ -447,6 +490,17 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea return nil, connectionErrorf(true, err, "transport: %v", err) } } + if t.statsHandler != nil { + outHeader := &stats.OutHeader{ + Client: true, + WireLength: bufLen, + FullMethod: callHdr.Method, + RemoteAddr: t.remoteAddr, + LocalAddr: t.localAddr, + Compression: callHdr.SendCompress, + } + t.statsHandler.HandleRPC(s.clientStatsCtx, outHeader) + } t.writableChan <- 0 return s, nil } @@ -454,15 +508,11 @@ 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) { - var updateStreams bool t.mu.Lock() if t.activeStreams == nil { t.mu.Unlock() return } - if t.streamsQuota != nil { - updateStreams = true - } 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. @@ -471,10 +521,27 @@ func (t *http2Client) CloseStream(s *Stream, err error) { return } t.mu.Unlock() - if updateStreams { - t.streamsQuota.add(1) - } + // 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 q := s.fc.resetPendingData(); q > 0 { if n := t.fc.onRead(q); n > 0 { t.controlBuf.put(&windowUpdate{0, n}) @@ -490,8 +557,9 @@ func (t *http2Client) CloseStream(s *Stream, err error) { } s.state = streamDone s.mu.Unlock() - if se, ok := err.(StreamError); ok && se.Code != codes.DeadlineExceeded { - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeCancel}) + if _, ok := err.(StreamError); ok { + rstStream = true + rstError = http2.ErrCodeCancel } } @@ -525,6 +593,12 @@ func (t *http2Client) Close() (err error) { s.mu.Unlock() s.write(recvMsg{err: ErrConnClosing}) } + if t.statsHandler != nil { + connEnd := &stats.ConnEnd{ + Client: true, + } + t.statsHandler.HandleConn(t.ctx, connEnd) + } return } @@ -582,19 +656,14 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error { var p []byte if r.Len() > 0 { size := http2MaxFrameLen - s.sendQuotaPool.add(0) // Wait until the stream has some quota to send the data. sq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, s.sendQuotaPool.acquire()) if err != nil { return err } - t.sendQuotaPool.add(0) // Wait until the transport has some quota to send the data. tq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, t.sendQuotaPool.acquire()) if err != nil { - if _, ok := err.(StreamError); ok || err == io.EOF { - t.sendQuotaPool.cancel() - } return err } if sq < size { @@ -704,7 +773,7 @@ func (t *http2Client) updateWindow(s *Stream, n uint32) { } func (t *http2Client) handleData(f *http2.DataFrame) { - size := len(f.Data()) + size := f.Header().Length if err := t.fc.onData(uint32(size)); err != nil { t.notifyError(connectionErrorf(true, err, "%v", err)) return @@ -718,6 +787,11 @@ func (t *http2Client) handleData(f *http2.DataFrame) { return } if size > 0 { + if f.Header().Flags.Has(http2.FlagDataPadded) { + if w := t.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { + t.controlBuf.put(&windowUpdate{0, w}) + } + } s.mu.Lock() if s.state == streamDone { s.mu.Unlock() @@ -731,19 +805,27 @@ func (t *http2Client) handleData(f *http2.DataFrame) { s.state = streamDone s.statusCode = codes.Internal s.statusDesc = err.Error() + s.rstStream = true + s.rstError = http2.ErrCodeFlowControl close(s.done) s.mu.Unlock() s.write(recvMsg{err: io.EOF}) - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) 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}) + } + } 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? - data := make([]byte, size) - copy(data, f.Data()) - s.write(recvMsg{data: data}) + if len(f.Data()) > 0 { + data := make([]byte, len(f.Data())) + copy(data, f.Data()) + s.write(recvMsg{data: data}) + } } // The server has closed the stream without sending trailers. Record that // the read direction is closed, and set the status appropriately. @@ -874,6 +956,24 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } endStream := frame.StreamEnded() + var isHeader bool + defer func() { + if t.statsHandler != nil { + if isHeader { + inHeader := &stats.InHeader{ + Client: true, + WireLength: int(frame.Header().Length), + } + t.statsHandler.HandleRPC(s.clientStatsCtx, inHeader) + } else { + inTrailer := &stats.InTrailer{ + Client: true, + WireLength: int(frame.Header().Length), + } + t.statsHandler.HandleRPC(s.clientStatsCtx, inTrailer) + } + } + }() s.mu.Lock() if !endStream { @@ -885,6 +985,7 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } close(s.headerChan) s.headerDone = true + isHeader = true } if !endStream || s.state == streamDone { s.mu.Unlock() @@ -925,6 +1026,7 @@ func (t *http2Client) reader() { t.notifyError(err) return } + atomic.CompareAndSwapUint32(&t.activity, 0, 1) sf, ok := frame.(*http2.SettingsFrame) if !ok { t.notifyError(err) @@ -935,6 +1037,7 @@ func (t *http2Client) reader() { // loop to keep reading incoming messages on this transport. for { frame, err := t.framer.readFrame() + atomic.CompareAndSwapUint32(&t.activity, 0, 1) if err != nil { // Abort an active stream if the http2.Framer returns a // http2.StreamError. This can happen only if the server's response @@ -986,21 +1089,15 @@ func (t *http2Client) applySettings(ss []http2.Setting) { s.Val = math.MaxInt32 } t.mu.Lock() - reset := t.streamsQuota != nil - if !reset { - t.streamsQuota = newQuotaPool(int(s.Val) - len(t.activeStreams)) - } ms := t.maxStreams t.maxStreams = int(s.Val) t.mu.Unlock() - if reset { - t.streamsQuota.reset(int(s.Val) - ms) - } + 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.reset(int(s.Val - t.streamSendQuota)) + stream.sendQuotaPool.add(int(s.Val - t.streamSendQuota)) } t.streamSendQuota = s.Val t.mu.Unlock() @@ -1028,6 +1125,12 @@ func (t *http2Client) controller() { t.framer.writeSettings(true, i.ss...) } 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. + t.streamsQuota.add(1) t.framer.writeRSTStream(true, i.streamID, i.code) case *flushIO: t.framer.flushWrite() @@ -1047,6 +1150,61 @@ func (t *http2Client) controller() { } } +// keepalive running in a separate goroutune makes sure the connection is alive by sending pings. +func (t *http2Client) keepalive() { + p := &ping{data: [8]byte{}} + timer := time.NewTimer(t.kp.Time) + for { + select { + case <-timer.C: + if atomic.CompareAndSwapUint32(&t.activity, 1, 0) { + timer.Reset(t.kp.Time) + continue + } + // Check if keepalive should go dormant. + t.mu.Lock() + if len(t.activeStreams) < 1 && !t.kp.PermitWithoutStream { + // Make awakenKeepalive writable. + <-t.awakenKeepalive + t.mu.Unlock() + select { + case <-t.awakenKeepalive: + // If the control gets here a ping has been sent + // need to reset the timer with keepalive.Timeout. + case <-t.shutdownChan: + return + } + } else { + t.mu.Unlock() + // Send ping. + t.controlBuf.put(p) + } + + // By the time control gets here a ping has been sent one way or the other. + timer.Reset(t.kp.Timeout) + select { + case <-timer.C: + if atomic.CompareAndSwapUint32(&t.activity, 1, 0) { + timer.Reset(t.kp.Time) + continue + } + t.Close() + return + case <-t.shutdownChan: + if !timer.Stop() { + <-timer.C + } + return + } + case <-t.shutdownChan: + if !timer.Stop() { + <-timer.C + } + return + } + } +} + func (t *http2Client) Error() <-chan struct{} { return t.errorChan } diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go index a62fb7c228f1..f5c590f438f7 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go @@ -50,6 +50,8 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + "google.golang.org/grpc/stats" + "google.golang.org/grpc/tap" ) // ErrIllegalHeaderWrite indicates that setting header is illegal because of @@ -58,9 +60,13 @@ var ErrIllegalHeaderWrite = errors.New("transport: the stream is done or WriteHe // http2Server implements the ServerTransport interface with HTTP2. type http2Server struct { + ctx context.Context conn net.Conn + remoteAddr net.Addr + localAddr net.Addr maxStreamID uint32 // max stream ID ever seen authInfo credentials.AuthInfo // auth info about the connection + inTapHandle tap.ServerInHandle // writableChan synchronizes write access to the transport. // A writer acquires the write lock by receiving a value on writableChan // and releases it by sending on writableChan. @@ -82,6 +88,8 @@ type http2Server struct { // sendQuotaPool provides flow control to outbound message. sendQuotaPool *quotaPool + stats stats.Handler + mu sync.Mutex // guard the following state transportState activeStreams map[uint32]*Stream @@ -91,12 +99,13 @@ type http2Server struct { // newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is // returned if something goes wrong. -func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthInfo) (_ ServerTransport, err error) { +func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err error) { framer := newFramer(conn) // Send initial settings as connection preface to client. var settings []http2.Setting // TODO(zhaoq): Have a better way to signal "no limit" because 0 is // permitted in the HTTP2 spec. + maxStreams := config.MaxStreams if maxStreams == 0 { maxStreams = math.MaxUint32 } else { @@ -121,12 +130,16 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI } var buf bytes.Buffer t := &http2Server{ + ctx: context.Background(), conn: conn, - authInfo: authInfo, + remoteAddr: conn.RemoteAddr(), + localAddr: conn.LocalAddr(), + authInfo: config.AuthInfo, framer: framer, hBuf: &buf, hEnc: hpack.NewEncoder(&buf), maxStreams: maxStreams, + inTapHandle: config.InTapHandle, controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), @@ -135,6 +148,15 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI shutdownChan: make(chan struct{}), activeStreams: make(map[uint32]*Stream), streamSendQuota: defaultWindowSize, + stats: config.StatsHandler, + } + if t.stats != nil { + t.ctx = t.stats.TagConn(t.ctx, &stats.ConnTagInfo{ + RemoteAddr: t.remoteAddr, + LocalAddr: t.localAddr, + }) + connBegin := &stats.ConnBegin{} + t.stats.HandleConn(t.ctx, connBegin) } go t.controller() t.writableChan <- 0 @@ -142,7 +164,7 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI } // operateHeader takes action on the decoded headers. -func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream)) (close bool) { +func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream), traceCtx func(context.Context, string) context.Context) (close bool) { buf := newRecvBuffer() s := &Stream{ id: frame.Header().StreamID, @@ -168,12 +190,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } s.recvCompress = state.encoding if state.timeoutSet { - s.ctx, s.cancel = context.WithTimeout(context.TODO(), state.timeout) + s.ctx, s.cancel = context.WithTimeout(t.ctx, state.timeout) } else { - s.ctx, s.cancel = context.WithCancel(context.TODO()) + s.ctx, s.cancel = context.WithCancel(t.ctx) } pr := &peer.Peer{ - Addr: t.conn.RemoteAddr(), + Addr: t.remoteAddr, } // Attach Auth info if there is any. if t.authInfo != nil { @@ -195,6 +217,18 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } s.recvCompress = state.encoding s.method = state.method + if t.inTapHandle != nil { + var err error + info := &tap.Info{ + FullMethodName: state.method, + } + s.ctx, err = t.inTapHandle(s.ctx, info) + if err != nil { + // TODO: Log the real error. + t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) + return + } + } t.mu.Lock() if t.state != reachable { t.mu.Unlock() @@ -218,13 +252,26 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( s.windowHandler = func(n int) { t.updateWindow(s, uint32(n)) } + s.ctx = traceCtx(s.ctx, s.method) + if t.stats != nil { + s.ctx = t.stats.TagRPC(s.ctx, &stats.RPCTagInfo{FullMethodName: s.method}) + inHeader := &stats.InHeader{ + FullMethod: s.method, + RemoteAddr: t.remoteAddr, + LocalAddr: t.localAddr, + Compression: s.recvCompress, + WireLength: int(frame.Header().Length), + } + t.stats.HandleRPC(s.ctx, inHeader) + } handle(s) return } // HandleStreams receives incoming streams using the given handler. This is // typically run in a separate goroutine. -func (t *http2Server) HandleStreams(handle func(*Stream)) { +// traceCtx attaches trace to ctx and returns the new context. +func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) { // Check the validity of client preface. preface := make([]byte, len(clientPreface)) if _, err := io.ReadFull(t.conn, preface); err != nil { @@ -279,7 +326,7 @@ func (t *http2Server) HandleStreams(handle func(*Stream)) { } switch frame := frame.(type) { case *http2.MetaHeadersFrame: - if t.operateHeaders(frame, handle) { + if t.operateHeaders(frame, handle, traceCtx) { t.Close() break } @@ -334,7 +381,7 @@ func (t *http2Server) updateWindow(s *Stream, n uint32) { } func (t *http2Server) handleData(f *http2.DataFrame) { - size := len(f.Data()) + size := f.Header().Length if err := t.fc.onData(uint32(size)); err != nil { grpclog.Printf("transport: http2Server %v", err) t.Close() @@ -349,6 +396,11 @@ func (t *http2Server) handleData(f *http2.DataFrame) { return } if size > 0 { + if f.Header().Flags.Has(http2.FlagDataPadded) { + if w := t.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { + t.controlBuf.put(&windowUpdate{0, w}) + } + } s.mu.Lock() if s.state == streamDone { s.mu.Unlock() @@ -364,13 +416,20 @@ func (t *http2Server) handleData(f *http2.DataFrame) { t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) 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}) + } + } 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? - data := make([]byte, size) - copy(data, f.Data()) - s.write(recvMsg{data: data}) + if len(f.Data()) > 0 { + data := make([]byte, len(f.Data())) + copy(data, f.Data()) + s.write(recvMsg{data: data}) + } } if f.Header().Flags.Has(http2.FlagDataEndStream) { // Received the end of stream from the client. @@ -492,9 +551,16 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry}) } } + bufLen := t.hBuf.Len() if err := t.writeHeaders(s, t.hBuf, false); err != nil { return err } + if t.stats != nil { + outHeader := &stats.OutHeader{ + WireLength: bufLen, + } + t.stats.HandleRPC(s.Context(), outHeader) + } t.writableChan <- 0 return nil } @@ -547,10 +613,17 @@ func (t *http2Server) WriteStatus(s *Stream, statusCode codes.Code, statusDesc s t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry}) } } + bufLen := t.hBuf.Len() if err := t.writeHeaders(s, t.hBuf, true); err != nil { t.Close() return err } + if t.stats != nil { + outTrailer := &stats.OutTrailer{ + WireLength: bufLen, + } + t.stats.HandleRPC(s.Context(), outTrailer) + } t.closeStream(s) t.writableChan <- 0 return nil @@ -579,19 +652,14 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error { return nil } size := http2MaxFrameLen - s.sendQuotaPool.add(0) // Wait until the stream has some quota to send the data. sq, err := wait(s.ctx, nil, nil, t.shutdownChan, s.sendQuotaPool.acquire()) if err != nil { return err } - t.sendQuotaPool.add(0) // Wait until the transport has some quota to send the data. tq, err := wait(s.ctx, nil, nil, t.shutdownChan, t.sendQuotaPool.acquire()) if err != nil { - if _, ok := err.(StreamError); ok { - t.sendQuotaPool.cancel() - } return err } if sq < size { @@ -659,7 +727,7 @@ func (t *http2Server) applySettings(ss []http2.Setting) { t.mu.Lock() defer t.mu.Unlock() for _, stream := range t.activeStreams { - stream.sendQuotaPool.reset(int(s.Val - t.streamSendQuota)) + stream.sendQuotaPool.add(int(s.Val - t.streamSendQuota)) } t.streamSendQuota = s.Val } @@ -736,6 +804,10 @@ func (t *http2Server) Close() (err error) { for _, s := range streams { s.cancel() } + if t.stats != nil { + connEnd := &stats.ConnEnd{} + t.stats.HandleConn(t.ctx, connEnd) + } return } @@ -767,7 +839,7 @@ func (t *http2Server) closeStream(s *Stream) { } func (t *http2Server) RemoteAddr() net.Addr { - return t.conn.RemoteAddr() + return t.remoteAddr } func (t *http2Server) Drain() { diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go index a3c68d4cac7e..6ce43c332e61 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go @@ -52,8 +52,6 @@ import ( ) const ( - // The primary user agent - primaryUA = "grpc-go/1.0" // http2MaxFrameLen specifies the max length of a HTTP2 frame. http2MaxFrameLen = 16384 // 16KB frame // http://http2.github.io/http2-spec/#SettingValues diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go index 21f757ac64eb..0fc4c146f90b 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go @@ -35,7 +35,7 @@ Package transport defines and implements message oriented communication channel to complete various transactions (e.g., an RPC). */ -package transport +package transport // import "google.golang.org/grpc/transport" import ( "bytes" @@ -45,10 +45,13 @@ import ( "sync" "golang.org/x/net/context" - "golang.org/x/net/trace" + "golang.org/x/net/http2" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" + "google.golang.org/grpc/tap" ) // recvMsg represents the received msg from the transport. All transport @@ -167,6 +170,11 @@ type Stream struct { id uint32 // nil for client side Stream. st ServerTransport + // clientStatsCtx keeps the user context for stats handling. + // It's only valid on client side. Server side stats context is same as s.ctx. + // All client side stats collection should use the clientStatsCtx (instead of the stream context) + // so that all the generated stats for a particular RPC can be associated in the processing phase. + clientStatsCtx context.Context // ctx is the associated context of the stream. ctx context.Context // cancel is always nil for client side Stream. @@ -207,6 +215,11 @@ type Stream struct { // the status received from the server. statusCode codes.Code statusDesc string + // 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 } // RecvCompress returns the compression algorithm applied to the inbound @@ -266,11 +279,6 @@ func (s *Stream) Context() context.Context { return s.ctx } -// TraceContext recreates the context of s with a trace.Trace. -func (s *Stream) TraceContext(tr trace.Trace) { - s.ctx = trace.NewContext(s.ctx, tr) -} - // Method returns the method for the stream. func (s *Stream) Method() string { return s.method @@ -330,6 +338,12 @@ func (s *Stream) Read(p []byte) (n int, err error) { return } +// GoString is implemented by Stream so context.String() won't +// race when printing %#v. +func (s *Stream) GoString() string { + return fmt.Sprintf("", s, s.method) +} + // The key to save transport.Stream in the context. type streamKey struct{} @@ -355,22 +369,39 @@ const ( draining ) +// ServerConfig consists of all the configurations to establish a server transport. +type ServerConfig struct { + MaxStreams uint32 + AuthInfo credentials.AuthInfo + InTapHandle tap.ServerInHandle + StatsHandler stats.Handler +} + // NewServerTransport creates a ServerTransport with conn or non-nil error // if it fails. -func NewServerTransport(protocol string, conn net.Conn, maxStreams uint32, authInfo credentials.AuthInfo) (ServerTransport, error) { - return newHTTP2Server(conn, maxStreams, authInfo) +func NewServerTransport(protocol string, conn net.Conn, config *ServerConfig) (ServerTransport, error) { + return newHTTP2Server(conn, config) } // ConnectOptions covers all relevant options for communicating with the server. type ConnectOptions struct { // UserAgent is the application user agent. UserAgent string + // Authority is the :authority pseudo-header to use. This field has no effect if + // TransportCredentials is set. + Authority string // Dialer specifies how to dial a network address. Dialer func(context.Context, string) (net.Conn, error) + // FailOnNonTempDialError specifies if gRPC fails on non-temporary dial errors. + FailOnNonTempDialError bool // PerRPCCredentials stores the PerRPCCredentials required to issue RPCs. PerRPCCredentials []credentials.PerRPCCredentials // TransportCredentials stores the Authenticator required to setup a client connection. TransportCredentials credentials.TransportCredentials + // KeepaliveParams stores the keepalive parameters. + KeepaliveParams keepalive.ClientParameters + // StatsHandler stores the handler for stats. + StatsHandler stats.Handler } // TargetInfo contains the information of the target such as network address and metadata. @@ -466,7 +497,7 @@ type ClientTransport interface { // Write methods for a given Stream will be called serially. type ServerTransport interface { // HandleStreams receives incoming streams using the given handler. - HandleStreams(func(*Stream)) + HandleStreams(func(*Stream), func(context.Context, string) context.Context) // WriteHeader sends the header metadata for the given stream. // WriteHeader may not be called on all streams. @@ -552,7 +583,7 @@ type StreamError struct { } func (e StreamError) Error() string { - return fmt.Sprintf("stream error: code = %d desc = %q", e.Code, e.Desc) + return fmt.Sprintf("stream error: code = %s desc = %q", e.Code, e.Desc) } // ContextErr converts the error from context package into a StreamError. diff --git a/vendor/google.golang.org/grpc/keepalive/keepalive.go b/vendor/google.golang.org/grpc/keepalive/keepalive.go new file mode 100644 index 000000000000..20672e49d901 --- /dev/null +++ b/vendor/google.golang.org/grpc/keepalive/keepalive.go @@ -0,0 +1,52 @@ +/* + * + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package keepalive defines configurable parameters for point-to-point healthcheck. +package keepalive + +import ( + "time" +) + +// ClientParameters is used to set keepalive parameters on the client-side. +// These configure how the client will actively probe to notice when a connection broken +// and to cause activity so intermediaries are aware the connection is still in use. +type ClientParameters struct { + // After a duration of this time if the client doesn't see any activity it pings the server to see if the transport is still alive. + Time time.Duration // The current default value is infinity. + // After having pinged for keepalive check, the client waits for a duration of Timeout and if no activity is seen even after that + // the connection is closed. + Timeout time.Duration // The current default value is 20 seconds. + // If true, client runs keepalive checks even with no active RPCs. + PermitWithoutStream bool +} diff --git a/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go b/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go new file mode 100644 index 000000000000..b24dcd8d343d --- /dev/null +++ b/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go @@ -0,0 +1,225 @@ +// Code generated by protoc-gen-go. +// source: test.proto +// DO NOT EDIT! + +/* +Package grpc_testing is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + SimpleRequest + SimpleResponse +*/ +package grpc_testing + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Unary request. +type SimpleRequest struct { + Id int32 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"` +} + +func (m *SimpleRequest) Reset() { *m = SimpleRequest{} } +func (m *SimpleRequest) String() string { return proto.CompactTextString(m) } +func (*SimpleRequest) ProtoMessage() {} +func (*SimpleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// Unary response, as configured by the request. +type SimpleResponse struct { + Id int32 `protobuf:"varint,3,opt,name=id" json:"id,omitempty"` +} + +func (m *SimpleResponse) Reset() { *m = SimpleResponse{} } +func (m *SimpleResponse) String() string { return proto.CompactTextString(m) } +func (*SimpleResponse) ProtoMessage() {} +func (*SimpleResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*SimpleRequest)(nil), "grpc.testing.SimpleRequest") + proto.RegisterType((*SimpleResponse)(nil), "grpc.testing.SimpleResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for TestService service + +type TestServiceClient interface { + // One request followed by one response. + // The server returns the client id as-is. + UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) +} + +type testServiceClient struct { + cc *grpc.ClientConn +} + +func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient { + return &testServiceClient{cc} +} + +func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) { + out := new(SimpleResponse) + err := grpc.Invoke(ctx, "/grpc.testing.TestService/UnaryCall", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testServiceClient) FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) { + stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[0], c.cc, "/grpc.testing.TestService/FullDuplexCall", opts...) + if err != nil { + return nil, err + } + x := &testServiceFullDuplexCallClient{stream} + return x, nil +} + +type TestService_FullDuplexCallClient interface { + Send(*SimpleRequest) error + Recv() (*SimpleResponse, error) + grpc.ClientStream +} + +type testServiceFullDuplexCallClient struct { + grpc.ClientStream +} + +func (x *testServiceFullDuplexCallClient) Send(m *SimpleRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testServiceFullDuplexCallClient) Recv() (*SimpleResponse, error) { + m := new(SimpleResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for TestService service + +type TestServiceServer interface { + // One request followed by one response. + // The server returns the client id as-is. + UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + FullDuplexCall(TestService_FullDuplexCallServer) error +} + +func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { + s.RegisterService(&_TestService_serviceDesc, srv) +} + +func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SimpleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServiceServer).UnaryCall(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.testing.TestService/UnaryCall", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServiceServer).UnaryCall(ctx, req.(*SimpleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestService_FullDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestServiceServer).FullDuplexCall(&testServiceFullDuplexCallServer{stream}) +} + +type TestService_FullDuplexCallServer interface { + Send(*SimpleResponse) error + Recv() (*SimpleRequest, error) + grpc.ServerStream +} + +type testServiceFullDuplexCallServer struct { + grpc.ServerStream +} + +func (x *testServiceFullDuplexCallServer) Send(m *SimpleResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testServiceFullDuplexCallServer) Recv() (*SimpleRequest, error) { + m := new(SimpleRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _TestService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.testing.TestService", + HandlerType: (*TestServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UnaryCall", + Handler: _TestService_UnaryCall_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "FullDuplexCall", + Handler: _TestService_FullDuplexCall_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "test.proto", +} + +func init() { proto.RegisterFile("test.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 167 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, + 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x49, 0x2f, 0x2a, 0x48, 0xd6, 0x03, 0x09, 0x64, + 0xe6, 0xa5, 0x2b, 0xc9, 0x73, 0xf1, 0x06, 0x67, 0xe6, 0x16, 0xe4, 0xa4, 0x06, 0xa5, 0x16, 0x96, + 0xa6, 0x16, 0x97, 0x08, 0xf1, 0x71, 0x31, 0x65, 0xa6, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xb0, 0x06, + 0x31, 0x65, 0xa6, 0x28, 0x29, 0x70, 0xf1, 0xc1, 0x14, 0x14, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x42, + 0x55, 0x30, 0xc3, 0x54, 0x18, 0x2d, 0x63, 0xe4, 0xe2, 0x0e, 0x49, 0x2d, 0x2e, 0x09, 0x4e, 0x2d, + 0x2a, 0xcb, 0x4c, 0x4e, 0x15, 0x72, 0xe3, 0xe2, 0x0c, 0xcd, 0x4b, 0x2c, 0xaa, 0x74, 0x4e, 0xcc, + 0xc9, 0x11, 0x92, 0xd6, 0x43, 0xb6, 0x4e, 0x0f, 0xc5, 0x2e, 0x29, 0x19, 0xec, 0x92, 0x50, 0x7b, + 0xfc, 0xb9, 0xf8, 0xdc, 0x4a, 0x73, 0x72, 0x5c, 0x4a, 0x0b, 0x72, 0x52, 0x2b, 0x28, 0x34, 0x4c, + 0x83, 0xd1, 0x80, 0x31, 0x89, 0x0d, 0x1c, 0x00, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8d, + 0x82, 0x5b, 0xdd, 0x0e, 0x01, 0x00, 0x00, +} diff --git a/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto b/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto new file mode 100644 index 000000000000..54e6f744f175 --- /dev/null +++ b/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package grpc.testing; + +message SimpleRequest { + int32 id = 2; +} + +message SimpleResponse { + int32 id = 3; +} + +// A simple test service. +service TestService { + // One request followed by one response. + // The server returns the client id as-is. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream SimpleRequest) returns (stream SimpleResponse); +} diff --git a/vendor/google.golang.org/grpc/stats/handlers.go b/vendor/google.golang.org/grpc/stats/handlers.go new file mode 100644 index 000000000000..26e1a8e2f081 --- /dev/null +++ b/vendor/google.golang.org/grpc/stats/handlers.go @@ -0,0 +1,76 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package stats + +import ( + "net" + + "golang.org/x/net/context" +) + +// ConnTagInfo defines the relevant information needed by connection context tagger. +type ConnTagInfo struct { + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // TODO add QOS related fields. +} + +// RPCTagInfo defines the relevant information needed by RPC context tagger. +type RPCTagInfo struct { + // FullMethodName is the RPC method in the format of /package.service/method. + FullMethodName string +} + +// Handler defines the interface for the related stats handling (e.g., RPCs, connections). +type Handler interface { + // TagRPC can attach some information to the given context. + // The returned context is used in the rest lifetime of the RPC. + TagRPC(context.Context, *RPCTagInfo) context.Context + // HandleRPC processes the RPC stats. + HandleRPC(context.Context, RPCStats) + + // TagConn can attach some information to the given context. + // The returned context will be used for stats handling. + // For conn stats handling, the context used in HandleConn for this + // connection will be derived from the context returned. + // For RPC stats handling, + // - On server side, the context used in HandleRPC for all RPCs on this + // connection will be derived from the context returned. + // - On client side, the context is not derived from the context returned. + TagConn(context.Context, *ConnTagInfo) context.Context + // HandleConn processes the Conn stats. + HandleConn(context.Context, ConnStats) +} diff --git a/vendor/google.golang.org/grpc/stats/stats.go b/vendor/google.golang.org/grpc/stats/stats.go new file mode 100644 index 000000000000..a82448a68bb1 --- /dev/null +++ b/vendor/google.golang.org/grpc/stats/stats.go @@ -0,0 +1,223 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package stats is for collecting and reporting various network and RPC stats. +// This package is for monitoring purpose only. All fields are read-only. +// All APIs are experimental. +package stats // import "google.golang.org/grpc/stats" + +import ( + "net" + "time" +) + +// RPCStats contains stats information about RPCs. +type RPCStats interface { + isRPCStats() + // IsClient returns true if this RPCStats is from client side. + IsClient() bool +} + +// Begin contains stats when an RPC begins. +// FailFast are only valid if Client is true. +type Begin struct { + // Client is true if this Begin is from client side. + Client bool + // BeginTime is the time when the RPC begins. + BeginTime time.Time + // FailFast indicates if this RPC is failfast. + FailFast bool +} + +// IsClient indicates if this is from client side. +func (s *Begin) IsClient() bool { return s.Client } + +func (s *Begin) isRPCStats() {} + +// InPayload contains the information for an incoming payload. +type InPayload struct { + // Client is true if this InPayload is from client side. + Client bool + // Payload is the payload with original type. + Payload interface{} + // Data is the serialized message payload. + Data []byte + // Length is the length of uncompressed data. + Length int + // WireLength is the length of data on wire (compressed, signed, encrypted). + WireLength int + // RecvTime is the time when the payload is received. + RecvTime time.Time +} + +// IsClient indicates if this is from client side. +func (s *InPayload) IsClient() bool { return s.Client } + +func (s *InPayload) isRPCStats() {} + +// InHeader contains stats when a header is received. +// FullMethod, addresses and Compression are only valid if Client is false. +type InHeader struct { + // Client is true if this InHeader is from client side. + Client bool + // WireLength is the wire length of header. + WireLength int + + // FullMethod is the full RPC method string, i.e., /package.service/method. + FullMethod string + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // Compression is the compression algorithm used for the RPC. + Compression string +} + +// IsClient indicates if this is from client side. +func (s *InHeader) IsClient() bool { return s.Client } + +func (s *InHeader) isRPCStats() {} + +// InTrailer contains stats when a trailer is received. +type InTrailer struct { + // Client is true if this InTrailer is from client side. + Client bool + // WireLength is the wire length of trailer. + WireLength int +} + +// IsClient indicates if this is from client side. +func (s *InTrailer) IsClient() bool { return s.Client } + +func (s *InTrailer) isRPCStats() {} + +// OutPayload contains the information for an outgoing payload. +type OutPayload struct { + // Client is true if this OutPayload is from client side. + Client bool + // Payload is the payload with original type. + Payload interface{} + // Data is the serialized message payload. + Data []byte + // Length is the length of uncompressed data. + Length int + // WireLength is the length of data on wire (compressed, signed, encrypted). + WireLength int + // SentTime is the time when the payload is sent. + SentTime time.Time +} + +// IsClient indicates if this is from client side. +func (s *OutPayload) IsClient() bool { return s.Client } + +func (s *OutPayload) isRPCStats() {} + +// OutHeader contains stats when a header is sent. +// FullMethod, addresses and Compression are only valid if Client is true. +type OutHeader struct { + // Client is true if this OutHeader is from client side. + Client bool + // WireLength is the wire length of header. + WireLength int + + // FullMethod is the full RPC method string, i.e., /package.service/method. + FullMethod string + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // Compression is the compression algorithm used for the RPC. + Compression string +} + +// IsClient indicates if this is from client side. +func (s *OutHeader) IsClient() bool { return s.Client } + +func (s *OutHeader) isRPCStats() {} + +// OutTrailer contains stats when a trailer is sent. +type OutTrailer struct { + // Client is true if this OutTrailer is from client side. + Client bool + // WireLength is the wire length of trailer. + WireLength int +} + +// IsClient indicates if this is from client side. +func (s *OutTrailer) IsClient() bool { return s.Client } + +func (s *OutTrailer) isRPCStats() {} + +// End contains stats when an RPC ends. +type End struct { + // Client is true if this End is from client side. + Client bool + // EndTime is the time when the RPC ends. + EndTime time.Time + // Error is the error just happened. Its type is gRPC error. + Error error +} + +// IsClient indicates if this is from client side. +func (s *End) IsClient() bool { return s.Client } + +func (s *End) isRPCStats() {} + +// ConnStats contains stats information about connections. +type ConnStats interface { + isConnStats() + // IsClient returns true if this ConnStats is from client side. + IsClient() bool +} + +// ConnBegin contains the stats of a connection when it is established. +type ConnBegin struct { + // Client is true if this ConnBegin is from client side. + Client bool +} + +// IsClient indicates if this is from client side. +func (s *ConnBegin) IsClient() bool { return s.Client } + +func (s *ConnBegin) isConnStats() {} + +// ConnEnd contains the stats of a connection when it ends. +type ConnEnd struct { + // Client is true if this ConnEnd is from client side. + Client bool +} + +// IsClient indicates if this is from client side. +func (s *ConnEnd) IsClient() bool { return s.Client } + +func (s *ConnEnd) isConnStats() {} diff --git a/vendor/google.golang.org/grpc/stats/stats_test.go b/vendor/google.golang.org/grpc/stats/stats_test.go new file mode 100644 index 000000000000..3e5424beb4b7 --- /dev/null +++ b/vendor/google.golang.org/grpc/stats/stats_test.go @@ -0,0 +1,981 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package stats_test + +import ( + "fmt" + "io" + "net" + "reflect" + "sync" + "testing" + "time" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" + testpb "google.golang.org/grpc/stats/grpc_testing" +) + +func init() { + grpc.EnableTracing = false +} + +type connCtxKey struct{} +type rpcCtxKey struct{} + +var ( + // For headers: + testMetadata = metadata.MD{ + "key1": []string{"value1"}, + "key2": []string{"value2"}, + } + // For trailers: + testTrailerMetadata = metadata.MD{ + "tkey1": []string{"trailerValue1"}, + "tkey2": []string{"trailerValue2"}, + } + // The id for which the service handler should return error. + errorID int32 = 32202 +) + +type testServer struct{} + +func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { + md, ok := metadata.FromContext(ctx) + if ok { + if err := grpc.SendHeader(ctx, md); err != nil { + return nil, grpc.Errorf(grpc.Code(err), "grpc.SendHeader(_, %v) = %v, want ", md, err) + } + if err := grpc.SetTrailer(ctx, testTrailerMetadata); err != nil { + return nil, grpc.Errorf(grpc.Code(err), "grpc.SetTrailer(_, %v) = %v, want ", testTrailerMetadata, err) + } + } + + if in.Id == errorID { + return nil, fmt.Errorf("got error id: %v", in.Id) + } + + return &testpb.SimpleResponse{Id: in.Id}, nil +} + +func (s *testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error { + md, ok := metadata.FromContext(stream.Context()) + if ok { + if err := stream.SendHeader(md); err != nil { + return grpc.Errorf(grpc.Code(err), "%v.SendHeader(%v) = %v, want %v", stream, md, err, nil) + } + stream.SetTrailer(testTrailerMetadata) + } + for { + in, err := stream.Recv() + if err == io.EOF { + // read done. + return nil + } + if err != nil { + return err + } + + if in.Id == errorID { + return fmt.Errorf("got error id: %v", in.Id) + } + + if err := stream.Send(&testpb.SimpleResponse{Id: in.Id}); err != nil { + return err + } + } +} + +// test is an end-to-end test. It should be created with the newTest +// func, modified as needed, and then started with its startServer method. +// It should be cleaned up with the tearDown method. +type test struct { + t *testing.T + compress string + clientStatsHandler stats.Handler + serverStatsHandler stats.Handler + + testServer testpb.TestServiceServer // nil means none + // srv and srvAddr are set once startServer is called. + srv *grpc.Server + srvAddr string + + cc *grpc.ClientConn // nil until requested via clientConn +} + +func (te *test) tearDown() { + if te.cc != nil { + te.cc.Close() + te.cc = nil + } + te.srv.Stop() +} + +type testConfig struct { + compress string +} + +// newTest returns a new test using the provided testing.T and +// environment. It is returned with default values. Tests should +// modify it before calling its startServer and clientConn methods. +func newTest(t *testing.T, tc *testConfig, ch stats.Handler, sh stats.Handler) *test { + te := &test{ + t: t, + compress: tc.compress, + clientStatsHandler: ch, + serverStatsHandler: sh, + } + return te +} + +// startServer starts a gRPC server listening. Callers should defer a +// call to te.tearDown to clean up. +func (te *test) startServer(ts testpb.TestServiceServer) { + te.testServer = ts + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + te.t.Fatalf("Failed to listen: %v", err) + } + var opts []grpc.ServerOption + if te.compress == "gzip" { + opts = append(opts, + grpc.RPCCompressor(grpc.NewGZIPCompressor()), + grpc.RPCDecompressor(grpc.NewGZIPDecompressor()), + ) + } + if te.serverStatsHandler != nil { + opts = append(opts, grpc.StatsHandler(te.serverStatsHandler)) + } + s := grpc.NewServer(opts...) + te.srv = s + if te.testServer != nil { + testpb.RegisterTestServiceServer(s, te.testServer) + } + _, port, err := net.SplitHostPort(lis.Addr().String()) + if err != nil { + te.t.Fatalf("Failed to parse listener address: %v", err) + } + addr := "127.0.0.1:" + port + + go s.Serve(lis) + te.srvAddr = addr +} + +func (te *test) clientConn() *grpc.ClientConn { + if te.cc != nil { + return te.cc + } + opts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock()} + if te.compress == "gzip" { + opts = append(opts, + grpc.WithCompressor(grpc.NewGZIPCompressor()), + grpc.WithDecompressor(grpc.NewGZIPDecompressor()), + ) + } + if te.clientStatsHandler != nil { + opts = append(opts, grpc.WithStatsHandler(te.clientStatsHandler)) + } + + var err error + te.cc, err = grpc.Dial(te.srvAddr, opts...) + if err != nil { + te.t.Fatalf("Dial(%q) = %v", te.srvAddr, err) + } + return te.cc +} + +type rpcConfig struct { + count int // Number of requests and responses for streaming RPCs. + success bool // Whether the RPC should succeed or return error. + failfast bool + streaming bool // Whether the rpc should be a streaming RPC. +} + +func (te *test) doUnaryCall(c *rpcConfig) (*testpb.SimpleRequest, *testpb.SimpleResponse, error) { + var ( + resp *testpb.SimpleResponse + req *testpb.SimpleRequest + err error + ) + tc := testpb.NewTestServiceClient(te.clientConn()) + if c.success { + req = &testpb.SimpleRequest{Id: errorID + 1} + } else { + req = &testpb.SimpleRequest{Id: errorID} + } + ctx := metadata.NewContext(context.Background(), testMetadata) + + resp, err = tc.UnaryCall(ctx, req, grpc.FailFast(c.failfast)) + return req, resp, err +} + +func (te *test) doFullDuplexCallRoundtrip(c *rpcConfig) ([]*testpb.SimpleRequest, []*testpb.SimpleResponse, error) { + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + tc := testpb.NewTestServiceClient(te.clientConn()) + stream, err := tc.FullDuplexCall(metadata.NewContext(context.Background(), testMetadata), grpc.FailFast(c.failfast)) + if err != nil { + return reqs, resps, err + } + var startID int32 + if !c.success { + startID = errorID + } + for i := 0; i < c.count; i++ { + req := &testpb.SimpleRequest{ + Id: int32(i) + startID, + } + reqs = append(reqs, req) + if err = stream.Send(req); err != nil { + return reqs, resps, err + } + var resp *testpb.SimpleResponse + if resp, err = stream.Recv(); err != nil { + return reqs, resps, err + } + resps = append(resps, resp) + } + if err = stream.CloseSend(); err != nil && err != io.EOF { + return reqs, resps, err + } + if _, err = stream.Recv(); err != io.EOF { + return reqs, resps, err + } + + return reqs, resps, nil +} + +type expectedData struct { + method string + serverAddr string + compression string + reqIdx int + requests []*testpb.SimpleRequest + respIdx int + responses []*testpb.SimpleResponse + err error + failfast bool +} + +type gotData struct { + ctx context.Context + client bool + s interface{} // This could be RPCStats or ConnStats. +} + +const ( + begin int = iota + end + inPayload + inHeader + inTrailer + outPayload + outHeader + outTrailer + connbegin + connend +) + +func checkBegin(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.Begin + ) + if st, ok = d.s.(*stats.Begin); !ok { + t.Fatalf("got %T, want Begin", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.BeginTime.IsZero() { + t.Fatalf("st.BeginTime = %v, want ", st.BeginTime) + } + if d.client { + if st.FailFast != e.failfast { + t.Fatalf("st.FailFast = %v, want %v", st.FailFast, e.failfast) + } + } +} + +func checkInHeader(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InHeader + ) + if st, ok = d.s.(*stats.InHeader); !ok { + t.Fatalf("got %T, want InHeader", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } + if !d.client { + if st.FullMethod != e.method { + t.Fatalf("st.FullMethod = %s, want %v", st.FullMethod, e.method) + } + if st.LocalAddr.String() != e.serverAddr { + t.Fatalf("st.LocalAddr = %v, want %v", st.LocalAddr, e.serverAddr) + } + if st.Compression != e.compression { + t.Fatalf("st.Compression = %v, want %v", st.Compression, e.compression) + } + + if connInfo, ok := d.ctx.Value(connCtxKey{}).(*stats.ConnTagInfo); ok { + if connInfo.RemoteAddr != st.RemoteAddr { + t.Fatalf("connInfo.RemoteAddr = %v, want %v", connInfo.RemoteAddr, st.RemoteAddr) + } + if connInfo.LocalAddr != st.LocalAddr { + t.Fatalf("connInfo.LocalAddr = %v, want %v", connInfo.LocalAddr, st.LocalAddr) + } + } else { + t.Fatalf("got context %v, want one with connCtxKey", d.ctx) + } + if rpcInfo, ok := d.ctx.Value(rpcCtxKey{}).(*stats.RPCTagInfo); ok { + if rpcInfo.FullMethodName != st.FullMethod { + t.Fatalf("rpcInfo.FullMethod = %s, want %v", rpcInfo.FullMethodName, st.FullMethod) + } + } else { + t.Fatalf("got context %v, want one with rpcCtxKey", d.ctx) + } + } +} + +func checkInPayload(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InPayload + ) + if st, ok = d.s.(*stats.InPayload); !ok { + t.Fatalf("got %T, want InPayload", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if d.client { + b, err := proto.Marshal(e.responses[e.respIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.responses[e.respIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.responses[e.respIdx]) + } + e.respIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } else { + b, err := proto.Marshal(e.requests[e.reqIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.requests[e.reqIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.requests[e.reqIdx]) + } + e.reqIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } + // TODO check WireLength and ReceivedTime. + if st.RecvTime.IsZero() { + t.Fatalf("st.ReceivedTime = %v, want ", st.RecvTime) + } +} + +func checkInTrailer(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InTrailer + ) + if st, ok = d.s.(*stats.InTrailer); !ok { + t.Fatalf("got %T, want InTrailer", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } +} + +func checkOutHeader(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutHeader + ) + if st, ok = d.s.(*stats.OutHeader); !ok { + t.Fatalf("got %T, want OutHeader", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } + if d.client { + if st.FullMethod != e.method { + t.Fatalf("st.FullMethod = %s, want %v", st.FullMethod, e.method) + } + if st.RemoteAddr.String() != e.serverAddr { + t.Fatalf("st.RemoteAddr = %v, want %v", st.RemoteAddr, e.serverAddr) + } + if st.Compression != e.compression { + t.Fatalf("st.Compression = %v, want %v", st.Compression, e.compression) + } + + if rpcInfo, ok := d.ctx.Value(rpcCtxKey{}).(*stats.RPCTagInfo); ok { + if rpcInfo.FullMethodName != st.FullMethod { + t.Fatalf("rpcInfo.FullMethod = %s, want %v", rpcInfo.FullMethodName, st.FullMethod) + } + } else { + t.Fatalf("got context %v, want one with rpcCtxKey", d.ctx) + } + } +} + +func checkOutPayload(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutPayload + ) + if st, ok = d.s.(*stats.OutPayload); !ok { + t.Fatalf("got %T, want OutPayload", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if d.client { + b, err := proto.Marshal(e.requests[e.reqIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.requests[e.reqIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.requests[e.reqIdx]) + } + e.reqIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } else { + b, err := proto.Marshal(e.responses[e.respIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.responses[e.respIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.responses[e.respIdx]) + } + e.respIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } + // TODO check WireLength and ReceivedTime. + if st.SentTime.IsZero() { + t.Fatalf("st.SentTime = %v, want ", st.SentTime) + } +} + +func checkOutTrailer(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutTrailer + ) + if st, ok = d.s.(*stats.OutTrailer); !ok { + t.Fatalf("got %T, want OutTrailer", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.Client { + t.Fatalf("st IsClient = true, want false") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } +} + +func checkEnd(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.End + ) + if st, ok = d.s.(*stats.End); !ok { + t.Fatalf("got %T, want End", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.EndTime.IsZero() { + t.Fatalf("st.EndTime = %v, want ", st.EndTime) + } + if grpc.Code(st.Error) != grpc.Code(e.err) || grpc.ErrorDesc(st.Error) != grpc.ErrorDesc(e.err) { + t.Fatalf("st.Error = %v, want %v", st.Error, e.err) + } +} + +func checkConnBegin(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.ConnBegin + ) + if st, ok = d.s.(*stats.ConnBegin); !ok { + t.Fatalf("got %T, want ConnBegin", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + st.IsClient() // TODO remove this. +} + +func checkConnEnd(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.ConnEnd + ) + if st, ok = d.s.(*stats.ConnEnd); !ok { + t.Fatalf("got %T, want ConnEnd", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + st.IsClient() // TODO remove this. +} + +type statshandler struct { + mu sync.Mutex + gotRPC []*gotData + gotConn []*gotData +} + +func (h *statshandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context { + return context.WithValue(ctx, connCtxKey{}, info) +} + +func (h *statshandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { + return context.WithValue(ctx, rpcCtxKey{}, info) +} + +func (h *statshandler) HandleConn(ctx context.Context, s stats.ConnStats) { + h.mu.Lock() + defer h.mu.Unlock() + h.gotConn = append(h.gotConn, &gotData{ctx, s.IsClient(), s}) +} + +func (h *statshandler) HandleRPC(ctx context.Context, s stats.RPCStats) { + h.mu.Lock() + defer h.mu.Unlock() + h.gotRPC = append(h.gotRPC, &gotData{ctx, s.IsClient(), s}) +} + +func checkConnStats(t *testing.T, got []*gotData) { + if len(got) <= 0 || len(got)%2 != 0 { + for i, g := range got { + t.Errorf(" - %v, %T = %+v, ctx: %v", i, g.s, g.s, g.ctx) + } + t.Fatalf("got %v stats, want even positive number", len(got)) + } + // The first conn stats must be a ConnBegin. + checkConnBegin(t, got[0], nil) + // The last conn stats must be a ConnEnd. + checkConnEnd(t, got[len(got)-1], nil) +} + +func checkServerStats(t *testing.T, got []*gotData, expect *expectedData, checkFuncs []func(t *testing.T, d *gotData, e *expectedData)) { + if len(got) != len(checkFuncs) { + for i, g := range got { + t.Errorf(" - %v, %T", i, g.s) + } + t.Fatalf("got %v stats, want %v stats", len(got), len(checkFuncs)) + } + + var rpcctx context.Context + for i := 0; i < len(got); i++ { + if _, ok := got[i].s.(stats.RPCStats); ok { + if rpcctx != nil && got[i].ctx != rpcctx { + t.Fatalf("got different contexts with stats %T", got[i].s) + } + rpcctx = got[i].ctx + } + } + + for i, f := range checkFuncs { + f(t, got[i], expect) + } +} + +func testServerStats(t *testing.T, tc *testConfig, cc *rpcConfig, checkFuncs []func(t *testing.T, d *gotData, e *expectedData)) { + h := &statshandler{} + te := newTest(t, tc, nil, h) + te.startServer(&testServer{}) + defer te.tearDown() + + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + if !cc.streaming { + req, resp, e := te.doUnaryCall(cc) + reqs = []*testpb.SimpleRequest{req} + resps = []*testpb.SimpleResponse{resp} + err = e + } else { + reqs, resps, err = te.doFullDuplexCallRoundtrip(cc) + } + if cc.success != (err == nil) { + t.Fatalf("cc.success: %v, got error: %v", cc.success, err) + } + te.cc.Close() + te.srv.GracefulStop() // Wait for the server to stop. + + for { + h.mu.Lock() + if len(h.gotRPC) >= len(checkFuncs) { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + for { + h.mu.Lock() + if _, ok := h.gotConn[len(h.gotConn)-1].s.(*stats.ConnEnd); ok { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + expect := &expectedData{ + serverAddr: te.srvAddr, + compression: tc.compress, + requests: reqs, + responses: resps, + err: err, + } + if !cc.streaming { + expect.method = "/grpc.testing.TestService/UnaryCall" + } else { + expect.method = "/grpc.testing.TestService/FullDuplexCall" + } + + checkConnStats(t, h.gotConn) + checkServerStats(t, h.gotRPC, expect, checkFuncs) +} + +func TestServerStatsUnaryRPC(t *testing.T) { + testServerStats(t, &testConfig{compress: ""}, &rpcConfig{success: true}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkInPayload, + checkOutHeader, + checkOutPayload, + checkOutTrailer, + checkEnd, + }) +} + +func TestServerStatsUnaryRPCError(t *testing.T) { + testServerStats(t, &testConfig{compress: ""}, &rpcConfig{success: false}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkInPayload, + checkOutHeader, + checkOutTrailer, + checkEnd, + }) +} + +func TestServerStatsStreamingRPC(t *testing.T) { + count := 5 + checkFuncs := []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkOutHeader, + } + ioPayFuncs := []func(t *testing.T, d *gotData, e *expectedData){ + checkInPayload, + checkOutPayload, + } + for i := 0; i < count; i++ { + checkFuncs = append(checkFuncs, ioPayFuncs...) + } + checkFuncs = append(checkFuncs, + checkOutTrailer, + checkEnd, + ) + testServerStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: true, streaming: true}, checkFuncs) +} + +func TestServerStatsStreamingRPCError(t *testing.T) { + count := 5 + testServerStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: false, streaming: true}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkOutHeader, + checkInPayload, + checkOutTrailer, + checkEnd, + }) +} + +type checkFuncWithCount struct { + f func(t *testing.T, d *gotData, e *expectedData) + c int // expected count +} + +func checkClientStats(t *testing.T, got []*gotData, expect *expectedData, checkFuncs map[int]*checkFuncWithCount) { + var expectLen int + for _, v := range checkFuncs { + expectLen += v.c + } + if len(got) != expectLen { + for i, g := range got { + t.Errorf(" - %v, %T", i, g.s) + } + t.Fatalf("got %v stats, want %v stats", len(got), expectLen) + } + + var rpcctx context.Context + for i := 0; i < len(got); i++ { + if _, ok := got[i].s.(stats.RPCStats); ok { + if rpcctx != nil && got[i].ctx != rpcctx { + t.Fatalf("got different contexts with stats %T", got[i].s) + } + rpcctx = got[i].ctx + } + } + + for _, s := range got { + switch s.s.(type) { + case *stats.Begin: + if checkFuncs[begin].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[begin].f(t, s, expect) + checkFuncs[begin].c-- + case *stats.OutHeader: + if checkFuncs[outHeader].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[outHeader].f(t, s, expect) + checkFuncs[outHeader].c-- + case *stats.OutPayload: + if checkFuncs[outPayload].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[outPayload].f(t, s, expect) + checkFuncs[outPayload].c-- + case *stats.InHeader: + if checkFuncs[inHeader].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inHeader].f(t, s, expect) + checkFuncs[inHeader].c-- + case *stats.InPayload: + if checkFuncs[inPayload].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inPayload].f(t, s, expect) + checkFuncs[inPayload].c-- + case *stats.InTrailer: + if checkFuncs[inTrailer].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inTrailer].f(t, s, expect) + checkFuncs[inTrailer].c-- + case *stats.End: + if checkFuncs[end].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[end].f(t, s, expect) + checkFuncs[end].c-- + case *stats.ConnBegin: + if checkFuncs[connbegin].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[connbegin].f(t, s, expect) + checkFuncs[connbegin].c-- + case *stats.ConnEnd: + if checkFuncs[connend].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[connend].f(t, s, expect) + checkFuncs[connend].c-- + default: + t.Fatalf("unexpected stats: %T", s.s) + } + } +} + +func testClientStats(t *testing.T, tc *testConfig, cc *rpcConfig, checkFuncs map[int]*checkFuncWithCount) { + h := &statshandler{} + te := newTest(t, tc, h, nil) + te.startServer(&testServer{}) + defer te.tearDown() + + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + if !cc.streaming { + req, resp, e := te.doUnaryCall(cc) + reqs = []*testpb.SimpleRequest{req} + resps = []*testpb.SimpleResponse{resp} + err = e + } else { + reqs, resps, err = te.doFullDuplexCallRoundtrip(cc) + } + if cc.success != (err == nil) { + t.Fatalf("cc.success: %v, got error: %v", cc.success, err) + } + te.cc.Close() + te.srv.GracefulStop() // Wait for the server to stop. + + lenRPCStats := 0 + for _, v := range checkFuncs { + lenRPCStats += v.c + } + for { + h.mu.Lock() + if len(h.gotRPC) >= lenRPCStats { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + for { + h.mu.Lock() + if _, ok := h.gotConn[len(h.gotConn)-1].s.(*stats.ConnEnd); ok { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + expect := &expectedData{ + serverAddr: te.srvAddr, + compression: tc.compress, + requests: reqs, + responses: resps, + failfast: cc.failfast, + err: err, + } + if !cc.streaming { + expect.method = "/grpc.testing.TestService/UnaryCall" + } else { + expect.method = "/grpc.testing.TestService/FullDuplexCall" + } + + checkConnStats(t, h.gotConn) + checkClientStats(t, h.gotRPC, expect, checkFuncs) +} + +func TestClientStatsUnaryRPC(t *testing.T) { + testClientStats(t, &testConfig{compress: ""}, &rpcConfig{success: true, failfast: false}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inPayload: {checkInPayload, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsUnaryRPCError(t *testing.T) { + testClientStats(t, &testConfig{compress: ""}, &rpcConfig{success: false, failfast: false}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsStreamingRPC(t *testing.T) { + count := 5 + testClientStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: true, failfast: false, streaming: true}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, count}, + inHeader: {checkInHeader, 1}, + inPayload: {checkInPayload, count}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsStreamingRPCError(t *testing.T) { + count := 5 + testClientStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: false, failfast: false, streaming: true}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} From 57f5eaa0bc29493bd49f9fff0cbe416db4b95409 Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Tue, 12 Dec 2017 11:52:58 +0100 Subject: [PATCH 2/2] bump(coreos/etcd): bump the grpc to 8050b9cbc271307e5a716a9d782803d09b0d6f2d --- .../vendor/google.golang.org/grpc/README.md | 25 - .../google.golang.org/grpc/backoff_test.go | 11 + .../google.golang.org/grpc/balancer_test.go | 438 ++++++++ .../vendor/google.golang.org/grpc/call.go | 79 +- .../google.golang.org/grpc/call_test.go | 293 ++++++ .../google.golang.org/grpc/clientconn.go | 160 +-- .../google.golang.org/grpc/clientconn_test.go | 190 ++++ .../vendor/google.golang.org/grpc/codegen.sh | 2 +- .../google.golang.org/grpc/codes/codes.go | 2 +- .../vendor/google.golang.org/grpc/coverage.sh | 3 +- .../grpc/credentials/credentials.go | 6 +- .../credentials_test.go} | 37 +- .../grpc/credentials/credentials_util_go17.go | 3 +- .../credentials/credentials_util_pre_go17.go | 2 + .../grpc/credentials/oauth/oauth.go | 180 ++++ .../etcd/vendor/google.golang.org/grpc/doc.go | 2 +- .../google.golang.org/grpc/grpclog/logger.go | 2 +- .../grpc/keepalive/keepalive.go | 52 + .../grpc/metadata/metadata.go | 7 +- .../grpc/metadata/metadata_test.go | 139 +++ .../vendor/google.golang.org/grpc/rpc_util.go | 87 +- .../google.golang.org/grpc/rpc_util_test.go | 234 +++++ .../vendor/google.golang.org/grpc/server.go | 217 +--- .../google.golang.org/grpc/server_test.go | 113 ++ .../grpc/stats/grpc_testing/test.pb.go | 225 ++++ .../grpc/stats/grpc_testing/test.proto | 23 + .../google.golang.org/grpc/stats/handlers.go | 76 ++ .../google.golang.org/grpc/stats/stats.go | 223 ++++ .../grpc/stats/stats_test.go | 981 ++++++++++++++++++ .../vendor/google.golang.org/grpc/stream.go | 154 +-- .../grpc/transport/control.go | 44 +- .../grpc/transport/handler_server.go | 2 +- .../grpc/transport/handler_server_test.go | 389 +++++++ .../grpc/transport/http2_client.go | 290 ++---- .../grpc/transport/http2_server.go | 110 +- .../grpc/transport/http_util.go | 2 + .../grpc/transport/http_util_test.go | 145 +++ .../grpc/transport/transport.go | 53 +- .../grpc/transport/transport_test.go | 832 +++++++++++++++ 39 files changed, 4812 insertions(+), 1021 deletions(-) create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/backoff_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/balancer_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn_test.go rename vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/{tap/tap.go => credentials/credentials_test.go} (65%) create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/oauth/oauth.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/keepalive/keepalive.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/handlers.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util_test.go create mode 100644 vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport_test.go diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md index 39120c2031c8..110a8cf42531 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/README.md @@ -18,22 +18,6 @@ Prerequisites This requires Go 1.5 or later. -A note on the version used: significant performance improvements in benchmarks -of grpc-go have been seen by upgrading the go version from 1.5 to the latest -1.7.1. - -From https://golang.org/doc/install, one way to install the latest version of go is: -``` -$ GO_VERSION=1.7.1 -$ OS=linux -$ ARCH=amd64 -$ curl -O https://storage.googleapis.com/golang/go${GO_VERSION}.${OS}-${ARCH}.tar.gz -$ sudo tar -C /usr/local -xzf go$GO_VERSION.$OS-$ARCH.tar.gz -$ # Put go on the PATH, keep the usual installation dir -$ sudo ln -s /usr/local/go/bin/go /usr/bin/go -$ rm go$GO_VERSION.$OS-$ARCH.tar.gz -``` - Constraints ----------- The grpc package should only depend on standard Go packages and a small number of exceptions. If your contribution introduces new dependencies which are NOT in the [list](http://godoc.org/google.golang.org/grpc?imports), you need a discussion with gRPC-Go authors and consultants. @@ -46,12 +30,3 @@ Status ------ GA -FAQ ---- - -#### Compiling error, undefined: grpc.SupportPackageIsVersion - -Please update proto package, gRPC package and rebuild the proto files: - - `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}` - - `go get -u google.golang.org/grpc` - - `protoc --go_out=plugins=grpc:. *.proto` diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/backoff_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/backoff_test.go new file mode 100644 index 000000000000..bfca7b176509 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/backoff_test.go @@ -0,0 +1,11 @@ +package grpc + +import "testing" + +func TestBackoffConfigDefaults(t *testing.T) { + b := BackoffConfig{} + setDefaults(&b) + if b != DefaultBackoffConfig { + t.Fatalf("expected BackoffConfig to pickup default parameters: %v != %v", b, DefaultBackoffConfig) + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/balancer_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/balancer_test.go new file mode 100644 index 000000000000..48f8b27d4629 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/balancer_test.go @@ -0,0 +1,438 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package grpc + +import ( + "fmt" + "math" + "sync" + "testing" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/naming" +) + +type testWatcher struct { + // the channel to receives name resolution updates + update chan *naming.Update + // the side channel to get to know how many updates in a batch + side chan int + // the channel to notifiy update injector that the update reading is done + readDone chan int +} + +func (w *testWatcher) Next() (updates []*naming.Update, err error) { + n := <-w.side + if n == 0 { + return nil, fmt.Errorf("w.side is closed") + } + for i := 0; i < n; i++ { + u := <-w.update + if u != nil { + updates = append(updates, u) + } + } + w.readDone <- 0 + return +} + +func (w *testWatcher) Close() { +} + +// Inject naming resolution updates to the testWatcher. +func (w *testWatcher) inject(updates []*naming.Update) { + w.side <- len(updates) + for _, u := range updates { + w.update <- u + } + <-w.readDone +} + +type testNameResolver struct { + w *testWatcher + addr string +} + +func (r *testNameResolver) Resolve(target string) (naming.Watcher, error) { + r.w = &testWatcher{ + update: make(chan *naming.Update, 1), + side: make(chan int, 1), + readDone: make(chan int), + } + r.w.side <- 1 + r.w.update <- &naming.Update{ + Op: naming.Add, + Addr: r.addr, + } + go func() { + <-r.w.readDone + }() + return r.w, nil +} + +func startServers(t *testing.T, numServers int, maxStreams uint32) ([]*server, *testNameResolver) { + var servers []*server + for i := 0; i < numServers; i++ { + s := newTestServer() + servers = append(servers, s) + go s.start(t, 0, maxStreams) + s.wait(t, 2*time.Second) + } + // Point to server[0] + addr := "127.0.0.1:" + servers[0].port + return servers, &testNameResolver{ + addr: addr, + } +} + +func TestNameDiscovery(t *testing.T) { + // Start 2 servers on 2 ports. + numServers := 2 + servers, r := startServers(t, numServers, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + req := "port" + var reply string + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err == nil || ErrorDesc(err) != servers[0].port { + t.Fatalf("grpc.Invoke(_, _, _, _, _) = %v, want %s", err, servers[0].port) + } + // Inject the name resolution change to remove servers[0] and add servers[1]. + var updates []*naming.Update + updates = append(updates, &naming.Update{ + Op: naming.Delete, + Addr: "127.0.0.1:" + servers[0].port, + }) + updates = append(updates, &naming.Update{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[1].port, + }) + r.w.inject(updates) + // Loop until the rpcs in flight talks to servers[1]. + for { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err != nil && ErrorDesc(err) == servers[1].port { + break + } + time.Sleep(10 * time.Millisecond) + } + cc.Close() + for i := 0; i < numServers; i++ { + servers[i].stop() + } +} + +func TestEmptyAddrs(t *testing.T) { + servers, r := startServers(t, 1, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + var reply string + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc); err != nil || reply != expectedResponse { + t.Fatalf("grpc.Invoke(_, _, _, _, _) = %v, reply = %q, want %q, ", err, reply, expectedResponse) + } + // Inject name resolution change to remove the server so that there is no address + // available after that. + u := &naming.Update{ + Op: naming.Delete, + Addr: "127.0.0.1:" + servers[0].port, + } + r.w.inject([]*naming.Update{u}) + // Loop until the above updates apply. + for { + time.Sleep(10 * time.Millisecond) + ctx, _ := context.WithTimeout(context.Background(), 10*time.Millisecond) + if err := Invoke(ctx, "/foo/bar", &expectedRequest, &reply, cc); err != nil { + break + } + } + cc.Close() + servers[0].stop() +} + +func TestRoundRobin(t *testing.T) { + // Start 3 servers on 3 ports. + numServers := 3 + servers, r := startServers(t, numServers, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + // Add servers[1] to the service discovery. + u := &naming.Update{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[1].port, + } + r.w.inject([]*naming.Update{u}) + req := "port" + var reply string + // Loop until servers[1] is up + for { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err != nil && ErrorDesc(err) == servers[1].port { + break + } + time.Sleep(10 * time.Millisecond) + } + // Add server2[2] to the service discovery. + u = &naming.Update{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[2].port, + } + r.w.inject([]*naming.Update{u}) + // Loop until both servers[2] are up. + for { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err != nil && ErrorDesc(err) == servers[2].port { + break + } + time.Sleep(10 * time.Millisecond) + } + // Check the incoming RPCs served in a round-robin manner. + for i := 0; i < 10; i++ { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err == nil || ErrorDesc(err) != servers[i%numServers].port { + t.Fatalf("Index %d: Invoke(_, _, _, _, _) = %v, want %s", i, err, servers[i%numServers].port) + } + } + cc.Close() + for i := 0; i < numServers; i++ { + servers[i].stop() + } +} + +func TestCloseWithPendingRPC(t *testing.T) { + servers, r := startServers(t, 1, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + var reply string + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); err != nil { + t.Fatalf("grpc.Invoke(_, _, _, _, _) = %v, want %s", err, servers[0].port) + } + // Remove the server. + updates := []*naming.Update{{ + Op: naming.Delete, + Addr: "127.0.0.1:" + servers[0].port, + }} + r.w.inject(updates) + // Loop until the above update applies. + for { + ctx, _ := context.WithTimeout(context.Background(), 10*time.Millisecond) + if err := Invoke(ctx, "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); Code(err) == codes.DeadlineExceeded { + break + } + time.Sleep(10 * time.Millisecond) + } + // Issue 2 RPCs which should be completed with error status once cc is closed. + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + var reply string + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); err == nil { + t.Errorf("grpc.Invoke(_, _, _, _, _) = %v, want not nil", err) + } + }() + go func() { + defer wg.Done() + var reply string + time.Sleep(5 * time.Millisecond) + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); err == nil { + t.Errorf("grpc.Invoke(_, _, _, _, _) = %v, want not nil", err) + } + }() + time.Sleep(5 * time.Millisecond) + cc.Close() + wg.Wait() + servers[0].stop() +} + +func TestGetOnWaitChannel(t *testing.T) { + servers, r := startServers(t, 1, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + // Remove all servers so that all upcoming RPCs will block on waitCh. + updates := []*naming.Update{{ + Op: naming.Delete, + Addr: "127.0.0.1:" + servers[0].port, + }} + r.w.inject(updates) + for { + var reply string + ctx, _ := context.WithTimeout(context.Background(), 10*time.Millisecond) + if err := Invoke(ctx, "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); Code(err) == codes.DeadlineExceeded { + break + } + time.Sleep(10 * time.Millisecond) + } + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + var reply string + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); err != nil { + t.Errorf("grpc.Invoke(_, _, _, _, _) = %v, want ", err) + } + }() + // Add a connected server to get the above RPC through. + updates = []*naming.Update{{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[0].port, + }} + r.w.inject(updates) + // Wait until the above RPC succeeds. + wg.Wait() + cc.Close() + servers[0].stop() +} + +func TestOneServerDown(t *testing.T) { + // Start 2 servers. + numServers := 2 + servers, r := startServers(t, numServers, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + // Add servers[1] to the service discovery. + var updates []*naming.Update + updates = append(updates, &naming.Update{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[1].port, + }) + r.w.inject(updates) + req := "port" + var reply string + // Loop until servers[1] is up + for { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err != nil && ErrorDesc(err) == servers[1].port { + break + } + time.Sleep(10 * time.Millisecond) + } + + var wg sync.WaitGroup + numRPC := 100 + sleepDuration := 10 * time.Millisecond + wg.Add(1) + go func() { + time.Sleep(sleepDuration) + // After sleepDuration, kill server[0]. + servers[0].stop() + wg.Done() + }() + + // All non-failfast RPCs should not block because there's at least one connection available. + for i := 0; i < numRPC; i++ { + wg.Add(1) + go func() { + time.Sleep(sleepDuration) + // After sleepDuration, invoke RPC. + // server[0] is killed around the same time to make it racy between balancer and gRPC internals. + Invoke(context.Background(), "/foo/bar", &req, &reply, cc, FailFast(false)) + wg.Done() + }() + } + wg.Wait() + cc.Close() + for i := 0; i < numServers; i++ { + servers[i].stop() + } +} + +func TestOneAddressRemoval(t *testing.T) { + // Start 2 servers. + numServers := 2 + servers, r := startServers(t, numServers, math.MaxUint32) + cc, err := Dial("foo.bar.com", WithBalancer(RoundRobin(r)), WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + // Add servers[1] to the service discovery. + var updates []*naming.Update + updates = append(updates, &naming.Update{ + Op: naming.Add, + Addr: "127.0.0.1:" + servers[1].port, + }) + r.w.inject(updates) + req := "port" + var reply string + // Loop until servers[1] is up + for { + if err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc); err != nil && ErrorDesc(err) == servers[1].port { + break + } + time.Sleep(10 * time.Millisecond) + } + + var wg sync.WaitGroup + numRPC := 100 + sleepDuration := 10 * time.Millisecond + wg.Add(1) + go func() { + time.Sleep(sleepDuration) + // After sleepDuration, delete server[0]. + var updates []*naming.Update + updates = append(updates, &naming.Update{ + Op: naming.Delete, + Addr: "127.0.0.1:" + servers[0].port, + }) + r.w.inject(updates) + wg.Done() + }() + + // All non-failfast RPCs should not fail because there's at least one connection available. + for i := 0; i < numRPC; i++ { + wg.Add(1) + go func() { + var reply string + time.Sleep(sleepDuration) + // After sleepDuration, invoke RPC. + // server[0] is removed around the same time to make it racy between balancer and gRPC internals. + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc, FailFast(false)); err != nil { + t.Errorf("grpc.Invoke(_, _, _, _, _) = %v, want not nil", err) + } + wg.Done() + }() + } + wg.Wait() + cc.Close() + for i := 0; i < numServers; i++ { + servers[i].stop() + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go index c1588c6375c7..772c817edd3d 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call.go @@ -36,13 +36,12 @@ package grpc import ( "bytes" "io" + "math" "time" "golang.org/x/net/context" "golang.org/x/net/trace" "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -50,8 +49,7 @@ import ( // On error, it returns the error and indicates whether the call should be retried. // // 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) { +func recvResponse(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 { @@ -65,34 +63,20 @@ func recvResponse(ctx context.Context, dopts dialOptions, t transport.ClientTran return } p := &parser{r: stream} - var inPayload *stats.InPayload - if dopts.copts.StatsHandler != nil { - inPayload = &stats.InPayload{ - Client: true, - } - } for { - if err = recv(p, dopts.codec, stream, dopts.dc, reply, dopts.maxMsgSize, inPayload); err != nil { + if err = recv(p, dopts.codec, stream, dopts.dc, reply, math.MaxInt32); err != nil { if err == io.EOF { break } return } } - if inPayload != nil && err == io.EOF && stream.StatusCode() == 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() - if peer, ok := peer.FromContext(stream.Context()); ok { - c.peer = peer - } return nil } // sendRequest writes out various information of an RPC such as Context and Message. -func sendRequest(ctx context.Context, dopts dialOptions, compressor Compressor, callHdr *transport.CallHdr, t transport.ClientTransport, args interface{}, opts *transport.Options) (_ *transport.Stream, err error) { +func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHdr *transport.CallHdr, t transport.ClientTransport, args interface{}, opts *transport.Options) (_ *transport.Stream, err error) { stream, err := t.NewStream(ctx, callHdr) if err != nil { return nil, err @@ -105,27 +89,15 @@ func sendRequest(ctx context.Context, dopts dialOptions, compressor Compressor, } } }() - var ( - cbuf *bytes.Buffer - outPayload *stats.OutPayload - ) + var cbuf *bytes.Buffer if compressor != nil { cbuf = new(bytes.Buffer) } - if dopts.copts.StatsHandler != nil { - outPayload = &stats.OutPayload{ - Client: true, - } - } - outBuf, err := encode(dopts.codec, args, compressor, cbuf, outPayload) + outBuf, err := encode(codec, args, compressor, cbuf) if err != nil { return nil, Errorf(codes.Internal, "grpc: %v", err) } err = t.Write(stream, outBuf, 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. @@ -146,16 +118,8 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli return invoke(ctx, method, args, reply, cc, opts...) } -func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (e error) { +func invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) { c := defaultCallInfo - if mc, ok := cc.getMethodConfig(method); ok { - c.failFast = !mc.WaitForReady - if mc.Timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, mc.Timeout) - defer cancel() - } - } for _, o := range opts { if err := o.before(&c); err != nil { return toRPCErr(err) @@ -176,32 +140,12 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli 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) + if err != nil { + c.traceInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) c.traceInfo.tr.SetError() } }() } - sh := cc.dopts.copts.StatsHandler - if sh != nil { - ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method}) - begin := &stats.Begin{ - Client: true, - BeginTime: time.Now(), - FailFast: c.failFast, - } - sh.HandleRPC(ctx, begin) - } - defer func() { - if sh != nil { - end := &stats.End{ - Client: true, - EndTime: time.Now(), - Error: e, - } - sh.HandleRPC(ctx, end) - } - }() topts := &transport.Options{ Last: true, Delay: false, @@ -223,7 +167,6 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli if cc.dopts.cp != nil { callHdr.SendCompress = cc.dopts.cp.Type() } - gopts := BalancerGetOptions{ BlockingWait: !c.failFast, } @@ -245,7 +188,7 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli if c.traceInfo.tr != nil { c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true) } - stream, err = sendRequest(ctx, cc.dopts, cc.dopts.cp, callHdr, t, args, topts) + stream, err = sendRequest(ctx, cc.dopts.codec, cc.dopts.cp, callHdr, t, args, topts) if err != nil { if put != nil { put() @@ -262,7 +205,7 @@ func invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli } return toRPCErr(err) } - err = recvResponse(ctx, cc.dopts, t, &c, stream, reply) + err = recvResponse(cc.dopts, t, &c, stream, reply) if err != nil { if put != nil { put() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call_test.go new file mode 100644 index 000000000000..64976d7b24c3 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/call_test.go @@ -0,0 +1,293 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package grpc + +import ( + "fmt" + "io" + "math" + "net" + "strconv" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/transport" +) + +var ( + expectedRequest = "ping" + expectedResponse = "pong" + weirdError = "format verbs: %v%s" + sizeLargeErr = 1024 * 1024 + canceled = 0 +) + +type testCodec struct { +} + +func (testCodec) Marshal(v interface{}) ([]byte, error) { + return []byte(*(v.(*string))), nil +} + +func (testCodec) Unmarshal(data []byte, v interface{}) error { + *(v.(*string)) = string(data) + return nil +} + +func (testCodec) String() string { + return "test" +} + +type testStreamHandler struct { + port string + t transport.ServerTransport +} + +func (h *testStreamHandler) handleStream(t *testing.T, s *transport.Stream) { + p := &parser{r: s} + for { + pf, req, err := p.recvMsg(math.MaxInt32) + if err == io.EOF { + break + } + if err != nil { + return + } + if pf != compressionNone { + t.Errorf("Received the mistaken message format %d, want %d", pf, compressionNone) + return + } + var v string + codec := testCodec{} + if err := codec.Unmarshal(req, &v); err != nil { + t.Errorf("Failed to unmarshal the received message: %v", err) + return + } + if v == "weird error" { + h.t.WriteStatus(s, codes.Internal, weirdError) + return + } + if v == "canceled" { + canceled++ + h.t.WriteStatus(s, codes.Internal, "") + return + } + if v == "port" { + h.t.WriteStatus(s, codes.Internal, h.port) + return + } + + if v != expectedRequest { + h.t.WriteStatus(s, codes.Internal, strings.Repeat("A", sizeLargeErr)) + return + } + } + // send a response back to end the stream. + reply, err := encode(testCodec{}, &expectedResponse, nil, nil) + if err != nil { + t.Errorf("Failed to encode the response: %v", err) + return + } + h.t.Write(s, reply, &transport.Options{}) + h.t.WriteStatus(s, codes.OK, "") +} + +type server struct { + lis net.Listener + port string + startedErr chan error // sent nil or an error after server starts + mu sync.Mutex + conns map[transport.ServerTransport]bool +} + +func newTestServer() *server { + return &server{startedErr: make(chan error, 1)} +} + +// start starts server. Other goroutines should block on s.startedErr for further operations. +func (s *server) start(t *testing.T, port int, maxStreams uint32) { + var err error + if port == 0 { + s.lis, err = net.Listen("tcp", "localhost:0") + } else { + s.lis, err = net.Listen("tcp", "localhost:"+strconv.Itoa(port)) + } + if err != nil { + s.startedErr <- fmt.Errorf("failed to listen: %v", err) + return + } + _, p, err := net.SplitHostPort(s.lis.Addr().String()) + if err != nil { + s.startedErr <- fmt.Errorf("failed to parse listener address: %v", err) + return + } + s.port = p + s.conns = make(map[transport.ServerTransport]bool) + s.startedErr <- nil + for { + conn, err := s.lis.Accept() + if err != nil { + return + } + st, err := transport.NewServerTransport("http2", conn, maxStreams, nil) + if err != nil { + continue + } + s.mu.Lock() + if s.conns == nil { + s.mu.Unlock() + st.Close() + return + } + s.conns[st] = true + s.mu.Unlock() + h := &testStreamHandler{ + port: s.port, + t: st, + } + go st.HandleStreams(func(s *transport.Stream) { + go h.handleStream(t, s) + }) + } +} + +func (s *server) wait(t *testing.T, timeout time.Duration) { + select { + case err := <-s.startedErr: + if err != nil { + t.Fatal(err) + } + case <-time.After(timeout): + t.Fatalf("Timed out after %v waiting for server to be ready", timeout) + } +} + +func (s *server) stop() { + s.lis.Close() + s.mu.Lock() + for c := range s.conns { + c.Close() + } + s.conns = nil + s.mu.Unlock() +} + +func setUp(t *testing.T, port int, maxStreams uint32) (*server, *ClientConn) { + server := newTestServer() + go server.start(t, port, maxStreams) + server.wait(t, 2*time.Second) + addr := "localhost:" + server.port + cc, err := Dial(addr, WithBlock(), WithInsecure(), WithCodec(testCodec{})) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + return server, cc +} + +func TestInvoke(t *testing.T) { + server, cc := setUp(t, 0, math.MaxUint32) + var reply string + if err := Invoke(context.Background(), "/foo/bar", &expectedRequest, &reply, cc); err != nil || reply != expectedResponse { + t.Fatalf("grpc.Invoke(_, _, _, _, _) = %v, want ", err) + } + cc.Close() + server.stop() +} + +func TestInvokeLargeErr(t *testing.T) { + server, cc := setUp(t, 0, math.MaxUint32) + var reply string + req := "hello" + err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc) + if _, ok := err.(*rpcError); !ok { + t.Fatalf("grpc.Invoke(_, _, _, _, _) receives non rpc error.") + } + if Code(err) != codes.Internal || len(ErrorDesc(err)) != sizeLargeErr { + t.Fatalf("grpc.Invoke(_, _, _, _, _) = %v, want an error of code %d and desc size %d", err, codes.Internal, sizeLargeErr) + } + cc.Close() + server.stop() +} + +// TestInvokeErrorSpecialChars checks that error messages don't get mangled. +func TestInvokeErrorSpecialChars(t *testing.T) { + server, cc := setUp(t, 0, math.MaxUint32) + var reply string + req := "weird error" + err := Invoke(context.Background(), "/foo/bar", &req, &reply, cc) + if _, ok := err.(*rpcError); !ok { + t.Fatalf("grpc.Invoke(_, _, _, _, _) receives non rpc error.") + } + if got, want := ErrorDesc(err), weirdError; got != want { + t.Fatalf("grpc.Invoke(_, _, _, _, _) error = %q, want %q", got, want) + } + cc.Close() + server.stop() +} + +// TestInvokeCancel checks that an Invoke with a canceled context is not sent. +func TestInvokeCancel(t *testing.T) { + server, cc := setUp(t, 0, math.MaxUint32) + var reply string + req := "canceled" + for i := 0; i < 100; i++ { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + Invoke(ctx, "/foo/bar", &req, &reply, cc) + } + if canceled != 0 { + t.Fatalf("received %d of 100 canceled requests", canceled) + } + cc.Close() + server.stop() +} + +// TestInvokeCancelClosedNonFail checks that a canceled non-failfast RPC +// on a closed client will terminate. +func TestInvokeCancelClosedNonFailFast(t *testing.T) { + server, cc := setUp(t, 0, math.MaxUint32) + var reply string + cc.Close() + req := "hello" + ctx, cancel := context.WithCancel(context.Background()) + cancel() + if err := Invoke(ctx, "/foo/bar", &req, &reply, cc, FailFast(false)); err == nil { + t.Fatalf("canceled invoke on closed connection should fail") + } + server.stop() +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go index 9858fe574d79..61674729a77b 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn.go @@ -36,7 +36,6 @@ package grpc import ( "errors" "fmt" - "math" "net" "strings" "sync" @@ -46,8 +45,6 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/keepalive" - "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -57,8 +54,6 @@ var ( 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. This error will be - // removed in Q1 2017. ErrClientConnTimeout = errors.New("grpc: timed out when dialing") // errNoTransportSecurity indicates that there is no transport security @@ -88,33 +83,22 @@ var ( // dialOptions configure a Dial call. dialOptions are set by the DialOption // values passed to Dial. type dialOptions struct { - unaryInt UnaryClientInterceptor - streamInt StreamClientInterceptor - codec Codec - cp Compressor - dc Decompressor - bs backoffStrategy - balancer Balancer - block bool - insecure bool - timeout time.Duration - scChan <-chan ServiceConfig - copts transport.ConnectOptions - maxMsgSize int -} - -const defaultClientMaxMsgSize = math.MaxInt32 + unaryInt UnaryClientInterceptor + streamInt StreamClientInterceptor + codec Codec + cp Compressor + dc Decompressor + bs backoffStrategy + balancer Balancer + block bool + insecure bool + timeout time.Duration + copts transport.ConnectOptions +} // DialOption configures how we set up the connection. type DialOption func(*dialOptions) -// WithMaxMsgSize returns a DialOption which sets the maximum message size the client can receive. -func WithMaxMsgSize(s int) DialOption { - return func(o *dialOptions) { - o.maxMsgSize = s - } -} - // WithCodec returns a DialOption which sets a codec for message marshaling and unmarshaling. func WithCodec(c Codec) DialOption { return func(o *dialOptions) { @@ -145,13 +129,6 @@ func WithBalancer(b Balancer) DialOption { } } -// WithServiceConfig returns a DialOption which has a channel to read the service configuration. -func WithServiceConfig(c <-chan ServiceConfig) DialOption { - return func(o *dialOptions) { - o.scChan = c - } -} - // WithBackoffMaxDelay configures the dialer to use the provided maximum delay // when backing off after failed connection attempts. func WithBackoffMaxDelay(md time.Duration) DialOption { @@ -222,8 +199,6 @@ func WithTimeout(d time.Duration) DialOption { } // 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) { @@ -235,25 +210,6 @@ func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption { } } -// WithStatsHandler returns a DialOption that specifies the stats handler -// for all the RPCs and underlying network connections in this ClientConn. -func WithStatsHandler(h stats.Handler) DialOption { - return func(o *dialOptions) { - o.copts.StatsHandler = h - } -} - -// FailOnNonTempDialError returns a DialOption that specified if gRPC fails on non-temporary dial errors. -// If f is true, and dialer returns a non-temporary error, gRPC will fail the connection to the network -// address and won't try to reconnect. -// The default value of FailOnNonTempDialError is false. -// This is an EXPERIMENTAL API. -func FailOnNonTempDialError(f bool) DialOption { - return func(o *dialOptions) { - o.copts.FailOnNonTempDialError = f - } -} - // WithUserAgent returns a DialOption that specifies a user agent string for all the RPCs. func WithUserAgent(s string) DialOption { return func(o *dialOptions) { @@ -261,13 +217,6 @@ func WithUserAgent(s string) DialOption { } } -// WithKeepaliveParams returns a DialOption that specifies keepalive paramaters for the client transport. -func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption { - return func(o *dialOptions) { - o.copts.KeepaliveParams = kp - } -} - // WithUnaryInterceptor returns a DialOption that specifies the interceptor for unary RPCs. func WithUnaryInterceptor(f UnaryClientInterceptor) DialOption { return func(o *dialOptions) { @@ -282,15 +231,6 @@ func WithStreamInterceptor(f StreamClientInterceptor) DialOption { } } -// WithAuthority returns a DialOption that specifies the value to be used as -// the :authority pseudo-header. This value only works with WithInsecure and -// has no effect if TransportCredentials are present. -func WithAuthority(a string) DialOption { - return func(o *dialOptions) { - o.copts.Authority = a - } -} - // Dial creates a client connection to the given target. func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) @@ -307,24 +247,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * conns: make(map[Address]*addrConn), } cc.ctx, cc.cancel = context.WithCancel(context.Background()) - cc.dopts.maxMsgSize = defaultClientMaxMsgSize - for _, opt := range opts { - opt(&cc.dopts) - } - - grpcUA := "grpc-go/" + Version - if cc.dopts.copts.UserAgent != "" { - cc.dopts.copts.UserAgent += " " + grpcUA - } else { - cc.dopts.copts.UserAgent = grpcUA - } - - if cc.dopts.timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout) - defer cancel() - } - defer func() { select { case <-ctx.Done(): @@ -337,17 +259,10 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } }() - if cc.dopts.scChan != nil { - // Wait for the initial service config. - select { - case sc, ok := <-cc.dopts.scChan: - if ok { - cc.sc = sc - } - case <-ctx.Done(): - return nil, ctx.Err() - } + for _, opt := range opts { + opt(&cc.dopts) } + // Set defaults. if cc.dopts.codec == nil { cc.dopts.codec = protoCodec{} @@ -358,8 +273,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * 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 { colonPos := strings.LastIndex(target, ":") if colonPos == -1 { @@ -371,9 +284,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * waitC := make(chan error, 1) go func() { var addrs []Address - if cc.dopts.balancer == nil && cc.sc.LB != nil { - cc.dopts.balancer = cc.sc.LB - } if cc.dopts.balancer == nil { // Connect to target directly if balancer is nil. addrs = append(addrs, Address{Addr: target}) @@ -409,6 +319,10 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * } close(waitC) }() + var timeoutCh <-chan time.Time + if cc.dopts.timeout > 0 { + timeoutCh = time.After(cc.dopts.timeout) + } select { case <-ctx.Done(): return nil, ctx.Err() @@ -416,17 +330,14 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if err != nil { return nil, err } + case <-timeoutCh: + return nil, ErrClientConnTimeout } - // If balancer is nil or balancer.Notify() is nil, ok will be false here. // The lbWatcher goroutine will not be created. if ok { go cc.lbWatcher() } - - if cc.dopts.scChan != nil { - go cc.scWatcher() - } return cc, nil } @@ -473,7 +384,6 @@ type ClientConn struct { dopts dialOptions mu sync.RWMutex - sc ServiceConfig conns map[Address]*addrConn } @@ -512,24 +422,6 @@ func (cc *ClientConn) lbWatcher() { } } -func (cc *ClientConn) scWatcher() { - for { - select { - case sc, ok := <-cc.dopts.scChan: - if !ok { - return - } - cc.mu.Lock() - // TODO: load balance policy runtime change is ignored. - // We may revist this decision in the future. - cc.sc = sc - cc.mu.Unlock() - case <-cc.ctx.Done(): - return - } - } -} - // resetAddrConn creates an addrConn for addr and adds it to cc.conns. // If there is an old addrConn for addr, it will be torn down, using tearDownErr as the reason. // If tearDownErr is nil, errConnDrain will be used instead. @@ -617,14 +509,6 @@ func (cc *ClientConn) resetAddrConn(addr Address, skipWait bool, tearDownErr err return nil } -// TODO: Avoid the locking here. -func (cc *ClientConn) getMethodConfig(method string) (m MethodConfig, ok bool) { - cc.mu.RLock() - defer cc.mu.RUnlock() - m, ok = cc.sc.Methods[method] - return -} - func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) { var ( ac *addrConn @@ -805,8 +689,6 @@ func (ac *addrConn) resetTransport(closeTransport bool) error { Metadata: ac.addr.Metadata, } newTransport, err := transport.NewClientTransport(ctx, sinfo, ac.dopts.copts) - // Don't call cancel in success path due to a race in Go 1.6: - // https://github.com/golang/go/issues/15078. if err != nil { cancel() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn_test.go new file mode 100644 index 000000000000..179be251ffb0 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/clientconn_test.go @@ -0,0 +1,190 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package grpc + +import ( + "testing" + "time" + + "golang.org/x/net/context" + + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/oauth" +) + +const tlsDir = "testdata/" + +func TestDialTimeout(t *testing.T) { + conn, err := Dial("Non-Existent.Server:80", WithTimeout(time.Millisecond), WithBlock(), WithInsecure()) + if err == nil { + conn.Close() + } + if err != ErrClientConnTimeout { + t.Fatalf("Dial(_, _) = %v, %v, want %v", conn, err, ErrClientConnTimeout) + } +} + +func TestTLSDialTimeout(t *testing.T) { + creds, err := credentials.NewClientTLSFromFile(tlsDir+"ca.pem", "x.test.youtube.com") + if err != nil { + t.Fatalf("Failed to create credentials %v", err) + } + conn, err := Dial("Non-Existent.Server:80", WithTransportCredentials(creds), WithTimeout(time.Millisecond), WithBlock()) + if err == nil { + conn.Close() + } + if err != ErrClientConnTimeout { + t.Fatalf("Dial(_, _) = %v, %v, want %v", conn, err, ErrClientConnTimeout) + } +} + +func TestTLSServerNameOverwrite(t *testing.T) { + overwriteServerName := "over.write.server.name" + creds, err := credentials.NewClientTLSFromFile(tlsDir+"ca.pem", overwriteServerName) + if err != nil { + t.Fatalf("Failed to create credentials %v", err) + } + conn, err := Dial("Non-Existent.Server:80", WithTransportCredentials(creds)) + if err != nil { + t.Fatalf("Dial(_, _) = _, %v, want _, ", err) + } + conn.Close() + if conn.authority != overwriteServerName { + t.Fatalf("%v.authority = %v, want %v", conn, conn.authority, overwriteServerName) + } +} + +func TestDialContextCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + if _, err := DialContext(ctx, "Non-Existent.Server:80", WithBlock(), WithInsecure()); err != context.Canceled { + t.Fatalf("DialContext(%v, _) = _, %v, want _, %v", ctx, err, context.Canceled) + } +} + +// blockingBalancer mimics the behavior of balancers whose initialization takes a long time. +// In this test, reading from blockingBalancer.Notify() blocks forever. +type blockingBalancer struct { + ch chan []Address +} + +func newBlockingBalancer() Balancer { + return &blockingBalancer{ch: make(chan []Address)} +} +func (b *blockingBalancer) Start(target string, config BalancerConfig) error { + return nil +} +func (b *blockingBalancer) Up(addr Address) func(error) { + return nil +} +func (b *blockingBalancer) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) { + return Address{}, nil, nil +} +func (b *blockingBalancer) Notify() <-chan []Address { + return b.ch +} +func (b *blockingBalancer) Close() error { + close(b.ch) + return nil +} + +func TestDialWithBlockingBalancer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + dialDone := make(chan struct{}) + go func() { + DialContext(ctx, "Non-Existent.Server:80", WithBlock(), WithInsecure(), WithBalancer(newBlockingBalancer())) + close(dialDone) + }() + cancel() + <-dialDone +} + +func TestCredentialsMisuse(t *testing.T) { + tlsCreds, err := credentials.NewClientTLSFromFile(tlsDir+"ca.pem", "x.test.youtube.com") + if err != nil { + t.Fatalf("Failed to create authenticator %v", err) + } + // Two conflicting credential configurations + if _, err := Dial("Non-Existent.Server:80", WithTransportCredentials(tlsCreds), WithBlock(), WithInsecure()); err != errCredentialsConflict { + t.Fatalf("Dial(_, _) = _, %v, want _, %v", err, errCredentialsConflict) + } + rpcCreds, err := oauth.NewJWTAccessFromKey(nil) + if err != nil { + t.Fatalf("Failed to create credentials %v", err) + } + // security info on insecure connection + if _, err := Dial("Non-Existent.Server:80", WithPerRPCCredentials(rpcCreds), WithBlock(), WithInsecure()); err != errTransportCredentialsMissing { + t.Fatalf("Dial(_, _) = _, %v, want _, %v", err, errTransportCredentialsMissing) + } +} + +func TestWithBackoffConfigDefault(t *testing.T) { + testBackoffConfigSet(t, &DefaultBackoffConfig) +} + +func TestWithBackoffConfig(t *testing.T) { + b := BackoffConfig{MaxDelay: DefaultBackoffConfig.MaxDelay / 2} + expected := b + setDefaults(&expected) // defaults should be set + testBackoffConfigSet(t, &expected, WithBackoffConfig(b)) +} + +func TestWithBackoffMaxDelay(t *testing.T) { + md := DefaultBackoffConfig.MaxDelay / 2 + expected := BackoffConfig{MaxDelay: md} + setDefaults(&expected) + testBackoffConfigSet(t, &expected, WithBackoffMaxDelay(md)) +} + +func testBackoffConfigSet(t *testing.T, expected *BackoffConfig, opts ...DialOption) { + opts = append(opts, WithInsecure()) + conn, err := Dial("foo:80", opts...) + if err != nil { + t.Fatalf("unexpected error dialing connection: %v", err) + } + + if conn.dopts.bs == nil { + t.Fatalf("backoff config not set") + } + + actual, ok := conn.dopts.bs.(BackoffConfig) + if !ok { + t.Fatalf("unexpected type of backoff config: %#v", conn.dopts.bs) + } + + if actual != *expected { + t.Fatalf("unexpected backoff config on connection: %v, want %v", actual, expected) + } + conn.Close() +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh index 4cdc6ba7c090..b00948884295 100755 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codegen.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash # This script serves as an example to demonstrate how to generate the gRPC-Go # interface and the related messages from .proto file. diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go index e14b464acfd4..37c5b860bd6d 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/codes/codes.go @@ -33,7 +33,7 @@ // Package codes defines the canonical error codes used by gRPC. It is // consistent across various languages. -package codes // import "google.golang.org/grpc/codes" +package codes // A Code is an unsigned 32-bit error code as defined in the gRPC spec. type Code uint32 diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh index b85f9181dee9..120235374a42 100755 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/coverage.sh @@ -1,5 +1,4 @@ -#!/usr/bin/env bash - +#!/bin/bash set -e diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go index 4d45c3e3c7f2..4154f1eeba9a 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials.go @@ -35,7 +35,7 @@ // which encapsulate all the state needed by a client to authenticate with a // server and make various assertions, e.g., about the client's identity, role, // or whether it is authorized to make a particular call. -package credentials // import "google.golang.org/grpc/credentials" +package credentials import ( "crypto/tls" @@ -165,7 +165,9 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, addr string, rawConn net case <-ctx.Done(): return nil, nil, ctx.Err() } - return conn, TLSInfo{conn.ConnectionState()}, nil + // TODO(zhaoq): Omit the auth info for client now. It is more for + // information than anything else. + return conn, nil, nil } func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) { diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_test.go similarity index 65% rename from vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go rename to vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_test.go index 0f3664767423..caf35b2feca8 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/tap/tap.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_test.go @@ -31,24 +31,31 @@ * */ -// Package tap defines the function handles which are executed on the transport -// layer of gRPC-Go and related information. Everything here is EXPERIMENTAL. -package tap +package credentials import ( - "golang.org/x/net/context" + "testing" ) -// Info defines the relevant information needed by the handles. -type Info struct { - // FullMethodName is the string of grpc method (in the format of - // /package.service/method). - FullMethodName string - // TODO: More to be added. +func TestTLSOverrideServerName(t *testing.T) { + expectedServerName := "server.name" + c := NewTLS(nil) + c.OverrideServerName(expectedServerName) + if c.Info().ServerName != expectedServerName { + t.Fatalf("c.Info().ServerName = %v, want %v", c.Info().ServerName, expectedServerName) + } } -// ServerInHandle defines the function which runs when a new stream is created -// on the server side. Note that it is executed in the per-connection I/O goroutine(s) instead -// of per-RPC goroutine. Therefore, users should NOT have any blocking/time-consuming -// work in this handle. Otherwise all the RPCs would slow down. -type ServerInHandle func(ctx context.Context, info *Info) (context.Context, error) +func TestTLSClone(t *testing.T) { + expectedServerName := "server.name" + c := NewTLS(nil) + c.OverrideServerName(expectedServerName) + cc := c.Clone() + if cc.Info().ServerName != expectedServerName { + t.Fatalf("cc.Info().ServerName = %v, want %v", cc.Info().ServerName, expectedServerName) + } + cc.OverrideServerName("") + if c.Info().ServerName != expectedServerName { + t.Fatalf("Change in clone should not affect the original, c.Info().ServerName = %v, want %v", c.Info().ServerName, expectedServerName) + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go index 7597b09e358b..9647b9ec83ae 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go @@ -1,5 +1,4 @@ // +build go1.7 -// +build !go1.8 /* * @@ -45,6 +44,8 @@ import ( // contains a mutex and must not be copied. // // If cfg is nil, a new zero tls.Config is returned. +// +// TODO replace this function with official clone function. func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { return &tls.Config{} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go index cfd40dfa34ab..09b8d12c79b7 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go @@ -44,6 +44,8 @@ import ( // contains a mutex and must not be copied. // // If cfg is nil, a new zero tls.Config is returned. +// +// TODO replace this function with official clone function. func cloneTLSConfig(cfg *tls.Config) *tls.Config { if cfg == nil { return &tls.Config{} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/oauth/oauth.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/oauth/oauth.go new file mode 100644 index 000000000000..25393cc641c5 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/credentials/oauth/oauth.go @@ -0,0 +1,180 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package oauth implements gRPC credentials using OAuth. +package oauth + +import ( + "fmt" + "io/ioutil" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "golang.org/x/oauth2/jwt" + "google.golang.org/grpc/credentials" +) + +// TokenSource supplies PerRPCCredentials from an oauth2.TokenSource. +type TokenSource struct { + oauth2.TokenSource +} + +// GetRequestMetadata gets the request metadata as a map from a TokenSource. +func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + token, err := ts.Token() + if err != nil { + return nil, err + } + return map[string]string{ + "authorization": token.Type() + " " + token.AccessToken, + }, nil +} + +// RequireTransportSecurity indicates whether the credentials requires transport security. +func (ts TokenSource) RequireTransportSecurity() bool { + return true +} + +type jwtAccess struct { + jsonKey []byte +} + +// NewJWTAccessFromFile creates PerRPCCredentials from the given keyFile. +func NewJWTAccessFromFile(keyFile string) (credentials.PerRPCCredentials, error) { + jsonKey, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err) + } + return NewJWTAccessFromKey(jsonKey) +} + +// NewJWTAccessFromKey creates PerRPCCredentials from the given jsonKey. +func NewJWTAccessFromKey(jsonKey []byte) (credentials.PerRPCCredentials, error) { + return jwtAccess{jsonKey}, nil +} + +func (j jwtAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + ts, err := google.JWTAccessTokenSourceFromJSON(j.jsonKey, uri[0]) + if err != nil { + return nil, err + } + token, err := ts.Token() + if err != nil { + return nil, err + } + return map[string]string{ + "authorization": token.TokenType + " " + token.AccessToken, + }, nil +} + +func (j jwtAccess) RequireTransportSecurity() bool { + return true +} + +// oauthAccess supplies PerRPCCredentials from a given token. +type oauthAccess struct { + token oauth2.Token +} + +// NewOauthAccess constructs the PerRPCCredentials using a given token. +func NewOauthAccess(token *oauth2.Token) credentials.PerRPCCredentials { + return oauthAccess{token: *token} +} + +func (oa oauthAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "authorization": oa.token.TokenType + " " + oa.token.AccessToken, + }, nil +} + +func (oa oauthAccess) RequireTransportSecurity() bool { + return true +} + +// NewComputeEngine constructs the PerRPCCredentials that fetches access tokens from +// Google Compute Engine (GCE)'s metadata server. It is only valid to use this +// if your program is running on a GCE instance. +// TODO(dsymonds): Deprecate and remove this. +func NewComputeEngine() credentials.PerRPCCredentials { + return TokenSource{google.ComputeTokenSource("")} +} + +// serviceAccount represents PerRPCCredentials via JWT signing key. +type serviceAccount struct { + config *jwt.Config +} + +func (s serviceAccount) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + token, err := s.config.TokenSource(ctx).Token() + if err != nil { + return nil, err + } + return map[string]string{ + "authorization": token.TokenType + " " + token.AccessToken, + }, nil +} + +func (s serviceAccount) RequireTransportSecurity() bool { + return true +} + +// NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice +// from a Google Developers service account. +func NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) { + config, err := google.JWTConfigFromJSON(jsonKey, scope...) + if err != nil { + return nil, err + } + return serviceAccount{config: config}, nil +} + +// NewServiceAccountFromFile constructs the PerRPCCredentials using the JSON key file +// of a Google Developers service account. +func NewServiceAccountFromFile(keyFile string, scope ...string) (credentials.PerRPCCredentials, error) { + jsonKey, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, fmt.Errorf("credentials: failed to read the service account key file: %v", err) + } + return NewServiceAccountFromKey(jsonKey, scope...) +} + +// NewApplicationDefault returns "Application Default Credentials". For more +// detail, see https://developers.google.com/accounts/docs/application-default-credentials. +func NewApplicationDefault(ctx context.Context, scope ...string) (credentials.PerRPCCredentials, error) { + t, err := google.DefaultTokenSource(ctx, scope...) + if err != nil { + return nil, err + } + return TokenSource{t}, nil +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go index a35f218852bd..b4c0e740e9c7 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/doc.go @@ -3,4 +3,4 @@ Package grpc implements an RPC system called gRPC. See www.grpc.io for more information about gRPC. */ -package grpc // import "google.golang.org/grpc" +package grpc diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go index 3b2933079e9c..2cc09be48945 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/grpclog/logger.go @@ -34,7 +34,7 @@ /* Package grpclog defines logging for grpc. */ -package grpclog // import "google.golang.org/grpc/grpclog" +package grpclog import ( "log" diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/keepalive/keepalive.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/keepalive/keepalive.go new file mode 100644 index 000000000000..20672e49d901 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/keepalive/keepalive.go @@ -0,0 +1,52 @@ +/* + * + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package keepalive defines configurable parameters for point-to-point healthcheck. +package keepalive + +import ( + "time" +) + +// ClientParameters is used to set keepalive parameters on the client-side. +// These configure how the client will actively probe to notice when a connection broken +// and to cause activity so intermediaries are aware the connection is still in use. +type ClientParameters struct { + // After a duration of this time if the client doesn't see any activity it pings the server to see if the transport is still alive. + Time time.Duration // The current default value is infinity. + // After having pinged for keepalive check, the client waits for a duration of Timeout and if no activity is seen even after that + // the connection is closed. + Timeout time.Duration // The current default value is 20 seconds. + // If true, client runs keepalive checks even with no active RPCs. + PermitWithoutStream bool +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go index 733239502865..cb29bf945793 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata.go @@ -32,8 +32,7 @@ */ // Package metadata define the structure of the metadata supported by gRPC library. -// Please refer to http://www.grpc.io/docs/guides/wire.html for more information about custom-metadata. -package metadata // import "google.golang.org/grpc/metadata" +package metadata import ( "encoding/base64" @@ -83,7 +82,6 @@ func DecodeKeyValue(k, v string) (string, string, error) { type MD map[string][]string // New creates a MD from given key-value map. -// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be applied Base64 encoding. func New(m map[string]string) MD { md := MD{} for k, v := range m { @@ -95,7 +93,6 @@ func New(m map[string]string) MD { // Pairs returns an MD formed by the mapping of key, value ... // Pairs panics if len(kv) is odd. -// Keys are automatically converted to lowercase. And for keys having "-bin" as suffix, their values will be appplied Base64 encoding. func Pairs(kv ...string) MD { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv))) @@ -144,8 +141,6 @@ func NewContext(ctx context.Context, md MD) context.Context { } // FromContext returns the MD in ctx if it exists. -// The returned md should be immutable, writing to it may cause races. -// Modification should be made to the copies of the returned md. func FromContext(ctx context.Context) (md MD, ok bool) { md, ok = ctx.Value(mdKey{}).(MD) return diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata_test.go new file mode 100644 index 000000000000..fef0a0f815d0 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/metadata/metadata_test.go @@ -0,0 +1,139 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package metadata + +import ( + "reflect" + "testing" +) + +const binaryValue = string(128) + +func TestEncodeKeyValue(t *testing.T) { + for _, test := range []struct { + // input + kin string + vin string + // output + kout string + vout string + }{ + {"key", "abc", "key", "abc"}, + {"KEY", "abc", "key", "abc"}, + {"key-bin", "abc", "key-bin", "YWJj"}, + {"key-bin", binaryValue, "key-bin", "woA="}, + } { + k, v := encodeKeyValue(test.kin, test.vin) + if k != test.kout || !reflect.DeepEqual(v, test.vout) { + t.Fatalf("encodeKeyValue(%q, %q) = %q, %q, want %q, %q", test.kin, test.vin, k, v, test.kout, test.vout) + } + } +} + +func TestDecodeKeyValue(t *testing.T) { + for _, test := range []struct { + // input + kin string + vin string + // output + kout string + vout string + err error + }{ + {"a", "abc", "a", "abc", nil}, + {"key-bin", "Zm9vAGJhcg==", "key-bin", "foo\x00bar", nil}, + {"key-bin", "woA=", "key-bin", binaryValue, nil}, + {"a", "abc,efg", "a", "abc,efg", nil}, + {"key-bin", "Zm9vAGJhcg==,Zm9vAGJhcg==", "key-bin", "foo\x00bar,foo\x00bar", nil}, + } { + k, v, err := DecodeKeyValue(test.kin, test.vin) + if k != test.kout || !reflect.DeepEqual(v, test.vout) || !reflect.DeepEqual(err, test.err) { + t.Fatalf("DecodeKeyValue(%q, %q) = %q, %q, %v, want %q, %q, %v", test.kin, test.vin, k, v, err, test.kout, test.vout, test.err) + } + } +} + +func TestPairsMD(t *testing.T) { + for _, test := range []struct { + // input + kv []string + // output + md MD + size int + }{ + {[]string{}, MD{}, 0}, + {[]string{"k1", "v1", "k2-bin", binaryValue}, New(map[string]string{ + "k1": "v1", + "k2-bin": binaryValue, + }), 2}, + } { + md := Pairs(test.kv...) + if !reflect.DeepEqual(md, test.md) { + t.Fatalf("Pairs(%v) = %v, want %v", test.kv, md, test.md) + } + if md.Len() != test.size { + t.Fatalf("Pairs(%v) generates md of size %d, want %d", test.kv, md.Len(), test.size) + } + } +} + +func TestCopy(t *testing.T) { + const key, val = "key", "val" + orig := Pairs(key, val) + copy := orig.Copy() + if !reflect.DeepEqual(orig, copy) { + t.Errorf("copied value not equal to the original, got %v, want %v", copy, orig) + } + orig[key][0] = "foo" + if v := copy[key][0]; v != val { + t.Errorf("change in original should not affect copy, got %q, want %q", v, val) + } +} + +func TestJoin(t *testing.T) { + for _, test := range []struct { + mds []MD + want MD + }{ + {[]MD{}, MD{}}, + {[]MD{Pairs("foo", "bar")}, Pairs("foo", "bar")}, + {[]MD{Pairs("foo", "bar"), Pairs("foo", "baz")}, Pairs("foo", "bar", "foo", "baz")}, + {[]MD{Pairs("foo", "bar"), Pairs("foo", "baz"), Pairs("zip", "zap")}, Pairs("foo", "bar", "foo", "baz", "zip", "zap")}, + } { + md := Join(test.mds...) + if !reflect.DeepEqual(md, test.want) { + t.Errorf("context's metadata is %v, want %v", md, test.want) + } + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go index 28aab0a3f986..a25eaa8a2874 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util.go @@ -42,14 +42,11 @@ import ( "io/ioutil" "math" "os" - "time" "github.com/golang/protobuf/proto" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -141,7 +138,6 @@ type callInfo struct { failFast bool headerMD metadata.MD trailerMD metadata.MD - peer *peer.Peer traceInfo traceInfo // in trace.go } @@ -185,20 +181,12 @@ func Trailer(md *metadata.MD) CallOption { }) } -// Peer returns a CallOption that retrieves peer information for a -// unary RPC. -func Peer(peer *peer.Peer) CallOption { - return afterCall(func(c *callInfo) { - *peer = *c.peer - }) -} - // 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 // 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 -// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md. Note: failFast is default to true. +// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md func FailFast(failFast bool) CallOption { return beforeCall(func(c *callInfo) error { c.failFast = failFast @@ -267,11 +255,9 @@ func (p *parser) recvMsg(maxMsgSize int) (pf payloadFormat, msg []byte, err erro // encode serializes msg and prepends the message header. If msg is nil, it // generates the message header of 0 message length. -func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayload *stats.OutPayload) ([]byte, error) { - var ( - b []byte - length uint - ) +func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer) ([]byte, error) { + var b []byte + var length uint if msg != nil { var err error // TODO(zhaoq): optimize to reduce memory alloc and copying. @@ -279,12 +265,6 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayl if err != nil { return nil, err } - if outPayload != nil { - outPayload.Payload = msg - // TODO truncate large payload. - outPayload.Data = b - outPayload.Length = len(b) - } if cp != nil { if err := cp.Do(cbuf, b); err != nil { return nil, err @@ -315,10 +295,6 @@ func encode(c Codec, msg interface{}, cp Compressor, cbuf *bytes.Buffer, outPayl // Copy encoded msg to buf copy(buf[5:], b) - if outPayload != nil { - outPayload.WireLength = len(buf) - } - return buf, nil } @@ -335,14 +311,11 @@ func checkRecvPayload(pf payloadFormat, recvCompress string, dc Decompressor) er return nil } -func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxMsgSize int, inPayload *stats.InPayload) error { +func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxMsgSize int) error { pf, d, err := p.recvMsg(maxMsgSize) if err != nil { return err } - if inPayload != nil { - inPayload.WireLength = len(d) - } if err := checkRecvPayload(pf, s.RecvCompress(), dc); err != nil { return err } @@ -360,13 +333,6 @@ func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{ if err := c.Unmarshal(d, m); err != nil { return Errorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err) } - if inPayload != nil { - inPayload.RecvTime = time.Now() - inPayload.Payload = m - // TODO truncate large payload. - inPayload.Data = d - inPayload.Length = len(d) - } return nil } @@ -377,7 +343,7 @@ type rpcError struct { } func (e *rpcError) Error() string { - return fmt.Sprintf("rpc error: code = %s desc = %s", e.code, e.desc) + return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) } // Code returns the error code for err if it was produced by the rpc system. @@ -482,44 +448,6 @@ func convertCode(err error) codes.Code { return codes.Unknown } -// 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 minumum 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. - // TODO: support this. - MaxReqSize uint32 - // MaxRespSize is the maximum allowed payload size for an individual response in a - // stream (server->client) in bytes. - // TODO: support this. - MaxRespSize uint32 -} - -// 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. - Methods map[string]MethodConfig -} - // SupportPackageIsVersion4 is referenced from generated protocol buffer files // to assert that that code is compatible with this version of the grpc package. // @@ -527,6 +455,3 @@ type ServiceConfig struct { // requires a synchronised update of grpc-go and protoc-gen-go. This constant // should not be referenced from any other code. const SupportPackageIsVersion4 = true - -// Version is the current grpc version. -const Version = "1.2.1" diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util_test.go new file mode 100644 index 000000000000..0ba2d44cf756 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/rpc_util_test.go @@ -0,0 +1,234 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package grpc + +import ( + "bytes" + "io" + "math" + "reflect" + "testing" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + perfpb "google.golang.org/grpc/test/codec_perf" + "google.golang.org/grpc/transport" +) + +func TestSimpleParsing(t *testing.T) { + bigMsg := bytes.Repeat([]byte{'x'}, 1<<24) + for _, test := range []struct { + // input + p []byte + // outputs + err error + b []byte + pt payloadFormat + }{ + {nil, io.EOF, nil, compressionNone}, + {[]byte{0, 0, 0, 0, 0}, nil, nil, compressionNone}, + {[]byte{0, 0, 0, 0, 1, 'a'}, nil, []byte{'a'}, compressionNone}, + {[]byte{1, 0}, io.ErrUnexpectedEOF, nil, compressionNone}, + {[]byte{0, 0, 0, 0, 10, 'a'}, io.ErrUnexpectedEOF, nil, compressionNone}, + // Check that messages with length >= 2^24 are parsed. + {append([]byte{0, 1, 0, 0, 0}, bigMsg...), nil, bigMsg, compressionNone}, + } { + buf := bytes.NewReader(test.p) + parser := &parser{r: buf} + pt, b, err := parser.recvMsg(math.MaxInt32) + if err != test.err || !bytes.Equal(b, test.b) || pt != test.pt { + t.Fatalf("parser{%v}.recvMsg(_) = %v, %v, %v\nwant %v, %v, %v", test.p, pt, b, err, test.pt, test.b, test.err) + } + } +} + +func TestMultipleParsing(t *testing.T) { + // Set a byte stream consists of 3 messages with their headers. + p := []byte{0, 0, 0, 0, 1, 'a', 0, 0, 0, 0, 2, 'b', 'c', 0, 0, 0, 0, 1, 'd'} + b := bytes.NewReader(p) + parser := &parser{r: b} + + wantRecvs := []struct { + pt payloadFormat + data []byte + }{ + {compressionNone, []byte("a")}, + {compressionNone, []byte("bc")}, + {compressionNone, []byte("d")}, + } + for i, want := range wantRecvs { + pt, data, err := parser.recvMsg(math.MaxInt32) + if err != nil || pt != want.pt || !reflect.DeepEqual(data, want.data) { + t.Fatalf("after %d calls, parser{%v}.recvMsg(_) = %v, %v, %v\nwant %v, %v, ", + i, p, pt, data, err, want.pt, want.data) + } + } + + pt, data, err := parser.recvMsg(math.MaxInt32) + if err != io.EOF { + t.Fatalf("after %d recvMsgs calls, parser{%v}.recvMsg(_) = %v, %v, %v\nwant _, _, %v", + len(wantRecvs), p, pt, data, err, io.EOF) + } +} + +func TestEncode(t *testing.T) { + for _, test := range []struct { + // input + msg proto.Message + cp Compressor + // outputs + b []byte + err error + }{ + {nil, nil, []byte{0, 0, 0, 0, 0}, nil}, + } { + b, err := encode(protoCodec{}, test.msg, nil, nil) + if err != test.err || !bytes.Equal(b, test.b) { + t.Fatalf("encode(_, _, %v, _) = %v, %v\nwant %v, %v", test.cp, b, err, test.b, test.err) + } + } +} + +func TestCompress(t *testing.T) { + for _, test := range []struct { + // input + data []byte + cp Compressor + dc Decompressor + // outputs + err error + }{ + {make([]byte, 1024), &gzipCompressor{}, &gzipDecompressor{}, nil}, + } { + b := new(bytes.Buffer) + if err := test.cp.Do(b, test.data); err != test.err { + t.Fatalf("Compressor.Do(_, %v) = %v, want %v", test.data, err, test.err) + } + if b.Len() >= len(test.data) { + t.Fatalf("The compressor fails to compress data.") + } + if p, err := test.dc.Do(b); err != nil || !bytes.Equal(test.data, p) { + t.Fatalf("Decompressor.Do(%v) = %v, %v, want %v, ", b, p, err, test.data) + } + } +} + +func TestToRPCErr(t *testing.T) { + for _, test := range []struct { + // input + errIn error + // outputs + errOut *rpcError + }{ + {transport.StreamError{codes.Unknown, ""}, Errorf(codes.Unknown, "").(*rpcError)}, + {transport.ErrConnClosing, Errorf(codes.Internal, transport.ErrConnClosing.Desc).(*rpcError)}, + } { + err := toRPCErr(test.errIn) + rpcErr, ok := err.(*rpcError) + if !ok { + t.Fatalf("toRPCErr{%v} returned type %T, want %T", test.errIn, err, rpcError{}) + } + if *rpcErr != *test.errOut { + t.Fatalf("toRPCErr{%v} = %v \nwant %v", test.errIn, err, test.errOut) + } + } +} + +func TestContextErr(t *testing.T) { + for _, test := range []struct { + // input + errIn error + // outputs + errOut transport.StreamError + }{ + {context.DeadlineExceeded, transport.StreamError{codes.DeadlineExceeded, context.DeadlineExceeded.Error()}}, + {context.Canceled, transport.StreamError{codes.Canceled, context.Canceled.Error()}}, + } { + err := transport.ContextErr(test.errIn) + if err != test.errOut { + t.Fatalf("ContextErr{%v} = %v \nwant %v", test.errIn, err, test.errOut) + } + } +} + +func TestErrorsWithSameParameters(t *testing.T) { + const description = "some description" + e1 := Errorf(codes.AlreadyExists, description).(*rpcError) + e2 := Errorf(codes.AlreadyExists, description).(*rpcError) + if e1 == e2 { + t.Fatalf("Error interfaces should not be considered equal - e1: %p - %v e2: %p - %v", e1, e1, e2, e2) + } + if Code(e1) != Code(e2) || ErrorDesc(e1) != ErrorDesc(e2) { + t.Fatalf("Expected errors to have same code and description - e1: %p - %v e2: %p - %v", e1, e1, e2, e2) + } +} + +// bmEncode benchmarks encoding a Protocol Buffer message containing mSize +// bytes. +func bmEncode(b *testing.B, mSize int) { + msg := &perfpb.Buffer{Body: make([]byte, mSize)} + encoded, _ := encode(protoCodec{}, msg, nil, nil) + encodedSz := int64(len(encoded)) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + encode(protoCodec{}, msg, nil, nil) + } + b.SetBytes(encodedSz) +} + +func BenchmarkEncode1B(b *testing.B) { + bmEncode(b, 1) +} + +func BenchmarkEncode1KiB(b *testing.B) { + bmEncode(b, 1024) +} + +func BenchmarkEncode8KiB(b *testing.B) { + bmEncode(b, 8*1024) +} + +func BenchmarkEncode64KiB(b *testing.B) { + bmEncode(b, 64*1024) +} + +func BenchmarkEncode512KiB(b *testing.B) { + bmEncode(b, 512*1024) +} + +func BenchmarkEncode1MiB(b *testing.B) { + bmEncode(b, 1024*1024) +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go index 157f35eea502..e0bb187ef975 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server.go @@ -54,8 +54,6 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/stats" - "google.golang.org/grpc/tap" "google.golang.org/grpc/transport" ) @@ -112,11 +110,8 @@ type options struct { maxMsgSize int unaryInt UnaryServerInterceptor streamInt StreamServerInterceptor - inTapHandle tap.ServerInHandle - statsHandler stats.Handler maxConcurrentStreams uint32 useHandlerImpl bool // use http.Handler-based server - unknownStreamDesc *StreamDesc } var defaultMaxMsgSize = 1024 * 1024 * 4 // use 4MB as the default message size limit @@ -191,42 +186,6 @@ func StreamInterceptor(i StreamServerInterceptor) ServerOption { } } -// InTapHandle returns a ServerOption that sets the tap handle for all the server -// transport to be created. Only one can be installed. -func InTapHandle(h tap.ServerInHandle) ServerOption { - return func(o *options) { - if o.inTapHandle != nil { - panic("The tap handle has been set.") - } - o.inTapHandle = h - } -} - -// StatsHandler returns a ServerOption that sets the stats handler for the server. -func StatsHandler(h stats.Handler) ServerOption { - return func(o *options) { - o.statsHandler = h - } -} - -// UnknownServiceHandler returns a ServerOption that allows for adding a custom -// unknown service handler. The provided method is a bidi-streaming RPC service -// handler that will be invoked instead of returning the the "unimplemented" gRPC -// error whenever a request is received for an unregistered service or method. -// The handling function has full access to the Context of the request and the -// stream, and the invocation passes through interceptors. -func UnknownServiceHandler(streamHandler StreamHandler) ServerOption { - return func(o *options) { - o.unknownStreamDesc = &StreamDesc{ - StreamName: "unknown_service_handler", - Handler: streamHandler, - // We need to assume that the users of the streamHandler will want to use both. - ClientStreams: true, - ServerStreams: true, - } - } -} - // NewServer creates a gRPC server which has no service registered and has not // started to accept requests yet. func NewServer(opt ...ServerOption) *Server { @@ -370,7 +329,6 @@ 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. func (s *Server) Serve(lis net.Listener) error { s.mu.Lock() s.printf("serving") @@ -454,23 +412,17 @@ func (s *Server) handleRawConn(rawConn net.Conn) { if s.opts.useHandlerImpl { s.serveUsingHandler(conn) } else { - s.serveHTTP2Transport(conn, authInfo) + s.serveNewHTTP2Transport(conn, authInfo) } } -// serveHTTP2Transport sets up a http/2 transport (using the +// serveNewHTTP2Transport sets up a new http/2 transport (using the // gRPC http2 server transport in transport/http2_server.go) and // serves streams on it. // This is run in its own goroutine (it does network I/O in // transport.NewServerTransport). -func (s *Server) serveHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) { - config := &transport.ServerConfig{ - MaxStreams: s.opts.maxConcurrentStreams, - AuthInfo: authInfo, - InTapHandle: s.opts.inTapHandle, - StatsHandler: s.opts.statsHandler, - } - st, err := transport.NewServerTransport("http2", c, config) +func (s *Server) serveNewHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) { + st, err := transport.NewServerTransport("http2", c, s.opts.maxConcurrentStreams, authInfo) if err != nil { s.mu.Lock() s.errorf("NewServerTransport(%q) failed: %v", c.RemoteAddr(), err) @@ -496,12 +448,6 @@ func (s *Server) serveStreams(st transport.ServerTransport) { defer wg.Done() s.handleStream(st, stream, s.traceInfo(st, stream)) }() - }, func(ctx context.Context, method string) context.Context { - if !EnableTracing { - return ctx - } - tr := trace.New("grpc.Recv."+methodFamily(method), method) - return trace.NewContext(ctx, tr) }) wg.Wait() } @@ -551,17 +497,15 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // traceInfo returns a traceInfo and associates it with stream, if tracing is enabled. // If tracing is not enabled, it returns nil. func (s *Server) traceInfo(st transport.ServerTransport, stream *transport.Stream) (trInfo *traceInfo) { - tr, ok := trace.FromContext(stream.Context()) - if !ok { + if !EnableTracing { return nil } - trInfo = &traceInfo{ - tr: tr, + tr: trace.New("grpc.Recv."+methodFamily(stream.Method()), stream.Method()), } trInfo.firstLine.client = false trInfo.firstLine.remoteAddr = st.RemoteAddr() - + stream.TraceContext(trInfo.tr) if dl, ok := stream.Context().Deadline(); ok { trInfo.firstLine.deadline = dl.Sub(time.Now()) } @@ -588,17 +532,11 @@ 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 { - var ( - cbuf *bytes.Buffer - outPayload *stats.OutPayload - ) + var cbuf *bytes.Buffer if cp != nil { cbuf = new(bytes.Buffer) } - if s.opts.statsHandler != nil { - outPayload = &stats.OutPayload{} - } - p, err := encode(s.opts.codec, msg, cp, cbuf, outPayload) + p, err := encode(s.opts.codec, msg, cp, cbuf) if err != nil { // This typically indicates a fatal issue (e.g., memory // corruption or hardware faults) the application program @@ -609,33 +547,10 @@ func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Str // the optimal option. grpclog.Fatalf("grpc: Server failed to encode response %v", err) } - err = t.Write(stream, p, opts) - if err == nil && outPayload != nil { - outPayload.SentTime = time.Now() - s.opts.statsHandler.HandleRPC(stream.Context(), outPayload) - } - return err + return t.Write(stream, p, opts) } 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 { - begin := &stats.Begin{ - BeginTime: time.Now(), - } - sh.HandleRPC(stream.Context(), begin) - } - defer func() { - if sh != nil { - end := &stats.End{ - EndTime: time.Now(), - } - if err != nil && err != io.EOF { - end.Error = toRPCErr(err) - } - sh.HandleRPC(stream.Context(), end) - } - }() if trInfo != nil { defer trInfo.tr.Finish() trInfo.firstLine.client = false @@ -664,14 +579,14 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err != nil { switch err := err.(type) { case *rpcError: - if e := t.WriteStatus(stream, err.code, err.desc); e != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) + if err := t.WriteStatus(stream, err.code, err.desc); err != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } case transport.ConnectionError: // Nothing to do here. case transport.StreamError: - if e := t.WriteStatus(stream, err.Code, err.Desc); e != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) + if err := t.WriteStatus(stream, err.Code, err.Desc); err != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } default: panic(fmt.Sprintf("grpc: Unexpected error (%T) from recvMsg: %v", err, err)) @@ -682,29 +597,20 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := checkRecvPayload(pf, stream.RecvCompress(), s.opts.dc); err != nil { switch err := err.(type) { case *rpcError: - if e := t.WriteStatus(stream, err.code, err.desc); e != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) + if err := t.WriteStatus(stream, err.code, err.desc); err != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } - return err default: - if e := t.WriteStatus(stream, codes.Internal, err.Error()); e != nil { - grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", e) + if err := t.WriteStatus(stream, codes.Internal, err.Error()); err != nil { + grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } - // TODO checkRecvPayload always return RPC error. Add a return here if necessary. - } - } - var inPayload *stats.InPayload - if sh != nil { - inPayload = &stats.InPayload{ - RecvTime: time.Now(), + } + return err } statusCode := codes.OK statusDesc := "" df := func(v interface{}) error { - if inPayload != nil { - inPayload.WireLength = len(req) - } if pf == compressionMade { var err error req, err = s.opts.dc.Do(bytes.NewReader(req)) @@ -712,7 +618,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := t.WriteStatus(stream, codes.Internal, err.Error()); err != nil { grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err) } - return Errorf(codes.Internal, err.Error()) + return err } } if len(req) > s.opts.maxMsgSize { @@ -724,12 +630,6 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if err := s.opts.codec.Unmarshal(req, v); err != nil { return err } - if inPayload != nil { - inPayload.Payload = v - inPayload.Data = req - inPayload.Length = len(req) - sh.HandleRPC(stream.Context(), inPayload) - } if trInfo != nil { trInfo.tr.LazyLog(&payload{sent: false, msg: v}, true) } @@ -750,8 +650,9 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. } if err := t.WriteStatus(stream, statusCode, statusDesc); err != nil { grpclog.Printf("grpc: Server.processUnaryRPC failed to write status: %v", err) + return err } - return Errorf(statusCode, statusDesc) + return nil } if trInfo != nil { trInfo.tr.LazyLog(stringer("OK"), false) @@ -776,46 +677,23 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport. if trInfo != nil { trInfo.tr.LazyLog(&payload{sent: true, msg: reply}, true) } - errWrite := t.WriteStatus(stream, statusCode, statusDesc) - if statusCode != codes.OK { - return Errorf(statusCode, statusDesc) - } - return errWrite + return t.WriteStatus(stream, statusCode, statusDesc) } } 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 { - begin := &stats.Begin{ - BeginTime: time.Now(), - } - sh.HandleRPC(stream.Context(), begin) - } - defer func() { - if sh != nil { - end := &stats.End{ - EndTime: time.Now(), - } - if err != nil && err != io.EOF { - end.Error = toRPCErr(err) - } - sh.HandleRPC(stream.Context(), end) - } - }() if s.opts.cp != nil { stream.SetSendCompress(s.opts.cp.Type()) } ss := &serverStream{ - t: t, - s: stream, - p: &parser{r: stream}, - codec: s.opts.codec, - cp: s.opts.cp, - dc: s.opts.dc, - maxMsgSize: s.opts.maxMsgSize, - trInfo: trInfo, - statsHandler: sh, + t: t, + s: stream, + p: &parser{r: stream}, + codec: s.opts.codec, + cp: s.opts.cp, + dc: s.opts.dc, + maxMsgSize: s.opts.maxMsgSize, + trInfo: trInfo, } if ss.cp != nil { ss.cbuf = new(bytes.Buffer) @@ -834,19 +712,15 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp }() } var appErr error - var server interface{} - if srv != nil { - server = srv.server - } if s.opts.streamInt == nil { - appErr = sd.Handler(server, ss) + appErr = sd.Handler(srv.server, ss) } else { info := &StreamServerInfo{ FullMethod: stream.Method(), IsClientStream: sd.ClientStreams, IsServerStream: sd.ServerStreams, } - appErr = s.opts.streamInt(server, ss, info, sd.Handler) + appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler) } if appErr != nil { if err, ok := appErr.(*rpcError); ok { @@ -870,11 +744,7 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp } ss.mu.Unlock() } - errWrite := t.WriteStatus(ss.s, ss.statusCode, ss.statusDesc) - if ss.statusCode != codes.OK { - return Errorf(ss.statusCode, ss.statusDesc) - } - return errWrite + return t.WriteStatus(ss.s, ss.statusCode, ss.statusDesc) } @@ -889,8 +759,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str trInfo.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{sm}}, true) trInfo.tr.SetError() } - errDesc := fmt.Sprintf("malformed method name: %q", stream.Method()) - if err := t.WriteStatus(stream, codes.InvalidArgument, errDesc); err != nil { + if err := t.WriteStatus(stream, codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", stream.Method())); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() @@ -906,16 +775,11 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str method := sm[pos+1:] srv, ok := s.m[service] if !ok { - if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil { - s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo) - return - } if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"Unknown service %v", []interface{}{service}}, true) trInfo.tr.SetError() } - errDesc := fmt.Sprintf("unknown service %v", service) - if err := t.WriteStatus(stream, codes.Unimplemented, errDesc); err != nil { + if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown service %v", service)); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() @@ -940,12 +804,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str trInfo.tr.LazyLog(&fmtStringer{"Unknown method %v", []interface{}{method}}, true) trInfo.tr.SetError() } - if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil { - s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo) - return - } - errDesc := fmt.Sprintf("unknown method %v", method) - if err := t.WriteStatus(stream, codes.Unimplemented, errDesc); err != nil { + if err := t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown method %v", method)); err != nil { if trInfo != nil { trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.SetError() diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server_test.go new file mode 100644 index 000000000000..23838806d428 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/server_test.go @@ -0,0 +1,113 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package grpc + +import ( + "net" + "reflect" + "strings" + "testing" +) + +type emptyServiceServer interface{} + +type testServer struct{} + +func TestStopBeforeServe(t *testing.T) { + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + + server := NewServer() + server.Stop() + err = server.Serve(lis) + if err != ErrServerStopped { + t.Fatalf("server.Serve() error = %v, want %v", err, ErrServerStopped) + } + + // server.Serve is responsible for closing the listener, even if the + // server was already stopped. + err = lis.Close() + if got, want := ErrorDesc(err), "use of closed network connection"; !strings.Contains(got, want) { + t.Errorf("Close() error = %q, want %q", got, want) + } +} + +func TestGetServiceInfo(t *testing.T) { + testSd := ServiceDesc{ + ServiceName: "grpc.testing.EmptyService", + HandlerType: (*emptyServiceServer)(nil), + Methods: []MethodDesc{ + { + MethodName: "EmptyCall", + Handler: nil, + }, + }, + Streams: []StreamDesc{ + { + StreamName: "EmptyStream", + Handler: nil, + ServerStreams: false, + ClientStreams: true, + }, + }, + Metadata: []int{0, 2, 1, 3}, + } + + server := NewServer() + server.RegisterService(&testSd, &testServer{}) + + info := server.GetServiceInfo() + want := map[string]ServiceInfo{ + "grpc.testing.EmptyService": { + Methods: []MethodInfo{ + { + Name: "EmptyCall", + IsClientStream: false, + IsServerStream: false, + }, + { + Name: "EmptyStream", + IsClientStream: true, + IsServerStream: false, + }}, + Metadata: []int{0, 2, 1, 3}, + }, + } + + if !reflect.DeepEqual(info, want) { + t.Errorf("GetServiceInfo() = %+v, want %+v", info, want) + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go new file mode 100644 index 000000000000..b24dcd8d343d --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.pb.go @@ -0,0 +1,225 @@ +// Code generated by protoc-gen-go. +// source: test.proto +// DO NOT EDIT! + +/* +Package grpc_testing is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + SimpleRequest + SimpleResponse +*/ +package grpc_testing + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// Unary request. +type SimpleRequest struct { + Id int32 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"` +} + +func (m *SimpleRequest) Reset() { *m = SimpleRequest{} } +func (m *SimpleRequest) String() string { return proto.CompactTextString(m) } +func (*SimpleRequest) ProtoMessage() {} +func (*SimpleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// Unary response, as configured by the request. +type SimpleResponse struct { + Id int32 `protobuf:"varint,3,opt,name=id" json:"id,omitempty"` +} + +func (m *SimpleResponse) Reset() { *m = SimpleResponse{} } +func (m *SimpleResponse) String() string { return proto.CompactTextString(m) } +func (*SimpleResponse) ProtoMessage() {} +func (*SimpleResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*SimpleRequest)(nil), "grpc.testing.SimpleRequest") + proto.RegisterType((*SimpleResponse)(nil), "grpc.testing.SimpleResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for TestService service + +type TestServiceClient interface { + // One request followed by one response. + // The server returns the client id as-is. + UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) +} + +type testServiceClient struct { + cc *grpc.ClientConn +} + +func NewTestServiceClient(cc *grpc.ClientConn) TestServiceClient { + return &testServiceClient{cc} +} + +func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) { + out := new(SimpleResponse) + err := grpc.Invoke(ctx, "/grpc.testing.TestService/UnaryCall", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testServiceClient) FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) { + stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[0], c.cc, "/grpc.testing.TestService/FullDuplexCall", opts...) + if err != nil { + return nil, err + } + x := &testServiceFullDuplexCallClient{stream} + return x, nil +} + +type TestService_FullDuplexCallClient interface { + Send(*SimpleRequest) error + Recv() (*SimpleResponse, error) + grpc.ClientStream +} + +type testServiceFullDuplexCallClient struct { + grpc.ClientStream +} + +func (x *testServiceFullDuplexCallClient) Send(m *SimpleRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testServiceFullDuplexCallClient) Recv() (*SimpleResponse, error) { + m := new(SimpleResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for TestService service + +type TestServiceServer interface { + // One request followed by one response. + // The server returns the client id as-is. + UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + FullDuplexCall(TestService_FullDuplexCallServer) error +} + +func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { + s.RegisterService(&_TestService_serviceDesc, srv) +} + +func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SimpleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServiceServer).UnaryCall(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.testing.TestService/UnaryCall", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServiceServer).UnaryCall(ctx, req.(*SimpleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestService_FullDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestServiceServer).FullDuplexCall(&testServiceFullDuplexCallServer{stream}) +} + +type TestService_FullDuplexCallServer interface { + Send(*SimpleResponse) error + Recv() (*SimpleRequest, error) + grpc.ServerStream +} + +type testServiceFullDuplexCallServer struct { + grpc.ServerStream +} + +func (x *testServiceFullDuplexCallServer) Send(m *SimpleResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testServiceFullDuplexCallServer) Recv() (*SimpleRequest, error) { + m := new(SimpleRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _TestService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.testing.TestService", + HandlerType: (*TestServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UnaryCall", + Handler: _TestService_UnaryCall_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "FullDuplexCall", + Handler: _TestService_FullDuplexCall_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "test.proto", +} + +func init() { proto.RegisterFile("test.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 167 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, + 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x49, 0x2f, 0x2a, 0x48, 0xd6, 0x03, 0x09, 0x64, + 0xe6, 0xa5, 0x2b, 0xc9, 0x73, 0xf1, 0x06, 0x67, 0xe6, 0x16, 0xe4, 0xa4, 0x06, 0xa5, 0x16, 0x96, + 0xa6, 0x16, 0x97, 0x08, 0xf1, 0x71, 0x31, 0x65, 0xa6, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xb0, 0x06, + 0x31, 0x65, 0xa6, 0x28, 0x29, 0x70, 0xf1, 0xc1, 0x14, 0x14, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x42, + 0x55, 0x30, 0xc3, 0x54, 0x18, 0x2d, 0x63, 0xe4, 0xe2, 0x0e, 0x49, 0x2d, 0x2e, 0x09, 0x4e, 0x2d, + 0x2a, 0xcb, 0x4c, 0x4e, 0x15, 0x72, 0xe3, 0xe2, 0x0c, 0xcd, 0x4b, 0x2c, 0xaa, 0x74, 0x4e, 0xcc, + 0xc9, 0x11, 0x92, 0xd6, 0x43, 0xb6, 0x4e, 0x0f, 0xc5, 0x2e, 0x29, 0x19, 0xec, 0x92, 0x50, 0x7b, + 0xfc, 0xb9, 0xf8, 0xdc, 0x4a, 0x73, 0x72, 0x5c, 0x4a, 0x0b, 0x72, 0x52, 0x2b, 0x28, 0x34, 0x4c, + 0x83, 0xd1, 0x80, 0x31, 0x89, 0x0d, 0x1c, 0x00, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8d, + 0x82, 0x5b, 0xdd, 0x0e, 0x01, 0x00, 0x00, +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto new file mode 100644 index 000000000000..54e6f744f175 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/grpc_testing/test.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package grpc.testing; + +message SimpleRequest { + int32 id = 2; +} + +message SimpleResponse { + int32 id = 3; +} + +// A simple test service. +service TestService { + // One request followed by one response. + // The server returns the client id as-is. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream SimpleRequest) returns (stream SimpleResponse); +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/handlers.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/handlers.go new file mode 100644 index 000000000000..26e1a8e2f081 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/handlers.go @@ -0,0 +1,76 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package stats + +import ( + "net" + + "golang.org/x/net/context" +) + +// ConnTagInfo defines the relevant information needed by connection context tagger. +type ConnTagInfo struct { + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // TODO add QOS related fields. +} + +// RPCTagInfo defines the relevant information needed by RPC context tagger. +type RPCTagInfo struct { + // FullMethodName is the RPC method in the format of /package.service/method. + FullMethodName string +} + +// Handler defines the interface for the related stats handling (e.g., RPCs, connections). +type Handler interface { + // TagRPC can attach some information to the given context. + // The returned context is used in the rest lifetime of the RPC. + TagRPC(context.Context, *RPCTagInfo) context.Context + // HandleRPC processes the RPC stats. + HandleRPC(context.Context, RPCStats) + + // TagConn can attach some information to the given context. + // The returned context will be used for stats handling. + // For conn stats handling, the context used in HandleConn for this + // connection will be derived from the context returned. + // For RPC stats handling, + // - On server side, the context used in HandleRPC for all RPCs on this + // connection will be derived from the context returned. + // - On client side, the context is not derived from the context returned. + TagConn(context.Context, *ConnTagInfo) context.Context + // HandleConn processes the Conn stats. + HandleConn(context.Context, ConnStats) +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats.go new file mode 100644 index 000000000000..a82448a68bb1 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats.go @@ -0,0 +1,223 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Package stats is for collecting and reporting various network and RPC stats. +// This package is for monitoring purpose only. All fields are read-only. +// All APIs are experimental. +package stats // import "google.golang.org/grpc/stats" + +import ( + "net" + "time" +) + +// RPCStats contains stats information about RPCs. +type RPCStats interface { + isRPCStats() + // IsClient returns true if this RPCStats is from client side. + IsClient() bool +} + +// Begin contains stats when an RPC begins. +// FailFast are only valid if Client is true. +type Begin struct { + // Client is true if this Begin is from client side. + Client bool + // BeginTime is the time when the RPC begins. + BeginTime time.Time + // FailFast indicates if this RPC is failfast. + FailFast bool +} + +// IsClient indicates if this is from client side. +func (s *Begin) IsClient() bool { return s.Client } + +func (s *Begin) isRPCStats() {} + +// InPayload contains the information for an incoming payload. +type InPayload struct { + // Client is true if this InPayload is from client side. + Client bool + // Payload is the payload with original type. + Payload interface{} + // Data is the serialized message payload. + Data []byte + // Length is the length of uncompressed data. + Length int + // WireLength is the length of data on wire (compressed, signed, encrypted). + WireLength int + // RecvTime is the time when the payload is received. + RecvTime time.Time +} + +// IsClient indicates if this is from client side. +func (s *InPayload) IsClient() bool { return s.Client } + +func (s *InPayload) isRPCStats() {} + +// InHeader contains stats when a header is received. +// FullMethod, addresses and Compression are only valid if Client is false. +type InHeader struct { + // Client is true if this InHeader is from client side. + Client bool + // WireLength is the wire length of header. + WireLength int + + // FullMethod is the full RPC method string, i.e., /package.service/method. + FullMethod string + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // Compression is the compression algorithm used for the RPC. + Compression string +} + +// IsClient indicates if this is from client side. +func (s *InHeader) IsClient() bool { return s.Client } + +func (s *InHeader) isRPCStats() {} + +// InTrailer contains stats when a trailer is received. +type InTrailer struct { + // Client is true if this InTrailer is from client side. + Client bool + // WireLength is the wire length of trailer. + WireLength int +} + +// IsClient indicates if this is from client side. +func (s *InTrailer) IsClient() bool { return s.Client } + +func (s *InTrailer) isRPCStats() {} + +// OutPayload contains the information for an outgoing payload. +type OutPayload struct { + // Client is true if this OutPayload is from client side. + Client bool + // Payload is the payload with original type. + Payload interface{} + // Data is the serialized message payload. + Data []byte + // Length is the length of uncompressed data. + Length int + // WireLength is the length of data on wire (compressed, signed, encrypted). + WireLength int + // SentTime is the time when the payload is sent. + SentTime time.Time +} + +// IsClient indicates if this is from client side. +func (s *OutPayload) IsClient() bool { return s.Client } + +func (s *OutPayload) isRPCStats() {} + +// OutHeader contains stats when a header is sent. +// FullMethod, addresses and Compression are only valid if Client is true. +type OutHeader struct { + // Client is true if this OutHeader is from client side. + Client bool + // WireLength is the wire length of header. + WireLength int + + // FullMethod is the full RPC method string, i.e., /package.service/method. + FullMethod string + // RemoteAddr is the remote address of the corresponding connection. + RemoteAddr net.Addr + // LocalAddr is the local address of the corresponding connection. + LocalAddr net.Addr + // Compression is the compression algorithm used for the RPC. + Compression string +} + +// IsClient indicates if this is from client side. +func (s *OutHeader) IsClient() bool { return s.Client } + +func (s *OutHeader) isRPCStats() {} + +// OutTrailer contains stats when a trailer is sent. +type OutTrailer struct { + // Client is true if this OutTrailer is from client side. + Client bool + // WireLength is the wire length of trailer. + WireLength int +} + +// IsClient indicates if this is from client side. +func (s *OutTrailer) IsClient() bool { return s.Client } + +func (s *OutTrailer) isRPCStats() {} + +// End contains stats when an RPC ends. +type End struct { + // Client is true if this End is from client side. + Client bool + // EndTime is the time when the RPC ends. + EndTime time.Time + // Error is the error just happened. Its type is gRPC error. + Error error +} + +// IsClient indicates if this is from client side. +func (s *End) IsClient() bool { return s.Client } + +func (s *End) isRPCStats() {} + +// ConnStats contains stats information about connections. +type ConnStats interface { + isConnStats() + // IsClient returns true if this ConnStats is from client side. + IsClient() bool +} + +// ConnBegin contains the stats of a connection when it is established. +type ConnBegin struct { + // Client is true if this ConnBegin is from client side. + Client bool +} + +// IsClient indicates if this is from client side. +func (s *ConnBegin) IsClient() bool { return s.Client } + +func (s *ConnBegin) isConnStats() {} + +// ConnEnd contains the stats of a connection when it ends. +type ConnEnd struct { + // Client is true if this ConnEnd is from client side. + Client bool +} + +// IsClient indicates if this is from client side. +func (s *ConnEnd) IsClient() bool { return s.Client } + +func (s *ConnEnd) isConnStats() {} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats_test.go new file mode 100644 index 000000000000..3e5424beb4b7 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stats/stats_test.go @@ -0,0 +1,981 @@ +/* + * + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package stats_test + +import ( + "fmt" + "io" + "net" + "reflect" + "sync" + "testing" + "time" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/stats" + testpb "google.golang.org/grpc/stats/grpc_testing" +) + +func init() { + grpc.EnableTracing = false +} + +type connCtxKey struct{} +type rpcCtxKey struct{} + +var ( + // For headers: + testMetadata = metadata.MD{ + "key1": []string{"value1"}, + "key2": []string{"value2"}, + } + // For trailers: + testTrailerMetadata = metadata.MD{ + "tkey1": []string{"trailerValue1"}, + "tkey2": []string{"trailerValue2"}, + } + // The id for which the service handler should return error. + errorID int32 = 32202 +) + +type testServer struct{} + +func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { + md, ok := metadata.FromContext(ctx) + if ok { + if err := grpc.SendHeader(ctx, md); err != nil { + return nil, grpc.Errorf(grpc.Code(err), "grpc.SendHeader(_, %v) = %v, want ", md, err) + } + if err := grpc.SetTrailer(ctx, testTrailerMetadata); err != nil { + return nil, grpc.Errorf(grpc.Code(err), "grpc.SetTrailer(_, %v) = %v, want ", testTrailerMetadata, err) + } + } + + if in.Id == errorID { + return nil, fmt.Errorf("got error id: %v", in.Id) + } + + return &testpb.SimpleResponse{Id: in.Id}, nil +} + +func (s *testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error { + md, ok := metadata.FromContext(stream.Context()) + if ok { + if err := stream.SendHeader(md); err != nil { + return grpc.Errorf(grpc.Code(err), "%v.SendHeader(%v) = %v, want %v", stream, md, err, nil) + } + stream.SetTrailer(testTrailerMetadata) + } + for { + in, err := stream.Recv() + if err == io.EOF { + // read done. + return nil + } + if err != nil { + return err + } + + if in.Id == errorID { + return fmt.Errorf("got error id: %v", in.Id) + } + + if err := stream.Send(&testpb.SimpleResponse{Id: in.Id}); err != nil { + return err + } + } +} + +// test is an end-to-end test. It should be created with the newTest +// func, modified as needed, and then started with its startServer method. +// It should be cleaned up with the tearDown method. +type test struct { + t *testing.T + compress string + clientStatsHandler stats.Handler + serverStatsHandler stats.Handler + + testServer testpb.TestServiceServer // nil means none + // srv and srvAddr are set once startServer is called. + srv *grpc.Server + srvAddr string + + cc *grpc.ClientConn // nil until requested via clientConn +} + +func (te *test) tearDown() { + if te.cc != nil { + te.cc.Close() + te.cc = nil + } + te.srv.Stop() +} + +type testConfig struct { + compress string +} + +// newTest returns a new test using the provided testing.T and +// environment. It is returned with default values. Tests should +// modify it before calling its startServer and clientConn methods. +func newTest(t *testing.T, tc *testConfig, ch stats.Handler, sh stats.Handler) *test { + te := &test{ + t: t, + compress: tc.compress, + clientStatsHandler: ch, + serverStatsHandler: sh, + } + return te +} + +// startServer starts a gRPC server listening. Callers should defer a +// call to te.tearDown to clean up. +func (te *test) startServer(ts testpb.TestServiceServer) { + te.testServer = ts + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + te.t.Fatalf("Failed to listen: %v", err) + } + var opts []grpc.ServerOption + if te.compress == "gzip" { + opts = append(opts, + grpc.RPCCompressor(grpc.NewGZIPCompressor()), + grpc.RPCDecompressor(grpc.NewGZIPDecompressor()), + ) + } + if te.serverStatsHandler != nil { + opts = append(opts, grpc.StatsHandler(te.serverStatsHandler)) + } + s := grpc.NewServer(opts...) + te.srv = s + if te.testServer != nil { + testpb.RegisterTestServiceServer(s, te.testServer) + } + _, port, err := net.SplitHostPort(lis.Addr().String()) + if err != nil { + te.t.Fatalf("Failed to parse listener address: %v", err) + } + addr := "127.0.0.1:" + port + + go s.Serve(lis) + te.srvAddr = addr +} + +func (te *test) clientConn() *grpc.ClientConn { + if te.cc != nil { + return te.cc + } + opts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock()} + if te.compress == "gzip" { + opts = append(opts, + grpc.WithCompressor(grpc.NewGZIPCompressor()), + grpc.WithDecompressor(grpc.NewGZIPDecompressor()), + ) + } + if te.clientStatsHandler != nil { + opts = append(opts, grpc.WithStatsHandler(te.clientStatsHandler)) + } + + var err error + te.cc, err = grpc.Dial(te.srvAddr, opts...) + if err != nil { + te.t.Fatalf("Dial(%q) = %v", te.srvAddr, err) + } + return te.cc +} + +type rpcConfig struct { + count int // Number of requests and responses for streaming RPCs. + success bool // Whether the RPC should succeed or return error. + failfast bool + streaming bool // Whether the rpc should be a streaming RPC. +} + +func (te *test) doUnaryCall(c *rpcConfig) (*testpb.SimpleRequest, *testpb.SimpleResponse, error) { + var ( + resp *testpb.SimpleResponse + req *testpb.SimpleRequest + err error + ) + tc := testpb.NewTestServiceClient(te.clientConn()) + if c.success { + req = &testpb.SimpleRequest{Id: errorID + 1} + } else { + req = &testpb.SimpleRequest{Id: errorID} + } + ctx := metadata.NewContext(context.Background(), testMetadata) + + resp, err = tc.UnaryCall(ctx, req, grpc.FailFast(c.failfast)) + return req, resp, err +} + +func (te *test) doFullDuplexCallRoundtrip(c *rpcConfig) ([]*testpb.SimpleRequest, []*testpb.SimpleResponse, error) { + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + tc := testpb.NewTestServiceClient(te.clientConn()) + stream, err := tc.FullDuplexCall(metadata.NewContext(context.Background(), testMetadata), grpc.FailFast(c.failfast)) + if err != nil { + return reqs, resps, err + } + var startID int32 + if !c.success { + startID = errorID + } + for i := 0; i < c.count; i++ { + req := &testpb.SimpleRequest{ + Id: int32(i) + startID, + } + reqs = append(reqs, req) + if err = stream.Send(req); err != nil { + return reqs, resps, err + } + var resp *testpb.SimpleResponse + if resp, err = stream.Recv(); err != nil { + return reqs, resps, err + } + resps = append(resps, resp) + } + if err = stream.CloseSend(); err != nil && err != io.EOF { + return reqs, resps, err + } + if _, err = stream.Recv(); err != io.EOF { + return reqs, resps, err + } + + return reqs, resps, nil +} + +type expectedData struct { + method string + serverAddr string + compression string + reqIdx int + requests []*testpb.SimpleRequest + respIdx int + responses []*testpb.SimpleResponse + err error + failfast bool +} + +type gotData struct { + ctx context.Context + client bool + s interface{} // This could be RPCStats or ConnStats. +} + +const ( + begin int = iota + end + inPayload + inHeader + inTrailer + outPayload + outHeader + outTrailer + connbegin + connend +) + +func checkBegin(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.Begin + ) + if st, ok = d.s.(*stats.Begin); !ok { + t.Fatalf("got %T, want Begin", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.BeginTime.IsZero() { + t.Fatalf("st.BeginTime = %v, want ", st.BeginTime) + } + if d.client { + if st.FailFast != e.failfast { + t.Fatalf("st.FailFast = %v, want %v", st.FailFast, e.failfast) + } + } +} + +func checkInHeader(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InHeader + ) + if st, ok = d.s.(*stats.InHeader); !ok { + t.Fatalf("got %T, want InHeader", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } + if !d.client { + if st.FullMethod != e.method { + t.Fatalf("st.FullMethod = %s, want %v", st.FullMethod, e.method) + } + if st.LocalAddr.String() != e.serverAddr { + t.Fatalf("st.LocalAddr = %v, want %v", st.LocalAddr, e.serverAddr) + } + if st.Compression != e.compression { + t.Fatalf("st.Compression = %v, want %v", st.Compression, e.compression) + } + + if connInfo, ok := d.ctx.Value(connCtxKey{}).(*stats.ConnTagInfo); ok { + if connInfo.RemoteAddr != st.RemoteAddr { + t.Fatalf("connInfo.RemoteAddr = %v, want %v", connInfo.RemoteAddr, st.RemoteAddr) + } + if connInfo.LocalAddr != st.LocalAddr { + t.Fatalf("connInfo.LocalAddr = %v, want %v", connInfo.LocalAddr, st.LocalAddr) + } + } else { + t.Fatalf("got context %v, want one with connCtxKey", d.ctx) + } + if rpcInfo, ok := d.ctx.Value(rpcCtxKey{}).(*stats.RPCTagInfo); ok { + if rpcInfo.FullMethodName != st.FullMethod { + t.Fatalf("rpcInfo.FullMethod = %s, want %v", rpcInfo.FullMethodName, st.FullMethod) + } + } else { + t.Fatalf("got context %v, want one with rpcCtxKey", d.ctx) + } + } +} + +func checkInPayload(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InPayload + ) + if st, ok = d.s.(*stats.InPayload); !ok { + t.Fatalf("got %T, want InPayload", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if d.client { + b, err := proto.Marshal(e.responses[e.respIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.responses[e.respIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.responses[e.respIdx]) + } + e.respIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } else { + b, err := proto.Marshal(e.requests[e.reqIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.requests[e.reqIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.requests[e.reqIdx]) + } + e.reqIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } + // TODO check WireLength and ReceivedTime. + if st.RecvTime.IsZero() { + t.Fatalf("st.ReceivedTime = %v, want ", st.RecvTime) + } +} + +func checkInTrailer(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.InTrailer + ) + if st, ok = d.s.(*stats.InTrailer); !ok { + t.Fatalf("got %T, want InTrailer", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } +} + +func checkOutHeader(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutHeader + ) + if st, ok = d.s.(*stats.OutHeader); !ok { + t.Fatalf("got %T, want OutHeader", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } + if d.client { + if st.FullMethod != e.method { + t.Fatalf("st.FullMethod = %s, want %v", st.FullMethod, e.method) + } + if st.RemoteAddr.String() != e.serverAddr { + t.Fatalf("st.RemoteAddr = %v, want %v", st.RemoteAddr, e.serverAddr) + } + if st.Compression != e.compression { + t.Fatalf("st.Compression = %v, want %v", st.Compression, e.compression) + } + + if rpcInfo, ok := d.ctx.Value(rpcCtxKey{}).(*stats.RPCTagInfo); ok { + if rpcInfo.FullMethodName != st.FullMethod { + t.Fatalf("rpcInfo.FullMethod = %s, want %v", rpcInfo.FullMethodName, st.FullMethod) + } + } else { + t.Fatalf("got context %v, want one with rpcCtxKey", d.ctx) + } + } +} + +func checkOutPayload(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutPayload + ) + if st, ok = d.s.(*stats.OutPayload); !ok { + t.Fatalf("got %T, want OutPayload", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if d.client { + b, err := proto.Marshal(e.requests[e.reqIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.requests[e.reqIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.requests[e.reqIdx]) + } + e.reqIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } else { + b, err := proto.Marshal(e.responses[e.respIdx]) + if err != nil { + t.Fatalf("failed to marshal message: %v", err) + } + if reflect.TypeOf(st.Payload) != reflect.TypeOf(e.responses[e.respIdx]) { + t.Fatalf("st.Payload = %T, want %T", st.Payload, e.responses[e.respIdx]) + } + e.respIdx++ + if string(st.Data) != string(b) { + t.Fatalf("st.Data = %v, want %v", st.Data, b) + } + if st.Length != len(b) { + t.Fatalf("st.Lenght = %v, want %v", st.Length, len(b)) + } + } + // TODO check WireLength and ReceivedTime. + if st.SentTime.IsZero() { + t.Fatalf("st.SentTime = %v, want ", st.SentTime) + } +} + +func checkOutTrailer(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.OutTrailer + ) + if st, ok = d.s.(*stats.OutTrailer); !ok { + t.Fatalf("got %T, want OutTrailer", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.Client { + t.Fatalf("st IsClient = true, want false") + } + // TODO check real length, not just > 0. + if st.WireLength <= 0 { + t.Fatalf("st.Lenght = 0, want > 0") + } +} + +func checkEnd(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.End + ) + if st, ok = d.s.(*stats.End); !ok { + t.Fatalf("got %T, want End", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + if st.EndTime.IsZero() { + t.Fatalf("st.EndTime = %v, want ", st.EndTime) + } + if grpc.Code(st.Error) != grpc.Code(e.err) || grpc.ErrorDesc(st.Error) != grpc.ErrorDesc(e.err) { + t.Fatalf("st.Error = %v, want %v", st.Error, e.err) + } +} + +func checkConnBegin(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.ConnBegin + ) + if st, ok = d.s.(*stats.ConnBegin); !ok { + t.Fatalf("got %T, want ConnBegin", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + st.IsClient() // TODO remove this. +} + +func checkConnEnd(t *testing.T, d *gotData, e *expectedData) { + var ( + ok bool + st *stats.ConnEnd + ) + if st, ok = d.s.(*stats.ConnEnd); !ok { + t.Fatalf("got %T, want ConnEnd", d.s) + } + if d.ctx == nil { + t.Fatalf("d.ctx = nil, want ") + } + st.IsClient() // TODO remove this. +} + +type statshandler struct { + mu sync.Mutex + gotRPC []*gotData + gotConn []*gotData +} + +func (h *statshandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context { + return context.WithValue(ctx, connCtxKey{}, info) +} + +func (h *statshandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { + return context.WithValue(ctx, rpcCtxKey{}, info) +} + +func (h *statshandler) HandleConn(ctx context.Context, s stats.ConnStats) { + h.mu.Lock() + defer h.mu.Unlock() + h.gotConn = append(h.gotConn, &gotData{ctx, s.IsClient(), s}) +} + +func (h *statshandler) HandleRPC(ctx context.Context, s stats.RPCStats) { + h.mu.Lock() + defer h.mu.Unlock() + h.gotRPC = append(h.gotRPC, &gotData{ctx, s.IsClient(), s}) +} + +func checkConnStats(t *testing.T, got []*gotData) { + if len(got) <= 0 || len(got)%2 != 0 { + for i, g := range got { + t.Errorf(" - %v, %T = %+v, ctx: %v", i, g.s, g.s, g.ctx) + } + t.Fatalf("got %v stats, want even positive number", len(got)) + } + // The first conn stats must be a ConnBegin. + checkConnBegin(t, got[0], nil) + // The last conn stats must be a ConnEnd. + checkConnEnd(t, got[len(got)-1], nil) +} + +func checkServerStats(t *testing.T, got []*gotData, expect *expectedData, checkFuncs []func(t *testing.T, d *gotData, e *expectedData)) { + if len(got) != len(checkFuncs) { + for i, g := range got { + t.Errorf(" - %v, %T", i, g.s) + } + t.Fatalf("got %v stats, want %v stats", len(got), len(checkFuncs)) + } + + var rpcctx context.Context + for i := 0; i < len(got); i++ { + if _, ok := got[i].s.(stats.RPCStats); ok { + if rpcctx != nil && got[i].ctx != rpcctx { + t.Fatalf("got different contexts with stats %T", got[i].s) + } + rpcctx = got[i].ctx + } + } + + for i, f := range checkFuncs { + f(t, got[i], expect) + } +} + +func testServerStats(t *testing.T, tc *testConfig, cc *rpcConfig, checkFuncs []func(t *testing.T, d *gotData, e *expectedData)) { + h := &statshandler{} + te := newTest(t, tc, nil, h) + te.startServer(&testServer{}) + defer te.tearDown() + + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + if !cc.streaming { + req, resp, e := te.doUnaryCall(cc) + reqs = []*testpb.SimpleRequest{req} + resps = []*testpb.SimpleResponse{resp} + err = e + } else { + reqs, resps, err = te.doFullDuplexCallRoundtrip(cc) + } + if cc.success != (err == nil) { + t.Fatalf("cc.success: %v, got error: %v", cc.success, err) + } + te.cc.Close() + te.srv.GracefulStop() // Wait for the server to stop. + + for { + h.mu.Lock() + if len(h.gotRPC) >= len(checkFuncs) { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + for { + h.mu.Lock() + if _, ok := h.gotConn[len(h.gotConn)-1].s.(*stats.ConnEnd); ok { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + expect := &expectedData{ + serverAddr: te.srvAddr, + compression: tc.compress, + requests: reqs, + responses: resps, + err: err, + } + if !cc.streaming { + expect.method = "/grpc.testing.TestService/UnaryCall" + } else { + expect.method = "/grpc.testing.TestService/FullDuplexCall" + } + + checkConnStats(t, h.gotConn) + checkServerStats(t, h.gotRPC, expect, checkFuncs) +} + +func TestServerStatsUnaryRPC(t *testing.T) { + testServerStats(t, &testConfig{compress: ""}, &rpcConfig{success: true}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkInPayload, + checkOutHeader, + checkOutPayload, + checkOutTrailer, + checkEnd, + }) +} + +func TestServerStatsUnaryRPCError(t *testing.T) { + testServerStats(t, &testConfig{compress: ""}, &rpcConfig{success: false}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkInPayload, + checkOutHeader, + checkOutTrailer, + checkEnd, + }) +} + +func TestServerStatsStreamingRPC(t *testing.T) { + count := 5 + checkFuncs := []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkOutHeader, + } + ioPayFuncs := []func(t *testing.T, d *gotData, e *expectedData){ + checkInPayload, + checkOutPayload, + } + for i := 0; i < count; i++ { + checkFuncs = append(checkFuncs, ioPayFuncs...) + } + checkFuncs = append(checkFuncs, + checkOutTrailer, + checkEnd, + ) + testServerStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: true, streaming: true}, checkFuncs) +} + +func TestServerStatsStreamingRPCError(t *testing.T) { + count := 5 + testServerStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: false, streaming: true}, []func(t *testing.T, d *gotData, e *expectedData){ + checkInHeader, + checkBegin, + checkOutHeader, + checkInPayload, + checkOutTrailer, + checkEnd, + }) +} + +type checkFuncWithCount struct { + f func(t *testing.T, d *gotData, e *expectedData) + c int // expected count +} + +func checkClientStats(t *testing.T, got []*gotData, expect *expectedData, checkFuncs map[int]*checkFuncWithCount) { + var expectLen int + for _, v := range checkFuncs { + expectLen += v.c + } + if len(got) != expectLen { + for i, g := range got { + t.Errorf(" - %v, %T", i, g.s) + } + t.Fatalf("got %v stats, want %v stats", len(got), expectLen) + } + + var rpcctx context.Context + for i := 0; i < len(got); i++ { + if _, ok := got[i].s.(stats.RPCStats); ok { + if rpcctx != nil && got[i].ctx != rpcctx { + t.Fatalf("got different contexts with stats %T", got[i].s) + } + rpcctx = got[i].ctx + } + } + + for _, s := range got { + switch s.s.(type) { + case *stats.Begin: + if checkFuncs[begin].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[begin].f(t, s, expect) + checkFuncs[begin].c-- + case *stats.OutHeader: + if checkFuncs[outHeader].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[outHeader].f(t, s, expect) + checkFuncs[outHeader].c-- + case *stats.OutPayload: + if checkFuncs[outPayload].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[outPayload].f(t, s, expect) + checkFuncs[outPayload].c-- + case *stats.InHeader: + if checkFuncs[inHeader].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inHeader].f(t, s, expect) + checkFuncs[inHeader].c-- + case *stats.InPayload: + if checkFuncs[inPayload].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inPayload].f(t, s, expect) + checkFuncs[inPayload].c-- + case *stats.InTrailer: + if checkFuncs[inTrailer].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[inTrailer].f(t, s, expect) + checkFuncs[inTrailer].c-- + case *stats.End: + if checkFuncs[end].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[end].f(t, s, expect) + checkFuncs[end].c-- + case *stats.ConnBegin: + if checkFuncs[connbegin].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[connbegin].f(t, s, expect) + checkFuncs[connbegin].c-- + case *stats.ConnEnd: + if checkFuncs[connend].c <= 0 { + t.Fatalf("unexpected stats: %T", s.s) + } + checkFuncs[connend].f(t, s, expect) + checkFuncs[connend].c-- + default: + t.Fatalf("unexpected stats: %T", s.s) + } + } +} + +func testClientStats(t *testing.T, tc *testConfig, cc *rpcConfig, checkFuncs map[int]*checkFuncWithCount) { + h := &statshandler{} + te := newTest(t, tc, h, nil) + te.startServer(&testServer{}) + defer te.tearDown() + + var ( + reqs []*testpb.SimpleRequest + resps []*testpb.SimpleResponse + err error + ) + if !cc.streaming { + req, resp, e := te.doUnaryCall(cc) + reqs = []*testpb.SimpleRequest{req} + resps = []*testpb.SimpleResponse{resp} + err = e + } else { + reqs, resps, err = te.doFullDuplexCallRoundtrip(cc) + } + if cc.success != (err == nil) { + t.Fatalf("cc.success: %v, got error: %v", cc.success, err) + } + te.cc.Close() + te.srv.GracefulStop() // Wait for the server to stop. + + lenRPCStats := 0 + for _, v := range checkFuncs { + lenRPCStats += v.c + } + for { + h.mu.Lock() + if len(h.gotRPC) >= lenRPCStats { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + for { + h.mu.Lock() + if _, ok := h.gotConn[len(h.gotConn)-1].s.(*stats.ConnEnd); ok { + h.mu.Unlock() + break + } + h.mu.Unlock() + time.Sleep(10 * time.Millisecond) + } + + expect := &expectedData{ + serverAddr: te.srvAddr, + compression: tc.compress, + requests: reqs, + responses: resps, + failfast: cc.failfast, + err: err, + } + if !cc.streaming { + expect.method = "/grpc.testing.TestService/UnaryCall" + } else { + expect.method = "/grpc.testing.TestService/FullDuplexCall" + } + + checkConnStats(t, h.gotConn) + checkClientStats(t, h.gotRPC, expect, checkFuncs) +} + +func TestClientStatsUnaryRPC(t *testing.T) { + testClientStats(t, &testConfig{compress: ""}, &rpcConfig{success: true, failfast: false}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inPayload: {checkInPayload, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsUnaryRPCError(t *testing.T) { + testClientStats(t, &testConfig{compress: ""}, &rpcConfig{success: false, failfast: false}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsStreamingRPC(t *testing.T) { + count := 5 + testClientStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: true, failfast: false, streaming: true}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, count}, + inHeader: {checkInHeader, 1}, + inPayload: {checkInPayload, count}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} + +func TestClientStatsStreamingRPCError(t *testing.T) { + count := 5 + testClientStats(t, &testConfig{compress: "gzip"}, &rpcConfig{count: count, success: false, failfast: false, streaming: true}, map[int]*checkFuncWithCount{ + begin: {checkBegin, 1}, + outHeader: {checkOutHeader, 1}, + outPayload: {checkOutPayload, 1}, + inHeader: {checkInHeader, 1}, + inTrailer: {checkInTrailer, 1}, + end: {checkEnd, 1}, + }) +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go index 0ef2077ce254..46810544f19f 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/stream.go @@ -37,6 +37,7 @@ import ( "bytes" "errors" "io" + "math" "sync" "time" @@ -44,7 +45,6 @@ import ( "golang.org/x/net/trace" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/stats" "google.golang.org/grpc/transport" ) @@ -97,7 +97,7 @@ type ClientStream interface { // 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) { +func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) { if cc.dopts.streamInt != nil { return cc.dopts.streamInt(ctx, desc, cc, method, newClientStream, opts...) } @@ -106,18 +106,11 @@ 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) { var ( - t transport.ClientTransport - s *transport.Stream - put func() - cancel context.CancelFunc + t transport.ClientTransport + s *transport.Stream + put func() ) c := defaultCallInfo - if mc, ok := cc.getMethodConfig(method); ok { - c.failFast = !mc.WaitForReady - if mc.Timeout > 0 { - ctx, cancel = context.WithTimeout(ctx, mc.Timeout) - } - } for _, o := range opts { if err := o.before(&c); err != nil { return nil, toRPCErr(err) @@ -150,26 +143,6 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } }() } - sh := cc.dopts.copts.StatsHandler - if sh != nil { - ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method}) - begin := &stats.Begin{ - Client: true, - BeginTime: time.Now(), - FailFast: c.failFast, - } - sh.HandleRPC(ctx, begin) - } - defer func() { - if err != nil && sh != nil { - // Only handle end stats if err != nil. - end := &stats.End{ - Client: true, - Error: err, - } - sh.HandleRPC(ctx, end) - } - }() gopts := BalancerGetOptions{ BlockingWait: !c.failFast, } @@ -207,14 +180,12 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth break } cs := &clientStream{ - opts: opts, - c: c, - desc: desc, - codec: cc.dopts.codec, - cp: cc.dopts.cp, - dc: cc.dopts.dc, - maxMsgSize: cc.dopts.maxMsgSize, - cancel: cancel, + opts: opts, + c: c, + desc: desc, + codec: cc.dopts.codec, + cp: cc.dopts.cp, + dc: cc.dopts.dc, put: put, t: t, @@ -223,9 +194,6 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth tracing: EnableTracing, trInfo: trInfo, - - statsCtx: ctx, - statsHandler: cc.dopts.copts.StatsHandler, } if cc.dopts.cp != nil { cs.cbuf = new(bytes.Buffer) @@ -259,18 +227,16 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth // 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 - cbuf *bytes.Buffer - dc Decompressor - maxMsgSize int - cancel context.CancelFunc + opts []CallOption + c callInfo + t transport.ClientTransport + s *transport.Stream + p *parser + desc *StreamDesc + codec Codec + cp Compressor + cbuf *bytes.Buffer + dc Decompressor tracing bool // set to EnableTracing when the clientStream is created. @@ -280,12 +246,6 @@ type clientStream struct { // 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. 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 } func (cs *clientStream) Context() context.Context { @@ -314,8 +274,6 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { } cs.mu.Unlock() } - // TODO Investigate how to signal the stats handling party. - // generate error stats if err != nil && err != io.EOF? defer func() { if err != nil { cs.finish(err) @@ -338,13 +296,7 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { } err = toRPCErr(err) }() - var outPayload *stats.OutPayload - if cs.statsHandler != nil { - outPayload = &stats.OutPayload{ - Client: true, - } - } - out, err := encode(cs.codec, m, cs.cp, cs.cbuf, outPayload) + out, err := encode(cs.codec, m, cs.cp, cs.cbuf) defer func() { if cs.cbuf != nil { cs.cbuf.Reset() @@ -353,37 +305,11 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) { if err != nil { return Errorf(codes.Internal, "grpc: %v", err) } - err = cs.t.Write(cs.s, out, &transport.Options{Last: false}) - if err == nil && outPayload != nil { - outPayload.SentTime = time.Now() - cs.statsHandler.HandleRPC(cs.statsCtx, outPayload) - } - return err + return cs.t.Write(cs.s, out, &transport.Options{Last: false}) } func (cs *clientStream) RecvMsg(m interface{}) (err error) { - defer func() { - if err != nil && cs.statsHandler != nil { - // Only generate End if err != nil. - // If err == nil, it's not the last RecvMsg. - // The last RecvMsg gets either an RPC error or io.EOF. - end := &stats.End{ - Client: true, - EndTime: time.Now(), - } - if err != io.EOF { - end.Error = toRPCErr(err) - } - cs.statsHandler.HandleRPC(cs.statsCtx, end) - } - }() - var inPayload *stats.InPayload - if cs.statsHandler != nil { - inPayload = &stats.InPayload{ - Client: true, - } - } - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, cs.maxMsgSize, inPayload) + err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32) defer func() { // err != nil indicates the termination of the stream. if err != nil { @@ -398,15 +324,11 @@ func (cs *clientStream) RecvMsg(m interface{}) (err error) { } 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. - err = recv(cs.p, cs.codec, cs.s, cs.dc, m, cs.maxMsgSize, nil) + err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32) cs.closeTransportStream(err) if err == nil { return toRPCErr(errors.New("grpc: client streaming protocol violation: get , want ")) @@ -462,11 +384,6 @@ func (cs *clientStream) closeTransportStream(err error) { } func (cs *clientStream) finish(err error) { - defer func() { - if cs.cancel != nil { - cs.cancel() - } - }() cs.mu.Lock() defer cs.mu.Unlock() for _, o := range cs.opts { @@ -524,8 +441,6 @@ type serverStream struct { statusDesc string trInfo *traceInfo - statsHandler stats.Handler - mu sync.Mutex // protects trInfo.tr after the service handler runs. } @@ -567,11 +482,7 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { ss.mu.Unlock() } }() - var outPayload *stats.OutPayload - if ss.statsHandler != nil { - outPayload = &stats.OutPayload{} - } - out, err := encode(ss.codec, m, ss.cp, ss.cbuf, outPayload) + out, err := encode(ss.codec, m, ss.cp, ss.cbuf) defer func() { if ss.cbuf != nil { ss.cbuf.Reset() @@ -584,10 +495,6 @@ func (ss *serverStream) SendMsg(m interface{}) (err error) { if err := ss.t.Write(ss.s, out, &transport.Options{Last: false}); err != nil { return toRPCErr(err) } - if outPayload != nil { - outPayload.SentTime = time.Now() - ss.statsHandler.HandleRPC(ss.s.Context(), outPayload) - } return nil } @@ -606,11 +513,7 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { ss.mu.Unlock() } }() - var inPayload *stats.InPayload - if ss.statsHandler != nil { - inPayload = &stats.InPayload{} - } - if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxMsgSize, inPayload); err != nil { + if err := recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxMsgSize); err != nil { if err == io.EOF { return err } @@ -619,8 +522,5 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) { } return toRPCErr(err) } - if inPayload != nil { - ss.statsHandler.HandleRPC(ss.s.Context(), inPayload) - } return nil } diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go index 0edbe53a700d..4ef0830b56ca 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/control.go @@ -35,9 +35,7 @@ package transport import ( "fmt" - "math" "sync" - "time" "golang.org/x/net/http2" ) @@ -46,12 +44,8 @@ const ( // The default value of flow control window size in HTTP2 spec. defaultWindowSize = 65535 // The initial window size for flow control. - initialWindowSize = defaultWindowSize // for an RPC - initialConnWindowSize = defaultWindowSize * 16 // for a connection - infinity = time.Duration(math.MaxInt64) - defaultKeepaliveTime = infinity - defaultKeepaliveTimeout = time.Duration(20 * time.Second) - defaultMaxStreamsClient = 100 + initialWindowSize = defaultWindowSize // for an RPC + initialConnWindowSize = defaultWindowSize * 16 // for a connection ) // The following defines various control items which could flow through @@ -117,9 +111,35 @@ func newQuotaPool(q int) *quotaPool { return qb } -// add cancels the pending quota sent on acquired, incremented by v and sends +// add adds n to the available quota and tries to send it on acquire. +func (qb *quotaPool) add(n int) { + qb.mu.Lock() + defer qb.mu.Unlock() + qb.quota += n + if qb.quota <= 0 { + return + } + select { + case qb.c <- qb.quota: + qb.quota = 0 + default: + } +} + +// cancel cancels the pending quota sent on acquire, if any. +func (qb *quotaPool) cancel() { + qb.mu.Lock() + defer qb.mu.Unlock() + select { + case n := <-qb.c: + qb.quota += n + default: + } +} + +// reset cancels the pending quota sent on acquired, incremented by v and sends // it back on acquire. -func (qb *quotaPool) add(v int) { +func (qb *quotaPool) reset(v int) { qb.mu.Lock() defer qb.mu.Unlock() select { @@ -131,10 +151,6 @@ func (qb *quotaPool) add(v int) { if qb.quota <= 0 { return } - // 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 diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go index 10b6dc0b19d7..114e34906a14 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server.go @@ -268,7 +268,7 @@ func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { }) } -func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), traceCtx func(context.Context, string) context.Context) { +func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream)) { // With this transport type there will be exactly 1 stream: this HTTP request. var ctx context.Context diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server_test.go new file mode 100644 index 000000000000..84fc917f650a --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/handler_server_test.go @@ -0,0 +1,389 @@ +/* + * Copyright 2016, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package transport + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +func TestHandlerTransport_NewServerHandlerTransport(t *testing.T) { + type testCase struct { + name string + req *http.Request + wantErr string + modrw func(http.ResponseWriter) http.ResponseWriter + check func(*serverHandlerTransport, *testCase) error + } + tests := []testCase{ + { + name: "http/1.1", + req: &http.Request{ + ProtoMajor: 1, + ProtoMinor: 1, + }, + wantErr: "gRPC requires HTTP/2", + }, + { + name: "bad method", + req: &http.Request{ + ProtoMajor: 2, + Method: "GET", + Header: http.Header{}, + RequestURI: "/", + }, + wantErr: "invalid gRPC request method", + }, + { + name: "bad content type", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/foo"}, + }, + RequestURI: "/service/foo.bar", + }, + wantErr: "invalid gRPC request content-type", + }, + { + name: "not flusher", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/grpc"}, + }, + RequestURI: "/service/foo.bar", + }, + modrw: func(w http.ResponseWriter) http.ResponseWriter { + // Return w without its Flush method + type onlyCloseNotifier interface { + http.ResponseWriter + http.CloseNotifier + } + return struct{ onlyCloseNotifier }{w.(onlyCloseNotifier)} + }, + wantErr: "gRPC requires a ResponseWriter supporting http.Flusher", + }, + { + name: "not closenotifier", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/grpc"}, + }, + RequestURI: "/service/foo.bar", + }, + modrw: func(w http.ResponseWriter) http.ResponseWriter { + // Return w without its CloseNotify method + type onlyFlusher interface { + http.ResponseWriter + http.Flusher + } + return struct{ onlyFlusher }{w.(onlyFlusher)} + }, + wantErr: "gRPC requires a ResponseWriter supporting http.CloseNotifier", + }, + { + name: "valid", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/grpc"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + }, + check: func(t *serverHandlerTransport, tt *testCase) error { + if t.req != tt.req { + return fmt.Errorf("t.req = %p; want %p", t.req, tt.req) + } + if t.rw == nil { + return errors.New("t.rw = nil; want non-nil") + } + return nil + }, + }, + { + name: "with timeout", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": []string{"application/grpc"}, + "Grpc-Timeout": {"200m"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + }, + check: func(t *serverHandlerTransport, tt *testCase) error { + if !t.timeoutSet { + return errors.New("timeout not set") + } + if want := 200 * time.Millisecond; t.timeout != want { + return fmt.Errorf("timeout = %v; want %v", t.timeout, want) + } + return nil + }, + }, + { + name: "with bad timeout", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": []string{"application/grpc"}, + "Grpc-Timeout": {"tomorrow"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + }, + wantErr: `stream error: code = 13 desc = "malformed time-out: transport: timeout unit is not recognized: \"tomorrow\""`, + }, + { + name: "with metadata", + req: &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": []string{"application/grpc"}, + "meta-foo": {"foo-val"}, + "meta-bar": {"bar-val1", "bar-val2"}, + "user-agent": {"x/y a/b"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + }, + check: func(ht *serverHandlerTransport, tt *testCase) error { + want := metadata.MD{ + "meta-bar": {"bar-val1", "bar-val2"}, + "user-agent": {"x/y"}, + "meta-foo": {"foo-val"}, + } + if !reflect.DeepEqual(ht.headerMD, want) { + return fmt.Errorf("metdata = %#v; want %#v", ht.headerMD, want) + } + return nil + }, + }, + } + + for _, tt := range tests { + rw := newTestHandlerResponseWriter() + if tt.modrw != nil { + rw = tt.modrw(rw) + } + got, gotErr := NewServerHandlerTransport(rw, tt.req) + if (gotErr != nil) != (tt.wantErr != "") || (gotErr != nil && gotErr.Error() != tt.wantErr) { + t.Errorf("%s: error = %v; want %q", tt.name, gotErr, tt.wantErr) + continue + } + if gotErr != nil { + continue + } + if tt.check != nil { + if err := tt.check(got.(*serverHandlerTransport), &tt); err != nil { + t.Errorf("%s: %v", tt.name, err) + } + } + } +} + +type testHandlerResponseWriter struct { + *httptest.ResponseRecorder + closeNotify chan bool +} + +func (w testHandlerResponseWriter) CloseNotify() <-chan bool { return w.closeNotify } +func (w testHandlerResponseWriter) Flush() {} + +func newTestHandlerResponseWriter() http.ResponseWriter { + return testHandlerResponseWriter{ + ResponseRecorder: httptest.NewRecorder(), + closeNotify: make(chan bool, 1), + } +} + +type handleStreamTest struct { + t *testing.T + bodyw *io.PipeWriter + req *http.Request + rw testHandlerResponseWriter + ht *serverHandlerTransport +} + +func newHandleStreamTest(t *testing.T) *handleStreamTest { + bodyr, bodyw := io.Pipe() + req := &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/grpc"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + Body: bodyr, + } + rw := newTestHandlerResponseWriter().(testHandlerResponseWriter) + ht, err := NewServerHandlerTransport(rw, req) + if err != nil { + t.Fatal(err) + } + return &handleStreamTest{ + t: t, + bodyw: bodyw, + ht: ht.(*serverHandlerTransport), + rw: rw, + } +} + +func TestHandlerTransport_HandleStreams(t *testing.T) { + st := newHandleStreamTest(t) + handleStream := func(s *Stream) { + if want := "/service/foo.bar"; s.method != want { + t.Errorf("stream method = %q; want %q", s.method, want) + } + st.bodyw.Close() // no body + st.ht.WriteStatus(s, codes.OK, "") + } + st.ht.HandleStreams(func(s *Stream) { go handleStream(s) }) + wantHeader := http.Header{ + "Date": nil, + "Content-Type": {"application/grpc"}, + "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Grpc-Status": {"0"}, + } + if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) { + t.Errorf("Header+Trailer Map: %#v; want %#v", st.rw.HeaderMap, wantHeader) + } +} + +// Tests that codes.Unimplemented will close the body, per comment in handler_server.go. +func TestHandlerTransport_HandleStreams_Unimplemented(t *testing.T) { + handleStreamCloseBodyTest(t, codes.Unimplemented, "thingy is unimplemented") +} + +// Tests that codes.InvalidArgument will close the body, per comment in handler_server.go. +func TestHandlerTransport_HandleStreams_InvalidArgument(t *testing.T) { + handleStreamCloseBodyTest(t, codes.InvalidArgument, "bad arg") +} + +func handleStreamCloseBodyTest(t *testing.T, statusCode codes.Code, msg string) { + st := newHandleStreamTest(t) + handleStream := func(s *Stream) { + st.ht.WriteStatus(s, statusCode, msg) + } + st.ht.HandleStreams(func(s *Stream) { go handleStream(s) }) + wantHeader := http.Header{ + "Date": nil, + "Content-Type": {"application/grpc"}, + "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Grpc-Status": {fmt.Sprint(uint32(statusCode))}, + "Grpc-Message": {encodeGrpcMessage(msg)}, + } + if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) { + t.Errorf("Header+Trailer mismatch.\n got: %#v\nwant: %#v", st.rw.HeaderMap, wantHeader) + } +} + +func TestHandlerTransport_HandleStreams_Timeout(t *testing.T) { + bodyr, bodyw := io.Pipe() + req := &http.Request{ + ProtoMajor: 2, + Method: "POST", + Header: http.Header{ + "Content-Type": {"application/grpc"}, + "Grpc-Timeout": {"200m"}, + }, + URL: &url.URL{ + Path: "/service/foo.bar", + }, + RequestURI: "/service/foo.bar", + Body: bodyr, + } + rw := newTestHandlerResponseWriter().(testHandlerResponseWriter) + ht, err := NewServerHandlerTransport(rw, req) + if err != nil { + t.Fatal(err) + } + runStream := func(s *Stream) { + defer bodyw.Close() + select { + case <-s.ctx.Done(): + case <-time.After(5 * time.Second): + t.Errorf("timeout waiting for ctx.Done") + return + } + err := s.ctx.Err() + if err != context.DeadlineExceeded { + t.Errorf("ctx.Err = %v; want %v", err, context.DeadlineExceeded) + return + } + ht.WriteStatus(s, codes.DeadlineExceeded, "too slow") + } + ht.HandleStreams(func(s *Stream) { go runStream(s) }) + wantHeader := http.Header{ + "Date": nil, + "Content-Type": {"application/grpc"}, + "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Grpc-Status": {"4"}, + "Grpc-Message": {encodeGrpcMessage("too slow")}, + } + if !reflect.DeepEqual(rw.HeaderMap, wantHeader) { + t.Errorf("Header+Trailer Map mismatch.\n got: %#v\nwant: %#v", rw.HeaderMap, wantHeader) + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go index 5f579b8b01b9..2b0f68016af5 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_client.go @@ -41,7 +41,6 @@ import ( "net" "strings" "sync" - "sync/atomic" "time" "golang.org/x/net/context" @@ -50,23 +49,18 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" - "google.golang.org/grpc/stats" ) // http2Client implements the ClientTransport interface with HTTP2. type http2Client struct { - ctx context.Context - target string // server name/addr - userAgent string - md interface{} - conn net.Conn // underlying communication channel - remoteAddr net.Addr - localAddr net.Addr - authInfo credentials.AuthInfo // auth info about the connection - nextID uint32 // the next stream ID to be used + target string // server name/addr + userAgent string + md interface{} + conn net.Conn // underlying communication channel + authInfo credentials.AuthInfo // auth info about the connection + nextID uint32 // the next stream ID to be used // writableChan synchronizes write access to the transport. // A writer acquires the write lock by sending a value on writableChan @@ -82,8 +76,6 @@ type http2Client struct { // goAway is closed to notify the upper layer (i.e., addrConn.transportMonitor) // that the server sent GoAway on this transport. goAway chan struct{} - // awakenKeepalive is used to wake up keepalive when after it has gone dormant. - awakenKeepalive chan struct{} framer *framer hBuf *bytes.Buffer // the buffer for HPACK encoding @@ -103,13 +95,6 @@ type http2Client struct { creds []credentials.PerRPCCredentials - // Boolean to keep track of reading activity on transport. - // 1 is true and 0 is false. - activity uint32 // Accessed atomically. - kp keepalive.ClientParameters - - statsHandler stats.Handler - mu sync.Mutex // guard the following variables state transportState // the state of underlying connection activeStreams map[uint32]*Stream @@ -165,9 +150,6 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( scheme := "http" conn, err := dial(ctx, opts.Dialer, addr.Addr) if err != nil { - if opts.FailOnNonTempDialError { - return nil, connectionErrorf(isTemporary(err), err, "transport: %v", err) - } return nil, connectionErrorf(true, err, "transport: %v", err) } // Any further errors will close the underlying connection @@ -187,31 +169,23 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( return nil, connectionErrorf(temp, err, "transport: %v", err) } } - kp := opts.KeepaliveParams - // Validate keepalive parameters. - if kp.Time == 0 { - kp.Time = defaultKeepaliveTime - } - if kp.Timeout == 0 { - kp.Timeout = defaultKeepaliveTimeout + ua := primaryUA + if opts.UserAgent != "" { + ua = opts.UserAgent + " " + ua } var buf bytes.Buffer t := &http2Client{ - ctx: ctx, - target: addr.Addr, - userAgent: opts.UserAgent, - md: addr.Metadata, - conn: conn, - remoteAddr: conn.RemoteAddr(), - localAddr: conn.LocalAddr(), - authInfo: authInfo, + target: addr.Addr, + userAgent: ua, + md: addr.Metadata, + conn: conn, + authInfo: authInfo, // The client initiated stream id is odd starting from 1. nextID: 1, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), errorChan: make(chan struct{}), goAway: make(chan struct{}), - awakenKeepalive: make(chan struct{}, 1), framer: newFramer(conn), hBuf: &buf, hEnc: hpack.NewEncoder(&buf), @@ -222,24 +196,8 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( state: reachable, activeStreams: make(map[uint32]*Stream), creds: opts.PerRPCCredentials, - maxStreams: defaultMaxStreamsClient, - streamsQuota: newQuotaPool(defaultMaxStreamsClient), + maxStreams: math.MaxInt32, streamSendQuota: defaultWindowSize, - kp: kp, - statsHandler: opts.StatsHandler, - } - // Make sure awakenKeepalive can't be written upon. - // keepalive routine will make it writable, if need be. - t.awakenKeepalive <- struct{}{} - if t.statsHandler != nil { - t.ctx = t.statsHandler.TagConn(t.ctx, &stats.ConnTagInfo{ - RemoteAddr: t.remoteAddr, - LocalAddr: t.localAddr, - }) - connBegin := &stats.ConnBegin{ - Client: true, - } - t.statsHandler.HandleConn(t.ctx, connBegin) } // Start the reader goroutine for incoming message. Each transport has // a dedicated goroutine which reads HTTP2 frame from network. Then it @@ -275,9 +233,6 @@ func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) ( } } go t.controller() - if t.kp.Time != infinity { - go t.keepalive() - } t.writableChan <- 0 return t, nil } @@ -315,13 +270,12 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream { // streams. func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) { pr := &peer.Peer{ - Addr: t.remoteAddr, + Addr: t.conn.RemoteAddr(), } // Attach Auth info if there is any. if t.authInfo != nil { pr.AuthInfo = t.authInfo } - userCtx := ctx ctx = peer.NewContext(ctx, pr) authData := make(map[string]string) for _, c := range t.creds { @@ -359,18 +313,21 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea t.mu.Unlock() return nil, ErrConnClosing } + checkStreamsQuota := t.streamsQuota != nil t.mu.Unlock() - sq, err := wait(ctx, nil, nil, t.shutdownChan, t.streamsQuota.acquire()) - if err != nil { - return nil, err - } - // Returns the quota balance back. - if sq > 1 { - t.streamsQuota.add(sq - 1) + if checkStreamsQuota { + sq, err := wait(ctx, nil, nil, t.shutdownChan, t.streamsQuota.acquire()) + if err != nil { + return nil, err + } + // Returns the quota balance back. + if sq > 1 { + t.streamsQuota.add(sq - 1) + } } if _, err := wait(ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil { // Return the quota back now because there is no stream returned to the caller. - if _, ok := err.(StreamError); ok { + if _, ok := err.(StreamError); ok && checkStreamsQuota { t.streamsQuota.add(1) } return nil, err @@ -378,7 +335,9 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea t.mu.Lock() if t.state == draining { t.mu.Unlock() - t.streamsQuota.add(1) + if checkStreamsQuota { + t.streamsQuota.add(1) + } // Need to make t writable again so that the rpc in flight can still proceed. t.writableChan <- 0 return nil, ErrStreamDrain @@ -388,19 +347,18 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea return nil, ErrConnClosing } s := t.newStream(ctx, callHdr) - s.clientStatsCtx = userCtx 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.framer.writePing(false, false, [8]byte{}) - default: - } - } + // This stream is not counted when applySetings(...) initialize t.streamsQuota. + // Reset t.streamsQuota to the right value. + var reset bool + if !checkStreamsQuota && t.streamsQuota != nil { + reset = true + } t.mu.Unlock() + if reset { + t.streamsQuota.reset(-1) + } // HPACK encodes various headers. Note that once WriteField(...) is // called, the corresponding headers/continuation frame has to be sent @@ -455,7 +413,6 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea } } first := true - bufLen := t.hBuf.Len() // Sends the headers in a single batch even when they span multiple frames. for !endHeaders { size := t.hBuf.Len() @@ -490,17 +447,6 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea return nil, connectionErrorf(true, err, "transport: %v", err) } } - if t.statsHandler != nil { - outHeader := &stats.OutHeader{ - Client: true, - WireLength: bufLen, - FullMethod: callHdr.Method, - RemoteAddr: t.remoteAddr, - LocalAddr: t.localAddr, - Compression: callHdr.SendCompress, - } - t.statsHandler.HandleRPC(s.clientStatsCtx, outHeader) - } t.writableChan <- 0 return s, nil } @@ -508,11 +454,15 @@ 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) { + var updateStreams bool t.mu.Lock() if t.activeStreams == nil { t.mu.Unlock() return } + if t.streamsQuota != nil { + updateStreams = true + } 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. @@ -521,27 +471,10 @@ func (t *http2Client) CloseStream(s *Stream, err error) { 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}) - }() + if updateStreams { + t.streamsQuota.add(1) + } s.mu.Lock() - rstStream = s.rstStream - rstError = s.rstError if q := s.fc.resetPendingData(); q > 0 { if n := t.fc.onRead(q); n > 0 { t.controlBuf.put(&windowUpdate{0, n}) @@ -557,9 +490,8 @@ func (t *http2Client) CloseStream(s *Stream, err error) { } s.state = streamDone s.mu.Unlock() - if _, ok := err.(StreamError); ok { - rstStream = true - rstError = http2.ErrCodeCancel + if se, ok := err.(StreamError); ok && se.Code != codes.DeadlineExceeded { + t.controlBuf.put(&resetStream{s.id, http2.ErrCodeCancel}) } } @@ -593,12 +525,6 @@ func (t *http2Client) Close() (err error) { s.mu.Unlock() s.write(recvMsg{err: ErrConnClosing}) } - if t.statsHandler != nil { - connEnd := &stats.ConnEnd{ - Client: true, - } - t.statsHandler.HandleConn(t.ctx, connEnd) - } return } @@ -656,14 +582,19 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error { var p []byte if r.Len() > 0 { size := http2MaxFrameLen + s.sendQuotaPool.add(0) // Wait until the stream has some quota to send the data. sq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, s.sendQuotaPool.acquire()) if err != nil { return err } + t.sendQuotaPool.add(0) // Wait until the transport has some quota to send the data. tq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, t.sendQuotaPool.acquire()) if err != nil { + if _, ok := err.(StreamError); ok || err == io.EOF { + t.sendQuotaPool.cancel() + } return err } if sq < size { @@ -773,7 +704,7 @@ func (t *http2Client) updateWindow(s *Stream, n uint32) { } func (t *http2Client) handleData(f *http2.DataFrame) { - size := f.Header().Length + size := len(f.Data()) if err := t.fc.onData(uint32(size)); err != nil { t.notifyError(connectionErrorf(true, err, "%v", err)) return @@ -787,11 +718,6 @@ func (t *http2Client) handleData(f *http2.DataFrame) { return } if size > 0 { - if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := t.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { - t.controlBuf.put(&windowUpdate{0, w}) - } - } s.mu.Lock() if s.state == streamDone { s.mu.Unlock() @@ -805,27 +731,19 @@ func (t *http2Client) handleData(f *http2.DataFrame) { s.state = streamDone s.statusCode = codes.Internal s.statusDesc = err.Error() - s.rstStream = true - s.rstError = http2.ErrCodeFlowControl close(s.done) s.mu.Unlock() s.write(recvMsg{err: io.EOF}) + t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) 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}) - } - } 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? - if len(f.Data()) > 0 { - data := make([]byte, len(f.Data())) - copy(data, f.Data()) - s.write(recvMsg{data: data}) - } + data := make([]byte, size) + copy(data, f.Data()) + s.write(recvMsg{data: data}) } // The server has closed the stream without sending trailers. Record that // the read direction is closed, and set the status appropriately. @@ -956,24 +874,6 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } endStream := frame.StreamEnded() - var isHeader bool - defer func() { - if t.statsHandler != nil { - if isHeader { - inHeader := &stats.InHeader{ - Client: true, - WireLength: int(frame.Header().Length), - } - t.statsHandler.HandleRPC(s.clientStatsCtx, inHeader) - } else { - inTrailer := &stats.InTrailer{ - Client: true, - WireLength: int(frame.Header().Length), - } - t.statsHandler.HandleRPC(s.clientStatsCtx, inTrailer) - } - } - }() s.mu.Lock() if !endStream { @@ -985,7 +885,6 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) { } close(s.headerChan) s.headerDone = true - isHeader = true } if !endStream || s.state == streamDone { s.mu.Unlock() @@ -1026,7 +925,6 @@ func (t *http2Client) reader() { t.notifyError(err) return } - atomic.CompareAndSwapUint32(&t.activity, 0, 1) sf, ok := frame.(*http2.SettingsFrame) if !ok { t.notifyError(err) @@ -1037,7 +935,6 @@ func (t *http2Client) reader() { // loop to keep reading incoming messages on this transport. for { frame, err := t.framer.readFrame() - atomic.CompareAndSwapUint32(&t.activity, 0, 1) if err != nil { // Abort an active stream if the http2.Framer returns a // http2.StreamError. This can happen only if the server's response @@ -1089,15 +986,21 @@ func (t *http2Client) applySettings(ss []http2.Setting) { s.Val = math.MaxInt32 } t.mu.Lock() + reset := t.streamsQuota != nil + if !reset { + t.streamsQuota = newQuotaPool(int(s.Val) - len(t.activeStreams)) + } ms := t.maxStreams t.maxStreams = int(s.Val) t.mu.Unlock() - t.streamsQuota.add(int(s.Val) - ms) + if reset { + t.streamsQuota.reset(int(s.Val) - ms) + } case http2.SettingInitialWindowSize: t.mu.Lock() for _, stream := range t.activeStreams { // Adjust the sending quota for each stream. - stream.sendQuotaPool.add(int(s.Val - t.streamSendQuota)) + stream.sendQuotaPool.reset(int(s.Val - t.streamSendQuota)) } t.streamSendQuota = s.Val t.mu.Unlock() @@ -1125,12 +1028,6 @@ func (t *http2Client) controller() { t.framer.writeSettings(true, i.ss...) } 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. - t.streamsQuota.add(1) t.framer.writeRSTStream(true, i.streamID, i.code) case *flushIO: t.framer.flushWrite() @@ -1150,61 +1047,6 @@ func (t *http2Client) controller() { } } -// keepalive running in a separate goroutune makes sure the connection is alive by sending pings. -func (t *http2Client) keepalive() { - p := &ping{data: [8]byte{}} - timer := time.NewTimer(t.kp.Time) - for { - select { - case <-timer.C: - if atomic.CompareAndSwapUint32(&t.activity, 1, 0) { - timer.Reset(t.kp.Time) - continue - } - // Check if keepalive should go dormant. - t.mu.Lock() - if len(t.activeStreams) < 1 && !t.kp.PermitWithoutStream { - // Make awakenKeepalive writable. - <-t.awakenKeepalive - t.mu.Unlock() - select { - case <-t.awakenKeepalive: - // If the control gets here a ping has been sent - // need to reset the timer with keepalive.Timeout. - case <-t.shutdownChan: - return - } - } else { - t.mu.Unlock() - // Send ping. - t.controlBuf.put(p) - } - - // By the time control gets here a ping has been sent one way or the other. - timer.Reset(t.kp.Timeout) - select { - case <-timer.C: - if atomic.CompareAndSwapUint32(&t.activity, 1, 0) { - timer.Reset(t.kp.Time) - continue - } - t.Close() - return - case <-t.shutdownChan: - if !timer.Stop() { - <-timer.C - } - return - } - case <-t.shutdownChan: - if !timer.Stop() { - <-timer.C - } - return - } - } -} - func (t *http2Client) Error() <-chan struct{} { return t.errorChan } diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go index f5c590f438f7..a62fb7c228f1 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http2_server.go @@ -50,8 +50,6 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" - "google.golang.org/grpc/stats" - "google.golang.org/grpc/tap" ) // ErrIllegalHeaderWrite indicates that setting header is illegal because of @@ -60,13 +58,9 @@ var ErrIllegalHeaderWrite = errors.New("transport: the stream is done or WriteHe // http2Server implements the ServerTransport interface with HTTP2. type http2Server struct { - ctx context.Context conn net.Conn - remoteAddr net.Addr - localAddr net.Addr maxStreamID uint32 // max stream ID ever seen authInfo credentials.AuthInfo // auth info about the connection - inTapHandle tap.ServerInHandle // writableChan synchronizes write access to the transport. // A writer acquires the write lock by receiving a value on writableChan // and releases it by sending on writableChan. @@ -88,8 +82,6 @@ type http2Server struct { // sendQuotaPool provides flow control to outbound message. sendQuotaPool *quotaPool - stats stats.Handler - mu sync.Mutex // guard the following state transportState activeStreams map[uint32]*Stream @@ -99,13 +91,12 @@ type http2Server struct { // newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is // returned if something goes wrong. -func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err error) { +func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthInfo) (_ ServerTransport, err error) { framer := newFramer(conn) // Send initial settings as connection preface to client. var settings []http2.Setting // TODO(zhaoq): Have a better way to signal "no limit" because 0 is // permitted in the HTTP2 spec. - maxStreams := config.MaxStreams if maxStreams == 0 { maxStreams = math.MaxUint32 } else { @@ -130,16 +121,12 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err } var buf bytes.Buffer t := &http2Server{ - ctx: context.Background(), conn: conn, - remoteAddr: conn.RemoteAddr(), - localAddr: conn.LocalAddr(), - authInfo: config.AuthInfo, + authInfo: authInfo, framer: framer, hBuf: &buf, hEnc: hpack.NewEncoder(&buf), maxStreams: maxStreams, - inTapHandle: config.InTapHandle, controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), @@ -148,15 +135,6 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err shutdownChan: make(chan struct{}), activeStreams: make(map[uint32]*Stream), streamSendQuota: defaultWindowSize, - stats: config.StatsHandler, - } - if t.stats != nil { - t.ctx = t.stats.TagConn(t.ctx, &stats.ConnTagInfo{ - RemoteAddr: t.remoteAddr, - LocalAddr: t.localAddr, - }) - connBegin := &stats.ConnBegin{} - t.stats.HandleConn(t.ctx, connBegin) } go t.controller() t.writableChan <- 0 @@ -164,7 +142,7 @@ 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) { +func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream)) (close bool) { buf := newRecvBuffer() s := &Stream{ id: frame.Header().StreamID, @@ -190,12 +168,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } s.recvCompress = state.encoding if state.timeoutSet { - s.ctx, s.cancel = context.WithTimeout(t.ctx, state.timeout) + s.ctx, s.cancel = context.WithTimeout(context.TODO(), state.timeout) } else { - s.ctx, s.cancel = context.WithCancel(t.ctx) + s.ctx, s.cancel = context.WithCancel(context.TODO()) } pr := &peer.Peer{ - Addr: t.remoteAddr, + Addr: t.conn.RemoteAddr(), } // Attach Auth info if there is any. if t.authInfo != nil { @@ -217,18 +195,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( } s.recvCompress = state.encoding s.method = state.method - if t.inTapHandle != nil { - var err error - info := &tap.Info{ - FullMethodName: state.method, - } - s.ctx, err = t.inTapHandle(s.ctx, info) - if err != nil { - // TODO: Log the real error. - t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) - return - } - } t.mu.Lock() if t.state != reachable { t.mu.Unlock() @@ -252,26 +218,13 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func( s.windowHandler = func(n int) { t.updateWindow(s, uint32(n)) } - s.ctx = traceCtx(s.ctx, s.method) - if t.stats != nil { - s.ctx = t.stats.TagRPC(s.ctx, &stats.RPCTagInfo{FullMethodName: s.method}) - inHeader := &stats.InHeader{ - FullMethod: s.method, - RemoteAddr: t.remoteAddr, - LocalAddr: t.localAddr, - Compression: s.recvCompress, - WireLength: int(frame.Header().Length), - } - t.stats.HandleRPC(s.ctx, inHeader) - } handle(s) return } // HandleStreams receives incoming streams using the given handler. This is // 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) { +func (t *http2Server) HandleStreams(handle func(*Stream)) { // Check the validity of client preface. preface := make([]byte, len(clientPreface)) if _, err := io.ReadFull(t.conn, preface); err != nil { @@ -326,7 +279,7 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context. } switch frame := frame.(type) { case *http2.MetaHeadersFrame: - if t.operateHeaders(frame, handle, traceCtx) { + if t.operateHeaders(frame, handle) { t.Close() break } @@ -381,7 +334,7 @@ func (t *http2Server) updateWindow(s *Stream, n uint32) { } func (t *http2Server) handleData(f *http2.DataFrame) { - size := f.Header().Length + size := len(f.Data()) if err := t.fc.onData(uint32(size)); err != nil { grpclog.Printf("transport: http2Server %v", err) t.Close() @@ -396,11 +349,6 @@ func (t *http2Server) handleData(f *http2.DataFrame) { return } if size > 0 { - if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := t.fc.onRead(uint32(size) - uint32(len(f.Data()))); w > 0 { - t.controlBuf.put(&windowUpdate{0, w}) - } - } s.mu.Lock() if s.state == streamDone { s.mu.Unlock() @@ -416,20 +364,13 @@ func (t *http2Server) handleData(f *http2.DataFrame) { t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) 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}) - } - } 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? - if len(f.Data()) > 0 { - data := make([]byte, len(f.Data())) - copy(data, f.Data()) - s.write(recvMsg{data: data}) - } + data := make([]byte, size) + copy(data, f.Data()) + s.write(recvMsg{data: data}) } if f.Header().Flags.Has(http2.FlagDataEndStream) { // Received the end of stream from the client. @@ -551,16 +492,9 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error { t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry}) } } - bufLen := t.hBuf.Len() if err := t.writeHeaders(s, t.hBuf, false); err != nil { return err } - if t.stats != nil { - outHeader := &stats.OutHeader{ - WireLength: bufLen, - } - t.stats.HandleRPC(s.Context(), outHeader) - } t.writableChan <- 0 return nil } @@ -613,17 +547,10 @@ func (t *http2Server) WriteStatus(s *Stream, statusCode codes.Code, statusDesc s t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: entry}) } } - bufLen := t.hBuf.Len() if err := t.writeHeaders(s, t.hBuf, true); err != nil { t.Close() return err } - if t.stats != nil { - outTrailer := &stats.OutTrailer{ - WireLength: bufLen, - } - t.stats.HandleRPC(s.Context(), outTrailer) - } t.closeStream(s) t.writableChan <- 0 return nil @@ -652,14 +579,19 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error { return nil } size := http2MaxFrameLen + s.sendQuotaPool.add(0) // Wait until the stream has some quota to send the data. sq, err := wait(s.ctx, nil, nil, t.shutdownChan, s.sendQuotaPool.acquire()) if err != nil { return err } + t.sendQuotaPool.add(0) // Wait until the transport has some quota to send the data. tq, err := wait(s.ctx, nil, nil, t.shutdownChan, t.sendQuotaPool.acquire()) if err != nil { + if _, ok := err.(StreamError); ok { + t.sendQuotaPool.cancel() + } return err } if sq < size { @@ -727,7 +659,7 @@ func (t *http2Server) applySettings(ss []http2.Setting) { t.mu.Lock() defer t.mu.Unlock() for _, stream := range t.activeStreams { - stream.sendQuotaPool.add(int(s.Val - t.streamSendQuota)) + stream.sendQuotaPool.reset(int(s.Val - t.streamSendQuota)) } t.streamSendQuota = s.Val } @@ -804,10 +736,6 @@ func (t *http2Server) Close() (err error) { for _, s := range streams { s.cancel() } - if t.stats != nil { - connEnd := &stats.ConnEnd{} - t.stats.HandleConn(t.ctx, connEnd) - } return } @@ -839,7 +767,7 @@ func (t *http2Server) closeStream(s *Stream) { } func (t *http2Server) RemoteAddr() net.Addr { - return t.remoteAddr + return t.conn.RemoteAddr() } func (t *http2Server) Drain() { diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go index 6ce43c332e61..a3c68d4cac7e 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util.go @@ -52,6 +52,8 @@ import ( ) const ( + // The primary user agent + primaryUA = "grpc-go/1.0" // http2MaxFrameLen specifies the max length of a HTTP2 frame. http2MaxFrameLen = 16384 // 16KB frame // http://http2.github.io/http2-spec/#SettingValues diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util_test.go new file mode 100644 index 000000000000..a5f8a85f7f48 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/http_util_test.go @@ -0,0 +1,145 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package transport + +import ( + "fmt" + "testing" + "time" +) + +func TestTimeoutEncode(t *testing.T) { + for _, test := range []struct { + in string + out string + }{ + {"12345678ns", "12345678n"}, + {"123456789ns", "123457u"}, + {"12345678us", "12345678u"}, + {"123456789us", "123457m"}, + {"12345678ms", "12345678m"}, + {"123456789ms", "123457S"}, + {"12345678s", "12345678S"}, + {"123456789s", "2057614M"}, + {"12345678m", "12345678M"}, + {"123456789m", "2057614H"}, + } { + d, err := time.ParseDuration(test.in) + if err != nil { + t.Fatalf("failed to parse duration string %s: %v", test.in, err) + } + out := encodeTimeout(d) + if out != test.out { + t.Fatalf("timeoutEncode(%s) = %s, want %s", test.in, out, test.out) + } + } +} + +func TestTimeoutDecode(t *testing.T) { + for _, test := range []struct { + // input + s string + // output + d time.Duration + err error + }{ + {"1234S", time.Second * 1234, nil}, + {"1234x", 0, fmt.Errorf("transport: timeout unit is not recognized: %q", "1234x")}, + {"1", 0, fmt.Errorf("transport: timeout string is too short: %q", "1")}, + {"", 0, fmt.Errorf("transport: timeout string is too short: %q", "")}, + } { + d, err := decodeTimeout(test.s) + if d != test.d || fmt.Sprint(err) != fmt.Sprint(test.err) { + t.Fatalf("timeoutDecode(%q) = %d, %v, want %d, %v", test.s, int64(d), err, int64(test.d), test.err) + } + } +} + +func TestValidContentType(t *testing.T) { + tests := []struct { + h string + want bool + }{ + {"application/grpc", true}, + {"application/grpc+", true}, + {"application/grpc+blah", true}, + {"application/grpc;", true}, + {"application/grpc;blah", true}, + {"application/grpcd", false}, + {"application/grpd", false}, + {"application/grp", false}, + } + for _, tt := range tests { + got := validContentType(tt.h) + if got != tt.want { + t.Errorf("validContentType(%q) = %v; want %v", tt.h, got, tt.want) + } + } +} + +func TestEncodeGrpcMessage(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"", ""}, + {"Hello", "Hello"}, + {"my favorite character is \u0000", "my favorite character is %00"}, + {"my favorite character is %", "my favorite character is %25"}, + } { + actual := encodeGrpcMessage(tt.input) + if tt.expected != actual { + t.Errorf("encodeGrpcMessage(%v) = %v, want %v", tt.input, actual, tt.expected) + } + } +} + +func TestDecodeGrpcMessage(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"", ""}, + {"Hello", "Hello"}, + {"H%61o", "Hao"}, + {"H%6", "H%6"}, + {"%G0", "%G0"}, + {"%E7%B3%BB%E7%BB%9F", "系统"}, + } { + actual := decodeGrpcMessage(tt.input) + if tt.expected != actual { + t.Errorf("dncodeGrpcMessage(%v) = %v, want %v", tt.input, actual, tt.expected) + } + } +} diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go index 0fc4c146f90b..21f757ac64eb 100644 --- a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport.go @@ -35,7 +35,7 @@ Package transport defines and implements message oriented communication channel to complete various transactions (e.g., an RPC). */ -package transport // import "google.golang.org/grpc/transport" +package transport import ( "bytes" @@ -45,13 +45,10 @@ import ( "sync" "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/keepalive" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/stats" - "google.golang.org/grpc/tap" ) // recvMsg represents the received msg from the transport. All transport @@ -170,11 +167,6 @@ type Stream struct { id uint32 // nil for client side Stream. st ServerTransport - // clientStatsCtx keeps the user context for stats handling. - // It's only valid on client side. Server side stats context is same as s.ctx. - // All client side stats collection should use the clientStatsCtx (instead of the stream context) - // so that all the generated stats for a particular RPC can be associated in the processing phase. - clientStatsCtx context.Context // ctx is the associated context of the stream. ctx context.Context // cancel is always nil for client side Stream. @@ -215,11 +207,6 @@ type Stream struct { // the status received from the server. statusCode codes.Code statusDesc string - // 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 } // RecvCompress returns the compression algorithm applied to the inbound @@ -279,6 +266,11 @@ func (s *Stream) Context() context.Context { return s.ctx } +// TraceContext recreates the context of s with a trace.Trace. +func (s *Stream) TraceContext(tr trace.Trace) { + s.ctx = trace.NewContext(s.ctx, tr) +} + // Method returns the method for the stream. func (s *Stream) Method() string { return s.method @@ -338,12 +330,6 @@ func (s *Stream) Read(p []byte) (n int, err error) { return } -// GoString is implemented by Stream so context.String() won't -// race when printing %#v. -func (s *Stream) GoString() string { - return fmt.Sprintf("", s, s.method) -} - // The key to save transport.Stream in the context. type streamKey struct{} @@ -369,39 +355,22 @@ const ( draining ) -// ServerConfig consists of all the configurations to establish a server transport. -type ServerConfig struct { - MaxStreams uint32 - AuthInfo credentials.AuthInfo - InTapHandle tap.ServerInHandle - StatsHandler stats.Handler -} - // NewServerTransport creates a ServerTransport with conn or non-nil error // if it fails. -func NewServerTransport(protocol string, conn net.Conn, config *ServerConfig) (ServerTransport, error) { - return newHTTP2Server(conn, config) +func NewServerTransport(protocol string, conn net.Conn, maxStreams uint32, authInfo credentials.AuthInfo) (ServerTransport, error) { + return newHTTP2Server(conn, maxStreams, authInfo) } // ConnectOptions covers all relevant options for communicating with the server. type ConnectOptions struct { // UserAgent is the application user agent. UserAgent string - // Authority is the :authority pseudo-header to use. This field has no effect if - // TransportCredentials is set. - Authority string // Dialer specifies how to dial a network address. Dialer func(context.Context, string) (net.Conn, error) - // FailOnNonTempDialError specifies if gRPC fails on non-temporary dial errors. - FailOnNonTempDialError bool // PerRPCCredentials stores the PerRPCCredentials required to issue RPCs. PerRPCCredentials []credentials.PerRPCCredentials // TransportCredentials stores the Authenticator required to setup a client connection. TransportCredentials credentials.TransportCredentials - // KeepaliveParams stores the keepalive parameters. - KeepaliveParams keepalive.ClientParameters - // StatsHandler stores the handler for stats. - StatsHandler stats.Handler } // TargetInfo contains the information of the target such as network address and metadata. @@ -497,7 +466,7 @@ type ClientTransport interface { // Write methods for a given Stream will be called serially. type ServerTransport interface { // HandleStreams receives incoming streams using the given handler. - HandleStreams(func(*Stream), func(context.Context, string) context.Context) + HandleStreams(func(*Stream)) // WriteHeader sends the header metadata for the given stream. // WriteHeader may not be called on all streams. @@ -583,7 +552,7 @@ type StreamError struct { } func (e StreamError) Error() string { - return fmt.Sprintf("stream error: code = %s desc = %q", e.Code, e.Desc) + return fmt.Sprintf("stream error: code = %d desc = %q", e.Code, e.Desc) } // ContextErr converts the error from context package into a StreamError. diff --git a/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport_test.go b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport_test.go new file mode 100644 index 000000000000..81320e636091 --- /dev/null +++ b/vendor/github.com/coreos/etcd/vendor/google.golang.org/grpc/transport/transport_test.go @@ -0,0 +1,832 @@ +/* + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package transport + +import ( + "bytes" + "fmt" + "io" + "math" + "net" + "strconv" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/net/context" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" + "google.golang.org/grpc/codes" +) + +type server struct { + lis net.Listener + port string + startedErr chan error // error (or nil) with server start value + mu sync.Mutex + conns map[ServerTransport]bool +} + +var ( + expectedRequest = []byte("ping") + expectedResponse = []byte("pong") + expectedRequestLarge = make([]byte, initialWindowSize*2) + expectedResponseLarge = make([]byte, initialWindowSize*2) + expectedInvalidHeaderField = "invalid/content-type" +) + +type testStreamHandler struct { + t *http2Server +} + +type hType int + +const ( + normal hType = iota + suspended + misbehaved + encodingRequiredStatus + invalidHeaderField +) + +func (h *testStreamHandler) handleStream(t *testing.T, s *Stream) { + req := expectedRequest + resp := expectedResponse + if s.Method() == "foo.Large" { + req = expectedRequestLarge + resp = expectedResponseLarge + } + p := make([]byte, len(req)) + _, err := io.ReadFull(s, p) + if err != nil { + return + } + if !bytes.Equal(p, req) { + t.Fatalf("handleStream got %v, want %v", p, req) + } + // send a response back to the client. + h.t.Write(s, resp, &Options{}) + // send the trailer to end the stream. + h.t.WriteStatus(s, codes.OK, "") +} + +// handleStreamSuspension blocks until s.ctx is canceled. +func (h *testStreamHandler) handleStreamSuspension(s *Stream) { + go func() { + <-s.ctx.Done() + }() +} + +func (h *testStreamHandler) handleStreamMisbehave(t *testing.T, s *Stream) { + conn, ok := s.ServerTransport().(*http2Server) + if !ok { + t.Fatalf("Failed to convert %v to *http2Server", s.ServerTransport()) + } + var sent int + p := make([]byte, http2MaxFrameLen) + for sent < initialWindowSize { + <-conn.writableChan + n := initialWindowSize - sent + // The last message may be smaller than http2MaxFrameLen + if n <= http2MaxFrameLen { + if s.Method() == "foo.Connection" { + // Violate connection level flow control window of client but do not + // violate any stream level windows. + p = make([]byte, n) + } else { + // Violate stream level flow control window of client. + p = make([]byte, n+1) + } + } + if err := conn.framer.writeData(true, s.id, false, p); err != nil { + conn.writableChan <- 0 + break + } + conn.writableChan <- 0 + sent += len(p) + } +} + +func (h *testStreamHandler) handleStreamEncodingRequiredStatus(t *testing.T, s *Stream) { + // raw newline is not accepted by http2 framer so it must be encoded. + h.t.WriteStatus(s, encodingTestStatusCode, encodingTestStatusDesc) +} + +func (h *testStreamHandler) handleStreamInvalidHeaderField(t *testing.T, s *Stream) { + <-h.t.writableChan + h.t.hBuf.Reset() + h.t.hEnc.WriteField(hpack.HeaderField{Name: "content-type", Value: expectedInvalidHeaderField}) + if err := h.t.writeHeaders(s, h.t.hBuf, false); err != nil { + t.Fatalf("Failed to write headers: %v", err) + } + h.t.writableChan <- 0 +} + +// start starts server. Other goroutines should block on s.readyChan for further operations. +func (s *server) start(t *testing.T, port int, maxStreams uint32, ht hType) { + var err error + if port == 0 { + s.lis, err = net.Listen("tcp", "localhost:0") + } else { + s.lis, err = net.Listen("tcp", "localhost:"+strconv.Itoa(port)) + } + if err != nil { + s.startedErr <- fmt.Errorf("failed to listen: %v", err) + return + } + _, p, err := net.SplitHostPort(s.lis.Addr().String()) + if err != nil { + s.startedErr <- fmt.Errorf("failed to parse listener address: %v", err) + return + } + s.port = p + s.conns = make(map[ServerTransport]bool) + s.startedErr <- nil + for { + conn, err := s.lis.Accept() + if err != nil { + return + } + transport, err := NewServerTransport("http2", conn, maxStreams, nil) + if err != nil { + return + } + s.mu.Lock() + if s.conns == nil { + s.mu.Unlock() + transport.Close() + return + } + s.conns[transport] = true + s.mu.Unlock() + h := &testStreamHandler{transport.(*http2Server)} + switch ht { + case suspended: + go transport.HandleStreams(h.handleStreamSuspension) + case misbehaved: + go transport.HandleStreams(func(s *Stream) { + go h.handleStreamMisbehave(t, s) + }) + case encodingRequiredStatus: + go transport.HandleStreams(func(s *Stream) { + go h.handleStreamEncodingRequiredStatus(t, s) + }) + case invalidHeaderField: + go transport.HandleStreams(func(s *Stream) { + go h.handleStreamInvalidHeaderField(t, s) + }) + default: + go transport.HandleStreams(func(s *Stream) { + go h.handleStream(t, s) + }) + } + } +} + +func (s *server) wait(t *testing.T, timeout time.Duration) { + select { + case err := <-s.startedErr: + if err != nil { + t.Fatal(err) + } + case <-time.After(timeout): + t.Fatalf("Timed out after %v waiting for server to be ready", timeout) + } +} + +func (s *server) stop() { + s.lis.Close() + s.mu.Lock() + for c := range s.conns { + c.Close() + } + s.conns = nil + s.mu.Unlock() +} + +func setUp(t *testing.T, port int, maxStreams uint32, ht hType) (*server, ClientTransport) { + server := &server{startedErr: make(chan error, 1)} + go server.start(t, port, maxStreams, ht) + server.wait(t, 2*time.Second) + addr := "localhost:" + server.port + var ( + ct ClientTransport + connErr error + ) + target := TargetInfo{ + Addr: addr, + } + ct, connErr = NewClientTransport(context.Background(), target, ConnectOptions{}) + if connErr != nil { + t.Fatalf("failed to create transport: %v", connErr) + } + return server, ct +} + +func TestClientSendAndReceive(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, normal) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Small", + } + s1, err1 := ct.NewStream(context.Background(), callHdr) + if err1 != nil { + t.Fatalf("failed to open stream: %v", err1) + } + if s1.id != 1 { + t.Fatalf("wrong stream id: %d", s1.id) + } + s2, err2 := ct.NewStream(context.Background(), callHdr) + if err2 != nil { + t.Fatalf("failed to open stream: %v", err2) + } + if s2.id != 3 { + t.Fatalf("wrong stream id: %d", s2.id) + } + opts := Options{ + Last: true, + Delay: false, + } + if err := ct.Write(s1, expectedRequest, &opts); err != nil && err != io.EOF { + t.Fatalf("failed to send data: %v", err) + } + p := make([]byte, len(expectedResponse)) + _, recvErr := io.ReadFull(s1, p) + if recvErr != nil || !bytes.Equal(p, expectedResponse) { + t.Fatalf("Error: %v, want ; Result: %v, want %v", recvErr, p, expectedResponse) + } + _, recvErr = io.ReadFull(s1, p) + if recvErr != io.EOF { + t.Fatalf("Error: %v; want ", recvErr) + } + ct.Close() + server.stop() +} + +func TestClientErrorNotify(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, normal) + go server.stop() + // ct.reader should detect the error and activate ct.Error(). + <-ct.Error() + ct.Close() +} + +func performOneRPC(ct ClientTransport) { + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Small", + } + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + return + } + opts := Options{ + Last: true, + Delay: false, + } + if err := ct.Write(s, expectedRequest, &opts); err == nil || err == io.EOF { + time.Sleep(5 * time.Millisecond) + // The following s.Recv()'s could error out because the + // underlying transport is gone. + // + // Read response + p := make([]byte, len(expectedResponse)) + io.ReadFull(s, p) + // Read io.EOF + io.ReadFull(s, p) + } +} + +func TestClientMix(t *testing.T) { + s, ct := setUp(t, 0, math.MaxUint32, normal) + go func(s *server) { + time.Sleep(5 * time.Second) + s.stop() + }(s) + go func(ct ClientTransport) { + <-ct.Error() + ct.Close() + }(ct) + for i := 0; i < 1000; i++ { + time.Sleep(10 * time.Millisecond) + go performOneRPC(ct) + } +} + +func TestLargeMessage(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, normal) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Large", + } + var wg sync.WaitGroup + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Errorf("%v.NewStream(_, _) = _, %v, want _, ", ct, err) + } + if err := ct.Write(s, expectedRequestLarge, &Options{Last: true, Delay: false}); err != nil && err != io.EOF { + t.Errorf("%v.Write(_, _, _) = %v, want ", ct, err) + } + p := make([]byte, len(expectedResponseLarge)) + if _, err := io.ReadFull(s, p); err != nil || !bytes.Equal(p, expectedResponseLarge) { + t.Errorf("io.ReadFull(_, %v) = _, %v, want %v, ", err, p, expectedResponse) + } + if _, err = io.ReadFull(s, p); err != io.EOF { + t.Errorf("Failed to complete the stream %v; want ", err) + } + }() + } + wg.Wait() + ct.Close() + server.stop() +} + +func TestGracefulClose(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, normal) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Small", + } + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Fatalf("%v.NewStream(_, _) = _, %v, want _, ", ct, err) + } + if err = ct.GracefulClose(); err != nil { + t.Fatalf("%v.GracefulClose() = %v, want ", ct, err) + } + var wg sync.WaitGroup + // Expect the failure for all the follow-up streams because ct has been closed gracefully. + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + if _, err := ct.NewStream(context.Background(), callHdr); err != ErrStreamDrain { + t.Errorf("%v.NewStream(_, _) = _, %v, want _, %v", ct, err, ErrStreamDrain) + } + }() + } + opts := Options{ + Last: true, + Delay: false, + } + // The stream which was created before graceful close can still proceed. + if err := ct.Write(s, expectedRequest, &opts); err != nil && err != io.EOF { + t.Fatalf("%v.Write(_, _, _) = %v, want ", ct, err) + } + p := make([]byte, len(expectedResponse)) + if _, err := io.ReadFull(s, p); err != nil || !bytes.Equal(p, expectedResponse) { + t.Fatalf("io.ReadFull(_, %v) = _, %v, want %v, ", err, p, expectedResponse) + } + if _, err = io.ReadFull(s, p); err != io.EOF { + t.Fatalf("Failed to complete the stream %v; want ", err) + } + wg.Wait() + ct.Close() + server.stop() +} + +func TestLargeMessageSuspension(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, suspended) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Large", + } + // Set a long enough timeout for writing a large message out. + ctx, _ := context.WithTimeout(context.Background(), time.Second) + s, err := ct.NewStream(ctx, callHdr) + if err != nil { + t.Fatalf("failed to open stream: %v", err) + } + // Write should not be done successfully due to flow control. + err = ct.Write(s, expectedRequestLarge, &Options{Last: true, Delay: false}) + expectedErr := streamErrorf(codes.DeadlineExceeded, "%v", context.DeadlineExceeded) + if err != expectedErr { + t.Fatalf("Write got %v, want %v", err, expectedErr) + } + ct.Close() + server.stop() +} + +func TestMaxStreams(t *testing.T) { + server, ct := setUp(t, 0, 1, suspended) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Large", + } + // Have a pending stream which takes all streams quota. + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Fatalf("Failed to open stream: %v", err) + } + cc, ok := ct.(*http2Client) + if !ok { + t.Fatalf("Failed to convert %v to *http2Client", ct) + } + done := make(chan struct{}) + ch := make(chan int) + ready := make(chan struct{}) + go func() { + for { + select { + case <-time.After(5 * time.Millisecond): + select { + case ch <- 0: + case <-ready: + return + } + case <-time.After(5 * time.Second): + close(done) + return + case <-ready: + return + } + } + }() + for { + select { + case <-ch: + case <-done: + t.Fatalf("Client has not received the max stream setting in 5 seconds.") + } + cc.mu.Lock() + // cc.streamsQuota should be initialized once receiving the 1st setting frame from + // the server. + if cc.streamsQuota != nil { + cc.mu.Unlock() + select { + case <-cc.streamsQuota.acquire(): + t.Fatalf("streamsQuota.acquire() becomes readable mistakenly.") + default: + if cc.streamsQuota.quota != 0 { + t.Fatalf("streamsQuota.quota got non-zero quota mistakenly.") + } + } + break + } + cc.mu.Unlock() + } + close(ready) + // Close the pending stream so that the streams quota becomes available for the next new stream. + ct.CloseStream(s, nil) + select { + case i := <-cc.streamsQuota.acquire(): + if i != 1 { + t.Fatalf("streamsQuota.acquire() got %d quota, want 1.", i) + } + cc.streamsQuota.add(i) + default: + t.Fatalf("streamsQuota.acquire() is not readable.") + } + if _, err := ct.NewStream(context.Background(), callHdr); err != nil { + t.Fatalf("Failed to open stream: %v", err) + } + ct.Close() + server.stop() +} + +func TestServerContextCanceledOnClosedConnection(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, suspended) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo", + } + var sc *http2Server + // Wait until the server transport is setup. + for { + server.mu.Lock() + if len(server.conns) == 0 { + server.mu.Unlock() + time.Sleep(time.Millisecond) + continue + } + for k := range server.conns { + var ok bool + sc, ok = k.(*http2Server) + if !ok { + t.Fatalf("Failed to convert %v to *http2Server", k) + } + } + server.mu.Unlock() + break + } + cc, ok := ct.(*http2Client) + if !ok { + t.Fatalf("Failed to convert %v to *http2Client", ct) + } + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Fatalf("Failed to open stream: %v", err) + } + // Make sure the headers frame is flushed out. + <-cc.writableChan + if err = cc.framer.writeData(true, s.id, false, make([]byte, http2MaxFrameLen)); err != nil { + t.Fatalf("Failed to write data: %v", err) + } + cc.writableChan <- 0 + // Loop until the server side stream is created. + var ss *Stream + for { + time.Sleep(time.Second) + sc.mu.Lock() + if len(sc.activeStreams) == 0 { + sc.mu.Unlock() + continue + } + ss = sc.activeStreams[s.id] + sc.mu.Unlock() + break + } + cc.Close() + select { + case <-ss.Context().Done(): + if ss.Context().Err() != context.Canceled { + t.Fatalf("ss.Context().Err() got %v, want %v", ss.Context().Err(), context.Canceled) + } + case <-time.After(5 * time.Second): + t.Fatalf("Failed to cancel the context of the sever side stream.") + } + server.stop() +} + +func TestServerWithMisbehavedClient(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, suspended) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo", + } + var sc *http2Server + // Wait until the server transport is setup. + for { + server.mu.Lock() + if len(server.conns) == 0 { + server.mu.Unlock() + time.Sleep(time.Millisecond) + continue + } + for k := range server.conns { + var ok bool + sc, ok = k.(*http2Server) + if !ok { + t.Fatalf("Failed to convert %v to *http2Server", k) + } + } + server.mu.Unlock() + break + } + cc, ok := ct.(*http2Client) + if !ok { + t.Fatalf("Failed to convert %v to *http2Client", ct) + } + // Test server behavior for violation of stream flow control window size restriction. + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Fatalf("Failed to open stream: %v", err) + } + var sent int + // Drain the stream flow control window + <-cc.writableChan + if err = cc.framer.writeData(true, s.id, false, make([]byte, http2MaxFrameLen)); err != nil { + t.Fatalf("Failed to write data: %v", err) + } + cc.writableChan <- 0 + sent += http2MaxFrameLen + // Wait until the server creates the corresponding stream and receive some data. + var ss *Stream + for { + time.Sleep(time.Millisecond) + sc.mu.Lock() + if len(sc.activeStreams) == 0 { + sc.mu.Unlock() + continue + } + ss = sc.activeStreams[s.id] + sc.mu.Unlock() + ss.fc.mu.Lock() + if ss.fc.pendingData > 0 { + ss.fc.mu.Unlock() + break + } + ss.fc.mu.Unlock() + } + if ss.fc.pendingData != http2MaxFrameLen || ss.fc.pendingUpdate != 0 || sc.fc.pendingData != http2MaxFrameLen || sc.fc.pendingUpdate != 0 { + t.Fatalf("Server mistakenly updates inbound flow control params: got %d, %d, %d, %d; want %d, %d, %d, %d", ss.fc.pendingData, ss.fc.pendingUpdate, sc.fc.pendingData, sc.fc.pendingUpdate, http2MaxFrameLen, 0, http2MaxFrameLen, 0) + } + // Keep sending until the server inbound window is drained for that stream. + for sent <= initialWindowSize { + <-cc.writableChan + if err = cc.framer.writeData(true, s.id, false, make([]byte, 1)); err != nil { + t.Fatalf("Failed to write data: %v", err) + } + cc.writableChan <- 0 + sent++ + } + // Server sent a resetStream for s already. + code := http2ErrConvTab[http2.ErrCodeFlowControl] + if _, err := io.ReadFull(s, make([]byte, 1)); err != io.EOF || s.statusCode != code { + t.Fatalf("%v got err %v with statusCode %d, want err with statusCode %d", s, err, s.statusCode, code) + } + + if ss.fc.pendingData != 0 || ss.fc.pendingUpdate != 0 || sc.fc.pendingData != 0 || sc.fc.pendingUpdate <= initialWindowSize { + t.Fatalf("Server mistakenly resets inbound flow control params: got %d, %d, %d, %d; want 0, 0, 0, >%d", ss.fc.pendingData, ss.fc.pendingUpdate, sc.fc.pendingData, sc.fc.pendingUpdate, initialWindowSize) + } + ct.CloseStream(s, nil) + // Test server behavior for violation of connection flow control window size restriction. + // + // Keep creating new streams until the connection window is drained on the server and + // the server tears down the connection. + for { + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + // The server tears down the connection. + break + } + <-cc.writableChan + cc.framer.writeData(true, s.id, true, make([]byte, http2MaxFrameLen)) + cc.writableChan <- 0 + } + ct.Close() + server.stop() +} + +func TestClientWithMisbehavedServer(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, misbehaved) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo.Stream", + } + conn, ok := ct.(*http2Client) + if !ok { + t.Fatalf("Failed to convert %v to *http2Client", ct) + } + // Test the logic for the violation of stream flow control window size restriction. + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + t.Fatalf("Failed to open stream: %v", err) + } + d := make([]byte, 1) + if err := ct.Write(s, d, &Options{Last: true, Delay: false}); err != nil && err != io.EOF { + t.Fatalf("Failed to write: %v", err) + } + // Read without window update. + for { + p := make([]byte, http2MaxFrameLen) + if _, err = s.dec.Read(p); err != nil { + break + } + } + if s.fc.pendingData <= initialWindowSize || s.fc.pendingUpdate != 0 || conn.fc.pendingData <= initialWindowSize || conn.fc.pendingUpdate != 0 { + t.Fatalf("Client mistakenly updates inbound flow control params: got %d, %d, %d, %d; want >%d, %d, >%d, %d", s.fc.pendingData, s.fc.pendingUpdate, conn.fc.pendingData, conn.fc.pendingUpdate, initialWindowSize, 0, initialWindowSize, 0) + } + if err != io.EOF || s.statusCode != codes.Internal { + t.Fatalf("Got err %v and the status code %d, want and the code %d", err, s.statusCode, codes.Internal) + } + conn.CloseStream(s, err) + if s.fc.pendingData != 0 || s.fc.pendingUpdate != 0 || conn.fc.pendingData != 0 || conn.fc.pendingUpdate <= initialWindowSize { + t.Fatalf("Client mistakenly resets inbound flow control params: got %d, %d, %d, %d; want 0, 0, 0, >%d", s.fc.pendingData, s.fc.pendingUpdate, conn.fc.pendingData, conn.fc.pendingUpdate, initialWindowSize) + } + // Test the logic for the violation of the connection flow control window size restriction. + // + // Generate enough streams to drain the connection window. Make the server flood the traffic + // to violate flow control window size of the connection. + callHdr.Method = "foo.Connection" + for i := 0; i < int(initialConnWindowSize/initialWindowSize+10); i++ { + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + break + } + if err := ct.Write(s, d, &Options{Last: true, Delay: false}); err != nil { + break + } + } + // http2Client.errChan is closed due to connection flow control window size violation. + <-conn.Error() + ct.Close() + server.stop() +} + +var ( + encodingTestStatusCode = codes.Internal + encodingTestStatusDesc = "\n" +) + +func TestEncodingRequiredStatus(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, encodingRequiredStatus) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo", + } + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + return + } + opts := Options{ + Last: true, + Delay: false, + } + if err := ct.Write(s, expectedRequest, &opts); err != nil && err != io.EOF { + t.Fatalf("Failed to write the request: %v", err) + } + p := make([]byte, http2MaxFrameLen) + if _, err := s.dec.Read(p); err != io.EOF { + t.Fatalf("Read got error %v, want %v", err, io.EOF) + } + if s.StatusCode() != encodingTestStatusCode || s.StatusDesc() != encodingTestStatusDesc { + t.Fatalf("stream with status code %d, status desc %v, want %d, %v", s.StatusCode(), s.StatusDesc(), encodingTestStatusCode, encodingTestStatusDesc) + } + ct.Close() + server.stop() +} + +func TestInvalidHeaderField(t *testing.T) { + server, ct := setUp(t, 0, math.MaxUint32, invalidHeaderField) + callHdr := &CallHdr{ + Host: "localhost", + Method: "foo", + } + s, err := ct.NewStream(context.Background(), callHdr) + if err != nil { + return + } + opts := Options{ + Last: true, + Delay: false, + } + if err := ct.Write(s, expectedRequest, &opts); err != nil && err != io.EOF { + t.Fatalf("Failed to write the request: %v", err) + } + p := make([]byte, http2MaxFrameLen) + _, err = s.dec.Read(p) + if se, ok := err.(StreamError); !ok || se.Code != codes.FailedPrecondition || !strings.Contains(err.Error(), expectedInvalidHeaderField) { + t.Fatalf("Read got error %v, want error with code %s and contains %q", err, codes.FailedPrecondition, expectedInvalidHeaderField) + } + ct.Close() + server.stop() +} + +func TestStreamContext(t *testing.T) { + expectedStream := &Stream{} + ctx := newContextWithStream(context.Background(), expectedStream) + s, ok := StreamFromContext(ctx) + if !ok || expectedStream != s { + t.Fatalf("GetStreamFromContext(%v) = %v, %t, want: %v, true", ctx, s, ok, expectedStream) + } +} + +func TestIsReservedHeader(t *testing.T) { + tests := []struct { + h string + want bool + }{ + {"", false}, // but should be rejected earlier + {"foo", false}, + {"content-type", true}, + {"grpc-message-type", true}, + {"grpc-encoding", true}, + {"grpc-message", true}, + {"grpc-status", true}, + {"grpc-timeout", true}, + {"te", true}, + } + for _, tt := range tests { + got := isReservedHeader(tt.h) + if got != tt.want { + t.Errorf("isReservedHeader(%q) = %v; want %v", tt.h, got, tt.want) + } + } +}