github.com/Jeffail/benthos/v3@v3.65.0/lib/stream/manager/api_test.go (about) 1 package manager_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "path/filepath" 12 "reflect" 13 "testing" 14 "time" 15 16 "github.com/Jeffail/benthos/v3/lib/cache" 17 "github.com/Jeffail/benthos/v3/lib/input" 18 "github.com/Jeffail/benthos/v3/lib/log" 19 bmanager "github.com/Jeffail/benthos/v3/lib/manager" 20 "github.com/Jeffail/benthos/v3/lib/message" 21 "github.com/Jeffail/benthos/v3/lib/metrics" 22 "github.com/Jeffail/benthos/v3/lib/output" 23 "github.com/Jeffail/benthos/v3/lib/stream" 24 "github.com/Jeffail/benthos/v3/lib/stream/manager" 25 "github.com/Jeffail/benthos/v3/lib/types" 26 "github.com/Jeffail/gabs/v2" 27 "github.com/gorilla/mux" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 yaml "gopkg.in/yaml.v3" 31 32 _ "github.com/Jeffail/benthos/v3/public/components/all" 33 ) 34 35 func router(m *manager.Type) *mux.Router { 36 router := mux.NewRouter() 37 router.HandleFunc("/streams", m.HandleStreamsCRUD) 38 router.HandleFunc("/streams/{id}", m.HandleStreamCRUD) 39 router.HandleFunc("/streams/{id}/stats", m.HandleStreamStats) 40 router.HandleFunc("/resources/{type}/{id}", m.HandleResourceCRUD) 41 return router 42 } 43 44 func genRequest(verb, url string, payload interface{}) *http.Request { 45 var body io.Reader 46 47 if payload != nil { 48 bodyBytes, err := json.Marshal(payload) 49 if err != nil { 50 panic(err) 51 } 52 body = bytes.NewReader(bodyBytes) 53 } 54 55 req, err := http.NewRequest(verb, url, body) 56 if err != nil { 57 panic(err) 58 } 59 60 return req 61 } 62 63 func genYAMLRequest(verb, url string, payload interface{}) *http.Request { 64 var body io.Reader 65 66 if payload != nil { 67 bodyBytes, err := yaml.Marshal(payload) 68 if err != nil { 69 panic(err) 70 } 71 body = bytes.NewReader(bodyBytes) 72 } 73 74 req, err := http.NewRequest(verb, url, body) 75 if err != nil { 76 panic(err) 77 } 78 79 return req 80 } 81 82 type listItemBody struct { 83 Active bool `json:"active"` 84 Uptime float64 `json:"uptime"` 85 UptimeStr string `json:"uptime_str"` 86 } 87 88 type listBody map[string]listItemBody 89 90 func parseListBody(data *bytes.Buffer) listBody { 91 result := listBody{} 92 if err := json.Unmarshal(data.Bytes(), &result); err != nil { 93 panic(err) 94 } 95 return result 96 } 97 98 type getBody struct { 99 Active bool `json:"active"` 100 Uptime float64 `json:"uptime"` 101 UptimeStr string `json:"uptime_str"` 102 Config stream.Config `json:"config"` 103 } 104 105 func parseGetBody(t *testing.T, data *bytes.Buffer) getBody { 106 t.Helper() 107 result := getBody{ 108 Config: stream.NewConfig(), 109 } 110 if err := yaml.Unmarshal(data.Bytes(), &result); err != nil { 111 t.Fatal(err) 112 } 113 return result 114 } 115 116 type endpointReg struct { 117 endpoints map[string]http.HandlerFunc 118 types.DudMgr 119 } 120 121 func (f *endpointReg) RegisterEndpoint(path, desc string, h http.HandlerFunc) { 122 f.endpoints[path] = h 123 } 124 125 func TestTypeAPIDisabled(t *testing.T) { 126 r := &endpointReg{endpoints: map[string]http.HandlerFunc{}} 127 _ = manager.New( 128 manager.OptSetLogger(log.Noop()), 129 manager.OptSetStats(metrics.Noop()), 130 manager.OptSetManager(r), 131 manager.OptSetAPITimeout(time.Millisecond*100), 132 manager.OptAPIEnabled(true), 133 ) 134 assert.NotEmpty(t, r.endpoints) 135 136 r = &endpointReg{endpoints: map[string]http.HandlerFunc{}} 137 _ = manager.New( 138 manager.OptSetLogger(log.Noop()), 139 manager.OptSetStats(metrics.Noop()), 140 manager.OptSetManager(r), 141 manager.OptSetAPITimeout(time.Millisecond*100), 142 manager.OptAPIEnabled(false), 143 ) 144 assert.Len(t, r.endpoints, 1) 145 assert.Contains(t, r.endpoints, "/ready") 146 } 147 148 func TestTypeAPIBadMethods(t *testing.T) { 149 mgr := manager.New( 150 manager.OptSetLogger(log.Noop()), 151 manager.OptSetStats(metrics.Noop()), 152 manager.OptSetManager(types.DudMgr{}), 153 manager.OptSetAPITimeout(time.Millisecond*100), 154 ) 155 156 r := router(mgr) 157 158 request := genRequest("DELETE", "/streams", nil) 159 response := httptest.NewRecorder() 160 r.ServeHTTP(response, request) 161 if exp, act := http.StatusBadRequest, response.Code; exp != act { 162 t.Errorf("Unexpected result: %v != %v", act, exp) 163 } 164 165 request = genRequest("DERP", "/streams/foo", nil) 166 response = httptest.NewRecorder() 167 r.ServeHTTP(response, request) 168 if exp, act := http.StatusBadRequest, response.Code; exp != act { 169 t.Errorf("Unexpected result: %v != %v", act, exp) 170 } 171 } 172 173 func harmlessConf() stream.Config { 174 c := stream.NewConfig() 175 c.Input.Type = "http_server" 176 c.Output.Type = "http_server" 177 return c 178 } 179 180 func TestTypeAPIBasicOperations(t *testing.T) { 181 mgr := manager.New( 182 manager.OptSetLogger(log.Noop()), 183 manager.OptSetStats(metrics.Noop()), 184 manager.OptSetManager(types.NoopMgr()), 185 manager.OptSetAPITimeout(time.Second*10), 186 ) 187 188 r := router(mgr) 189 conf, err := harmlessConf().Sanitised() 190 require.NoError(t, err) 191 192 request := genRequest("PUT", "/streams/foo", conf) 193 response := httptest.NewRecorder() 194 r.ServeHTTP(response, request) 195 require.Equal(t, http.StatusNotFound, response.Code, response.Body.String()) 196 197 request = genRequest("GET", "/streams/foo", nil) 198 response = httptest.NewRecorder() 199 r.ServeHTTP(response, request) 200 assert.Equal(t, http.StatusNotFound, response.Code) 201 202 request = genRequest("POST", "/streams/foo", conf) 203 response = httptest.NewRecorder() 204 r.ServeHTTP(response, request) 205 assert.Equal(t, http.StatusOK, response.Code) 206 207 request = genRequest("POST", "/streams/foo", conf) 208 response = httptest.NewRecorder() 209 r.ServeHTTP(response, request) 210 assert.Equal(t, http.StatusBadRequest, response.Code) 211 212 request = genRequest("GET", "/streams/bar", nil) 213 response = httptest.NewRecorder() 214 r.ServeHTTP(response, request) 215 assert.Equal(t, http.StatusNotFound, response.Code) 216 217 request = genRequest("GET", "/streams/foo", conf) 218 response = httptest.NewRecorder() 219 r.ServeHTTP(response, request) 220 assert.Equal(t, http.StatusOK, response.Code) 221 222 info := parseGetBody(t, response.Body) 223 assert.True(t, info.Active) 224 225 actSanit, err := info.Config.Sanitised() 226 require.NoError(t, err) 227 assert.Equal(t, conf, actSanit) 228 229 newConf := harmlessConf() 230 newConf.Buffer.Type = "memory" 231 newConfSanit, err := newConf.Sanitised() 232 require.NoError(t, err) 233 234 request = genRequest("PUT", "/streams/foo", newConfSanit) 235 response = httptest.NewRecorder() 236 r.ServeHTTP(response, request) 237 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 238 239 request = genRequest("GET", "/streams/foo", conf) 240 response = httptest.NewRecorder() 241 r.ServeHTTP(response, request) 242 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 243 244 info = parseGetBody(t, response.Body) 245 assert.True(t, info.Active) 246 247 actSanit, err = info.Config.Sanitised() 248 require.NoError(t, err) 249 assert.Equal(t, newConfSanit, actSanit) 250 251 request = genRequest("DELETE", "/streams/foo", conf) 252 response = httptest.NewRecorder() 253 r.ServeHTTP(response, request) 254 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 255 256 request = genRequest("DELETE", "/streams/foo", conf) 257 response = httptest.NewRecorder() 258 r.ServeHTTP(response, request) 259 assert.Equal(t, http.StatusNotFound, response.Code, response.Body.String()) 260 261 testVar := "__TEST_INPUT_TYPE" 262 originalEnv, orignalSet := os.LookupEnv(testVar) 263 defer func() { 264 _ = os.Unsetenv(testVar) 265 if orignalSet { 266 _ = os.Setenv(testVar, originalEnv) 267 } 268 }() 269 _ = os.Setenv(testVar, "http_server") 270 newConf = harmlessConf() 271 newConf.Input.Type = "${__TEST_INPUT_TYPE}" 272 273 request = genRequest("POST", "/streams/fooEnv?chilled=true", newConf) 274 response = httptest.NewRecorder() 275 r.ServeHTTP(response, request) 276 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 277 278 request = genRequest("GET", "/streams/fooEnv", nil) 279 response = httptest.NewRecorder() 280 r.ServeHTTP(response, request) 281 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 282 283 info = parseGetBody(t, response.Body) 284 // replace the env var with the expected value in the struct 285 // because we will be comparing it to the rendered version. 286 newConf.Input.Type = "http_server" 287 assert.True(t, info.Active) 288 assert.Equal(t, newConf, info.Config) 289 290 request = genRequest("DELETE", "/streams/fooEnv", conf) 291 response = httptest.NewRecorder() 292 r.ServeHTTP(response, request) 293 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 294 } 295 296 func TestTypeAPIPatch(t *testing.T) { 297 mgr := manager.New( 298 manager.OptSetLogger(log.Noop()), 299 manager.OptSetStats(metrics.Noop()), 300 manager.OptSetManager(types.DudMgr{}), 301 manager.OptSetAPITimeout(time.Millisecond*100), 302 ) 303 304 r := router(mgr) 305 conf := harmlessConf() 306 307 request := genRequest("PATCH", "/streams/foo", conf) 308 response := httptest.NewRecorder() 309 r.ServeHTTP(response, request) 310 if exp, act := http.StatusNotFound, response.Code; exp != act { 311 t.Errorf("Unexpected result: %v != %v", act, exp) 312 } 313 314 request = genRequest("POST", "/streams/foo?chilled=true", conf) 315 response = httptest.NewRecorder() 316 r.ServeHTTP(response, request) 317 if exp, act := http.StatusOK, response.Code; exp != act { 318 t.Errorf("Unexpected result: %v != %v", act, exp) 319 } 320 321 patchConf := map[string]interface{}{ 322 "input": map[string]interface{}{ 323 "http_server": map[string]interface{}{ 324 "path": "/foobarbaz", 325 }, 326 }, 327 } 328 request = genRequest("PATCH", "/streams/foo", patchConf) 329 response = httptest.NewRecorder() 330 r.ServeHTTP(response, request) 331 if exp, act := http.StatusOK, response.Code; exp != act { 332 t.Errorf("Unexpected result: %v != %v", act, exp) 333 } 334 335 conf.Input.HTTPServer.Path = "/foobarbaz" 336 request = genRequest("GET", "/streams/foo", conf) 337 response = httptest.NewRecorder() 338 r.ServeHTTP(response, request) 339 if exp, act := http.StatusOK, response.Code; exp != act { 340 t.Errorf("Unexpected result: %v != %v", act, exp) 341 } 342 info := parseGetBody(t, response.Body) 343 if !info.Active { 344 t.Fatal("Stream not active") 345 } 346 if act, exp := info.Config.Input.HTTPServer.Path, conf.Input.HTTPServer.Path; exp != act { 347 t.Errorf("Unexpected config: %v != %v", act, exp) 348 } 349 if act, exp := info.Config.Input.Type, conf.Input.Type; exp != act { 350 t.Errorf("Unexpected config: %v != %v", act, exp) 351 } 352 } 353 354 func TestTypeAPIBasicOperationsYAML(t *testing.T) { 355 mgr := manager.New( 356 manager.OptSetLogger(log.Noop()), 357 manager.OptSetStats(metrics.Noop()), 358 manager.OptSetManager(types.NoopMgr()), 359 manager.OptSetAPITimeout(time.Second*10), 360 ) 361 362 r := router(mgr) 363 conf := harmlessConf() 364 365 request := genYAMLRequest("PUT", "/streams/foo?chilled=true", conf) 366 response := httptest.NewRecorder() 367 r.ServeHTTP(response, request) 368 assert.Equal(t, http.StatusNotFound, response.Code) 369 370 request = genYAMLRequest("GET", "/streams/foo", nil) 371 response = httptest.NewRecorder() 372 r.ServeHTTP(response, request) 373 assert.Equal(t, http.StatusNotFound, response.Code) 374 375 request = genYAMLRequest("POST", "/streams/foo?chilled=true", conf) 376 response = httptest.NewRecorder() 377 r.ServeHTTP(response, request) 378 assert.Equal(t, http.StatusOK, response.Code) 379 380 request = genYAMLRequest("POST", "/streams/foo", conf) 381 response = httptest.NewRecorder() 382 r.ServeHTTP(response, request) 383 assert.Equal(t, http.StatusBadRequest, response.Code) 384 385 request = genYAMLRequest("GET", "/streams/bar", nil) 386 response = httptest.NewRecorder() 387 r.ServeHTTP(response, request) 388 assert.Equal(t, http.StatusNotFound, response.Code) 389 390 request = genYAMLRequest("GET", "/streams/foo", conf) 391 response = httptest.NewRecorder() 392 r.ServeHTTP(response, request) 393 assert.Equal(t, http.StatusOK, response.Code) 394 395 info := parseGetBody(t, response.Body) 396 require.True(t, info.Active) 397 assert.Equal(t, conf, info.Config) 398 399 newConf := harmlessConf() 400 newConf.Buffer.Type = "memory" 401 402 request = genYAMLRequest("PUT", "/streams/foo?chilled=true", newConf) 403 response = httptest.NewRecorder() 404 r.ServeHTTP(response, request) 405 assert.Equal(t, http.StatusOK, response.Code) 406 407 request = genYAMLRequest("GET", "/streams/foo", conf) 408 response = httptest.NewRecorder() 409 r.ServeHTTP(response, request) 410 assert.Equal(t, http.StatusOK, response.Code) 411 412 info = parseGetBody(t, response.Body) 413 require.True(t, info.Active) 414 assert.Equal(t, newConf, info.Config) 415 416 request = genYAMLRequest("DELETE", "/streams/foo", conf) 417 response = httptest.NewRecorder() 418 r.ServeHTTP(response, request) 419 assert.Equal(t, http.StatusOK, response.Code) 420 421 request = genYAMLRequest("DELETE", "/streams/foo", conf) 422 response = httptest.NewRecorder() 423 r.ServeHTTP(response, request) 424 assert.Equal(t, http.StatusNotFound, response.Code) 425 } 426 427 func TestTypeAPIList(t *testing.T) { 428 mgr := manager.New( 429 manager.OptSetLogger(log.Noop()), 430 manager.OptSetStats(metrics.Noop()), 431 manager.OptSetManager(types.DudMgr{}), 432 manager.OptSetAPITimeout(time.Millisecond*100), 433 ) 434 435 r := router(mgr) 436 437 request := genRequest("GET", "/streams", nil) 438 response := httptest.NewRecorder() 439 r.ServeHTTP(response, request) 440 if exp, act := http.StatusOK, response.Code; exp != act { 441 t.Errorf("Unexpected result: %v != %v", act, exp) 442 } 443 info := parseListBody(response.Body) 444 if exp, act := (listBody{}), info; !reflect.DeepEqual(exp, act) { 445 t.Errorf("Wrong list response: %v != %v", act, exp) 446 } 447 448 if err := mgr.Create("foo", harmlessConf()); err != nil { 449 t.Fatal(err) 450 } 451 452 request = genRequest("GET", "/streams", nil) 453 response = httptest.NewRecorder() 454 r.ServeHTTP(response, request) 455 if exp, act := http.StatusOK, response.Code; exp != act { 456 t.Errorf("Unexpected result: %v != %v", act, exp) 457 } 458 info = parseListBody(response.Body) 459 if exp, act := true, info["foo"].Active; !reflect.DeepEqual(exp, act) { 460 t.Errorf("Wrong list response: %v != %v", act, exp) 461 } 462 } 463 464 func TestTypeAPISetStreams(t *testing.T) { 465 mgr := manager.New( 466 manager.OptSetLogger(log.Noop()), 467 manager.OptSetStats(metrics.Noop()), 468 manager.OptSetManager(types.DudMgr{}), 469 manager.OptSetAPITimeout(time.Millisecond*100), 470 ) 471 472 r := router(mgr) 473 474 require.NoError(t, mgr.Create("foo", harmlessConf())) 475 require.NoError(t, mgr.Create("bar", harmlessConf())) 476 477 request := genRequest("GET", "/streams", nil) 478 response := httptest.NewRecorder() 479 r.ServeHTTP(response, request) 480 assert.Equal(t, http.StatusOK, response.Code) 481 482 info := parseListBody(response.Body) 483 assert.True(t, info["foo"].Active) 484 assert.True(t, info["bar"].Active) 485 486 barConf := harmlessConf() 487 barConf.Input.HTTPServer.Path = "BAR_ONE" 488 bar2Conf := harmlessConf() 489 bar2Conf.Input.HTTPServer.Path = "BAR_TWO" 490 bazConf := harmlessConf() 491 bazConf.Input.HTTPServer.Path = "BAZ_ONE" 492 493 streamsBody := map[string]interface{}{} 494 streamsBody["bar"], _ = barConf.Sanitised() 495 streamsBody["bar2"], _ = bar2Conf.Sanitised() 496 streamsBody["baz"], _ = bazConf.Sanitised() 497 498 request = genRequest("POST", "/streams", streamsBody) 499 response = httptest.NewRecorder() 500 r.ServeHTTP(response, request) 501 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 502 503 request = genRequest("GET", "/streams", nil) 504 response = httptest.NewRecorder() 505 r.ServeHTTP(response, request) 506 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 507 508 info = parseListBody(response.Body) 509 assert.NotContains(t, info, "foo") 510 assert.Contains(t, info, "bar") 511 assert.Contains(t, info, "baz") 512 513 request = genRequest("GET", "/streams/bar", nil) 514 response = httptest.NewRecorder() 515 r.ServeHTTP(response, request) 516 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 517 518 conf := parseGetBody(t, response.Body) 519 assert.Equal(t, "BAR_ONE", conf.Config.Input.HTTPServer.Path) 520 521 request = genRequest("GET", "/streams/bar2", nil) 522 response = httptest.NewRecorder() 523 r.ServeHTTP(response, request) 524 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 525 526 conf = parseGetBody(t, response.Body) 527 assert.Equal(t, "BAR_TWO", conf.Config.Input.HTTPServer.Path) 528 529 request = genRequest("GET", "/streams/baz", nil) 530 response = httptest.NewRecorder() 531 r.ServeHTTP(response, request) 532 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 533 534 conf = parseGetBody(t, response.Body) 535 assert.Equal(t, "BAZ_ONE", conf.Config.Input.HTTPServer.Path) 536 } 537 538 func TestTypeAPIStreamsDefaultConf(t *testing.T) { 539 mgr := manager.New( 540 manager.OptSetLogger(log.Noop()), 541 manager.OptSetStats(metrics.Noop()), 542 manager.OptSetManager(types.DudMgr{}), 543 manager.OptSetAPITimeout(time.Millisecond*100), 544 ) 545 546 r := router(mgr) 547 548 body := []byte(`{ 549 "foo": { 550 "input": { 551 "nanomsg": {} 552 }, 553 "output": { 554 "nanomsg": {} 555 } 556 } 557 }`) 558 559 request, err := http.NewRequest("POST", "/streams", bytes.NewReader(body)) 560 require.NoError(t, err) 561 562 response := httptest.NewRecorder() 563 r.ServeHTTP(response, request) 564 assert.Equal(t, http.StatusOK, response.Code) 565 566 status, err := mgr.Read("foo") 567 require.NoError(t, err) 568 569 assert.Equal(t, status.Config().Input.Nanomsg.PollTimeout, "5s") 570 } 571 572 func TestTypeAPIStreamsLinting(t *testing.T) { 573 mgr := manager.New( 574 manager.OptSetLogger(log.Noop()), 575 manager.OptSetStats(metrics.Noop()), 576 manager.OptSetManager(types.DudMgr{}), 577 manager.OptSetAPITimeout(time.Millisecond*100), 578 ) 579 580 r := router(mgr) 581 582 body := []byte(`{ 583 "foo": { 584 "input": { 585 "nanomsg": {} 586 }, 587 "output": { 588 "type":"nanomsg", 589 "file": {} 590 } 591 }, 592 "bar": { 593 "input": { 594 "type":"nanomsg", 595 "file": {} 596 }, 597 "output": { 598 "nanomsg": {} 599 } 600 } 601 }`) 602 603 request, err := http.NewRequest("POST", "/streams", bytes.NewReader(body)) 604 require.NoError(t, err) 605 606 response := httptest.NewRecorder() 607 r.ServeHTTP(response, request) 608 assert.Equal(t, http.StatusBadRequest, response.Code) 609 610 expLints := `{"lint_errors":["stream 'bar': line 14: field file is invalid when the component type is nanomsg (input)","stream 'foo': line 8: field file is invalid when the component type is nanomsg (output)"]}` 611 assert.Equal(t, expLints, response.Body.String()) 612 613 request, err = http.NewRequest("POST", "/streams?chilled=true", bytes.NewReader(body)) 614 require.NoError(t, err) 615 616 response = httptest.NewRecorder() 617 r.ServeHTTP(response, request) 618 assert.Equal(t, http.StatusOK, response.Code) 619 } 620 621 func TestTypeAPIDefaultConf(t *testing.T) { 622 mgr := manager.New( 623 manager.OptSetLogger(log.Noop()), 624 manager.OptSetStats(metrics.Noop()), 625 manager.OptSetManager(types.DudMgr{}), 626 manager.OptSetAPITimeout(time.Millisecond*100), 627 ) 628 629 r := router(mgr) 630 631 body := []byte(`{ 632 "input": { 633 "nanomsg": {} 634 }, 635 "output": { 636 "nanomsg": {} 637 } 638 }`) 639 640 request, err := http.NewRequest("POST", "/streams/foo", bytes.NewReader(body)) 641 require.NoError(t, err) 642 643 response := httptest.NewRecorder() 644 r.ServeHTTP(response, request) 645 assert.Equal(t, http.StatusOK, response.Code) 646 647 status, err := mgr.Read("foo") 648 require.NoError(t, err) 649 650 assert.Equal(t, status.Config().Input.Nanomsg.PollTimeout, "5s") 651 } 652 653 func TestTypeAPILinting(t *testing.T) { 654 mgr := manager.New( 655 manager.OptSetLogger(log.Noop()), 656 manager.OptSetStats(metrics.Noop()), 657 manager.OptSetManager(types.DudMgr{}), 658 manager.OptSetAPITimeout(time.Millisecond*100), 659 ) 660 661 r := router(mgr) 662 663 body := []byte(`{ 664 "input": { 665 "type":"nanomsg", 666 "file": {} 667 }, 668 "output": { 669 "nanomsg": {} 670 }, 671 "cache_resources": [ 672 {"label":"not_interested","memory":{}} 673 ] 674 }`) 675 676 request, err := http.NewRequest("POST", "/streams/foo", bytes.NewReader(body)) 677 require.NoError(t, err) 678 679 response := httptest.NewRecorder() 680 r.ServeHTTP(response, request) 681 assert.Equal(t, http.StatusBadRequest, response.Code) 682 683 expLints := `{"lint_errors":["line 4: field file is invalid when the component type is nanomsg (input)","line 9: field cache_resources not recognised"]}` 684 assert.Equal(t, expLints, response.Body.String()) 685 686 request, err = http.NewRequest("POST", "/streams/foo?chilled=true", bytes.NewReader(body)) 687 require.NoError(t, err) 688 689 response = httptest.NewRecorder() 690 r.ServeHTTP(response, request) 691 assert.Equal(t, http.StatusOK, response.Code) 692 } 693 694 func TestResourceAPILinting(t *testing.T) { 695 tests := []struct { 696 name string 697 ctype string 698 config string 699 lints []string 700 }{ 701 { 702 name: "cache bad", 703 ctype: "cache", 704 config: `memory: 705 ttl: 123 706 nope: nah 707 compaction_interval: 1s`, 708 lints: []string{ 709 "line 3: field nope not recognised", 710 }, 711 }, 712 { 713 name: "input bad", 714 ctype: "input", 715 config: `http_server: 716 path: /foo/bar 717 nope: nah`, 718 lints: []string{ 719 "line 3: field nope not recognised", 720 }, 721 }, 722 { 723 name: "output bad", 724 ctype: "output", 725 config: `http_server: 726 path: /foo/bar 727 nope: nah`, 728 lints: []string{ 729 "line 3: field nope not recognised", 730 }, 731 }, 732 { 733 name: "processor bad", 734 ctype: "processor", 735 config: `split: 736 size: 10 737 nope: nah`, 738 lints: []string{ 739 "line 3: field nope not recognised", 740 }, 741 }, 742 { 743 name: "rate limit bad", 744 ctype: "rate_limit", 745 config: `local: 746 count: 10 747 nope: nah`, 748 lints: []string{ 749 "line 3: field nope not recognised", 750 }, 751 }, 752 } 753 754 for _, test := range tests { 755 t.Run(test.name, func(t *testing.T) { 756 bmgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop()) 757 require.NoError(t, err) 758 759 mgr := manager.New( 760 manager.OptSetLogger(log.Noop()), 761 manager.OptSetStats(metrics.Noop()), 762 manager.OptSetManager(bmgr), 763 manager.OptSetAPITimeout(time.Millisecond*100), 764 ) 765 766 r := router(mgr) 767 768 url := fmt.Sprintf("/resources/%v/foo", test.ctype) 769 body := []byte(test.config) 770 771 request, err := http.NewRequest("POST", url, bytes.NewReader(body)) 772 require.NoError(t, err) 773 774 response := httptest.NewRecorder() 775 r.ServeHTTP(response, request) 776 assert.Equal(t, http.StatusBadRequest, response.Code) 777 778 expLints, err := json.Marshal(struct { 779 LintErrors []string `json:"lint_errors"` 780 }{ 781 LintErrors: test.lints, 782 }) 783 require.NoError(t, err) 784 785 assert.Equal(t, string(expLints), response.Body.String()) 786 787 request, err = http.NewRequest("POST", url+"?chilled=true", bytes.NewReader(body)) 788 require.NoError(t, err) 789 790 response = httptest.NewRecorder() 791 r.ServeHTTP(response, request) 792 assert.Equal(t, http.StatusOK, response.Code) 793 }) 794 } 795 } 796 797 func TestTypeAPIGetStats(t *testing.T) { 798 mgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop()) 799 require.NoError(t, err) 800 801 smgr := manager.New( 802 manager.OptSetLogger(log.Noop()), 803 manager.OptSetStats(metrics.Noop()), 804 manager.OptSetManager(mgr), 805 manager.OptSetAPITimeout(time.Millisecond*100), 806 ) 807 808 r := router(smgr) 809 810 err = smgr.Create("foo", harmlessConf()) 811 require.NoError(t, err) 812 813 <-time.After(time.Millisecond * 100) 814 815 request := genRequest("GET", "/streams/not_exist/stats", nil) 816 response := httptest.NewRecorder() 817 r.ServeHTTP(response, request) 818 assert.Equal(t, http.StatusNotFound, response.Code) 819 820 request = genRequest("POST", "/streams/foo/stats", nil) 821 response = httptest.NewRecorder() 822 r.ServeHTTP(response, request) 823 assert.Equal(t, http.StatusBadRequest, response.Code) 824 825 request = genRequest("GET", "/streams/foo/stats", nil) 826 response = httptest.NewRecorder() 827 r.ServeHTTP(response, request) 828 assert.Equal(t, http.StatusOK, response.Code) 829 830 stats, err := gabs.ParseJSON(response.Body.Bytes()) 831 require.NoError(t, err) 832 833 assert.Equal(t, 1.0, stats.S("input", "running").Data(), response.Body.String()) 834 } 835 836 func TestTypeAPISetResources(t *testing.T) { 837 bmgr, err := bmanager.NewV2(bmanager.NewResourceConfig(), types.DudMgr{}, log.Noop(), metrics.Noop()) 838 require.NoError(t, err) 839 840 tChan := make(chan types.Transaction) 841 bmgr.SetPipe("feed_in", tChan) 842 843 mgr := manager.New( 844 manager.OptSetLogger(log.Noop()), 845 manager.OptSetStats(metrics.Noop()), 846 manager.OptSetManager(bmgr), 847 manager.OptSetAPITimeout(time.Millisecond*100), 848 ) 849 850 tmpDir := t.TempDir() 851 852 dir1 := filepath.Join(tmpDir, "dir1") 853 require.NoError(t, os.MkdirAll(dir1, 0o750)) 854 855 dir2 := filepath.Join(tmpDir, "dir2") 856 require.NoError(t, os.MkdirAll(dir2, 0o750)) 857 858 r := router(mgr) 859 860 cacheConf := cache.NewConfig() 861 cacheConf.Type = cache.TypeFile 862 cacheConf.File.Directory = dir1 863 864 request := genYAMLRequest("POST", "/resources/cache/foocache?chilled=true", cacheConf) 865 response := httptest.NewRecorder() 866 r.ServeHTTP(response, request) 867 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 868 869 streamConf := stream.NewConfig() 870 streamConf.Input.Type = input.TypeInproc 871 streamConf.Input.Inproc = "feed_in" 872 streamConf.Output.Type = output.TypeCache 873 streamConf.Output.Cache.Key = `${! json("id") }` 874 streamConf.Output.Cache.Target = "foocache" 875 876 request = genYAMLRequest("POST", "/streams/foo?chilled=true", streamConf) 877 response = httptest.NewRecorder() 878 r.ServeHTTP(response, request) 879 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 880 881 resChan := make(chan types.Response) 882 select { 883 case tChan <- types.NewTransaction(message.New([][]byte{[]byte(`{"id":"first","content":"hello world"}`)}), resChan): 884 case <-time.After(time.Second * 5): 885 t.Fatal("timed out") 886 } 887 select { 888 case <-resChan: 889 case <-time.After(time.Second * 5): 890 t.Fatal("timed out") 891 } 892 893 cacheConf.File.Directory = dir2 894 895 request = genYAMLRequest("POST", "/resources/cache/foocache?chilled=true", cacheConf) 896 response = httptest.NewRecorder() 897 r.ServeHTTP(response, request) 898 assert.Equal(t, http.StatusOK, response.Code, response.Body.String()) 899 900 select { 901 case tChan <- types.NewTransaction(message.New([][]byte{[]byte(`{"id":"second","content":"hello world 2"}`)}), resChan): 902 case <-time.After(time.Second * 5): 903 t.Fatal("timed out") 904 } 905 select { 906 case <-resChan: 907 case <-time.After(time.Second * 5): 908 t.Fatal("timed out") 909 } 910 911 files, err := os.ReadDir(dir1) 912 require.NoError(t, err) 913 assert.Len(t, files, 1) 914 915 file1Bytes, err := os.ReadFile(filepath.Join(dir1, "first")) 916 require.NoError(t, err) 917 assert.Equal(t, `{"id":"first","content":"hello world"}`, string(file1Bytes)) 918 919 files, err = os.ReadDir(dir2) 920 require.NoError(t, err) 921 assert.Len(t, files, 1) 922 923 file2Bytes, err := os.ReadFile(filepath.Join(dir2, "second")) 924 require.NoError(t, err) 925 assert.Equal(t, `{"id":"second","content":"hello world 2"}`, string(file2Bytes)) 926 }