From 58ba322bb45d99659e3944bad05a389a49da5e5e Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Mon, 9 Mar 2020 14:46:28 -0700 Subject: [PATCH] clientv3: embed API version Signed-off-by: Gyuho Lee --- clientv3/ctx.go | 13 +++++++++++++ clientv3/ctx_test.go | 20 ++++++++++++++++++-- clientv3/retry_interceptor.go | 2 ++ etcdserver/api/v3rpc/rpctypes/md.go | 2 ++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/clientv3/ctx.go b/clientv3/ctx.go index c1e8be6fe..869b0fa69 100644 --- a/clientv3/ctx.go +++ b/clientv3/ctx.go @@ -18,6 +18,7 @@ import ( "context" "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" + "go.etcd.io/etcd/version" "google.golang.org/grpc/metadata" ) @@ -33,3 +34,15 @@ func WithRequireLeader(ctx context.Context) context.Context { md.Set(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader) return metadata.NewOutgoingContext(ctx, md) } + +// embeds client version +func withVersion(ctx context.Context) context.Context { + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { // no outgoing metadata ctx key, create one + md = metadata.Pairs(rpctypes.MetadataClientAPIVersionKey, version.APIVersion) + return metadata.NewOutgoingContext(ctx, md) + } + // overwrite/add version key/value + md.Set(rpctypes.MetadataClientAPIVersionKey, version.APIVersion) + return metadata.NewOutgoingContext(ctx, md) +} diff --git a/clientv3/ctx_test.go b/clientv3/ctx_test.go index d87728a98..89d966643 100644 --- a/clientv3/ctx_test.go +++ b/clientv3/ctx_test.go @@ -20,6 +20,7 @@ import ( "testing" "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes" + "go.etcd.io/etcd/version" "google.golang.org/grpc/metadata" ) @@ -42,10 +43,25 @@ func TestMetadataWithRequireLeader(t *testing.T) { if !ok { t.Fatal("expected outgoing metadata ctx key") } - if ss := md.Get("hasleader"); !reflect.DeepEqual(ss, []string{"true"}) { - t.Fatalf("unexpected metadata for 'hasleader' %v", ss) + if ss := md.Get(rpctypes.MetadataRequireLeaderKey); !reflect.DeepEqual(ss, []string{rpctypes.MetadataHasLeader}) { + t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataRequireLeaderKey, ss) } if ss := md.Get("hello"); !reflect.DeepEqual(ss, []string{"1", "2"}) { t.Fatalf("unexpected metadata for 'hello' %v", ss) } } + +func TestMetadataWithClientAPIVersion(t *testing.T) { + ctx := withVersion(WithRequireLeader(context.TODO())) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + t.Fatal("expected outgoing metadata ctx key") + } + if ss := md.Get(rpctypes.MetadataRequireLeaderKey); !reflect.DeepEqual(ss, []string{rpctypes.MetadataHasLeader}) { + t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataRequireLeaderKey, ss) + } + if ss := md.Get(rpctypes.MetadataClientAPIVersionKey); !reflect.DeepEqual(ss, []string{version.APIVersion}) { + t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataClientAPIVersionKey, ss) + } +} diff --git a/clientv3/retry_interceptor.go b/clientv3/retry_interceptor.go index aac679ecc..2c266e55b 100644 --- a/clientv3/retry_interceptor.go +++ b/clientv3/retry_interceptor.go @@ -38,6 +38,7 @@ import ( func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.UnaryClientInterceptor { intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs) return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + ctx = withVersion(ctx) grpcOpts, retryOpts := filterCallOptions(opts) callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts) // short circuit for simplicity, and avoiding allocations. @@ -103,6 +104,7 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.StreamClientInterceptor { intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs) return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + ctx = withVersion(ctx) grpcOpts, retryOpts := filterCallOptions(opts) callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts) // short circuit for simplicity, and avoiding allocations. diff --git a/etcdserver/api/v3rpc/rpctypes/md.go b/etcdserver/api/v3rpc/rpctypes/md.go index 5c590e1ae..90b8b835b 100644 --- a/etcdserver/api/v3rpc/rpctypes/md.go +++ b/etcdserver/api/v3rpc/rpctypes/md.go @@ -17,4 +17,6 @@ package rpctypes var ( MetadataRequireLeaderKey = "hasleader" MetadataHasLeader = "true" + + MetadataClientAPIVersionKey = "client-api-version" )