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 }