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  }