github.com/yandex/pandora@v0.5.32/tests/acceptance/grpc_test.go (about)

     1  package acceptance
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log/slog"
     7  	"net"
     8  	"os"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/spf13/afero"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/stretchr/testify/suite"
    16  	"github.com/yandex/pandora/cli"
    17  	"github.com/yandex/pandora/core/engine"
    18  	"github.com/yandex/pandora/examples/grpc/server"
    19  	"github.com/yandex/pandora/lib/pointer"
    20  	"github.com/yandex/pandora/lib/testutil"
    21  	"go.uber.org/zap"
    22  	"google.golang.org/grpc"
    23  	"google.golang.org/grpc/metadata"
    24  	"google.golang.org/grpc/reflection"
    25  	"gopkg.in/yaml.v2"
    26  )
    27  
    28  func TestCheckGRPCReflectServer(t *testing.T) {
    29  	fs := afero.NewOsFs()
    30  	testOnce.Do(importDependencies(fs))
    31  	pandoraLogger := testutil.NewNullLogger()
    32  	pandoraMetrics := engine.NewMetrics("reflect")
    33  	baseFile, err := os.ReadFile("testdata/grpc/base.yaml")
    34  	require.NoError(t, err)
    35  
    36  	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
    37  
    38  	t.Run("reflect not found", func(t *testing.T) {
    39  		grpcServer := grpc.NewServer()
    40  		srv := server.NewServer(logger, time.Now().UnixNano())
    41  		server.RegisterTargetServiceServer(grpcServer, srv)
    42  		grpcAddress := ":18888"
    43  		// Don't register reflection handler
    44  		// reflection.Register(grpcServer)
    45  		l, err := net.Listen("tcp", grpcAddress)
    46  		require.NoError(t, err)
    47  		go func() {
    48  			err = grpcServer.Serve(l)
    49  			require.NoError(t, err)
    50  		}()
    51  
    52  		defer func() {
    53  			grpcServer.Stop()
    54  		}()
    55  
    56  		conf := parseFileContentToCliConfig(t, baseFile, nil)
    57  
    58  		require.Equal(t, 1, len(conf.Engine.Pools))
    59  		aggr := &aggregator{}
    60  		conf.Engine.Pools[0].Aggregator = aggr
    61  		pandora := engine.New(pandoraLogger, pandoraMetrics, conf.Engine)
    62  		err = pandora.Run(context.Background())
    63  		require.Error(t, err)
    64  		require.Contains(t, err.Error(), "gun warm up failed")
    65  		require.Contains(t, err.Error(), "unknown service grpc.reflection.v1alpha.ServerReflection")
    66  
    67  		st, err := srv.Stats(context.Background(), nil)
    68  		require.NoError(t, err)
    69  		require.Equal(t, int64(0), st.Hello)
    70  	})
    71  
    72  	t.Run("reflect on another port", func(t *testing.T) {
    73  		grpcServer := grpc.NewServer()
    74  		srv := server.NewServer(logger, time.Now().UnixNano())
    75  		server.RegisterTargetServiceServer(grpcServer, srv)
    76  		grpcAddress := ":18888"
    77  		l, err := net.Listen("tcp", grpcAddress)
    78  		require.NoError(t, err)
    79  		go func() {
    80  			err := grpcServer.Serve(l)
    81  			require.NoError(t, err)
    82  		}()
    83  
    84  		reflectionGrpcServer := grpc.NewServer()
    85  		reflectionSrv := server.NewServer(logger, time.Now().UnixNano())
    86  		server.RegisterTargetServiceServer(reflectionGrpcServer, reflectionSrv)
    87  		grpcAddress = ":18889"
    88  		reflection.Register(reflectionGrpcServer)
    89  		listenRef, err := net.Listen("tcp", grpcAddress)
    90  		require.NoError(t, err)
    91  		go func() {
    92  			err := reflectionGrpcServer.Serve(listenRef)
    93  			require.NoError(t, err)
    94  		}()
    95  		defer func() {
    96  			grpcServer.Stop()
    97  			reflectionGrpcServer.Stop()
    98  		}()
    99  
   100  		conf := parseFileContentToCliConfig(t, baseFile, func(c *PandoraConfigGRPC) {
   101  			c.Pools[0].Gun.ReflectPort = pointer.ToInt64(18889)
   102  		})
   103  
   104  		require.Equal(t, 1, len(conf.Engine.Pools))
   105  		aggr := &aggregator{}
   106  		conf.Engine.Pools[0].Aggregator = aggr
   107  		pandora := engine.New(pandoraLogger, pandoraMetrics, conf.Engine)
   108  		err = pandora.Run(context.Background())
   109  		require.NoError(t, err)
   110  
   111  		st, err := srv.Stats(context.Background(), nil)
   112  		require.NoError(t, err)
   113  		require.Equal(t, int64(8), st.Hello)
   114  	})
   115  
   116  	t.Run("reflect with custom metadata", func(t *testing.T) {
   117  		metadataKey := "testKey"
   118  		metadataValue := "testValue"
   119  		wrongMDValuesLengthError := errors.New("wrong metadata values length")
   120  		wrongMDValueError := errors.New("wrong metadata value")
   121  		metadataChecker := func(ctx context.Context) (context.Context, error) {
   122  			md, ok := metadata.FromIncomingContext(ctx)
   123  			if !ok {
   124  				return nil, wrongMDValuesLengthError
   125  			}
   126  			vals := md.Get(metadataKey)
   127  			if len(vals) != 1 {
   128  				return nil, wrongMDValuesLengthError
   129  			}
   130  			if vals[0] != metadataValue {
   131  				return nil, wrongMDValueError
   132  			}
   133  			return ctx, nil
   134  		}
   135  		grpcServer := grpc.NewServer(
   136  			grpc.UnaryInterceptor(MetadataServerInterceptor(metadataChecker)),
   137  			grpc.StreamInterceptor(MetadataServerStreamInterceptor(metadataChecker)))
   138  		srv := server.NewServer(logger, time.Now().UnixNano())
   139  		server.RegisterTargetServiceServer(grpcServer, srv)
   140  		grpcAddress := "localhost:18888"
   141  		reflection.Register(grpcServer)
   142  		l, err := net.Listen("tcp", grpcAddress)
   143  		require.NoError(t, err)
   144  		go func() {
   145  			err = grpcServer.Serve(l)
   146  			require.NoError(t, err)
   147  		}()
   148  
   149  		defer func() {
   150  			grpcServer.Stop()
   151  		}()
   152  
   153  		cases := []struct {
   154  			name string
   155  			conf *cli.CliConfig
   156  			err  error
   157  		}{
   158  			{
   159  				name: "success",
   160  				conf: parseFileContentToCliConfig(t, baseFile, func(c *PandoraConfigGRPC) {
   161  					c.Pools[0].Gun.ReflectMetadata = map[string]string{metadataKey: metadataValue}
   162  				}),
   163  			},
   164  			{
   165  				name: "no metadata",
   166  				conf: parseFileContentToCliConfig(t, baseFile, nil),
   167  				err:  wrongMDValuesLengthError,
   168  			},
   169  			{
   170  				name: "wrong metadata value",
   171  				conf: parseFileContentToCliConfig(t, baseFile, func(c *PandoraConfigGRPC) {
   172  					c.Pools[0].Gun.ReflectMetadata = map[string]string{metadataKey: "wrong-value"}
   173  				}),
   174  				err: wrongMDValueError,
   175  			},
   176  		}
   177  
   178  		for _, cc := range cases {
   179  			t.Run(cc.name, func(t *testing.T) {
   180  				require.Equal(t, 1, len(cc.conf.Engine.Pools))
   181  				aggr := &aggregator{}
   182  				cc.conf.Engine.Pools[0].Aggregator = aggr
   183  
   184  				pandora := engine.New(pandoraLogger, pandoraMetrics, cc.conf.Engine)
   185  				err = pandora.Run(context.Background())
   186  
   187  				if cc.err == nil {
   188  					require.NoError(t, err)
   189  				} else {
   190  					require.Error(t, err)
   191  					require.Contains(t, err.Error(), cc.err.Error())
   192  				}
   193  			})
   194  		}
   195  	})
   196  }
   197  
   198  func TestGrpcGunSuite(t *testing.T) {
   199  	suite.Run(t, new(GrpcGunSuite))
   200  }
   201  
   202  type GrpcGunSuite struct {
   203  	suite.Suite
   204  	fs      afero.Fs
   205  	log     *zap.Logger
   206  	metrics engine.Metrics
   207  }
   208  
   209  func (s *GrpcGunSuite) SetupSuite() {
   210  	s.fs = afero.NewOsFs()
   211  	testOnce.Do(importDependencies(s.fs))
   212  
   213  	s.log = testutil.NewNullLogger()
   214  	s.metrics = engine.NewMetrics("grpc_suite")
   215  }
   216  
   217  func (s *GrpcGunSuite) Test_Run() {
   218  	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
   219  	baseFile, err := os.ReadFile("testdata/grpc/base.yaml")
   220  	s.Require().NoError(err)
   221  
   222  	tests := []struct {
   223  		name      string
   224  		overwrite func(c *PandoraConfigGRPC)
   225  		wantCnt   int64
   226  	}{
   227  		{
   228  			name:    "default testdata/grpc/base.yaml",
   229  			wantCnt: 8,
   230  		},
   231  		{
   232  			name: "add pool-size testdata/grpc/base.yaml",
   233  			overwrite: func(c *PandoraConfigGRPC) {
   234  				c.Pools[0].Gun.SharedClient.Enabled = true
   235  				c.Pools[0].Gun.SharedClient.ClientNumber = 2
   236  			},
   237  			wantCnt: 8,
   238  		},
   239  	}
   240  	for _, tt := range tests {
   241  		s.Run(tt.name, func() {
   242  
   243  			grpcServer := grpc.NewServer()
   244  			srv := server.NewServer(logger, time.Now().UnixNano())
   245  			server.RegisterTargetServiceServer(grpcServer, srv)
   246  			reflection.Register(grpcServer)
   247  			l, err := net.Listen("tcp", ":18888")
   248  			s.Require().NoError(err)
   249  			go func() {
   250  				err = grpcServer.Serve(l)
   251  				s.Require().NoError(err)
   252  			}()
   253  			defer func() {
   254  				grpcServer.Stop()
   255  			}()
   256  
   257  			conf := parseFileContentToCliConfig(s.T(), baseFile, tt.overwrite)
   258  
   259  			aggr := &aggregator{}
   260  			conf.Engine.Pools[0].Aggregator = aggr
   261  			pandora := engine.New(s.log, s.metrics, conf.Engine)
   262  
   263  			err = pandora.Run(context.Background())
   264  			s.Require().NoError(err)
   265  			stats, err := srv.Stats(context.Background(), nil)
   266  			s.Require().NoError(err)
   267  			s.Require().Equal(tt.wantCnt, stats.Hello)
   268  		})
   269  	}
   270  }
   271  
   272  func parseFileContentToCliConfig(t *testing.T, baseFile []byte, overwrite func(c *PandoraConfigGRPC)) *cli.CliConfig {
   273  	cfg := PandoraConfigGRPC{}
   274  	err := yaml.Unmarshal(baseFile, &cfg)
   275  	require.NoError(t, err)
   276  	if overwrite != nil {
   277  		overwrite(&cfg)
   278  	}
   279  	b, err := yaml.Marshal(cfg)
   280  	require.NoError(t, err)
   281  	mapCfg := map[string]any{}
   282  	err = yaml.Unmarshal(b, &mapCfg)
   283  	require.NoError(t, err)
   284  
   285  	return decodeConfig(t, mapCfg)
   286  }
   287  
   288  func MetadataServerInterceptor(metadataChecker func(ctx context.Context) (context.Context, error)) grpc.UnaryServerInterceptor {
   289  	return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
   290  		ctx, err = metadataChecker(ctx)
   291  		if err != nil {
   292  			return nil, fmt.Errorf("metadata checker: %w", err)
   293  		}
   294  		return handler(ctx, req)
   295  	}
   296  }
   297  
   298  func MetadataServerStreamInterceptor(metadataChecker func(ctx context.Context) (context.Context, error)) grpc.StreamServerInterceptor {
   299  	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
   300  		ctx := ss.Context()
   301  		_, err = metadataChecker(ctx)
   302  		if err != nil {
   303  			return fmt.Errorf("metadata checker: %w", err)
   304  		}
   305  		return handler(srv, ss)
   306  	}
   307  }