vitess.io/vitess@v0.16.2/go/vt/vtadmin/cluster/dynamic/interceptors.go (about) 1 package dynamic 2 3 import ( 4 "context" 5 "strings" 6 7 "google.golang.org/grpc" 8 "google.golang.org/grpc/metadata" 9 "google.golang.org/protobuf/proto" 10 11 "vitess.io/vitess/go/vt/log" 12 "vitess.io/vitess/go/vt/vtadmin/cluster" 13 14 vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin" 15 ) 16 17 // StreamServerInterceptor returns a StreamServerInterceptor that redirects a 18 // streaming RPC to a dynamic API if the incoming context has a cluster spec in 19 // its metadata. Otherwise, the interceptor is a no-op, and the original stream 20 // handler is invoked. 21 func StreamServerInterceptor(api API) grpc.StreamServerInterceptor { 22 return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 23 c, id, ok, err := clusterFromIncomingContextMetadata(ss.Context()) 24 switch { // TODO: see if it's possible to de-duplicate this somehow. Unfortunately the callbacks have different signatures which might make it impossible. 25 case !ok: 26 // No dynamic cluster metadata, proceed directly to handler. 27 return handler(srv, ss) 28 case id == "": 29 // There was a cluster spec in the metadata, but we couldn't even 30 // get an id out of it. Warn and fallback to static API. 31 log.Warningf("failed to unmarshal dynamic cluster spec from incoming context metadata; falling back to static API; error: %v", err) 32 return handler(srv, ss) 33 } 34 35 if err != nil { 36 log.Warningf("failed to extract valid cluster from incoming metadata; attempting to use existing cluster with id=%s; error: %v", id, err) 37 } 38 39 dynamicAPI := api.WithCluster(c, id) 40 streamHandler := streamHandlersByName[nameFromFullMethod(info.FullMethod)] 41 return streamHandler(dynamicAPI, ss) 42 } 43 } 44 45 // UnaryServerInterceptor returns a UnaryServerInterceptor that redirects a 46 // unary RPC to a dynamic API if the incoming context has a cluster spec in its 47 // metadata. Otherwise, the interceptor is a no-op, and the original method 48 // handler is invoked. 49 func UnaryServerInterceptor(api API) grpc.UnaryServerInterceptor { 50 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 51 c, id, ok, err := clusterFromIncomingContextMetadata(ctx) 52 switch { 53 case !ok: 54 // No dynamic cluster metadata, proceed directly to handler. 55 return handler(ctx, req) 56 case id == "": 57 // There was a cluster spec in the metadata, but we couldn't even 58 // get an id out of it. Warn and fallback to static API. 59 log.Warningf("failed to unmarshal dynamic cluster spec from incoming context metadata; falling back to static API; error: %v", err) 60 return handler(ctx, req) 61 } 62 63 if err != nil { 64 log.Warningf("failed to extract valid cluster from incoming metadata; attempting to use existing cluster with id=%s; error: %v", id, err) 65 } 66 67 dynamicAPI := api.WithCluster(c, id) 68 method := methodHandlersByName[nameFromFullMethod(info.FullMethod)] 69 70 // NOTE: because we don't have access to the interceptor 71 // chain (but we _could_ if we wanted to add a method to the 72 // DynamicAPI interface), this MUST be the last interceptor 73 // in the chain. 74 return method(dynamicAPI, ctx, dec(req), nil) 75 } 76 } 77 78 func clusterFromIncomingContextMetadata(ctx context.Context) (*cluster.Cluster, string, bool, error) { 79 md, ok := metadata.FromIncomingContext(ctx) 80 if !ok { 81 return nil, "", false, nil 82 } 83 84 clusterMetadata := md.Get("cluster") 85 if len(clusterMetadata) != 1 { 86 return nil, "", false, nil 87 } 88 89 c, id, err := ClusterFromString(ctx, clusterMetadata[0]) 90 return c, id, true, err 91 } 92 93 // dec returns a function that merges the src proto.Message into dst. 94 func dec(src interface{}) func(dst interface{}) error { 95 return func(dst interface{}) error { 96 // gRPC handlers expect a function called `dec` which 97 // decodes an arbitrary req into a req of the correct type 98 // for the particular handler. 99 // 100 // Because we are doing a lookup that matches on method 101 // name, we know that the `req` passed to the interceptor 102 // and the `req2` passed to `dec` are the same type, we're 103 // just going to proto.Merge blindly and return nil. 104 proto.Merge(dst.(proto.Message), src.(proto.Message)) 105 return nil 106 } 107 } 108 109 func nameFromFullMethod(fullMethod string) string { 110 parts := strings.Split(fullMethod, "/") 111 return parts[len(parts)-1] 112 } 113 114 var ( 115 methodHandlersByName = map[string]methodHandler{} 116 streamHandlersByName = map[string]grpc.StreamHandler{} 117 ) 118 119 // for whatever reason, grpc exports the StreamHandler type but _not_ the 120 // methodHandler type, but this is an identical type. Furthermore, the cast in 121 // the init() below will fail to compile if our types ever stop aligning. 122 // 123 // c.f. https://github.com/grpc/grpc-go/blob/v1.39.0/server.go#L81 124 type methodHandler func(srv interface{}, ctx context.Context, dec func(in interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) 125 126 func init() { 127 for _, m := range vtadminpb.VTAdmin_ServiceDesc.Methods { 128 methodHandlersByName[m.MethodName] = methodHandler(m.Handler) 129 } 130 131 for _, s := range vtadminpb.VTAdmin_ServiceDesc.Streams { 132 streamHandlersByName[s.StreamName] = s.Handler 133 } 134 }