github.com/openshift-online/ocm-sdk-go@v0.1.473/metrics/handler_wrapper_test.go (about) 1 /* 2 Copyright (c) 2021 Red Hat, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // This file contains tests for the metrics transport wrapper. 18 19 package metrics 20 21 import ( 22 "net/http" 23 "net/http/httptest" 24 25 . "github.com/onsi/ginkgo/v2/dsl/core" // nolint 26 . "github.com/onsi/ginkgo/v2/dsl/table" // nolint 27 . "github.com/onsi/gomega" // nolint 28 . "github.com/onsi/gomega/ghttp" // nolint 29 30 . "github.com/openshift-online/ocm-sdk-go/testing" 31 ) 32 33 var _ = Describe("Create", func() { 34 It("Can't be created without a subsystem", func() { 35 wrapper, err := NewHandlerWrapper(). 36 Build() 37 Expect(err).To(HaveOccurred()) 38 Expect(wrapper).To(BeNil()) 39 message := err.Error() 40 Expect(message).To(ContainSubstring("subsystem")) 41 Expect(message).To(ContainSubstring("mandatory")) 42 }) 43 }) 44 45 var _ = Describe("Metrics", func() { 46 var ( 47 server *MetricsServer 48 wrapper *HandlerWrapper 49 handler http.Handler 50 ) 51 52 BeforeEach(func() { 53 var err error 54 55 // Start the metrics server: 56 server = NewMetricsServer() 57 58 // Create the wrapper: 59 wrapper, err = NewHandlerWrapper(). 60 Path("/my/path"). 61 Subsystem("my"). 62 Registerer(server.Registry()). 63 Build() 64 Expect(err).ToNot(HaveOccurred()) 65 }) 66 67 AfterEach(func() { 68 // Stop the metrics server: 69 server.Close() 70 }) 71 72 // Get sends a GET request to the API server. 73 var Send = func(method, path string) { 74 request := httptest.NewRequest(method, "http://localhost"+path, nil) 75 recorder := httptest.NewRecorder() 76 handler.ServeHTTP(recorder, request) 77 } 78 79 It("Calls wrapped handler", func() { 80 // Prepare the handler: 81 called := false 82 handler = wrapper.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 83 called = true 84 })) 85 86 // Send the request: 87 Send(http.MethodGet, "/api") 88 89 // Check that the wrapped handler was called: 90 Expect(called).To(BeTrue()) 91 }) 92 93 Describe("Request count", func() { 94 It("Honours subsystem", func() { 95 // Prepare the handler: 96 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 97 98 // Send the request: 99 Send(http.MethodGet, "/api") 100 101 // Verify the metrics: 102 metrics := server.Metrics() 103 Expect(metrics).To(MatchLine(`^my_request_count\{.*\} .*$`)) 104 }) 105 106 DescribeTable( 107 "Counts correctly", 108 func(count int) { 109 // Prepare the handler: 110 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 111 112 // Send the requests: 113 for i := 0; i < count; i++ { 114 Send(http.MethodGet, "/api") 115 } 116 117 // Verify the metrics: 118 metrics := server.Metrics() 119 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*\} %d$`, count)) 120 }, 121 Entry("One", 1), 122 Entry("Two", 2), 123 Entry("Trhee", 3), 124 ) 125 126 DescribeTable( 127 "Includes method label", 128 func(method string) { 129 // Prepare the handler: 130 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 131 132 // Send the requests: 133 Send(method, "/api") 134 135 // Verify the metrics: 136 metrics := server.Metrics() 137 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*method="%s".*\} .*$`, method)) 138 }, 139 Entry("GET", http.MethodGet), 140 Entry("POST", http.MethodPost), 141 Entry("PATCH", http.MethodPatch), 142 Entry("DELETE", http.MethodDelete), 143 ) 144 145 DescribeTable( 146 "Includes path label", 147 func(path, label string) { 148 // Prepare the handler: 149 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 150 151 // Send the requests: 152 Send(http.MethodGet, path) 153 154 // Verify the metrics: 155 metrics := server.Metrics() 156 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*path="%s".*\} .*$`, label)) 157 }, 158 Entry( 159 "Empty", 160 "", 161 "/-", 162 ), 163 Entry( 164 "One slash", 165 "/", 166 "/-", 167 ), 168 Entry( 169 "Two slashes", 170 "//", 171 "/-", 172 ), 173 Entry( 174 "Tree slashes", 175 "///", 176 "/-", 177 ), 178 Entry( 179 "API root", 180 "/api", 181 "/api", 182 ), 183 Entry( 184 "API root with trailing slash", 185 "/api/", 186 "/api", 187 ), 188 Entry( 189 "Unknown root", 190 "/junk/", 191 "/-", 192 ), 193 Entry( 194 "Service root", 195 "/api/clusters_mgmt", 196 "/api/clusters_mgmt", 197 ), 198 Entry( 199 "Unknown service root", 200 "/api/junk", 201 "/-", 202 ), 203 Entry( 204 "Version root", 205 "/api/clusters_mgmt/v1", 206 "/api/clusters_mgmt/v1", 207 ), 208 Entry( 209 "Unknown version root", 210 "/api/junk/v1", 211 "/-", 212 ), 213 Entry( 214 "Collection", 215 "/api/clusters_mgmt/v1/clusters", 216 "/api/clusters_mgmt/v1/clusters", 217 ), 218 Entry( 219 "Unknown collection", 220 "/api/clusters_mgmt/v1/junk", 221 "/-", 222 ), 223 Entry( 224 "Collection item", 225 "/api/clusters_mgmt/v1/clusters/123", 226 "/api/clusters_mgmt/v1/clusters/-", 227 ), 228 Entry( 229 "Collection item action", 230 "/api/clusters_mgmt/v1/clusters/123/hibernate", 231 "/api/clusters_mgmt/v1/clusters/-/hibernate", 232 ), 233 Entry( 234 "Unknown collection item action", 235 "/api/clusters_mgmt/v1/clusters/123/junk", 236 "/-", 237 ), 238 Entry( 239 "Subcollection", 240 "/api/clusters_mgmt/v1/clusters/123/groups", 241 "/api/clusters_mgmt/v1/clusters/-/groups", 242 ), 243 Entry( 244 "Unknown subcollection", 245 "/api/clusters_mgmt/v1/clusters/123/junks", 246 "/-", 247 ), 248 Entry( 249 "Subcollection item", 250 "/api/clusters_mgmt/v1/clusters/123/groups/456", 251 "/api/clusters_mgmt/v1/clusters/-/groups/-", 252 ), 253 Entry( 254 "Too long", 255 "/api/clusters_mgmt/v1/clusters/123/groups/456/junk", 256 "/-", 257 ), 258 Entry( 259 "Explicitly specified path", 260 "/my/path", 261 "/my/path", 262 ), 263 Entry( 264 "Unknown path", 265 "/your/path", 266 "/-", 267 ), 268 ) 269 270 DescribeTable( 271 "Includes code label", 272 func(code int) { 273 // Prepare the handler: 274 handler = wrapper.Wrap(RespondWith(code, nil)) 275 276 // Send the requests: 277 Send(http.MethodGet, "/api") 278 279 // Verify the metrics: 280 metrics := server.Metrics() 281 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*code="%d".*\} .*$`, code)) 282 }, 283 Entry("200", http.StatusOK), 284 Entry("201", http.StatusCreated), 285 Entry("202", http.StatusAccepted), 286 Entry("401", http.StatusUnauthorized), 287 Entry("404", http.StatusNotFound), 288 Entry("500", http.StatusInternalServerError), 289 ) 290 291 DescribeTable( 292 "Includes API service label", 293 func(path, label string) { 294 // Prepare the handler: 295 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 296 297 // Send the requests: 298 Send(http.MethodGet, path) 299 300 // Verify the metrics: 301 metrics := server.Metrics() 302 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*apiservice="%s".*\} .*$`, label)) 303 }, 304 Entry( 305 "Empty", 306 "", 307 "", 308 ), 309 Entry( 310 "Root", 311 "/", 312 "", 313 ), 314 Entry( 315 "Clusters root", 316 "/api/clusters_mgmt", 317 "ocm-clusters-service", 318 ), 319 Entry( 320 "Clusters version", 321 "/api/clusters_mgmt/v1", 322 "ocm-clusters-service", 323 ), 324 Entry( 325 "Clusters collection", 326 "/api/clusters_mgmt/v1/clusters", 327 "ocm-clusters-service", 328 ), 329 Entry( 330 "Clusters item", 331 "/api/clusters_mgmt/v1/clusters/123", 332 "ocm-clusters-service", 333 ), 334 Entry( 335 "Accounts root", 336 "/api/accounts_mgmt", 337 "ocm-accounts-service", 338 ), 339 Entry( 340 "Accounts version", 341 "/api/accounts_mgmt/v1", 342 "ocm-accounts-service", 343 ), 344 Entry( 345 "Accounts collection", 346 "/api/accounts_mgmt/v1/accounts", 347 "ocm-accounts-service", 348 ), 349 Entry( 350 "Accounts item", 351 "/api/accounts_mgmt/v1/accounts/123", 352 "ocm-accounts-service", 353 ), 354 Entry( 355 "Logs root", 356 "/api/service_logs", 357 "ocm-logs-service", 358 ), 359 Entry( 360 "Logs version", 361 "/api/service_logs/v1", 362 "ocm-logs-service", 363 ), 364 Entry( 365 "Logs collection", 366 "/api/service_logs/v1/accounts", 367 "ocm-logs-service", 368 ), 369 Entry( 370 "Logs item", 371 "/api/service_logs/v1/accounts/123", 372 "ocm-logs-service", 373 ), 374 Entry( 375 "Future service not in existing service list", 376 "/api/future_mgmt/v1/", 377 "ocm-future_mgmt"), 378 ) 379 }) 380 381 Describe("Request duration", func() { 382 It("Honours subsystem", func() { 383 // Prepare the handler: 384 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 385 386 // Send the request: 387 Send(http.MethodGet, "/api") 388 389 // Verify the metrics: 390 metrics := server.Metrics() 391 Expect(metrics).To(MatchLine(`^my_request_duration_bucket\{.*\} .*$`)) 392 Expect(metrics).To(MatchLine(`^my_request_duration_sum\{.*\} .*$`)) 393 Expect(metrics).To(MatchLine(`^my_request_duration_count\{.*\} .*$`)) 394 }) 395 396 It("Honours buckets", func() { 397 // Prepare the handler: 398 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 399 400 // Send the request: 401 Send(http.MethodGet, "/api") 402 403 // Verify the metrics: 404 metrics := server.Metrics() 405 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="0.1"\} .*$`)) 406 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="1"\} .*$`)) 407 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="10"\} .*$`)) 408 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="30"\} .*$`)) 409 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="\+Inf"\} .*$`)) 410 }) 411 412 DescribeTable( 413 "Counts correctly", 414 func(count int) { 415 // Prepare the handler: 416 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 417 418 // Send the requests: 419 for i := 0; i < count; i++ { 420 Send(http.MethodGet, "/api") 421 } 422 423 // Verify the metrics: 424 metrics := server.Metrics() 425 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*\} %d$`, count)) 426 }, 427 Entry("One", 1), 428 Entry("Two", 2), 429 Entry("Trhee", 3), 430 ) 431 432 DescribeTable( 433 "Includes method label", 434 func(method string) { 435 // Prepare the handler: 436 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 437 438 // Send the requests: 439 Send(method, "/api") 440 441 // Verify the metrics: 442 metrics := server.Metrics() 443 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*method="%s".*\} .*$`, method)) 444 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*method="%s".*\} .*$`, method)) 445 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*method="%s".*\} .*$`, method)) 446 }, 447 Entry("GET", http.MethodGet), 448 Entry("POST", http.MethodPost), 449 Entry("PATCH", http.MethodPatch), 450 Entry("DELETE", http.MethodDelete), 451 ) 452 453 DescribeTable( 454 "Includes path label", 455 func(path, label string) { 456 // Prepare the handler: 457 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 458 459 // Send the requests: 460 Send(http.MethodGet, path) 461 462 // Verify the metrics: 463 metrics := server.Metrics() 464 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*path="%s".*\} .*$`, label)) 465 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*path="%s".*\} .*$`, label)) 466 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*path="%s".*\} .*$`, label)) 467 }, 468 Entry( 469 "Empty", 470 "", 471 "/-", 472 ), 473 Entry( 474 "One slash", 475 "/", 476 "/-", 477 ), 478 Entry( 479 "Two slashes", 480 "//", 481 "/-", 482 ), 483 Entry( 484 "Tree slashes", 485 "///", 486 "/-", 487 ), 488 Entry( 489 "API root", 490 "/api", 491 "/api", 492 ), 493 Entry( 494 "API root with trailing slash", 495 "/api/", 496 "/api", 497 ), 498 Entry( 499 "Unknown root", 500 "/junk/", 501 "/-", 502 ), 503 Entry( 504 "Service root", 505 "/api/clusters_mgmt", 506 "/api/clusters_mgmt", 507 ), 508 Entry( 509 "Unknown service root", 510 "/api/junk", 511 "/-", 512 ), 513 Entry( 514 "Version root", 515 "/api/clusters_mgmt/v1", 516 "/api/clusters_mgmt/v1", 517 ), 518 Entry( 519 "Unknown version root", 520 "/api/junk/v1", 521 "/-", 522 ), 523 Entry( 524 "Collection", 525 "/api/clusters_mgmt/v1/clusters", 526 "/api/clusters_mgmt/v1/clusters", 527 ), 528 Entry( 529 "Unknown collection", 530 "/api/clusters_mgmt/v1/junk", 531 "/-", 532 ), 533 Entry( 534 "Collection item", 535 "/api/clusters_mgmt/v1/clusters/123", 536 "/api/clusters_mgmt/v1/clusters/-", 537 ), 538 Entry( 539 "Collection item action", 540 "/api/clusters_mgmt/v1/clusters/123/hibernate", 541 "/api/clusters_mgmt/v1/clusters/-/hibernate", 542 ), 543 Entry( 544 "Unknown collection item action", 545 "/api/clusters_mgmt/v1/clusters/123/junk", 546 "/-", 547 ), 548 Entry( 549 "Subcollection", 550 "/api/clusters_mgmt/v1/clusters/123/groups", 551 "/api/clusters_mgmt/v1/clusters/-/groups", 552 ), 553 Entry( 554 "Unknown subcollection", 555 "/api/clusters_mgmt/v1/clusters/123/junks", 556 "/-", 557 ), 558 Entry( 559 "Subcollection item", 560 "/api/clusters_mgmt/v1/clusters/123/groups/456", 561 "/api/clusters_mgmt/v1/clusters/-/groups/-", 562 ), 563 Entry( 564 "Too long", 565 "/api/clusters_mgmt/v1/clusters/123/groups/456/junk", 566 "/-", 567 ), 568 Entry( 569 "Explicitly specified path", 570 "/my/path", 571 "/my/path", 572 ), 573 Entry( 574 "Unknown path", 575 "/your/path", 576 "/-", 577 ), 578 ) 579 580 DescribeTable( 581 "Includes code label", 582 func(code int) { 583 // Prepare the handler: 584 handler = wrapper.Wrap(RespondWith(code, nil)) 585 586 // Send the requests: 587 Send(http.MethodGet, "/api") 588 589 // Verify the metrics: 590 metrics := server.Metrics() 591 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*code="%d".*\} .*$`, code)) 592 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*code="%d".*\} .*$`, code)) 593 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*code="%d".*\} .*$`, code)) 594 }, 595 Entry("200", http.StatusOK), 596 Entry("201", http.StatusCreated), 597 Entry("202", http.StatusAccepted), 598 Entry("401", http.StatusUnauthorized), 599 Entry("404", http.StatusNotFound), 600 Entry("500", http.StatusInternalServerError), 601 ) 602 603 DescribeTable( 604 "Includes API service label", 605 func(path, label string) { 606 // Prepare the handler: 607 handler = wrapper.Wrap(RespondWith(http.StatusOK, nil)) 608 609 // Send the requests: 610 Send(http.MethodGet, path) 611 612 // Verify the metrics: 613 metrics := server.Metrics() 614 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*apiservice="%s".*\} .*$`, label)) 615 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*apiservice="%s".*\} .*$`, label)) 616 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*apiservice="%s".*\} .*$`, label)) 617 }, 618 Entry( 619 "Empty", 620 "", 621 "", 622 ), 623 Entry( 624 "Root", 625 "/", 626 "", 627 ), 628 Entry( 629 "Clusters root", 630 "/api/clusters_mgmt", 631 "ocm-clusters-service", 632 ), 633 Entry( 634 "Clusters version", 635 "/api/clusters_mgmt/v1", 636 "ocm-clusters-service", 637 ), 638 Entry( 639 "Clusters collection", 640 "/api/clusters_mgmt/v1/clusters", 641 "ocm-clusters-service", 642 ), 643 Entry( 644 "Clusters item", 645 "/api/clusters_mgmt/v1/clusters/123", 646 "ocm-clusters-service", 647 ), 648 Entry( 649 "Accounts root", 650 "/api/accounts_mgmt", 651 "ocm-accounts-service", 652 ), 653 Entry( 654 "Accounts version", 655 "/api/accounts_mgmt/v1", 656 "ocm-accounts-service", 657 ), 658 Entry( 659 "Accounts collection", 660 "/api/accounts_mgmt/v1/accounts", 661 "ocm-accounts-service", 662 ), 663 Entry( 664 "Accounts item", 665 "/api/accounts_mgmt/v1/accounts/123", 666 "ocm-accounts-service", 667 ), 668 Entry( 669 "Logs root", 670 "/api/service_logs", 671 "ocm-logs-service", 672 ), 673 Entry( 674 "Logs version", 675 "/api/service_logs/v1", 676 "ocm-logs-service", 677 ), 678 Entry( 679 "Logs collection", 680 "/api/service_logs/v1/accounts", 681 "ocm-logs-service", 682 ), 683 Entry( 684 "Logs item", 685 "/api/service_logs/v1/accounts/123", 686 "ocm-logs-service", 687 ), 688 ) 689 }) 690 })