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  })