github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/promremote/options_test.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package promremote 22 23 import ( 24 "testing" 25 "time" 26 27 "github.com/m3db/m3/src/cmd/services/m3query/config" 28 "github.com/m3db/m3/src/query/storage/m3" 29 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 30 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 "github.com/uber-go/tally" 34 "go.uber.org/zap" 35 ) 36 37 func TestNewFromConfiguration(t *testing.T) { 38 logger := zap.NewNop() 39 opts, err := NewOptions(&config.PrometheusRemoteBackendConfiguration{ 40 Endpoints: []config.PrometheusRemoteBackendEndpointConfiguration{{ 41 Name: "testEndpoint", 42 Address: "testAddress", 43 StoragePolicy: &config.PrometheusRemoteBackendStoragePolicyConfiguration{ 44 Resolution: time.Second, 45 Retention: time.Millisecond, 46 Downsample: &m3.DownsampleClusterStaticNamespaceConfiguration{ 47 All: true, 48 }, 49 }, 50 }}, 51 RequestTimeout: ptrDuration(time.Nanosecond), 52 ConnectTimeout: ptrDuration(time.Microsecond), 53 KeepAlive: ptrDuration(time.Millisecond), 54 IdleConnTimeout: ptrDuration(time.Second), 55 MaxIdleConns: ptrInt(1), 56 }, tally.NoopScope, logger) 57 require.NoError(t, err) 58 59 assert.Equal(t, []EndpointOptions{{ 60 name: "testEndpoint", 61 address: "testAddress", 62 attributes: storagemetadata.Attributes{ 63 MetricsType: storagemetadata.AggregatedMetricsType, 64 Resolution: time.Second, 65 Retention: time.Millisecond, 66 }, 67 downsampleOptions: &m3.ClusterNamespaceDownsampleOptions{ 68 All: true, 69 }, 70 }}, opts.endpoints) 71 assert.Equal(t, tally.NoopScope, opts.scope) 72 assert.Equal(t, logger, opts.logger) 73 assert.Equal(t, time.Nanosecond, opts.httpOptions.RequestTimeout) 74 assert.Equal(t, time.Microsecond, opts.httpOptions.ConnectTimeout) 75 assert.Equal(t, time.Millisecond, opts.httpOptions.KeepAlive) 76 assert.Equal(t, time.Second, opts.httpOptions.IdleConnTimeout) 77 assert.Equal(t, 1, opts.httpOptions.MaxIdleConns) 78 assert.Equal(t, true, opts.httpOptions.DisableCompression) 79 } 80 81 func TestUnaggregatedEndpoint(t *testing.T) { 82 opts, err := NewOptions(&config.PrometheusRemoteBackendConfiguration{ 83 Endpoints: []config.PrometheusRemoteBackendEndpointConfiguration{{ 84 Name: "testEndpoint", 85 Address: "testAddress", 86 }}, 87 }, tally.NoopScope, zap.NewNop()) 88 require.NoError(t, err) 89 assert.Equal(t, storagemetadata.UnaggregatedMetricsType, opts.endpoints[0].attributes.MetricsType) 90 assert.Equal(t, time.Duration(0), opts.endpoints[0].attributes.Retention) 91 assert.Equal(t, time.Duration(0), opts.endpoints[0].attributes.Resolution) 92 assert.Nil(t, opts.endpoints[0].downsampleOptions) 93 } 94 95 func TestDefaultDownsampleAll(t *testing.T) { 96 opts, err := NewOptions(&config.PrometheusRemoteBackendConfiguration{ 97 Endpoints: []config.PrometheusRemoteBackendEndpointConfiguration{{ 98 Name: "testEndpoint", 99 Address: "testAddress", 100 StoragePolicy: &config.PrometheusRemoteBackendStoragePolicyConfiguration{ 101 Resolution: time.Second, 102 Retention: time.Millisecond, 103 }, 104 }}, 105 }, tally.NoopScope, zap.NewNop()) 106 require.NoError(t, err) 107 assert.NotNil(t, opts.endpoints[0].downsampleOptions) 108 assert.True(t, opts.endpoints[0].downsampleOptions.All) 109 } 110 111 func TestHTTPDefaults(t *testing.T) { 112 cfg, err := NewOptions(&config.PrometheusRemoteBackendConfiguration{ 113 Endpoints: []config.PrometheusRemoteBackendEndpointConfiguration{getValidEndpointConfiguration()}, 114 }, tally.NoopScope, zap.NewNop()) 115 require.NoError(t, err) 116 opts := cfg.httpOptions 117 118 assert.Equal(t, 60*time.Second, opts.RequestTimeout) 119 assert.Equal(t, 5*time.Second, opts.ConnectTimeout) 120 assert.Equal(t, 60*time.Second, opts.KeepAlive) 121 assert.Equal(t, 60*time.Second, opts.IdleConnTimeout) 122 assert.Equal(t, 100, opts.MaxIdleConns) 123 assert.Equal(t, true, opts.DisableCompression) 124 } 125 126 func TestValidation(t *testing.T) { 127 t.Run("can't be nil", func(t *testing.T) { 128 assertValidationError(t, nil, "prometheusRemoteBackend configuration is required") 129 }) 130 131 t.Run("at least 1 endpoint", func(t *testing.T) { 132 cfg := getValidConfig() 133 cfg.Endpoints = nil 134 assertValidationError(t, &cfg, "at least one endpoint must be configured when using prom-remote backend type") 135 }) 136 137 t.Run("valid endpoint", func(t *testing.T) { 138 cfg := getValidConfig() 139 cfg.Endpoints[0].Address = "" 140 assertValidationError(t, &cfg, "endpoint address must be set") 141 }) 142 143 t.Run("name required for endpoint", func(t *testing.T) { 144 cfg := getValidConfig() 145 cfg.Endpoints[0].Name = "" 146 assertValidationError(t, &cfg, "endpoint name must be set") 147 cfg.Endpoints[0].Name = " " 148 assertValidationError(t, &cfg, "endpoint name must be set") 149 }) 150 151 t.Run("name must be unique", func(t *testing.T) { 152 cfg := getValidConfig() 153 endpoint := getValidEndpointConfiguration() 154 cfg.Endpoints = []config.PrometheusRemoteBackendEndpointConfiguration{endpoint, endpoint} 155 assertValidationError(t, &cfg, "endpoint name testName is not unique, ensure all endpoint names are unique") 156 }) 157 158 t.Run("non negative keep alive", func(t *testing.T) { 159 cfg := getValidConfig() 160 cfg.KeepAlive = ptrDuration(-1) 161 assertValidationError(t, &cfg, "keepAlive can't be negative") 162 }) 163 164 t.Run("non negative max idle conns", func(t *testing.T) { 165 cfg := getValidConfig() 166 cfg.MaxIdleConns = ptrInt(-1) 167 assertValidationError(t, &cfg, "maxIdleConns can't be negative") 168 }) 169 170 t.Run("non negative idle conn timeout", func(t *testing.T) { 171 cfg := getValidConfig() 172 cfg.IdleConnTimeout = ptrDuration(-1) 173 assertValidationError(t, &cfg, "idleConnTimeout can't be negative") 174 }) 175 176 t.Run("non negative request timeout", func(t *testing.T) { 177 cfg := getValidConfig() 178 cfg.RequestTimeout = ptrDuration(-1) 179 assertValidationError(t, &cfg, "requestTimeout can't be negative") 180 }) 181 182 t.Run("non negative connect timeout", func(t *testing.T) { 183 cfg := getValidConfig() 184 cfg.ConnectTimeout = ptrDuration(-1) 185 assertValidationError(t, &cfg, "connectTimeout can't be negative") 186 }) 187 } 188 189 func TestValidateEndpoint(t *testing.T) { 190 t.Run("address required", func(t *testing.T) { 191 cfg := getValidEndpointConfiguration() 192 cfg.Address = "" 193 assertEndpointValidationError(t, cfg, "endpoint address must be set") 194 }) 195 196 t.Run("address spaces trimmed", func(t *testing.T) { 197 cfg := getValidEndpointConfiguration() 198 cfg.Address = " " 199 assertEndpointValidationError(t, cfg, "endpoint address must be set") 200 }) 201 202 t.Run("storage policy is optional", func(t *testing.T) { 203 cfg := getValidEndpointConfiguration() 204 cfg.StoragePolicy = nil 205 err := validateEndpointConfiguration(cfg) 206 require.NoError(t, err) 207 }) 208 209 t.Run("retention must be positive", func(t *testing.T) { 210 cfg := getValidEndpointConfiguration() 211 cfg.StoragePolicy.Retention = -1 212 assertEndpointValidationError(t, cfg, "endpoint retention must be positive") 213 214 cfg.StoragePolicy.Retention = 0 215 assertEndpointValidationError(t, cfg, "endpoint retention must be positive") 216 }) 217 218 t.Run("resolution must be positive", func(t *testing.T) { 219 cfg := getValidEndpointConfiguration() 220 cfg.StoragePolicy.Resolution = -1 221 assertEndpointValidationError(t, cfg, "endpoint resolution must be positive") 222 223 cfg.StoragePolicy.Resolution = 0 224 assertEndpointValidationError(t, cfg, "endpoint resolution must be positive") 225 }) 226 } 227 228 func assertValidationError(t *testing.T, cfg *config.PrometheusRemoteBackendConfiguration, expectedMsg string) { 229 _, err := NewOptions(cfg, tally.NoopScope, zap.NewNop()) 230 require.Error(t, err) 231 assert.Contains(t, err.Error(), expectedMsg) 232 } 233 234 func assertEndpointValidationError( 235 t *testing.T, 236 cfg config.PrometheusRemoteBackendEndpointConfiguration, 237 expectedMsg string, 238 ) { 239 err := validateEndpointConfiguration(cfg) 240 require.Error(t, err) 241 assert.Contains(t, err.Error(), expectedMsg) 242 } 243 244 func getValidConfig() config.PrometheusRemoteBackendConfiguration { 245 return config.PrometheusRemoteBackendConfiguration{ 246 Endpoints: []config.PrometheusRemoteBackendEndpointConfiguration{getValidEndpointConfiguration()}, 247 } 248 } 249 250 func getValidEndpointConfiguration() config.PrometheusRemoteBackendEndpointConfiguration { 251 return config.PrometheusRemoteBackendEndpointConfiguration{ 252 Name: "testName", 253 Address: "testAddress", 254 StoragePolicy: &config.PrometheusRemoteBackendStoragePolicyConfiguration{ 255 Retention: time.Second, 256 Resolution: time.Second, 257 }, 258 } 259 } 260 261 func ptrDuration(n time.Duration) *time.Duration { return &n } 262 263 func ptrInt(n int) *int { return &n }