github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/cloudwatch/cloudwatch_test.go (about)

     1  package cloudwatchacquisition
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"os"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/crowdsecurity/go-cs-lib/cstest"
    13  
    14  	"github.com/aws/aws-sdk-go/aws"
    15  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    16  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    17  	"github.com/crowdsecurity/crowdsec/pkg/types"
    18  	log "github.com/sirupsen/logrus"
    19  	"github.com/stretchr/testify/require"
    20  	"gopkg.in/tomb.v2"
    21  )
    22  
    23  /*
    24   test plan :
    25  	- start on bad group/bad stream
    26  	- start on good settings (oneshot) -> check expected messages
    27  	- start on good settings (stream) -> check expected messages within given time
    28  	- check shutdown/restart
    29  */
    30  
    31  func deleteAllLogGroups(t *testing.T, cw *CloudwatchSource) {
    32  	input := &cloudwatchlogs.DescribeLogGroupsInput{}
    33  	result, err := cw.cwClient.DescribeLogGroups(input)
    34  	require.NoError(t, err)
    35  	for _, group := range result.LogGroups {
    36  		_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
    37  			LogGroupName: group.LogGroupName,
    38  		})
    39  		require.NoError(t, err)
    40  	}
    41  }
    42  
    43  func checkForLocalStackAvailability() error {
    44  	v := os.Getenv("AWS_ENDPOINT_FORCE")
    45  	if v == "" {
    46  		return fmt.Errorf("missing aws endpoint for tests : AWS_ENDPOINT_FORCE")
    47  	}
    48  
    49  	v = strings.TrimPrefix(v, "http://")
    50  
    51  	_, err := net.Dial("tcp", v)
    52  	if err != nil {
    53  		return fmt.Errorf("while dialing %s : %s : aws endpoint isn't available", v, err)
    54  	}
    55  
    56  	return nil
    57  }
    58  
    59  func TestMain(m *testing.M) {
    60  	if runtime.GOOS == "windows" {
    61  		os.Exit(0)
    62  	}
    63  	if err := checkForLocalStackAvailability(); err != nil {
    64  		log.Fatalf("local stack error : %s", err)
    65  	}
    66  	def_PollNewStreamInterval = 1 * time.Second
    67  	def_PollStreamInterval = 1 * time.Second
    68  	def_StreamReadTimeout = 10 * time.Second
    69  	def_MaxStreamAge = 5 * time.Second
    70  	def_PollDeadStreamInterval = 5 * time.Second
    71  	os.Exit(m.Run())
    72  }
    73  
    74  func TestWatchLogGroupForStreams(t *testing.T) {
    75  	if runtime.GOOS == "windows" {
    76  		t.Skip("Skipping test on windows")
    77  	}
    78  	log.SetLevel(log.DebugLevel)
    79  	tests := []struct {
    80  		config              []byte
    81  		expectedCfgErr      string
    82  		expectedStartErr    string
    83  		name                string
    84  		setup               func(*testing.T, *CloudwatchSource)
    85  		run                 func(*testing.T, *CloudwatchSource)
    86  		teardown            func(*testing.T, *CloudwatchSource)
    87  		expectedResLen      int
    88  		expectedResMessages []string
    89  	}{
    90  		// require a group name that doesn't exist
    91  		{
    92  			name: "group_does_not_exists",
    93  			config: []byte(`
    94  source: cloudwatch
    95  aws_region: us-east-1
    96  labels:
    97    type: test_source
    98  group_name: b
    99  stream_name: test_stream`),
   100  			expectedStartErr: "The specified log group does not exist",
   101  			setup: func(t *testing.T, cw *CloudwatchSource) {
   102  				deleteAllLogGroups(t, cw)
   103  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   104  					LogGroupName: aws.String("test_group_not_used_1"),
   105  				})
   106  				require.NoError(t, err)
   107  			},
   108  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   109  				_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   110  					LogGroupName: aws.String("test_group_not_used_1"),
   111  				})
   112  				require.NoError(t, err)
   113  			},
   114  		},
   115  		// test stream mismatch
   116  		{
   117  			name: "group_exists_bad_stream_name",
   118  			config: []byte(`
   119  source: cloudwatch
   120  aws_region: us-east-1
   121  labels:
   122    type: test_source
   123  group_name: test_group1
   124  stream_name: test_stream_bad`),
   125  			setup: func(t *testing.T, cw *CloudwatchSource) {
   126  				deleteAllLogGroups(t, cw)
   127  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   128  					LogGroupName: aws.String("test_group1"),
   129  				})
   130  				require.NoError(t, err)
   131  
   132  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   133  					LogGroupName:  aws.String("test_group1"),
   134  					LogStreamName: aws.String("test_stream"),
   135  				})
   136  				require.NoError(t, err)
   137  
   138  				// have a message before we start - won't be popped, but will trigger stream monitoring
   139  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   140  					LogGroupName:  aws.String("test_group1"),
   141  					LogStreamName: aws.String("test_stream"),
   142  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   143  						{
   144  							Message:   aws.String("test_message_1"),
   145  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   146  						},
   147  					},
   148  				})
   149  				require.NoError(t, err)
   150  			},
   151  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   152  				_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   153  					LogGroupName: aws.String("test_group1"),
   154  				})
   155  				require.NoError(t, err)
   156  			},
   157  			expectedResLen: 0,
   158  		},
   159  		// test stream mismatch
   160  		{
   161  			name: "group_exists_bad_stream_regexp",
   162  			config: []byte(`
   163  source: cloudwatch
   164  aws_region: us-east-1
   165  labels:
   166    type: test_source
   167  group_name: test_group1
   168  stream_regexp: test_bad[0-9]+`),
   169  			setup: func(t *testing.T, cw *CloudwatchSource) {
   170  				deleteAllLogGroups(t, cw)
   171  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   172  					LogGroupName: aws.String("test_group1"),
   173  				})
   174  				require.NoError(t, err)
   175  
   176  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   177  					LogGroupName:  aws.String("test_group1"),
   178  					LogStreamName: aws.String("test_stream"),
   179  				})
   180  				require.NoError(t, err)
   181  
   182  				// have a message before we start - won't be popped, but will trigger stream monitoring
   183  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   184  					LogGroupName:  aws.String("test_group1"),
   185  					LogStreamName: aws.String("test_stream"),
   186  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   187  						{
   188  							Message:   aws.String("test_message_1"),
   189  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   190  						},
   191  					},
   192  				})
   193  				require.NoError(t, err)
   194  			},
   195  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   196  				_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   197  					LogGroupName: aws.String("test_group1"),
   198  				})
   199  				require.NoError(t, err)
   200  			},
   201  			expectedResLen: 0,
   202  		},
   203  		// require a group name that does exist and contains a stream in which we are going to put events
   204  		{
   205  			name: "group_exists_stream_exists_has_events",
   206  			config: []byte(`
   207  source: cloudwatch
   208  aws_region: us-east-1
   209  labels:
   210    type: test_source
   211  group_name: test_log_group1
   212  log_level: trace
   213  stream_name: test_stream`),
   214  			// expectedStartErr: "The specified log group does not exist",
   215  			setup: func(t *testing.T, cw *CloudwatchSource) {
   216  				deleteAllLogGroups(t, cw)
   217  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   218  					LogGroupName: aws.String("test_log_group1"),
   219  				})
   220  				require.NoError(t, err)
   221  
   222  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   223  					LogGroupName:  aws.String("test_log_group1"),
   224  					LogStreamName: aws.String("test_stream"),
   225  				})
   226  				require.NoError(t, err)
   227  
   228  				// have a message before we start - won't be popped, but will trigger stream monitoring
   229  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   230  					LogGroupName:  aws.String("test_log_group1"),
   231  					LogStreamName: aws.String("test_stream"),
   232  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   233  						{
   234  							Message:   aws.String("test_message_1"),
   235  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   236  						},
   237  					},
   238  				})
   239  				require.NoError(t, err)
   240  			},
   241  			run: func(t *testing.T, cw *CloudwatchSource) {
   242  				// wait for new stream pickup + stream poll interval
   243  				time.Sleep(def_PollNewStreamInterval + (1 * time.Second))
   244  				time.Sleep(def_PollStreamInterval + (1 * time.Second))
   245  				_, err := cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   246  					LogGroupName:  aws.String("test_log_group1"),
   247  					LogStreamName: aws.String("test_stream"),
   248  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   249  						{
   250  							Message:   aws.String("test_message_4"),
   251  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   252  						},
   253  						// and add an event in the future that will be popped
   254  						{
   255  							Message:   aws.String("test_message_5"),
   256  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   257  						},
   258  					},
   259  				})
   260  				require.NoError(t, err)
   261  			},
   262  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   263  				_, err := cw.cwClient.DeleteLogStream(&cloudwatchlogs.DeleteLogStreamInput{
   264  					LogGroupName:  aws.String("test_log_group1"),
   265  					LogStreamName: aws.String("test_stream"),
   266  				})
   267  				require.NoError(t, err)
   268  
   269  				_, err = cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   270  					LogGroupName: aws.String("test_log_group1"),
   271  				})
   272  				require.NoError(t, err)
   273  			},
   274  			expectedResLen:      3,
   275  			expectedResMessages: []string{"test_message_1", "test_message_4", "test_message_5"},
   276  		},
   277  		// have a stream generate events, reach time-out and gets polled again
   278  		{
   279  			name: "group_exists_stream_exists_has_events+timeout",
   280  			config: []byte(`
   281  source: cloudwatch
   282  aws_region: us-east-1
   283  labels:
   284    type: test_source
   285  group_name: test_log_group1
   286  log_level: trace
   287  stream_name: test_stream`),
   288  			// expectedStartErr: "The specified log group does not exist",
   289  			setup: func(t *testing.T, cw *CloudwatchSource) {
   290  				deleteAllLogGroups(t, cw)
   291  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   292  					LogGroupName: aws.String("test_log_group1"),
   293  				})
   294  				require.NoError(t, err)
   295  
   296  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   297  					LogGroupName:  aws.String("test_log_group1"),
   298  					LogStreamName: aws.String("test_stream"),
   299  				})
   300  				require.NoError(t, err)
   301  
   302  				// have a message before we start - won't be popped, but will trigger stream monitoring
   303  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   304  					LogGroupName:  aws.String("test_log_group1"),
   305  					LogStreamName: aws.String("test_stream"),
   306  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   307  						{
   308  							Message:   aws.String("test_message_1"),
   309  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   310  						},
   311  					},
   312  				})
   313  				require.NoError(t, err)
   314  			},
   315  			run: func(t *testing.T, cw *CloudwatchSource) {
   316  				// wait for new stream pickup + stream poll interval
   317  				time.Sleep(def_PollNewStreamInterval + (1 * time.Second))
   318  				time.Sleep(def_PollStreamInterval + (1 * time.Second))
   319  				// send some events
   320  				_, err := cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   321  					LogGroupName:  aws.String("test_log_group1"),
   322  					LogStreamName: aws.String("test_stream"),
   323  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   324  						{
   325  							Message:   aws.String("test_message_41"),
   326  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   327  						},
   328  					},
   329  				})
   330  				require.NoError(t, err)
   331  				// wait for the stream to time-out
   332  				time.Sleep(def_StreamReadTimeout + (1 * time.Second))
   333  				// and send events again
   334  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   335  					LogGroupName:  aws.String("test_log_group1"),
   336  					LogStreamName: aws.String("test_stream"),
   337  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   338  						{
   339  							Message:   aws.String("test_message_51"),
   340  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   341  						},
   342  					},
   343  				})
   344  				require.NoError(t, err)
   345  				// wait for new stream pickup + stream poll interval
   346  				time.Sleep(def_PollNewStreamInterval + (1 * time.Second))
   347  				time.Sleep(def_PollStreamInterval + (1 * time.Second))
   348  			},
   349  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   350  				_, err := cw.cwClient.DeleteLogStream(&cloudwatchlogs.DeleteLogStreamInput{
   351  					LogGroupName:  aws.String("test_log_group1"),
   352  					LogStreamName: aws.String("test_stream"),
   353  				})
   354  				require.NoError(t, err)
   355  
   356  				_, err = cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   357  					LogGroupName: aws.String("test_log_group1"),
   358  				})
   359  				require.NoError(t, err)
   360  			},
   361  			expectedResLen:      3,
   362  			expectedResMessages: []string{"test_message_1", "test_message_41", "test_message_51"},
   363  		},
   364  		// have a stream generate events, reach time-out and dead body collection
   365  		{
   366  			name: "group_exists_stream_exists_has_events+timeout+GC",
   367  			config: []byte(`
   368  source: cloudwatch
   369  aws_region: us-east-1
   370  labels:
   371    type: test_source
   372  group_name: test_log_group1
   373  log_level: trace
   374  stream_name: test_stream`),
   375  			// expectedStartErr: "The specified log group does not exist",
   376  			setup: func(t *testing.T, cw *CloudwatchSource) {
   377  				deleteAllLogGroups(t, cw)
   378  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   379  					LogGroupName: aws.String("test_log_group1"),
   380  				})
   381  				require.NoError(t, err)
   382  
   383  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   384  					LogGroupName:  aws.String("test_log_group1"),
   385  					LogStreamName: aws.String("test_stream"),
   386  				})
   387  				require.NoError(t, err)
   388  
   389  				// have a message before we start - won't be popped, but will trigger stream monitoring
   390  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   391  					LogGroupName:  aws.String("test_log_group1"),
   392  					LogStreamName: aws.String("test_stream"),
   393  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   394  						{
   395  							Message:   aws.String("test_message_1"),
   396  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   397  						},
   398  					},
   399  				})
   400  				require.NoError(t, err)
   401  			},
   402  			run: func(t *testing.T, cw *CloudwatchSource) {
   403  				// wait for new stream pickup + stream poll interval
   404  				time.Sleep(def_PollNewStreamInterval + (1 * time.Second))
   405  				time.Sleep(def_PollStreamInterval + (1 * time.Second))
   406  				time.Sleep(def_PollDeadStreamInterval + (1 * time.Second))
   407  			},
   408  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   409  				_, err := cw.cwClient.DeleteLogStream(&cloudwatchlogs.DeleteLogStreamInput{
   410  					LogGroupName:  aws.String("test_log_group1"),
   411  					LogStreamName: aws.String("test_stream"),
   412  				})
   413  				require.NoError(t, err)
   414  
   415  				_, err = cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   416  					LogGroupName: aws.String("test_log_group1"),
   417  				})
   418  				require.NoError(t, err)
   419  			},
   420  			expectedResLen: 1,
   421  		},
   422  	}
   423  
   424  	for _, tc := range tests {
   425  		tc := tc
   426  		t.Run(tc.name, func(t *testing.T) {
   427  			dbgLogger := log.New().WithField("test", tc.name)
   428  			dbgLogger.Logger.SetLevel(log.DebugLevel)
   429  			dbgLogger.Infof("starting test")
   430  			cw := CloudwatchSource{}
   431  			err := cw.Configure(tc.config, dbgLogger, configuration.METRICS_NONE)
   432  			cstest.RequireErrorContains(t, err, tc.expectedCfgErr)
   433  
   434  			if tc.expectedCfgErr != "" {
   435  				return
   436  			}
   437  
   438  			// run pre-routine : tests use it to set group & streams etc.
   439  			if tc.setup != nil {
   440  				tc.setup(t, &cw)
   441  			}
   442  			out := make(chan types.Event)
   443  			tmb := tomb.Tomb{}
   444  			var rcvdEvts []types.Event
   445  
   446  			dbgLogger.Infof("running StreamingAcquisition")
   447  			actmb := tomb.Tomb{}
   448  			actmb.Go(func() error {
   449  				err := cw.StreamingAcquisition(out, &actmb)
   450  				dbgLogger.Infof("acquis done")
   451  				cstest.RequireErrorContains(t, err, tc.expectedStartErr)
   452  				return nil
   453  			})
   454  
   455  			// let's empty output chan
   456  			tmb.Go(func() error {
   457  				for {
   458  					select {
   459  					case in := <-out:
   460  						log.Debugf("received event %+v", in)
   461  						rcvdEvts = append(rcvdEvts, in)
   462  					case <-tmb.Dying():
   463  						log.Debugf("pumper died")
   464  						return nil
   465  					}
   466  				}
   467  			})
   468  
   469  			if tc.run != nil {
   470  				tc.run(t, &cw)
   471  			} else {
   472  				dbgLogger.Warning("no code to run")
   473  			}
   474  
   475  			time.Sleep(5 * time.Second)
   476  			dbgLogger.Infof("killing collector")
   477  			tmb.Kill(nil)
   478  			<-tmb.Dead()
   479  			dbgLogger.Infof("killing datasource")
   480  			actmb.Kill(nil)
   481  			<-actmb.Dead()
   482  			// dbgLogger.Infof("collected events : %d -> %+v", len(rcvd_evts), rcvd_evts)
   483  			// check results
   484  			if tc.expectedResLen != -1 {
   485  				if tc.expectedResLen != len(rcvdEvts) {
   486  					t.Fatalf("%s : expected %d results got %d -> %v", tc.name, tc.expectedResLen, len(rcvdEvts), rcvdEvts)
   487  				}
   488  				dbgLogger.Debugf("got %d expected messages", len(rcvdEvts))
   489  			}
   490  			if len(tc.expectedResMessages) != 0 {
   491  				res := tc.expectedResMessages
   492  				for idx, v := range rcvdEvts {
   493  					if len(res) == 0 {
   494  						t.Fatalf("result %d/%d : received '%s', didn't expect anything (recvd:%d, expected:%d)", idx, len(rcvdEvts), v.Line.Raw, len(rcvdEvts), len(tc.expectedResMessages))
   495  					}
   496  					if res[0] != v.Line.Raw {
   497  						t.Fatalf("result %d/%d : expected '%s', received '%s' (recvd:%d, expected:%d)", idx, len(rcvdEvts), res[0], v.Line.Raw, len(rcvdEvts), len(tc.expectedResMessages))
   498  					}
   499  					dbgLogger.Debugf("got message '%s'", res[0])
   500  					res = res[1:]
   501  				}
   502  				if len(res) != 0 {
   503  					t.Fatalf("leftover unmatched results : %v", res)
   504  				}
   505  
   506  			}
   507  			if tc.teardown != nil {
   508  				tc.teardown(t, &cw)
   509  			}
   510  		})
   511  	}
   512  }
   513  
   514  func TestConfiguration(t *testing.T) {
   515  	if runtime.GOOS == "windows" {
   516  		t.Skip("Skipping test on windows")
   517  	}
   518  	log.SetLevel(log.DebugLevel)
   519  	tests := []struct {
   520  		config           []byte
   521  		expectedCfgErr   string
   522  		expectedStartErr string
   523  		name             string
   524  	}{
   525  		{
   526  			name: "group_does_not_exists",
   527  			config: []byte(`
   528  source: cloudwatch
   529  aws_region: us-east-1
   530  labels:
   531    type: test_source
   532  group_name: test_group
   533  stream_name: test_stream`),
   534  			expectedStartErr: "The specified log group does not exist",
   535  		},
   536  		{
   537  			config: []byte(`
   538  xxx: cloudwatch
   539  labels:
   540    type: test_source
   541  group_name: test_group
   542  stream_name: test_stream`),
   543  			expectedCfgErr: "field xxx not found in type",
   544  		},
   545  		{
   546  			name: "missing_group_name",
   547  			config: []byte(`
   548  source: cloudwatch
   549  aws_region: us-east-1
   550  labels:
   551    type: test_source
   552  stream_name: test_stream`),
   553  			expectedCfgErr: "group_name is mandatory for CloudwatchSource",
   554  		},
   555  	}
   556  
   557  	for _, tc := range tests {
   558  		tc := tc
   559  		t.Run(tc.name, func(t *testing.T) {
   560  			dbgLogger := log.New().WithField("test", tc.name)
   561  			dbgLogger.Logger.SetLevel(log.DebugLevel)
   562  			cw := CloudwatchSource{}
   563  			err := cw.Configure(tc.config, dbgLogger, configuration.METRICS_NONE)
   564  			cstest.RequireErrorContains(t, err, tc.expectedCfgErr)
   565  			if tc.expectedCfgErr != "" {
   566  				return
   567  			}
   568  
   569  			out := make(chan types.Event)
   570  			tmb := tomb.Tomb{}
   571  
   572  			switch cw.GetMode() {
   573  			case "tail":
   574  				err = cw.StreamingAcquisition(out, &tmb)
   575  			case "cat":
   576  				err = cw.OneShotAcquisition(out, &tmb)
   577  			}
   578  
   579  			cstest.RequireErrorContains(t, err, tc.expectedStartErr)
   580  
   581  			log.Debugf("killing ...")
   582  			tmb.Kill(nil)
   583  			<-tmb.Dead()
   584  			log.Debugf("dead :)")
   585  		})
   586  	}
   587  }
   588  
   589  func TestConfigureByDSN(t *testing.T) {
   590  	if runtime.GOOS == "windows" {
   591  		t.Skip("Skipping test on windows")
   592  	}
   593  	log.SetLevel(log.DebugLevel)
   594  	tests := []struct {
   595  		dsn            string
   596  		labels         map[string]string
   597  		expectedCfgErr string
   598  		name           string
   599  	}{
   600  		{
   601  			name:           "missing_query",
   602  			dsn:            "cloudwatch://bad_log_group:bad_stream_name",
   603  			expectedCfgErr: "query is mandatory (at least start_date and end_date or backlog)",
   604  		},
   605  		{
   606  			name: "backlog",
   607  			dsn:  "cloudwatch://bad_log_group:bad_stream_name?backlog=30m&log_level=info&profile=test",
   608  			// expectedCfgErr: "query is mandatory (at least start_date and end_date or backlog)",
   609  		},
   610  		{
   611  			name: "start_date/end_date",
   612  			dsn:  "cloudwatch://bad_log_group:bad_stream_name?start_date=2021/05/15 14:04&end_date=2021/05/15 15:04",
   613  			// expectedCfgErr: "query is mandatory (at least start_date and end_date or backlog)",
   614  		},
   615  		{
   616  			name:           "bad_log_level",
   617  			dsn:            "cloudwatch://bad_log_group:bad_stream_name?backlog=4h&log_level=",
   618  			expectedCfgErr: "unknown level : not a valid logrus Level: ",
   619  		},
   620  	}
   621  
   622  	for _, tc := range tests {
   623  		tc := tc
   624  		t.Run(tc.name, func(t *testing.T) {
   625  			dbgLogger := log.New().WithField("test", tc.name)
   626  			dbgLogger.Logger.SetLevel(log.DebugLevel)
   627  			cw := CloudwatchSource{}
   628  			err := cw.ConfigureByDSN(tc.dsn, tc.labels, dbgLogger, "")
   629  			cstest.RequireErrorContains(t, err, tc.expectedCfgErr)
   630  		})
   631  	}
   632  }
   633  
   634  func TestOneShotAcquisition(t *testing.T) {
   635  	if runtime.GOOS == "windows" {
   636  		t.Skip("Skipping test on windows")
   637  	}
   638  	log.SetLevel(log.DebugLevel)
   639  	tests := []struct {
   640  		dsn                 string
   641  		expectedCfgErr      string
   642  		expectedStartErr    string
   643  		name                string
   644  		setup               func(*testing.T, *CloudwatchSource)
   645  		run                 func(*testing.T, *CloudwatchSource)
   646  		teardown            func(*testing.T, *CloudwatchSource)
   647  		expectedResLen      int
   648  		expectedResMessages []string
   649  	}{
   650  		// stream with no data
   651  		{
   652  			name: "empty_stream",
   653  			dsn:  "cloudwatch://test_log_group1:test_stream?backlog=1h",
   654  			// expectedStartErr: "The specified log group does not exist",
   655  			setup: func(t *testing.T, cw *CloudwatchSource) {
   656  				deleteAllLogGroups(t, cw)
   657  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   658  					LogGroupName: aws.String("test_log_group1"),
   659  				})
   660  				require.NoError(t, err)
   661  
   662  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   663  					LogGroupName:  aws.String("test_log_group1"),
   664  					LogStreamName: aws.String("test_stream"),
   665  				})
   666  				require.NoError(t, err)
   667  			},
   668  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   669  				_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   670  					LogGroupName: aws.String("test_log_group1"),
   671  				})
   672  				require.NoError(t, err)
   673  			},
   674  			expectedResLen: 0,
   675  		},
   676  		// stream with one event
   677  		{
   678  			name: "get_one_event",
   679  			dsn:  "cloudwatch://test_log_group1:test_stream?backlog=1h",
   680  			// expectedStartErr: "The specified log group does not exist",
   681  			setup: func(t *testing.T, cw *CloudwatchSource) {
   682  				deleteAllLogGroups(t, cw)
   683  				_, err := cw.cwClient.CreateLogGroup(&cloudwatchlogs.CreateLogGroupInput{
   684  					LogGroupName: aws.String("test_log_group1"),
   685  				})
   686  				require.NoError(t, err)
   687  
   688  				_, err = cw.cwClient.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{
   689  					LogGroupName:  aws.String("test_log_group1"),
   690  					LogStreamName: aws.String("test_stream"),
   691  				})
   692  				require.NoError(t, err)
   693  
   694  				// this one is too much in the back
   695  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   696  					LogGroupName:  aws.String("test_log_group1"),
   697  					LogStreamName: aws.String("test_stream"),
   698  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   699  						{
   700  							Message:   aws.String("test_message_1"),
   701  							Timestamp: aws.Int64(time.Now().UTC().Add(-(2 * time.Hour)).UTC().Unix() * 1000),
   702  						},
   703  					},
   704  				})
   705  				require.NoError(t, err)
   706  
   707  				// this one can be read
   708  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   709  					LogGroupName:  aws.String("test_log_group1"),
   710  					LogStreamName: aws.String("test_stream"),
   711  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   712  						{
   713  							Message:   aws.String("test_message_2"),
   714  							Timestamp: aws.Int64(time.Now().UTC().Unix() * 1000),
   715  						},
   716  					},
   717  				})
   718  				require.NoError(t, err)
   719  
   720  				// this one is in the past
   721  				_, err = cw.cwClient.PutLogEvents(&cloudwatchlogs.PutLogEventsInput{
   722  					LogGroupName:  aws.String("test_log_group1"),
   723  					LogStreamName: aws.String("test_stream"),
   724  					LogEvents: []*cloudwatchlogs.InputLogEvent{
   725  						{
   726  							Message:   aws.String("test_message_3"),
   727  							Timestamp: aws.Int64(time.Now().UTC().Add(-(3 * time.Hour)).UTC().Unix() * 1000),
   728  						},
   729  					},
   730  				})
   731  				require.NoError(t, err)
   732  			},
   733  			teardown: func(t *testing.T, cw *CloudwatchSource) {
   734  				_, err := cw.cwClient.DeleteLogGroup(&cloudwatchlogs.DeleteLogGroupInput{
   735  					LogGroupName: aws.String("test_log_group1"),
   736  				})
   737  				require.NoError(t, err)
   738  			},
   739  			expectedResLen:      1,
   740  			expectedResMessages: []string{"test_message_2"},
   741  		},
   742  	}
   743  
   744  	for _, tc := range tests {
   745  		tc := tc
   746  		t.Run(tc.name, func(t *testing.T) {
   747  			dbgLogger := log.New().WithField("test", tc.name)
   748  			dbgLogger.Logger.SetLevel(log.DebugLevel)
   749  			dbgLogger.Infof("starting test")
   750  			cw := CloudwatchSource{}
   751  			err := cw.ConfigureByDSN(tc.dsn, map[string]string{"type": "test"}, dbgLogger, "")
   752  			cstest.RequireErrorContains(t, err, tc.expectedCfgErr)
   753  			if tc.expectedCfgErr != "" {
   754  				return
   755  			}
   756  
   757  			dbgLogger.Infof("config done test")
   758  			// run pre-routine : tests use it to set group & streams etc.
   759  			if tc.setup != nil {
   760  				tc.setup(t, &cw)
   761  			}
   762  			out := make(chan types.Event, 100)
   763  			tmb := tomb.Tomb{}
   764  			var rcvdEvts []types.Event
   765  
   766  			dbgLogger.Infof("running StreamingAcquisition")
   767  			err = cw.OneShotAcquisition(out, &tmb)
   768  			dbgLogger.Infof("acquis done")
   769  			cstest.RequireErrorContains(t, err, tc.expectedStartErr)
   770  			close(out)
   771  			// let's empty output chan
   772  			for evt := range out {
   773  				rcvdEvts = append(rcvdEvts, evt)
   774  			}
   775  
   776  			if tc.run != nil {
   777  				tc.run(t, &cw)
   778  			} else {
   779  				dbgLogger.Warning("no code to run")
   780  			}
   781  			if tc.expectedResLen != -1 {
   782  				if tc.expectedResLen != len(rcvdEvts) {
   783  					t.Fatalf("%s : expected %d results got %d -> %v", tc.name, tc.expectedResLen, len(rcvdEvts), rcvdEvts)
   784  				} else {
   785  					dbgLogger.Debugf("got %d expected messages", len(rcvdEvts))
   786  				}
   787  			}
   788  			if len(tc.expectedResMessages) != 0 {
   789  				res := tc.expectedResMessages
   790  				for idx, v := range rcvdEvts {
   791  					if len(res) == 0 {
   792  						t.Fatalf("result %d/%d : received '%s', didn't expect anything (recvd:%d, expected:%d)", idx, len(rcvdEvts), v.Line.Raw, len(rcvdEvts), len(tc.expectedResMessages))
   793  					}
   794  					if res[0] != v.Line.Raw {
   795  						t.Fatalf("result %d/%d : expected '%s', received '%s' (recvd:%d, expected:%d)", idx, len(rcvdEvts), res[0], v.Line.Raw, len(rcvdEvts), len(tc.expectedResMessages))
   796  					}
   797  					dbgLogger.Debugf("got message '%s'", res[0])
   798  					res = res[1:]
   799  				}
   800  				if len(res) != 0 {
   801  					t.Fatalf("leftover unmatched results : %v", res)
   802  				}
   803  
   804  			}
   805  			if tc.teardown != nil {
   806  				tc.teardown(t, &cw)
   807  			}
   808  		})
   809  	}
   810  }