github.com/openshift-online/ocm-sdk-go@v0.1.473/metrics/transport_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 "io" 23 "net/http" 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 := NewTransportWrapper(). 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 apiServer *Server 48 metricsServer *MetricsServer 49 apiClient *http.Client 50 ) 51 52 BeforeEach(func() { 53 // Start the servers: 54 apiServer = NewServer() 55 metricsServer = NewMetricsServer() 56 57 // Create the API client: 58 apiWrapper, err := NewTransportWrapper(). 59 Path("/my/path"). 60 Subsystem("my"). 61 Registerer(metricsServer.Registry()). 62 Build() 63 Expect(err).ToNot(HaveOccurred()) 64 apiTransport := apiWrapper.Wrap(http.DefaultTransport) 65 Expect(apiTransport).ToNot(BeNil()) 66 apiClient = &http.Client{ 67 Transport: apiTransport, 68 } 69 }) 70 71 AfterEach(func() { 72 // Stop the servers: 73 metricsServer.Close() 74 apiServer.Close() 75 76 // Close connections: 77 apiClient.CloseIdleConnections() 78 }) 79 80 // Send sends a GET request to the API server. 81 var Send = func(method, path string) { 82 request, err := http.NewRequest(method, apiServer.URL()+path, nil) 83 Expect(err).ToNot(HaveOccurred()) 84 response, err := apiClient.Do(request) 85 Expect(err).ToNot(HaveOccurred()) 86 defer func() { 87 err = response.Body.Close() 88 Expect(err).ToNot(HaveOccurred()) 89 }() 90 _, err = io.Copy(io.Discard, response.Body) 91 Expect(err).ToNot(HaveOccurred()) 92 } 93 94 Describe("Request count", func() { 95 It("Honours subsystem", func() { 96 // Prepare the server: 97 apiServer.AppendHandlers( 98 RespondWith(http.StatusOK, nil), 99 ) 100 101 // Send the request: 102 Send(http.MethodGet, "/api") 103 104 // Verify the metrics: 105 metrics := metricsServer.Metrics() 106 Expect(metrics).To(MatchLine(`^my_request_count\{.*\} .*$`)) 107 }) 108 109 DescribeTable( 110 "Counts correctly", 111 func(count int) { 112 // Prepare the server: 113 for i := 0; i < count; i++ { 114 apiServer.AppendHandlers( 115 RespondWith(http.StatusOK, nil), 116 ) 117 } 118 119 // Send the requests: 120 for i := 0; i < count; i++ { 121 Send(http.MethodGet, "/api") 122 } 123 124 // Verify the metrics: 125 metrics := metricsServer.Metrics() 126 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*\} %d$`, count)) 127 }, 128 Entry("One", 1), 129 Entry("Two", 2), 130 Entry("Trhee", 3), 131 ) 132 133 DescribeTable( 134 "Includes method label", 135 func(method string) { 136 // Prepare the server: 137 apiServer.AppendHandlers( 138 RespondWith(http.StatusOK, nil), 139 ) 140 141 // Send the requests: 142 Send(method, "/api") 143 144 // Verify the metrics: 145 metrics := metricsServer.Metrics() 146 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*method="%s".*\} .*$`, method)) 147 }, 148 Entry("GET", http.MethodGet), 149 Entry("POST", http.MethodPost), 150 Entry("PATCH", http.MethodPatch), 151 Entry("DELETE", http.MethodDelete), 152 ) 153 154 DescribeTable( 155 "Includes path label", 156 func(path, label string) { 157 // Prepare the server: 158 apiServer.AppendHandlers( 159 RespondWith(http.StatusOK, nil), 160 ) 161 162 // Send the requests: 163 Send(http.MethodGet, path) 164 165 // Verify the metrics: 166 metrics := metricsServer.Metrics() 167 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*path="%s".*\} .*$`, label)) 168 }, 169 Entry( 170 "Empty", 171 "", 172 "/-", 173 ), 174 Entry( 175 "One slash", 176 "/", 177 "/-", 178 ), 179 Entry( 180 "Two slashes", 181 "//", 182 "/-", 183 ), 184 Entry( 185 "Tree slashes", 186 "///", 187 "/-", 188 ), 189 Entry( 190 "API root", 191 "/api", 192 "/api", 193 ), 194 Entry( 195 "API root with trailing slash", 196 "/api/", 197 "/api", 198 ), 199 Entry( 200 "Unknown root", 201 "/junk/", 202 "/-", 203 ), 204 Entry( 205 "Service root", 206 "/api/clusters_mgmt", 207 "/api/clusters_mgmt", 208 ), 209 Entry( 210 "Unknown service root", 211 "/api/junk", 212 "/-", 213 ), 214 Entry( 215 "Version root", 216 "/api/clusters_mgmt/v1", 217 "/api/clusters_mgmt/v1", 218 ), 219 Entry( 220 "Unknown version root", 221 "/api/junk/v1", 222 "/-", 223 ), 224 Entry( 225 "Collection", 226 "/api/clusters_mgmt/v1/clusters", 227 "/api/clusters_mgmt/v1/clusters", 228 ), 229 Entry( 230 "Unknown collection", 231 "/api/clusters_mgmt/v1/junk", 232 "/-", 233 ), 234 Entry( 235 "Collection item", 236 "/api/clusters_mgmt/v1/clusters/123", 237 "/api/clusters_mgmt/v1/clusters/-", 238 ), 239 Entry( 240 "Collection item action", 241 "/api/clusters_mgmt/v1/clusters/123/hibernate", 242 "/api/clusters_mgmt/v1/clusters/-/hibernate", 243 ), 244 Entry( 245 "Unknown collection item action", 246 "/api/clusters_mgmt/v1/clusters/123/junk", 247 "/-", 248 ), 249 Entry( 250 "Subcollection", 251 "/api/clusters_mgmt/v1/clusters/123/groups", 252 "/api/clusters_mgmt/v1/clusters/-/groups", 253 ), 254 Entry( 255 "Unknown subcollection", 256 "/api/clusters_mgmt/v1/clusters/123/junks", 257 "/-", 258 ), 259 Entry( 260 "Subcollection item", 261 "/api/clusters_mgmt/v1/clusters/123/groups/456", 262 "/api/clusters_mgmt/v1/clusters/-/groups/-", 263 ), 264 Entry( 265 "Too long", 266 "/api/clusters_mgmt/v1/clusters/123/groups/456/junk", 267 "/-", 268 ), 269 Entry( 270 "Explicitly specified path", 271 "/my/path", 272 "/my/path", 273 ), 274 Entry( 275 "Unknown path", 276 "/your/path", 277 "/-", 278 ), 279 ) 280 281 DescribeTable( 282 "Includes code label", 283 func(code int) { 284 // Prepare the server: 285 apiServer.AppendHandlers( 286 RespondWith(code, nil), 287 ) 288 289 // Send the requests: 290 Send(http.MethodGet, "/api") 291 292 // Verify the metrics: 293 metrics := metricsServer.Metrics() 294 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*code="%d".*\} .*$`, code)) 295 }, 296 Entry("200", http.StatusOK), 297 Entry("201", http.StatusCreated), 298 Entry("202", http.StatusAccepted), 299 Entry("401", http.StatusUnauthorized), 300 Entry("404", http.StatusNotFound), 301 Entry("500", http.StatusInternalServerError), 302 ) 303 304 DescribeTable( 305 "Includes API service label", 306 func(path, label string) { 307 // Prepare the server: 308 apiServer.AppendHandlers( 309 RespondWith(http.StatusOK, nil), 310 ) 311 312 // Send the requests: 313 Send(http.MethodGet, path) 314 315 // Verify the metrics: 316 metrics := metricsServer.Metrics() 317 Expect(metrics).To(MatchLine(`^\w+_request_count\{.*apiservice="%s".*\} .*$`, label)) 318 }, 319 Entry( 320 "Empty", 321 "", 322 "", 323 ), 324 Entry( 325 "Root", 326 "/", 327 "", 328 ), 329 Entry( 330 "Clusters root", 331 "/api/clusters_mgmt", 332 "ocm-clusters-service", 333 ), 334 Entry( 335 "Clusters version", 336 "/api/clusters_mgmt/v1", 337 "ocm-clusters-service", 338 ), 339 Entry( 340 "Clusters collection", 341 "/api/clusters_mgmt/v1/clusters", 342 "ocm-clusters-service", 343 ), 344 Entry( 345 "Clusters item", 346 "/api/clusters_mgmt/v1/clusters/123", 347 "ocm-clusters-service", 348 ), 349 Entry( 350 "Accounts root", 351 "/api/accounts_mgmt", 352 "ocm-accounts-service", 353 ), 354 Entry( 355 "Accounts version", 356 "/api/accounts_mgmt/v1", 357 "ocm-accounts-service", 358 ), 359 Entry( 360 "Accounts collection", 361 "/api/accounts_mgmt/v1/accounts", 362 "ocm-accounts-service", 363 ), 364 Entry( 365 "Accounts item", 366 "/api/accounts_mgmt/v1/accounts/123", 367 "ocm-accounts-service", 368 ), 369 Entry( 370 "Logs root", 371 "/api/service_logs", 372 "ocm-logs-service", 373 ), 374 Entry( 375 "Logs version", 376 "/api/service_logs/v1", 377 "ocm-logs-service", 378 ), 379 Entry( 380 "Logs collection", 381 "/api/service_logs/v1/accounts", 382 "ocm-logs-service", 383 ), 384 Entry( 385 "Logs item", 386 "/api/service_logs/v1/accounts/123", 387 "ocm-logs-service", 388 ), 389 ) 390 }) 391 392 Describe("Request duration", func() { 393 It("Honours subsystem", func() { 394 // Prepare the server: 395 apiServer.AppendHandlers( 396 RespondWith(http.StatusOK, nil), 397 ) 398 399 // Send the request: 400 Send(http.MethodGet, "/api") 401 402 // Verify the metrics: 403 metrics := metricsServer.Metrics() 404 Expect(metrics).To(MatchLine(`^my_request_duration_bucket\{.*\} .*$`)) 405 Expect(metrics).To(MatchLine(`^my_request_duration_sum\{.*\} .*$`)) 406 Expect(metrics).To(MatchLine(`^my_request_duration_count\{.*\} .*$`)) 407 }) 408 409 It("Honours buckets", func() { 410 // Prepare the server: 411 apiServer.AppendHandlers( 412 RespondWith(http.StatusOK, nil), 413 ) 414 415 // Send the request: 416 Send(http.MethodGet, "/api") 417 418 // Verify the metrics: 419 metrics := metricsServer.Metrics() 420 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="0.1"\} .*$`)) 421 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="1"\} .*$`)) 422 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="10"\} .*$`)) 423 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="30"\} .*$`)) 424 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*,le="\+Inf"\} .*$`)) 425 }) 426 427 DescribeTable( 428 "Counts correctly", 429 func(count int) { 430 // Prepare the server: 431 for i := 0; i < count; i++ { 432 apiServer.AppendHandlers( 433 RespondWith(http.StatusOK, nil), 434 ) 435 } 436 437 // Send the requests: 438 for i := 0; i < count; i++ { 439 Send(http.MethodGet, "/api") 440 } 441 442 // Verify the metrics: 443 metrics := metricsServer.Metrics() 444 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*\} %d$`, count)) 445 }, 446 Entry("One", 1), 447 Entry("Two", 2), 448 Entry("Trhee", 3), 449 ) 450 451 DescribeTable( 452 "Includes method label", 453 func(method string) { 454 // Prepare the server: 455 apiServer.AppendHandlers( 456 RespondWith(http.StatusOK, nil), 457 ) 458 459 // Send the requests: 460 Send(method, "/api") 461 462 // Verify the metrics: 463 metrics := metricsServer.Metrics() 464 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*method="%s".*\} .*$`, method)) 465 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*method="%s".*\} .*$`, method)) 466 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*method="%s".*\} .*$`, method)) 467 }, 468 Entry("GET", http.MethodGet), 469 Entry("POST", http.MethodPost), 470 Entry("PATCH", http.MethodPatch), 471 Entry("DELETE", http.MethodDelete), 472 ) 473 474 DescribeTable( 475 "Includes path label", 476 func(path, label string) { 477 // Prepare the server: 478 apiServer.AppendHandlers( 479 RespondWith(http.StatusOK, nil), 480 ) 481 482 // Send the requests: 483 Send(http.MethodGet, path) 484 485 // Verify the metrics: 486 metrics := metricsServer.Metrics() 487 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*path="%s".*\} .*$`, label)) 488 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*path="%s".*\} .*$`, label)) 489 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*path="%s".*\} .*$`, label)) 490 }, 491 Entry( 492 "Empty", 493 "", 494 "/-", 495 ), 496 Entry( 497 "One slash", 498 "/", 499 "/-", 500 ), 501 Entry( 502 "Two slashes", 503 "//", 504 "/-", 505 ), 506 Entry( 507 "Tree slashes", 508 "///", 509 "/-", 510 ), 511 Entry( 512 "API root", 513 "/api", 514 "/api", 515 ), 516 Entry( 517 "API root with trailing slash", 518 "/api/", 519 "/api", 520 ), 521 Entry( 522 "Unknown root", 523 "/junk/", 524 "/-", 525 ), 526 Entry( 527 "Service root", 528 "/api/clusters_mgmt", 529 "/api/clusters_mgmt", 530 ), 531 Entry( 532 "Unknown service root", 533 "/api/junk", 534 "/-", 535 ), 536 Entry( 537 "Version root", 538 "/api/clusters_mgmt/v1", 539 "/api/clusters_mgmt/v1", 540 ), 541 Entry( 542 "Unknown version root", 543 "/api/junk/v1", 544 "/-", 545 ), 546 Entry( 547 "Collection", 548 "/api/clusters_mgmt/v1/clusters", 549 "/api/clusters_mgmt/v1/clusters", 550 ), 551 Entry( 552 "Unknown collection", 553 "/api/clusters_mgmt/v1/junk", 554 "/-", 555 ), 556 Entry( 557 "Collection item", 558 "/api/clusters_mgmt/v1/clusters/123", 559 "/api/clusters_mgmt/v1/clusters/-", 560 ), 561 Entry( 562 "Collection item action", 563 "/api/clusters_mgmt/v1/clusters/123/hibernate", 564 "/api/clusters_mgmt/v1/clusters/-/hibernate", 565 ), 566 Entry( 567 "Unknown collection item action", 568 "/api/clusters_mgmt/v1/clusters/123/junk", 569 "/-", 570 ), 571 Entry( 572 "Subcollection", 573 "/api/clusters_mgmt/v1/clusters/123/groups", 574 "/api/clusters_mgmt/v1/clusters/-/groups", 575 ), 576 Entry( 577 "Unknown subcollection", 578 "/api/clusters_mgmt/v1/clusters/123/junks", 579 "/-", 580 ), 581 Entry( 582 "Subcollection item", 583 "/api/clusters_mgmt/v1/clusters/123/groups/456", 584 "/api/clusters_mgmt/v1/clusters/-/groups/-", 585 ), 586 Entry( 587 "Too long", 588 "/api/clusters_mgmt/v1/clusters/123/groups/456/junk", 589 "/-", 590 ), 591 Entry( 592 "Explicitly specified path", 593 "/my/path", 594 "/my/path", 595 ), 596 Entry( 597 "Unknown path", 598 "/your/path", 599 "/-", 600 ), 601 ) 602 603 DescribeTable( 604 "Includes code label", 605 func(code int) { 606 // Prepare the server: 607 apiServer.AppendHandlers( 608 RespondWith(code, nil), 609 ) 610 611 // Send the requests: 612 Send(http.MethodGet, "/api") 613 614 // Verify the metrics: 615 metrics := metricsServer.Metrics() 616 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*code="%d".*\} .*$`, code)) 617 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*code="%d".*\} .*$`, code)) 618 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*code="%d".*\} .*$`, code)) 619 }, 620 Entry("200", http.StatusOK), 621 Entry("201", http.StatusCreated), 622 Entry("202", http.StatusAccepted), 623 Entry("401", http.StatusUnauthorized), 624 Entry("404", http.StatusNotFound), 625 Entry("500", http.StatusInternalServerError), 626 ) 627 628 DescribeTable( 629 "Includes API service label", 630 func(path, label string) { 631 // Prepare the server: 632 apiServer.AppendHandlers( 633 RespondWith(http.StatusOK, nil), 634 ) 635 636 // Send the requests: 637 Send(http.MethodGet, path) 638 639 // Verify the metrics: 640 metrics := metricsServer.Metrics() 641 Expect(metrics).To(MatchLine(`^\w+_request_duration_bucket\{.*apiservice="%s".*\} .*$`, label)) 642 Expect(metrics).To(MatchLine(`^\w+_request_duration_sum\{.*apiservice="%s".*\} .*$`, label)) 643 Expect(metrics).To(MatchLine(`^\w+_request_duration_count\{.*apiservice="%s".*\} .*$`, label)) 644 }, 645 Entry( 646 "Empty", 647 "", 648 "", 649 ), 650 Entry( 651 "Root", 652 "/", 653 "", 654 ), 655 Entry( 656 "Clusters root", 657 "/api/clusters_mgmt", 658 "ocm-clusters-service", 659 ), 660 Entry( 661 "Clusters version", 662 "/api/clusters_mgmt/v1", 663 "ocm-clusters-service", 664 ), 665 Entry( 666 "Clusters collection", 667 "/api/clusters_mgmt/v1/clusters", 668 "ocm-clusters-service", 669 ), 670 Entry( 671 "Clusters item", 672 "/api/clusters_mgmt/v1/clusters/123", 673 "ocm-clusters-service", 674 ), 675 Entry( 676 "Accounts root", 677 "/api/accounts_mgmt", 678 "ocm-accounts-service", 679 ), 680 Entry( 681 "Accounts version", 682 "/api/accounts_mgmt/v1", 683 "ocm-accounts-service", 684 ), 685 Entry( 686 "Accounts collection", 687 "/api/accounts_mgmt/v1/accounts", 688 "ocm-accounts-service", 689 ), 690 Entry( 691 "Accounts item", 692 "/api/accounts_mgmt/v1/accounts/123", 693 "ocm-accounts-service", 694 ), 695 Entry( 696 "Logs root", 697 "/api/service_logs", 698 "ocm-logs-service", 699 ), 700 Entry( 701 "Logs version", 702 "/api/service_logs/v1", 703 "ocm-logs-service", 704 ), 705 Entry( 706 "Logs collection", 707 "/api/service_logs/v1/accounts", 708 "ocm-logs-service", 709 ), 710 Entry( 711 "Logs item", 712 "/api/service_logs/v1/accounts/123", 713 "ocm-logs-service", 714 ), 715 ) 716 }) 717 })