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 }