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  }