github.com/kubeshop/testkube@v1.17.23/pkg/logs/events_test.go (about)

     1  package logs
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/nats-io/nats.go"
    11  	"github.com/nats-io/nats.go/jetstream"
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    15  	"github.com/kubeshop/testkube/pkg/event"
    16  	"github.com/kubeshop/testkube/pkg/event/bus"
    17  	"github.com/kubeshop/testkube/pkg/logs/adapter"
    18  	"github.com/kubeshop/testkube/pkg/logs/client"
    19  	"github.com/kubeshop/testkube/pkg/logs/events"
    20  	"github.com/kubeshop/testkube/pkg/logs/state"
    21  )
    22  
    23  var waitTime = time.Second
    24  
    25  func TestLogs_EventsFlow(t *testing.T) {
    26  	t.Parallel()
    27  
    28  	t.Run("should remove all adapters when stop event handled", func(t *testing.T) {
    29  		// given context with 1s deadline
    30  		ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
    31  		defer cancel()
    32  
    33  		// and NATS test server with connection
    34  		ns, nc := bus.TestServerWithConnection()
    35  		defer ns.Shutdown()
    36  
    37  		id := "stop-test"
    38  
    39  		// and jetstream configured
    40  		js, err := jetstream.New(nc)
    41  		assert.NoError(t, err)
    42  
    43  		// and KV store
    44  		kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "state-test"})
    45  		assert.NoError(t, err)
    46  		assert.NotNil(t, kv)
    47  
    48  		// and logs state manager
    49  		state := state.NewState(kv)
    50  
    51  		logsStream, err := client.NewNatsLogStream(nc)
    52  		assert.NoError(t, err)
    53  
    54  		// and initialized log service
    55  		log := NewLogsService(nc, js, state, logsStream).
    56  			WithRandomPort()
    57  
    58  		// given example adapters
    59  		a := NewMockAdapter("aaa")
    60  		b := NewMockAdapter("bbb")
    61  
    62  		// with 4 adapters (the same adapter is added 4 times so it'll receive 4 times more messages)
    63  		log.AddAdapter(a)
    64  		log.AddAdapter(b)
    65  
    66  		// and log service running
    67  		go func() {
    68  			log.Run(ctx)
    69  		}()
    70  
    71  		// and ready to get messages
    72  		<-log.Ready
    73  
    74  		// and logs stream client
    75  		stream, err := client.NewNatsLogStream(nc)
    76  		assert.NoError(t, err)
    77  
    78  		// and initialized log stream for given ID
    79  		meta, err := stream.Init(ctx, id)
    80  		assert.NotEmpty(t, meta.Name)
    81  		assert.NoError(t, err)
    82  
    83  		// when start event triggered
    84  		_, err = stream.Start(ctx, id)
    85  		assert.NoError(t, err)
    86  
    87  		// and when data pushed to the log stream
    88  		stream.Push(ctx, id, events.NewLog("hello 1"))
    89  		stream.Push(ctx, id, events.NewLog("hello 2"))
    90  
    91  		// and stop event triggered
    92  		_, err = stream.Stop(ctx, id)
    93  		assert.NoError(t, err)
    94  
    95  		// cooldown stop time
    96  		time.Sleep(waitTime)
    97  
    98  		// then all adapters should be gracefully stopped
    99  		assert.Equal(t, 0, log.GetConsumersStats(ctx).Count)
   100  	})
   101  
   102  	t.Run("should start and stop on test event", func(t *testing.T) {
   103  		// given context with 1s deadline
   104  		ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
   105  		defer cancel()
   106  
   107  		// and NATS test server with connection
   108  		ns, nc := bus.TestServerWithConnection()
   109  		defer ns.Shutdown()
   110  
   111  		id := "id1"
   112  
   113  		// and jetstream configured
   114  		js, err := jetstream.New(nc)
   115  		assert.NoError(t, err)
   116  
   117  		// and KV store
   118  		kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "start-stop-on-test"})
   119  		assert.NoError(t, err)
   120  		assert.NotNil(t, kv)
   121  
   122  		// and logs state manager
   123  		state := state.NewState(kv)
   124  
   125  		logsStream, err := client.NewNatsLogStream(nc)
   126  		assert.NoError(t, err)
   127  
   128  		// and initialized log service
   129  		log := NewLogsService(nc, js, state, logsStream).
   130  			WithRandomPort()
   131  
   132  		// given example adapter
   133  		a := NewMockAdapter()
   134  
   135  		messagesCount := 10000
   136  
   137  		// with 4 adapters (the same adapter is added 4 times so it'll receive 4 times more messages)
   138  		log.AddAdapter(a)
   139  
   140  		// and log service running
   141  		go func() {
   142  			log.Run(ctx)
   143  		}()
   144  
   145  		// and test event emitter
   146  		ec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
   147  		assert.NoError(t, err)
   148  		eventBus := bus.NewNATSBus(ec)
   149  		emitter := event.NewEmitter(eventBus, "test-cluster", map[string]string{})
   150  
   151  		// and stream client
   152  		stream, err := client.NewNatsLogStream(nc)
   153  		assert.NoError(t, err)
   154  
   155  		// and initialized log stream for given ID
   156  		meta, err := stream.Init(ctx, id)
   157  		assert.NotEmpty(t, meta.Name)
   158  		assert.NoError(t, err)
   159  
   160  		// and ready to get messages
   161  		<-log.Ready
   162  
   163  		// when start event triggered
   164  		emitter.Notify(testkube.NewEventStartTest(&testkube.Execution{Id: "id1"}))
   165  
   166  		for i := 0; i < messagesCount; i++ {
   167  			// and when data pushed to the log stream
   168  			err = stream.Push(ctx, id, events.NewLog("hello"))
   169  			assert.NoError(t, err)
   170  		}
   171  
   172  		// and wait for message to be propagated
   173  		emitter.Notify(testkube.NewEventEndTestFailed(&testkube.Execution{Id: "id1"}))
   174  
   175  		time.Sleep(waitTime)
   176  
   177  		assertMessagesCount(t, a, messagesCount)
   178  
   179  	})
   180  
   181  	t.Run("should react on new message and pass data to adapter", func(t *testing.T) {
   182  		// given context with 1s deadline
   183  		ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
   184  		defer cancel()
   185  
   186  		// and NATS test server with connection
   187  		ns, nc := bus.TestServerWithConnection()
   188  		defer ns.Shutdown()
   189  
   190  		id := "messages-test"
   191  
   192  		// and jetstream configured
   193  		js, err := jetstream.New(nc)
   194  		assert.NoError(t, err)
   195  
   196  		// and KV store
   197  		kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "state-test"})
   198  		assert.NoError(t, err)
   199  		assert.NotNil(t, kv)
   200  
   201  		// and logs state manager
   202  		state := state.NewState(kv)
   203  
   204  		logsStream, err := client.NewNatsLogStream(nc)
   205  		assert.NoError(t, err)
   206  
   207  		// and initialized log service
   208  		log := NewLogsService(nc, js, state, logsStream).
   209  			WithRandomPort()
   210  
   211  		// given example adapter
   212  		a1 := NewMockAdapter()
   213  		a2 := NewMockAdapter()
   214  		a3 := NewMockAdapter()
   215  		a4 := NewMockAdapter()
   216  
   217  		messagesCount := 1000
   218  
   219  		// with 4 adapters (the same adapter is added 4 times so it'll receive 4 times more messages)
   220  		log.AddAdapter(a1)
   221  		log.AddAdapter(a2)
   222  		log.AddAdapter(a3)
   223  		log.AddAdapter(a4)
   224  
   225  		// and log service running
   226  		go func() {
   227  			log.Run(ctx)
   228  		}()
   229  
   230  		// and ready to get messages
   231  		<-log.Ready
   232  
   233  		// and stream client
   234  		stream, err := client.NewNatsLogStream(nc)
   235  		assert.NoError(t, err)
   236  
   237  		// and initialized log stream for given ID
   238  		meta, err := stream.Init(ctx, id)
   239  		assert.NotEmpty(t, meta.Name)
   240  		assert.NoError(t, err)
   241  
   242  		// when start event triggered
   243  		_, err = stream.Start(ctx, id)
   244  		assert.NoError(t, err)
   245  
   246  		for i := 0; i < messagesCount; i++ {
   247  			// and when data pushed to the log stream
   248  			err = stream.Push(ctx, id, events.NewLog("hello"))
   249  			assert.NoError(t, err)
   250  		}
   251  
   252  		// and wait for message to be propagated
   253  		_, err = stream.Stop(ctx, id)
   254  		assert.NoError(t, err)
   255  
   256  		// cool down
   257  		time.Sleep(waitTime)
   258  
   259  		// then each adapter should receive messages
   260  		assertMessagesCount(t, a1, messagesCount)
   261  		assertMessagesCount(t, a2, messagesCount)
   262  		assertMessagesCount(t, a3, messagesCount)
   263  		assertMessagesCount(t, a4, messagesCount)
   264  
   265  	})
   266  
   267  	t.Run("can get stats about consumers in given pod", func(t *testing.T) {
   268  		// given context with 1s deadline
   269  		ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
   270  		defer cancel()
   271  
   272  		// and NATS test server with connection
   273  		ns, nc := bus.TestServerWithConnection()
   274  		defer ns.Shutdown()
   275  
   276  		// and jetstream configured
   277  		js, err := jetstream.New(nc)
   278  		assert.NoError(t, err)
   279  
   280  		id := "executionid1"
   281  
   282  		// and KV store
   283  		kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: "state-test"})
   284  		assert.NoError(t, err)
   285  		assert.NotNil(t, kv)
   286  
   287  		// and logs state manager
   288  		state := state.NewState(kv)
   289  
   290  		logsStream, err := client.NewNatsLogStream(nc)
   291  		assert.NoError(t, err)
   292  
   293  		// and initialized log service
   294  		log := NewLogsService(nc, js, state, logsStream).
   295  			WithRandomPort()
   296  
   297  		// given example adapters
   298  		a := NewMockAdapter("aaa")
   299  		b := NewMockAdapter("bbb")
   300  
   301  		// with 4 adapters (the same adapter is added 4 times so it'll receive 4 times more messages)
   302  		log.AddAdapter(a)
   303  		log.AddAdapter(b)
   304  
   305  		// and log service running
   306  		go func() {
   307  			log.Run(ctx)
   308  		}()
   309  
   310  		// and ready to get messages
   311  		<-log.Ready
   312  
   313  		// and logs stream client
   314  		stream, err := client.NewNatsLogStream(nc)
   315  		assert.NoError(t, err)
   316  
   317  		// and initialized log stream for given ID
   318  		meta, err := stream.Init(ctx, id)
   319  		assert.NotEmpty(t, meta.Name)
   320  		assert.NoError(t, err)
   321  
   322  		// when start event triggered
   323  		_, err = stream.Start(ctx, id)
   324  		assert.NoError(t, err)
   325  
   326  		// then we should have 2 consumers
   327  		stats := log.GetConsumersStats(ctx)
   328  		assert.Equal(t, 2, stats.Count)
   329  
   330  		stream.Push(ctx, id, events.NewLog("hello 1"))
   331  		stream.Push(ctx, id, events.NewLog("hello 1"))
   332  		stream.Push(ctx, id, events.NewLog("hello 1"))
   333  
   334  		// when stop event triggered
   335  		r, err := stream.Stop(ctx, id)
   336  		assert.NoError(t, err)
   337  		assert.False(t, r.Error)
   338  		assert.Equal(t, "stop-queued", string(r.Message))
   339  
   340  		// there will be wait for mess
   341  		time.Sleep(waitTime)
   342  
   343  		// then all adapters should be gracefully stopped
   344  		assert.Equal(t, 0, log.GetConsumersStats(ctx).Count)
   345  	})
   346  
   347  }
   348  
   349  // Mock adapter
   350  var _ adapter.Adapter = &MockAdapter{}
   351  
   352  // NewMockAdapter creates new mocked adapter to check amount of messages passed to it
   353  func NewMockAdapter(name ...string) *MockAdapter {
   354  	n := "default"
   355  	if len(name) > 0 {
   356  		n = name[0]
   357  	}
   358  
   359  	return &MockAdapter{
   360  		Messages: []events.Log{},
   361  		name:     n,
   362  	}
   363  }
   364  
   365  type MockAdapter struct {
   366  	lock     sync.Mutex
   367  	Messages []events.Log
   368  	name     string
   369  }
   370  
   371  func (s *MockAdapter) Init(ctx context.Context, id string) error {
   372  	return nil
   373  }
   374  
   375  func (s *MockAdapter) Notify(ctx context.Context, id string, e events.Log) error {
   376  	// don't count finished logs
   377  	if events.IsFinished(&e) {
   378  		return nil
   379  	}
   380  	s.lock.Lock()
   381  	defer s.lock.Unlock()
   382  
   383  	e.Metadata = map[string]string{"id": id}
   384  	s.Messages = append(s.Messages, e)
   385  	return nil
   386  }
   387  
   388  func (s *MockAdapter) Stop(ctx context.Context, id string) error {
   389  	fmt.Printf("stopping %s \n", id)
   390  	return nil
   391  }
   392  
   393  func (s *MockAdapter) Name() string {
   394  	return s.name
   395  }
   396  
   397  func assertMessagesCount(t *testing.T, a *MockAdapter, expectedCount int) {
   398  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   399  	defer cancel()
   400  	ticker := time.NewTicker(100 * time.Millisecond)
   401  	for {
   402  
   403  		select {
   404  		case <-ctx.Done():
   405  			t.Errorf("timeout waiting for messages count %d (expected:%d)", len(a.Messages), expectedCount)
   406  			t.Fail()
   407  			return
   408  		case <-ticker.C:
   409  			if len(a.Messages) == expectedCount {
   410  				return
   411  			}
   412  		}
   413  	}
   414  }