github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/acquisition_test.go (about) 1 package acquisition 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/prometheus/client_golang/prometheus" 10 log "github.com/sirupsen/logrus" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 tomb "gopkg.in/tomb.v2" 14 "gopkg.in/yaml.v2" 15 16 "github.com/crowdsecurity/go-cs-lib/cstest" 17 18 "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" 19 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 20 "github.com/crowdsecurity/crowdsec/pkg/types" 21 ) 22 23 type MockSource struct { 24 configuration.DataSourceCommonCfg `yaml:",inline"` 25 Toto string `yaml:"toto"` 26 logger *log.Entry 27 } 28 29 func (f *MockSource) UnmarshalConfig(cfg []byte) error { 30 err := yaml.UnmarshalStrict(cfg, &f) 31 if err != nil { 32 return err 33 } 34 35 return nil 36 } 37 38 func (f *MockSource) Configure(cfg []byte, logger *log.Entry, metricsLevel int) error { 39 f.logger = logger 40 if err := f.UnmarshalConfig(cfg); err != nil { 41 return err 42 } 43 44 if f.Mode == "" { 45 f.Mode = configuration.CAT_MODE 46 } 47 48 if f.Mode != configuration.CAT_MODE && f.Mode != configuration.TAIL_MODE { 49 return fmt.Errorf("mode %s is not supported", f.Mode) 50 } 51 52 if f.Toto == "" { 53 return fmt.Errorf("expect non-empty toto") 54 } 55 56 return nil 57 } 58 func (f *MockSource) GetMode() string { return f.Mode } 59 func (f *MockSource) OneShotAcquisition(chan types.Event, *tomb.Tomb) error { return nil } 60 func (f *MockSource) StreamingAcquisition(chan types.Event, *tomb.Tomb) error { return nil } 61 func (f *MockSource) CanRun() error { return nil } 62 func (f *MockSource) GetMetrics() []prometheus.Collector { return nil } 63 func (f *MockSource) GetAggregMetrics() []prometheus.Collector { return nil } 64 func (f *MockSource) Dump() interface{} { return f } 65 func (f *MockSource) GetName() string { return "mock" } 66 func (f *MockSource) ConfigureByDSN(string, map[string]string, *log.Entry, string) error { 67 return fmt.Errorf("not supported") 68 } 69 func (f *MockSource) GetUuid() string { return "" } 70 71 // copy the mocksource, but this one can't run 72 type MockSourceCantRun struct { 73 MockSource 74 } 75 76 func (f *MockSourceCantRun) CanRun() error { return fmt.Errorf("can't run bro") } 77 func (f *MockSourceCantRun) GetName() string { return "mock_cant_run" } 78 79 // appendMockSource is only used to add mock source for tests 80 func appendMockSource() { 81 if GetDataSourceIface("mock") == nil { 82 AcquisitionSources["mock"] = func() DataSource { return &MockSource{} } 83 } 84 85 if GetDataSourceIface("mock_cant_run") == nil { 86 AcquisitionSources["mock_cant_run"] = func() DataSource { return &MockSourceCantRun{} } 87 } 88 } 89 90 func TestDataSourceConfigure(t *testing.T) { 91 appendMockSource() 92 93 tests := []struct { 94 TestName string 95 String string 96 ExpectedError string 97 }{ 98 { 99 TestName: "basic_valid_config", 100 String: ` 101 mode: cat 102 labels: 103 test: foobar 104 log_level: info 105 source: mock 106 toto: test_value1 107 `, 108 }, 109 { 110 TestName: "basic_debug_config", 111 String: ` 112 mode: cat 113 labels: 114 test: foobar 115 log_level: debug 116 source: mock 117 toto: test_value1 118 `, 119 }, 120 { 121 TestName: "basic_tailmode_config", 122 String: ` 123 mode: tail 124 labels: 125 test: foobar 126 log_level: debug 127 source: mock 128 toto: test_value1 129 `, 130 }, 131 { 132 TestName: "bad_mode_config", 133 String: ` 134 mode: ratata 135 labels: 136 test: foobar 137 log_level: debug 138 source: mock 139 toto: test_value1 140 `, 141 ExpectedError: "failed to configure datasource mock: mode ratata is not supported", 142 }, 143 { 144 TestName: "bad_type_config", 145 String: ` 146 mode: cat 147 labels: 148 test: foobar 149 log_level: debug 150 source: tutu 151 `, 152 ExpectedError: "cannot find source tutu", 153 }, 154 { 155 TestName: "mismatch_config", 156 String: ` 157 mode: cat 158 labels: 159 test: foobar 160 log_level: debug 161 source: mock 162 wowo: ajsajasjas 163 `, 164 ExpectedError: "field wowo not found in type acquisition.MockSource", 165 }, 166 { 167 TestName: "cant_run_error", 168 String: ` 169 mode: cat 170 labels: 171 test: foobar 172 log_level: debug 173 source: mock_cant_run 174 wowo: ajsajasjas 175 `, 176 ExpectedError: "datasource 'mock_cant_run' is not available: can't run bro", 177 }, 178 } 179 180 for _, tc := range tests { 181 tc := tc 182 t.Run(tc.TestName, func(t *testing.T) { 183 common := configuration.DataSourceCommonCfg{} 184 yaml.Unmarshal([]byte(tc.String), &common) 185 ds, err := DataSourceConfigure(common, configuration.METRICS_NONE) 186 cstest.RequireErrorContains(t, err, tc.ExpectedError) 187 if tc.ExpectedError != "" { 188 return 189 } 190 191 switch tc.TestName { 192 case "basic_valid_config": 193 mock := (*ds).Dump().(*MockSource) 194 assert.Equal(t, "test_value1", mock.Toto) 195 assert.Equal(t, "cat", mock.Mode) 196 assert.Equal(t, log.InfoLevel, mock.logger.Logger.Level) 197 assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels) 198 case "basic_debug_config": 199 mock := (*ds).Dump().(*MockSource) 200 assert.Equal(t, "test_value1", mock.Toto) 201 assert.Equal(t, "cat", mock.Mode) 202 assert.Equal(t, log.DebugLevel, mock.logger.Logger.Level) 203 assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels) 204 case "basic_tailmode_config": 205 mock := (*ds).Dump().(*MockSource) 206 assert.Equal(t, "test_value1", mock.Toto) 207 assert.Equal(t, "tail", mock.Mode) 208 assert.Equal(t, log.DebugLevel, mock.logger.Logger.Level) 209 assert.Equal(t, map[string]string{"test": "foobar"}, mock.Labels) 210 } 211 }) 212 } 213 } 214 215 func TestLoadAcquisitionFromFile(t *testing.T) { 216 appendMockSource() 217 218 tests := []struct { 219 TestName string 220 Config csconfig.CrowdsecServiceCfg 221 ExpectedError string 222 ExpectedLen int 223 }{ 224 { 225 TestName: "non_existent_file", 226 Config: csconfig.CrowdsecServiceCfg{ 227 AcquisitionFiles: []string{"does_not_exist"}, 228 }, 229 ExpectedError: "open does_not_exist: " + cstest.FileNotFoundMessage, 230 ExpectedLen: 0, 231 }, 232 { 233 TestName: "invalid_yaml_file", 234 Config: csconfig.CrowdsecServiceCfg{ 235 AcquisitionFiles: []string{"test_files/badyaml.yaml"}, 236 }, 237 ExpectedError: "failed to yaml decode test_files/badyaml.yaml: yaml: unmarshal errors", 238 ExpectedLen: 0, 239 }, 240 { 241 TestName: "invalid_empty_yaml", 242 Config: csconfig.CrowdsecServiceCfg{ 243 AcquisitionFiles: []string{"test_files/emptyitem.yaml"}, 244 }, 245 ExpectedLen: 0, 246 }, 247 { 248 TestName: "basic_valid", 249 Config: csconfig.CrowdsecServiceCfg{ 250 AcquisitionFiles: []string{"test_files/basic_filemode.yaml"}, 251 }, 252 ExpectedLen: 2, 253 }, 254 { 255 TestName: "missing_labels", 256 Config: csconfig.CrowdsecServiceCfg{ 257 AcquisitionFiles: []string{"test_files/missing_labels.yaml"}, 258 }, 259 ExpectedError: "missing labels in test_files/missing_labels.yaml", 260 }, 261 { 262 TestName: "backward_compat", 263 Config: csconfig.CrowdsecServiceCfg{ 264 AcquisitionFiles: []string{"test_files/backward_compat.yaml"}, 265 }, 266 ExpectedLen: 2, 267 }, 268 { 269 TestName: "bad_type", 270 Config: csconfig.CrowdsecServiceCfg{ 271 AcquisitionFiles: []string{"test_files/bad_source.yaml"}, 272 }, 273 ExpectedError: "unknown data source does_not_exist in test_files/bad_source.yaml", 274 }, 275 { 276 TestName: "invalid_filetype_config", 277 Config: csconfig.CrowdsecServiceCfg{ 278 AcquisitionFiles: []string{"test_files/bad_filetype.yaml"}, 279 }, 280 ExpectedError: "while configuring datasource of type file from test_files/bad_filetype.yaml", 281 }, 282 } 283 for _, tc := range tests { 284 tc := tc 285 t.Run(tc.TestName, func(t *testing.T) { 286 dss, err := LoadAcquisitionFromFile(&tc.Config, nil) 287 cstest.RequireErrorContains(t, err, tc.ExpectedError) 288 if tc.ExpectedError != "" { 289 return 290 } 291 292 assert.Len(t, dss, tc.ExpectedLen) 293 }) 294 } 295 } 296 297 /* 298 test start acquisition : 299 - create mock parser in cat mode : start acquisition, check it returns, count items in chan 300 - create mock parser in tail mode : start acquisition, sleep, check item count, tomb kill it, wait for it to return 301 */ 302 303 type MockCat struct { 304 configuration.DataSourceCommonCfg `yaml:",inline"` 305 logger *log.Entry 306 } 307 308 func (f *MockCat) Configure(cfg []byte, logger *log.Entry, metricsLevel int) error { 309 f.logger = logger 310 if f.Mode == "" { 311 f.Mode = configuration.CAT_MODE 312 } 313 314 if f.Mode != configuration.CAT_MODE { 315 return fmt.Errorf("mode %s is not supported", f.Mode) 316 } 317 318 return nil 319 } 320 321 func (f *MockCat) UnmarshalConfig(cfg []byte) error { return nil } 322 func (f *MockCat) GetName() string { return "mock_cat" } 323 func (f *MockCat) GetMode() string { return "cat" } 324 func (f *MockCat) OneShotAcquisition(out chan types.Event, tomb *tomb.Tomb) error { 325 for i := 0; i < 10; i++ { 326 evt := types.Event{} 327 evt.Line.Src = "test" 328 out <- evt 329 } 330 331 return nil 332 } 333 func (f *MockCat) StreamingAcquisition(chan types.Event, *tomb.Tomb) error { 334 return fmt.Errorf("can't run in tail") 335 } 336 func (f *MockCat) CanRun() error { return nil } 337 func (f *MockCat) GetMetrics() []prometheus.Collector { return nil } 338 func (f *MockCat) GetAggregMetrics() []prometheus.Collector { return nil } 339 func (f *MockCat) Dump() interface{} { return f } 340 func (f *MockCat) ConfigureByDSN(string, map[string]string, *log.Entry, string) error { 341 return fmt.Errorf("not supported") 342 } 343 func (f *MockCat) GetUuid() string { return "" } 344 345 //---- 346 347 type MockTail struct { 348 configuration.DataSourceCommonCfg `yaml:",inline"` 349 logger *log.Entry 350 } 351 352 func (f *MockTail) Configure(cfg []byte, logger *log.Entry, metricsLevel int) error { 353 f.logger = logger 354 if f.Mode == "" { 355 f.Mode = configuration.TAIL_MODE 356 } 357 358 if f.Mode != configuration.TAIL_MODE { 359 return fmt.Errorf("mode %s is not supported", f.Mode) 360 } 361 362 return nil 363 } 364 365 func (f *MockTail) UnmarshalConfig(cfg []byte) error { return nil } 366 func (f *MockTail) GetName() string { return "mock_tail" } 367 func (f *MockTail) GetMode() string { return "tail" } 368 func (f *MockTail) OneShotAcquisition(out chan types.Event, tomb *tomb.Tomb) error { 369 return fmt.Errorf("can't run in cat mode") 370 } 371 func (f *MockTail) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error { 372 for i := 0; i < 10; i++ { 373 evt := types.Event{} 374 evt.Line.Src = "test" 375 out <- evt 376 } 377 <-t.Dying() 378 379 return nil 380 } 381 func (f *MockTail) CanRun() error { return nil } 382 func (f *MockTail) GetMetrics() []prometheus.Collector { return nil } 383 func (f *MockTail) GetAggregMetrics() []prometheus.Collector { return nil } 384 func (f *MockTail) Dump() interface{} { return f } 385 func (f *MockTail) ConfigureByDSN(string, map[string]string, *log.Entry, string) error { 386 return fmt.Errorf("not supported") 387 } 388 func (f *MockTail) GetUuid() string { return "" } 389 390 //func StartAcquisition(sources []DataSource, output chan types.Event, AcquisTomb *tomb.Tomb) error { 391 392 func TestStartAcquisitionCat(t *testing.T) { 393 sources := []DataSource{ 394 &MockCat{}, 395 } 396 out := make(chan types.Event) 397 acquisTomb := tomb.Tomb{} 398 399 go func() { 400 if err := StartAcquisition(sources, out, &acquisTomb); err != nil { 401 t.Errorf("unexpected error") 402 } 403 }() 404 405 count := 0 406 READLOOP: 407 for { 408 select { 409 case <-out: 410 count++ 411 case <-time.After(1 * time.Second): 412 break READLOOP 413 } 414 } 415 416 assert.Equal(t, 10, count) 417 } 418 419 func TestStartAcquisitionTail(t *testing.T) { 420 sources := []DataSource{ 421 &MockTail{}, 422 } 423 out := make(chan types.Event) 424 acquisTomb := tomb.Tomb{} 425 426 go func() { 427 if err := StartAcquisition(sources, out, &acquisTomb); err != nil { 428 t.Errorf("unexpected error") 429 } 430 }() 431 432 count := 0 433 READLOOP: 434 for { 435 select { 436 case <-out: 437 count++ 438 case <-time.After(1 * time.Second): 439 break READLOOP 440 } 441 } 442 443 assert.Equal(t, 10, count) 444 445 acquisTomb.Kill(nil) 446 time.Sleep(1 * time.Second) 447 require.NoError(t, acquisTomb.Err(), "tomb is not dead") 448 } 449 450 type MockTailError struct { 451 MockTail 452 } 453 454 func (f *MockTailError) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error { 455 for i := 0; i < 10; i++ { 456 evt := types.Event{} 457 evt.Line.Src = "test" 458 out <- evt 459 } 460 t.Kill(fmt.Errorf("got error (tomb)")) 461 462 return fmt.Errorf("got error") 463 } 464 465 func TestStartAcquisitionTailError(t *testing.T) { 466 sources := []DataSource{ 467 &MockTailError{}, 468 } 469 out := make(chan types.Event) 470 acquisTomb := tomb.Tomb{} 471 472 go func() { 473 if err := StartAcquisition(sources, out, &acquisTomb); err != nil && err.Error() != "got error (tomb)" { 474 t.Errorf("expected error, got '%s'", err) 475 } 476 }() 477 478 count := 0 479 READLOOP: 480 for { 481 select { 482 case <-out: 483 count++ 484 case <-time.After(1 * time.Second): 485 break READLOOP 486 } 487 } 488 assert.Equal(t, 10, count) 489 //acquisTomb.Kill(nil) 490 time.Sleep(1 * time.Second) 491 cstest.RequireErrorContains(t, acquisTomb.Err(), "got error (tomb)") 492 } 493 494 type MockSourceByDSN struct { 495 configuration.DataSourceCommonCfg `yaml:",inline"` 496 Toto string `yaml:"toto"` 497 logger *log.Entry //nolint: unused 498 } 499 500 func (f *MockSourceByDSN) UnmarshalConfig(cfg []byte) error { return nil } 501 func (f *MockSourceByDSN) Configure(cfg []byte, logger *log.Entry, metricsLevel int) error { 502 return nil 503 } 504 func (f *MockSourceByDSN) GetMode() string { return f.Mode } 505 func (f *MockSourceByDSN) OneShotAcquisition(chan types.Event, *tomb.Tomb) error { return nil } 506 func (f *MockSourceByDSN) StreamingAcquisition(chan types.Event, *tomb.Tomb) error { return nil } 507 func (f *MockSourceByDSN) CanRun() error { return nil } 508 func (f *MockSourceByDSN) GetMetrics() []prometheus.Collector { return nil } 509 func (f *MockSourceByDSN) GetAggregMetrics() []prometheus.Collector { return nil } 510 func (f *MockSourceByDSN) Dump() interface{} { return f } 511 func (f *MockSourceByDSN) GetName() string { return "mockdsn" } 512 func (f *MockSourceByDSN) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error { 513 dsn = strings.TrimPrefix(dsn, "mockdsn://") 514 if dsn != "test_expect" { 515 return fmt.Errorf("unexpected value") 516 } 517 518 return nil 519 } 520 func (f *MockSourceByDSN) GetUuid() string { return "" } 521 522 func TestConfigureByDSN(t *testing.T) { 523 tests := []struct { 524 dsn string 525 ExpectedError string 526 ExpectedResLen int 527 }{ 528 { 529 dsn: "baddsn", 530 ExpectedError: "baddsn isn't valid dsn (no protocol)", 531 }, 532 { 533 dsn: "foobar://toto", 534 ExpectedError: "no acquisition for protocol foobar://", 535 }, 536 { 537 dsn: "mockdsn://test_expect", 538 ExpectedResLen: 1, 539 }, 540 { 541 dsn: "mockdsn://bad", 542 ExpectedError: "unexpected value", 543 }, 544 } 545 546 if GetDataSourceIface("mockdsn") == nil { 547 AcquisitionSources["mockdsn"] = func() DataSource { return &MockSourceByDSN{} } 548 } 549 550 for _, tc := range tests { 551 tc := tc 552 t.Run(tc.dsn, func(t *testing.T) { 553 srcs, err := LoadAcquisitionFromDSN(tc.dsn, map[string]string{"type": "test_label"}, "") 554 cstest.RequireErrorContains(t, err, tc.ExpectedError) 555 556 assert.Len(t, srcs, tc.ExpectedResLen) 557 }) 558 } 559 }