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 }