github.com/kubeshop/testkube@v1.17.23/pkg/logs/adapter/cloud_test.go (about) 1 package adapter 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "math" 8 "math/rand" 9 "net" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/pkg/errors" 15 "github.com/stretchr/testify/assert" 16 "google.golang.org/grpc" 17 "google.golang.org/grpc/codes" 18 "google.golang.org/grpc/credentials/insecure" 19 "google.golang.org/grpc/metadata" 20 "google.golang.org/grpc/status" 21 22 "github.com/kubeshop/testkube/pkg/agent" 23 "github.com/kubeshop/testkube/pkg/log" 24 "github.com/kubeshop/testkube/pkg/logs/events" 25 "github.com/kubeshop/testkube/pkg/logs/pb" 26 ) 27 28 func TestCloudAdapter(t *testing.T) { 29 30 t.Run("GRPC server receives log data", func(t *testing.T) { 31 // given grpc test server 32 ts := NewTestServer().WithRandomPort() 33 go ts.Run() 34 35 ctx := context.Background() 36 id := "id1" 37 38 // and connection 39 grpcConn, err := agent.NewGRPCConnection(ctx, true, true, ts.Url, "", "", "", log.DefaultLogger) 40 assert.NoError(t, err) 41 defer grpcConn.Close() 42 43 // and log stream client 44 grpcClient := pb.NewCloudLogsServiceClient(grpcConn) 45 a := NewCloudAdapter(grpcClient, "APIKEY") 46 47 // when stream is initialized 48 err = a.Init(ctx, id) 49 assert.NoError(t, err) 50 // and data is sent to it 51 err = a.Notify(ctx, id, *events.NewLog("log1")) 52 assert.NoError(t, err) 53 err = a.Notify(ctx, id, *events.NewLog("log2")) 54 assert.NoError(t, err) 55 err = a.Notify(ctx, id, *events.NewLog("log3")) 56 assert.NoError(t, err) 57 err = a.Notify(ctx, id, *events.NewLog("log4")) 58 assert.NoError(t, err) 59 // and stream is stopped after sending logs to it 60 err = a.Stop(ctx, id) 61 assert.NoError(t, err) 62 63 // cooldown 64 time.Sleep(time.Millisecond * 100) 65 66 // then all messahes should be delivered to the GRPC server 67 ts.AssertMessagesProcessed(t, id, 4) 68 }) 69 70 t.Run("cleaning GRPC connections in adapter on Stop", func(t *testing.T) { 71 // given new test server 72 ts := NewTestServer().WithRandomPort() 73 go ts.Run() 74 75 ctx := context.Background() 76 id1 := "id1" 77 id2 := "id2" 78 id3 := "id3" 79 80 // and connection 81 grpcConn, err := agent.NewGRPCConnection(ctx, true, true, ts.Url, "", "", "", log.DefaultLogger) 82 assert.NoError(t, err) 83 defer grpcConn.Close() 84 grpcClient := pb.NewCloudLogsServiceClient(grpcConn) 85 a := NewCloudAdapter(grpcClient, "APIKEY") 86 87 // when 3 streams are initialized, data is sent, and then stopped 88 err = a.Init(ctx, id1) 89 assert.NoError(t, err) 90 err = a.Notify(ctx, id1, *events.NewLog("log1")) 91 assert.NoError(t, err) 92 err = a.Stop(ctx, id1) 93 assert.NoError(t, err) 94 95 err = a.Init(ctx, id2) 96 assert.NoError(t, err) 97 err = a.Notify(ctx, id2, *events.NewLog("log2")) 98 assert.NoError(t, err) 99 err = a.Stop(ctx, id2) 100 assert.NoError(t, err) 101 102 err = a.Init(ctx, id3) 103 assert.NoError(t, err) 104 err = a.Notify(ctx, id3, *events.NewLog("log3")) 105 assert.NoError(t, err) 106 err = a.Stop(ctx, id3) 107 assert.NoError(t, err) 108 109 // cooldown 110 time.Sleep(time.Millisecond * 100) 111 112 // then messages should be delivered 113 ts.AssertMessagesProcessed(t, id1, 1) 114 ts.AssertMessagesProcessed(t, id2, 1) 115 ts.AssertMessagesProcessed(t, id3, 1) 116 117 // and no stream are registered anymore in cloud adapter 118 assertNoStreams(t, a) 119 }) 120 121 t.Run("Send and receive a lot of messages", func(t *testing.T) { 122 // given test server 123 ts := NewTestServer().WithRandomPort() 124 go ts.Run() 125 126 ctx := context.Background() 127 id := "id1M" 128 129 // and grpc connetion to the server 130 grpcConn, err := agent.NewGRPCConnection(ctx, true, true, ts.Url, "", "", "", log.DefaultLogger) 131 assert.NoError(t, err) 132 defer grpcConn.Close() 133 134 // and logs stream client 135 grpcClient := pb.NewCloudLogsServiceClient(grpcConn) 136 a := NewCloudAdapter(grpcClient, "APIKEY") 137 138 // when streams are initialized 139 err = a.Init(ctx, id) 140 assert.NoError(t, err) 141 142 messageCount := 10_000 143 for i := 0; i < messageCount; i++ { 144 // and data is sent 145 err = a.Notify(ctx, id, *events.NewLog("log1")) 146 assert.NoError(t, err) 147 } 148 149 // cooldown 150 time.Sleep(time.Millisecond * 100) 151 152 // then messages should be delivered to GRPC server 153 ts.AssertMessagesProcessed(t, id, messageCount) 154 }) 155 156 t.Run("Send to a lot of streams in parallel", func(t *testing.T) { 157 // given test server 158 ts := NewTestServer().WithRandomPort() 159 go ts.Run() 160 161 ctx := context.Background() 162 163 // and grpc connetion to the server 164 grpcConn, err := agent.NewGRPCConnection(ctx, true, true, ts.Url, "", "", "", log.DefaultLogger) 165 assert.NoError(t, err) 166 defer grpcConn.Close() 167 168 // and logs stream client 169 grpcClient := pb.NewCloudLogsServiceClient(grpcConn) 170 a := NewCloudAdapter(grpcClient, "APIKEY") 171 172 streamsCount := 100 173 messageCount := 1_000 174 175 // when streams are initialized 176 var wg sync.WaitGroup 177 wg.Add(streamsCount) 178 for j := 0; j < streamsCount; j++ { 179 err = a.Init(ctx, fmt.Sprintf("id%d", j)) 180 assert.NoError(t, err) 181 182 go func(j int) { 183 defer wg.Done() 184 for i := 0; i < messageCount; i++ { 185 // and when data are sent 186 err = a.Notify(ctx, fmt.Sprintf("id%d", j), *events.NewLog("log1")) 187 assert.NoError(t, err) 188 } 189 }(j) 190 } 191 192 wg.Wait() 193 194 // and wait for cooldown 195 time.Sleep(time.Millisecond * 100) 196 197 // then each stream should receive valid data amount 198 for j := 0; j < streamsCount; j++ { 199 ts.AssertMessagesProcessed(t, fmt.Sprintf("id%d", j), messageCount) 200 } 201 }) 202 203 } 204 205 func assertNoStreams(t *testing.T, a *CloudAdapter) { 206 t.Helper() 207 // no stream are registered anymore 208 count := 0 209 a.streams.Range(func(key, value any) bool { 210 count++ 211 return true 212 }) 213 assert.Equal(t, count, 0) 214 } 215 216 // Cloud Logs server mock 217 func NewTestServer() *TestServer { 218 return &TestServer{ 219 Received: make(map[string][]*pb.Log), 220 } 221 } 222 223 type TestServer struct { 224 Url string 225 pb.UnimplementedCloudLogsServiceServer 226 Received map[string][]*pb.Log 227 lock sync.Mutex 228 } 229 230 func getVal(ctx context.Context, key string) (string, error) { 231 md, ok := metadata.FromIncomingContext(ctx) 232 if !ok { 233 return "", status.Error(codes.Unauthenticated, "api-key header is missing") 234 } 235 apiKeyMeta := md.Get(key) 236 if len(apiKeyMeta) != 1 { 237 return "", status.Error(codes.Unauthenticated, "api-key header is empty") 238 } 239 if apiKeyMeta[0] == "" { 240 return "", status.Error(codes.Unauthenticated, "api-key header value is empty") 241 } 242 243 return apiKeyMeta[0], nil 244 } 245 246 func (s *TestServer) Stream(stream pb.CloudLogsService_StreamServer) error { 247 ctx := stream.Context() 248 v, err := getVal(ctx, "execution-id") 249 if err != nil { 250 return err 251 } 252 id := v 253 254 s.lock.Lock() 255 s.Received[id] = []*pb.Log{} 256 s.lock.Unlock() 257 258 for { 259 in, err := stream.Recv() 260 if err != nil { 261 if err == io.EOF { 262 err := stream.SendAndClose(&pb.StreamResponse{Message: "completed"}) 263 if err != nil { 264 return status.Error(codes.Internal, "can't close stream: "+err.Error()) 265 } 266 return nil 267 } 268 return status.Error(codes.Internal, "can't receive stream: "+err.Error()) 269 } 270 271 s.lock.Lock() 272 s.Received[id] = append(s.Received[id], in) 273 s.lock.Unlock() 274 } 275 } 276 277 func (s *TestServer) WithRandomPort() *TestServer { 278 port := rand.Intn(1000) + 18000 279 s.Url = fmt.Sprintf("127.0.0.1:%d", port) 280 return s 281 } 282 283 func (s *TestServer) Run() (err error) { 284 lis, err := net.Listen("tcp", s.Url) 285 if err != nil { 286 return errors.Errorf("net listen: %v", err) 287 } 288 289 var opts []grpc.ServerOption 290 creds := insecure.NewCredentials() 291 opts = append(opts, grpc.Creds(creds), grpc.MaxRecvMsgSize(math.MaxInt32)) 292 293 // register server logs 294 srv := grpc.NewServer(opts...) 295 srv.RegisterService(&pb.CloudLogsService_ServiceDesc, s) 296 srv.Serve(lis) 297 298 if err != nil { 299 return errors.Wrap(err, "grpc server error") 300 } 301 return nil 302 } 303 304 func (s *TestServer) AssertMessagesProcessed(t *testing.T, id string, messageCount int) { 305 var received int 306 307 for i := 0; i < 100; i++ { 308 s.lock.Lock() 309 received = len(s.Received[id]) 310 s.lock.Unlock() 311 312 if received == messageCount { 313 return 314 } 315 time.Sleep(time.Millisecond * 10) 316 } 317 318 assert.Equal(t, messageCount, received) 319 }