github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/promremote/storage_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  	"context"
    25  	"io"
    26  	"math/rand"
    27  	"net/http"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/storage"
    33  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    34  	"github.com/m3db/m3/src/query/storage/promremote/promremotetest"
    35  	"github.com/m3db/m3/src/query/ts"
    36  	xerrors "github.com/m3db/m3/src/x/errors"
    37  	"github.com/m3db/m3/src/x/tallytest"
    38  	xtime "github.com/m3db/m3/src/x/time"
    39  
    40  	"github.com/prometheus/prometheus/prompb"
    41  	"github.com/stretchr/testify/assert"
    42  	"github.com/stretchr/testify/require"
    43  	"github.com/uber-go/tally"
    44  	"go.uber.org/zap"
    45  )
    46  
    47  var (
    48  	logger, _ = zap.NewDevelopment()
    49  	scope     = tally.NewTestScope("test_scope", map[string]string{})
    50  )
    51  
    52  func TestWrite(t *testing.T) {
    53  	fakeProm := promremotetest.NewServer(t)
    54  	defer fakeProm.Close()
    55  
    56  	promStorage, err := NewStorage(Options{
    57  		endpoints: []EndpointOptions{{name: "testEndpoint", address: fakeProm.WriteAddr()}},
    58  		scope:     scope,
    59  		logger:    logger,
    60  	})
    61  	require.NoError(t, err)
    62  	defer closeWithCheck(t, promStorage)
    63  
    64  	now := xtime.Now()
    65  	wq, err := storage.NewWriteQuery(storage.WriteQueryOptions{
    66  		Tags: models.Tags{
    67  			Opts: models.NewTagOptions(),
    68  			Tags: []models.Tag{{
    69  				Name:  []byte("test_tag_name"),
    70  				Value: []byte("test_tag_value"),
    71  			}},
    72  		},
    73  		Datapoints: ts.Datapoints{{
    74  			Timestamp: now,
    75  			Value:     42,
    76  		}},
    77  		Unit: xtime.Millisecond,
    78  	})
    79  	require.NoError(t, err)
    80  	err = promStorage.Write(context.TODO(), wq)
    81  	require.NoError(t, err)
    82  
    83  	promWrite := fakeProm.GetLastWriteRequest()
    84  
    85  	expectedLabel := prompb.Label{
    86  		Name:  "test_tag_name",
    87  		Value: "test_tag_value",
    88  	}
    89  	expectedSample := prompb.Sample{
    90  		Value:     42,
    91  		Timestamp: now.ToNormalizedTime(time.Millisecond),
    92  	}
    93  	require.Len(t, promWrite.Timeseries, 1)
    94  	require.Len(t, promWrite.Timeseries[0].Labels, 1)
    95  	require.Len(t, promWrite.Timeseries[0].Samples, 1)
    96  	assert.Equal(t, expectedLabel, promWrite.Timeseries[0].Labels[0])
    97  	assert.Equal(t, expectedSample, promWrite.Timeseries[0].Samples[0])
    98  
    99  	tallytest.AssertCounterValue(
   100  		t, 1, scope.Snapshot(), "test_scope.prom_remote_storage.writeSingle.success",
   101  		map[string]string{"endpoint_name": "testEndpoint"},
   102  	)
   103  	tallytest.AssertCounterValue(
   104  		t, 0, scope.Snapshot(), "test_scope.prom_remote_storage.writeSingle.errors",
   105  		map[string]string{"endpoint_name": "testEndpoint"},
   106  	)
   107  }
   108  
   109  func TestWriteBasedOnRetention(t *testing.T) {
   110  	promShortRetention := promremotetest.NewServer(t)
   111  	defer promShortRetention.Close()
   112  	promMediumRetention := promremotetest.NewServer(t)
   113  	defer promMediumRetention.Close()
   114  	promLongRetention := promremotetest.NewServer(t)
   115  	defer promLongRetention.Close()
   116  	promLongRetention2 := promremotetest.NewServer(t)
   117  	defer promLongRetention2.Close()
   118  	reset := func() {
   119  		promShortRetention.Reset()
   120  		promMediumRetention.Reset()
   121  		promLongRetention.Reset()
   122  		promLongRetention2.Reset()
   123  	}
   124  
   125  	mediumRetentionAttr := storagemetadata.Attributes{
   126  		MetricsType: storagemetadata.AggregatedMetricsType,
   127  		Retention:   720 * time.Hour,
   128  		Resolution:  5 * time.Minute,
   129  	}
   130  	shortRetentionAttr := storagemetadata.Attributes{
   131  		MetricsType: storagemetadata.AggregatedMetricsType,
   132  		Retention:   120 * time.Hour,
   133  		Resolution:  15 * time.Second,
   134  	}
   135  	longRetentionAttr := storagemetadata.Attributes{
   136  		Resolution: 10 * time.Minute,
   137  		Retention:  8760 * time.Hour,
   138  	}
   139  	promStorage, err := NewStorage(Options{
   140  		endpoints: []EndpointOptions{
   141  			{
   142  				address:    promShortRetention.WriteAddr(),
   143  				attributes: shortRetentionAttr,
   144  			},
   145  			{
   146  				address:    promMediumRetention.WriteAddr(),
   147  				attributes: mediumRetentionAttr,
   148  			},
   149  			{
   150  				address:    promLongRetention.WriteAddr(),
   151  				attributes: longRetentionAttr,
   152  			},
   153  			{
   154  				address:    promLongRetention2.WriteAddr(),
   155  				attributes: longRetentionAttr,
   156  			},
   157  		},
   158  		scope:  scope,
   159  		logger: logger,
   160  	})
   161  	require.NoError(t, err)
   162  	defer closeWithCheck(t, promStorage)
   163  
   164  	t.Run("send short retention write", func(t *testing.T) {
   165  		reset()
   166  		err := writeTestMetric(t, promStorage, shortRetentionAttr)
   167  		require.NoError(t, err)
   168  		assert.NotNil(t, promShortRetention.GetLastWriteRequest())
   169  		assert.Nil(t, promMediumRetention.GetLastWriteRequest())
   170  		assert.Nil(t, promLongRetention.GetLastWriteRequest())
   171  	})
   172  
   173  	t.Run("send medium retention write", func(t *testing.T) {
   174  		reset()
   175  		err := writeTestMetric(t, promStorage, mediumRetentionAttr)
   176  		require.NoError(t, err)
   177  		assert.Nil(t, promShortRetention.GetLastWriteRequest())
   178  		assert.NotNil(t, promMediumRetention.GetLastWriteRequest())
   179  		assert.Nil(t, promLongRetention.GetLastWriteRequest())
   180  	})
   181  
   182  	t.Run("send write to multiple instances configured with same retention", func(t *testing.T) {
   183  		reset()
   184  		err := writeTestMetric(t, promStorage, longRetentionAttr)
   185  		require.NoError(t, err)
   186  		assert.Nil(t, promShortRetention.GetLastWriteRequest())
   187  		assert.Nil(t, promMediumRetention.GetLastWriteRequest())
   188  		assert.NotNil(t, promLongRetention.GetLastWriteRequest())
   189  		assert.NotNil(t, promLongRetention2.GetLastWriteRequest())
   190  	})
   191  
   192  	t.Run("send unconfigured retention write", func(t *testing.T) {
   193  		reset()
   194  		err := writeTestMetric(t, promStorage, storagemetadata.Attributes{
   195  			Resolution: mediumRetentionAttr.Resolution + 1,
   196  			Retention:  mediumRetentionAttr.Retention,
   197  		})
   198  		require.Error(t, err)
   199  		err = writeTestMetric(t, promStorage, storagemetadata.Attributes{
   200  			Resolution: mediumRetentionAttr.Resolution,
   201  			Retention:  mediumRetentionAttr.Retention + 1,
   202  		})
   203  		require.Error(t, err)
   204  		assert.Contains(t, err.Error(), "write did not match any of known endpoints")
   205  		assert.Nil(t, promShortRetention.GetLastWriteRequest())
   206  		assert.Nil(t, promMediumRetention.GetLastWriteRequest())
   207  		assert.Nil(t, promLongRetention.GetLastWriteRequest())
   208  		const droppedWrites = "test_scope.prom_remote_storage.dropped_writes"
   209  		tallytest.AssertCounterValue(t, 2, scope.Snapshot(), droppedWrites, map[string]string{})
   210  	})
   211  
   212  	t.Run("error should not prevent sending to other instances", func(t *testing.T) {
   213  		reset()
   214  		promLongRetention.SetError("test err", http.StatusInternalServerError)
   215  		err := writeTestMetric(t, promStorage, longRetentionAttr)
   216  		require.Error(t, err)
   217  		assert.Contains(t, err.Error(), "test err")
   218  		assert.NotNil(t, promLongRetention2.GetLastWriteRequest())
   219  	})
   220  }
   221  
   222  func TestErrorHandling(t *testing.T) {
   223  	svr := promremotetest.NewServer(t)
   224  	defer svr.Close()
   225  
   226  	attr := storagemetadata.Attributes{
   227  		MetricsType: storagemetadata.AggregatedMetricsType,
   228  		Retention:   720 * time.Hour,
   229  		Resolution:  5 * time.Minute,
   230  	}
   231  	promStorage, err := NewStorage(Options{
   232  		endpoints: []EndpointOptions{{address: svr.WriteAddr(), attributes: attr}},
   233  		scope:     scope,
   234  		logger:    logger,
   235  	})
   236  	require.NoError(t, err)
   237  	defer closeWithCheck(t, promStorage)
   238  
   239  	t.Run("wrap non 5xx errors as invalid params error", func(t *testing.T) {
   240  		svr.Reset()
   241  		svr.SetError("test err", http.StatusForbidden)
   242  		err := writeTestMetric(t, promStorage, attr)
   243  		require.Error(t, err)
   244  		assert.True(t, xerrors.IsInvalidParams(err))
   245  	})
   246  
   247  	t.Run("429 should not be wrapped as invalid params", func(t *testing.T) {
   248  		svr.Reset()
   249  		svr.SetError("test err", http.StatusTooManyRequests)
   250  		err := writeTestMetric(t, promStorage, attr)
   251  		require.Error(t, err)
   252  		assert.False(t, xerrors.IsInvalidParams(err))
   253  	})
   254  }
   255  
   256  func closeWithCheck(t *testing.T, c io.Closer) {
   257  	require.NoError(t, c.Close())
   258  }
   259  
   260  func writeTestMetric(t *testing.T, s storage.Storage, attr storagemetadata.Attributes) error {
   261  	//nolint: gosec
   262  	datapoint := ts.Datapoint{Value: rand.Float64(), Timestamp: xtime.Now()}
   263  	wq, err := storage.NewWriteQuery(storage.WriteQueryOptions{
   264  		Tags: models.Tags{
   265  			Opts: models.NewTagOptions(),
   266  			Tags: []models.Tag{{
   267  				Name:  []byte("test_tag_name"),
   268  				Value: []byte("test_tag_value"),
   269  			}},
   270  		},
   271  		Datapoints: ts.Datapoints{datapoint},
   272  		Unit:       xtime.Millisecond,
   273  		Attributes: attr,
   274  	})
   275  	require.NoError(t, err)
   276  	return s.Write(context.TODO(), wq)
   277  }