github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/client/tcp_client_test.go (about)

     1  // Copyright (c) 2018 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  //nolint:dupl,exhaustive
    22  package client
    23  
    24  import (
    25  	"errors"
    26  	"math"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  
    35  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    36  	"github.com/m3db/m3/src/cluster/kv/mem"
    37  	"github.com/m3db/m3/src/cluster/placement"
    38  	"github.com/m3db/m3/src/cluster/shard"
    39  	"github.com/m3db/m3/src/metrics/aggregation"
    40  	"github.com/m3db/m3/src/metrics/metadata"
    41  	"github.com/m3db/m3/src/metrics/metric"
    42  	"github.com/m3db/m3/src/metrics/metric/aggregated"
    43  	"github.com/m3db/m3/src/metrics/metric/unaggregated"
    44  	"github.com/m3db/m3/src/metrics/pipeline"
    45  	"github.com/m3db/m3/src/metrics/pipeline/applied"
    46  	"github.com/m3db/m3/src/metrics/policy"
    47  	"github.com/m3db/m3/src/x/clock"
    48  	"github.com/m3db/m3/src/x/instrument"
    49  	xtime "github.com/m3db/m3/src/x/time"
    50  )
    51  
    52  var (
    53  	testNowNanos     = time.Now().UnixNano()
    54  	testCutoverNanos = testNowNanos - int64(time.Minute)
    55  	testCutoffNanos  = testNowNanos + int64(time.Hour)
    56  	testCounter      = unaggregated.MetricUnion{
    57  		Type:       metric.CounterType,
    58  		ID:         []byte("foo"),
    59  		CounterVal: 1234,
    60  	}
    61  	testBatchTimer = unaggregated.MetricUnion{
    62  		Type:          metric.TimerType,
    63  		ID:            []byte("foo"),
    64  		BatchTimerVal: []float64{222.22, 345.67, 901.23345},
    65  	}
    66  	testGauge = unaggregated.MetricUnion{
    67  		Type:     metric.GaugeType,
    68  		ID:       []byte("foo"),
    69  		GaugeVal: 123.456,
    70  	}
    71  	testTimed = aggregated.Metric{
    72  		Type:      metric.CounterType,
    73  		ID:        []byte("testTimed"),
    74  		TimeNanos: 1234,
    75  		Value:     178,
    76  	}
    77  	testForwarded = aggregated.ForwardedMetric{
    78  		Type:      metric.CounterType,
    79  		ID:        []byte("testForwarded"),
    80  		TimeNanos: 1234,
    81  		Values:    []float64{34567, 256, 178},
    82  	}
    83  	testPassthrough = aggregated.Metric{
    84  		Type:      metric.CounterType,
    85  		ID:        []byte("testPassthrough"),
    86  		TimeNanos: 12345,
    87  		Value:     123,
    88  	}
    89  	testStagedMetadatas = metadata.StagedMetadatas{
    90  		{
    91  			CutoverNanos: 100,
    92  			Tombstoned:   false,
    93  			Metadata: metadata.Metadata{
    94  				Pipelines: []metadata.PipelineMetadata{
    95  					{
    96  						AggregationID: aggregation.DefaultID,
    97  						StoragePolicies: []policy.StoragePolicy{
    98  							policy.NewStoragePolicy(20*time.Second, xtime.Second, 6*time.Hour),
    99  							policy.NewStoragePolicy(time.Minute, xtime.Minute, 2*24*time.Hour),
   100  							policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 25*24*time.Hour),
   101  						},
   102  					},
   103  				},
   104  			},
   105  		},
   106  		{
   107  			CutoverNanos: 200,
   108  			Tombstoned:   true,
   109  			Metadata: metadata.Metadata{
   110  				Pipelines: []metadata.PipelineMetadata{
   111  					{
   112  						AggregationID: aggregation.DefaultID,
   113  						StoragePolicies: []policy.StoragePolicy{
   114  							policy.NewStoragePolicy(time.Second, xtime.Second, time.Hour),
   115  						},
   116  					},
   117  				},
   118  			},
   119  		},
   120  	}
   121  	testTimedMetadata = metadata.TimedMetadata{
   122  		AggregationID: aggregation.DefaultID,
   123  		StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour),
   124  	}
   125  	testForwardMetadata = metadata.ForwardMetadata{
   126  		AggregationID: aggregation.DefaultID,
   127  		StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour),
   128  		Pipeline: applied.NewPipeline([]applied.OpUnion{
   129  			{
   130  				Type: pipeline.RollupOpType,
   131  				Rollup: applied.RollupOp{
   132  					ID:            []byte("foo"),
   133  					AggregationID: aggregation.MustCompressTypes(aggregation.Count),
   134  				},
   135  			},
   136  		}),
   137  		SourceID:          1234,
   138  		NumForwardedTimes: 3,
   139  	}
   140  	testPassthroughMetadata = policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour)
   141  	testPlacementInstances  = []placement.Instance{
   142  		placement.NewInstance().
   143  			SetID("instance1").
   144  			SetEndpoint("instance1_endpoint").
   145  			SetShards(shard.NewShards([]shard.Shard{
   146  				shard.NewShard(0).
   147  					SetState(shard.Initializing).
   148  					SetCutoverNanos(testCutoverNanos).
   149  					SetCutoffNanos(testCutoffNanos),
   150  				shard.NewShard(1).
   151  					SetState(shard.Initializing).
   152  					SetCutoverNanos(testCutoverNanos).
   153  					SetCutoffNanos(testCutoffNanos),
   154  			})),
   155  		placement.NewInstance().
   156  			SetID("instance2").
   157  			SetEndpoint("instance2_endpoint").
   158  			SetShards(shard.NewShards([]shard.Shard{
   159  				shard.NewShard(2).
   160  					SetState(shard.Initializing).
   161  					SetCutoverNanos(testCutoverNanos).
   162  					SetCutoffNanos(testCutoffNanos),
   163  				shard.NewShard(3).
   164  					SetState(shard.Initializing).
   165  					SetCutoverNanos(testCutoverNanos).
   166  					SetCutoffNanos(testCutoffNanos),
   167  			})),
   168  		placement.NewInstance().
   169  			SetID("instance3").
   170  			SetEndpoint("instance3_endpoint").
   171  			SetShards(shard.NewShards([]shard.Shard{
   172  				shard.NewShard(0).
   173  					SetState(shard.Initializing).
   174  					SetCutoverNanos(testCutoverNanos).
   175  					SetCutoffNanos(testCutoffNanos),
   176  				shard.NewShard(1).
   177  					SetState(shard.Initializing).
   178  					SetCutoverNanos(testCutoverNanos).
   179  					SetCutoffNanos(testCutoffNanos),
   180  			})),
   181  		placement.NewInstance().
   182  			SetID("instance4").
   183  			SetEndpoint("instance4_endpoint").
   184  			SetShards(shard.NewShards([]shard.Shard{
   185  				shard.NewShard(2).
   186  					SetState(shard.Initializing).
   187  					SetCutoverNanos(testCutoverNanos).
   188  					SetCutoffNanos(testCutoffNanos),
   189  				shard.NewShard(3).
   190  					SetState(shard.Initializing).
   191  					SetCutoverNanos(testCutoverNanos).
   192  					SetCutoffNanos(testCutoffNanos),
   193  			})),
   194  	}
   195  	testPlacement = placement.NewPlacement().
   196  			SetVersion(1).
   197  			SetCutoverNanos(12345).
   198  			SetShards([]uint32{0, 1, 2, 3}).
   199  			SetInstances(testPlacementInstances)
   200  )
   201  
   202  func TestTCPClientWriteUntimedMetricClosed(t *testing.T) {
   203  	c := mustNewTestTCPClient(t, testOptions())
   204  	require.NoError(t, c.Close())
   205  	for _, input := range []unaggregated.MetricUnion{testCounter, testBatchTimer, testGauge} {
   206  		var err error
   207  		switch input.Type {
   208  		case metric.CounterType:
   209  			err = c.WriteUntimedCounter(input.Counter(), testStagedMetadatas)
   210  		case metric.TimerType:
   211  			err = c.WriteUntimedBatchTimer(input.BatchTimer(), testStagedMetadatas)
   212  		case metric.GaugeType:
   213  			err = c.WriteUntimedGauge(input.Gauge(), testStagedMetadatas)
   214  		}
   215  		require.Error(t, err)
   216  	}
   217  }
   218  
   219  func TestTCPClientWriteUntimedMetricPlacementError(t *testing.T) {
   220  	ctrl := gomock.NewController(t)
   221  	defer ctrl.Finish()
   222  
   223  	errInvalidPlacement := errors.New("invalid placement")
   224  	watcher := placement.NewMockWatcher(ctrl)
   225  	watcher.EXPECT().Get().
   226  		Return(nil, errInvalidPlacement).
   227  		MinTimes(1)
   228  	c := mustNewTestTCPClient(t, testOptions())
   229  	c.placementWatcher = watcher
   230  
   231  	for _, input := range []unaggregated.MetricUnion{testCounter, testBatchTimer, testGauge} {
   232  		var err error
   233  		switch input.Type {
   234  		case metric.CounterType:
   235  			err = c.WriteUntimedCounter(input.Counter(), testStagedMetadatas)
   236  		case metric.TimerType:
   237  			err = c.WriteUntimedBatchTimer(input.BatchTimer(), testStagedMetadatas)
   238  		case metric.GaugeType:
   239  			err = c.WriteUntimedGauge(input.Gauge(), testStagedMetadatas)
   240  		}
   241  		require.Equal(t, errInvalidPlacement, err)
   242  	}
   243  }
   244  
   245  func TestTCPClientWriteUntimedMetricPlacementNil(t *testing.T) {
   246  	ctrl := gomock.NewController(t)
   247  	defer ctrl.Finish()
   248  
   249  	watcher := placement.NewMockWatcher(ctrl)
   250  	watcher.EXPECT().Get().
   251  		Return(nil, nil).
   252  		MinTimes(1)
   253  	c := mustNewTestTCPClient(t, testOptions())
   254  	c.placementWatcher = watcher
   255  
   256  	for _, input := range []unaggregated.MetricUnion{testCounter, testBatchTimer, testGauge} {
   257  		var err error
   258  		switch input.Type {
   259  		case metric.CounterType:
   260  			err = c.WriteUntimedCounter(input.Counter(), testStagedMetadatas)
   261  		case metric.TimerType:
   262  			err = c.WriteUntimedBatchTimer(input.BatchTimer(), testStagedMetadatas)
   263  		case metric.GaugeType:
   264  			err = c.WriteUntimedGauge(input.Gauge(), testStagedMetadatas)
   265  		}
   266  		require.Equal(t, errNilPlacement, err)
   267  	}
   268  }
   269  
   270  func TestTCPClientWriteUntimedMetricSuccess(t *testing.T) {
   271  	ctrl := gomock.NewController(t)
   272  	defer ctrl.Finish()
   273  
   274  	var (
   275  		instancesRes []placement.Instance
   276  		shardRes     uint32
   277  		payloadRes   payloadUnion
   278  	)
   279  	writerMgr := NewMockinstanceWriterManager(ctrl)
   280  	writerMgr.EXPECT().
   281  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   282  		DoAndReturn(func(
   283  			instance placement.Instance,
   284  			shardID uint32,
   285  			payload payloadUnion,
   286  		) error {
   287  			instancesRes = append(instancesRes, instance)
   288  			shardRes = shardID
   289  			payloadRes = payload
   290  			return nil
   291  		}).
   292  		MinTimes(1)
   293  	watcher := placement.NewMockWatcher(ctrl)
   294  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   295  	c := mustNewTestTCPClient(t, testOptions())
   296  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   297  	c.writerMgr = writerMgr
   298  	c.placementWatcher = watcher
   299  
   300  	expectedInstances := []placement.Instance{
   301  		testPlacementInstances[0],
   302  		testPlacementInstances[2],
   303  	}
   304  	for _, input := range []unaggregated.MetricUnion{testCounter, testBatchTimer, testGauge} {
   305  		// Reset states in each iteration.
   306  		instancesRes = instancesRes[:0]
   307  		shardRes = 0
   308  		payloadRes = payloadUnion{}
   309  
   310  		var err error
   311  		switch input.Type {
   312  		case metric.CounterType:
   313  			err = c.WriteUntimedCounter(input.Counter(), testStagedMetadatas)
   314  		case metric.TimerType:
   315  			err = c.WriteUntimedBatchTimer(input.BatchTimer(), testStagedMetadatas)
   316  		case metric.GaugeType:
   317  			err = c.WriteUntimedGauge(input.Gauge(), testStagedMetadatas)
   318  		}
   319  
   320  		require.NoError(t, err)
   321  		require.Equal(t, expectedInstances, instancesRes)
   322  		require.Equal(t, uint32(1), shardRes)
   323  		require.Equal(t, untimedType, payloadRes.payloadType)
   324  		require.Equal(t, input, payloadRes.untimed.metric)
   325  		require.Equal(t, testStagedMetadatas, payloadRes.untimed.metadatas)
   326  	}
   327  }
   328  
   329  func TestTCPClientWriteUntimedMetricPartialError(t *testing.T) {
   330  	ctrl := gomock.NewController(t)
   331  	defer ctrl.Finish()
   332  
   333  	var (
   334  		instancesRes     []placement.Instance
   335  		shardRes         uint32
   336  		payloadRes       payloadUnion
   337  		errInstanceWrite = errors.New("instance write error")
   338  	)
   339  	writerMgr := NewMockinstanceWriterManager(ctrl)
   340  	writerMgr.EXPECT().
   341  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   342  		DoAndReturn(func(
   343  			instance placement.Instance,
   344  			shardID uint32,
   345  			payload payloadUnion,
   346  		) error {
   347  			if instance.ID() == testPlacementInstances[0].ID() {
   348  				return errInstanceWrite
   349  			}
   350  			instancesRes = append(instancesRes, instance)
   351  			shardRes = shardID
   352  			payloadRes = payload
   353  			return nil
   354  		}).
   355  		MinTimes(1)
   356  	watcher := placement.NewMockWatcher(ctrl)
   357  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   358  	c := mustNewTestTCPClient(t, testOptions())
   359  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   360  	c.writerMgr = writerMgr
   361  	c.placementWatcher = watcher
   362  
   363  	expectedInstances := []placement.Instance{
   364  		testPlacementInstances[2],
   365  	}
   366  	err := c.WriteUntimedCounter(testCounter.Counter(), testStagedMetadatas)
   367  	require.Error(t, err)
   368  	require.True(t, strings.Contains(err.Error(), errInstanceWrite.Error()))
   369  	require.Equal(t, expectedInstances, instancesRes)
   370  	require.Equal(t, uint32(1), shardRes)
   371  	require.Equal(t, untimedType, payloadRes.payloadType)
   372  	require.Equal(t, testCounter, payloadRes.untimed.metric)
   373  	require.Equal(t, testStagedMetadatas, payloadRes.untimed.metadatas)
   374  }
   375  
   376  func TestTCPClientWriteUntimedMetricBeforeShardCutover(t *testing.T) {
   377  	ctrl := gomock.NewController(t)
   378  	defer ctrl.Finish()
   379  
   380  	var instancesRes []placement.Instance
   381  	watcher := placement.NewMockWatcher(ctrl)
   382  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   383  	c := mustNewTestTCPClient(t, testOptions())
   384  	c.shardCutoverWarmupDuration = time.Second
   385  	c.nowFn = func() time.Time { return time.Unix(0, testCutoverNanos-1).Add(-time.Second) }
   386  	c.writerMgr = nil
   387  	c.placementWatcher = watcher
   388  
   389  	err := c.WriteUntimedCounter(testCounter.Counter(), testStagedMetadatas)
   390  	require.NoError(t, err)
   391  	require.Nil(t, instancesRes)
   392  }
   393  
   394  func TestTCPClientWriteUntimedMetricAfterShardCutoff(t *testing.T) {
   395  	ctrl := gomock.NewController(t)
   396  	defer ctrl.Finish()
   397  
   398  	var instancesRes []placement.Instance
   399  	watcher := placement.NewMockWatcher(ctrl)
   400  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   401  	c := mustNewTestTCPClient(t, testOptions())
   402  	c.shardCutoffLingerDuration = time.Second
   403  	c.nowFn = func() time.Time { return time.Unix(0, testCutoffNanos+1).Add(time.Second) }
   404  	c.writerMgr = nil
   405  	c.placementWatcher = watcher
   406  
   407  	err := c.WriteUntimedCounter(testCounter.Counter(), testStagedMetadatas)
   408  	require.NoError(t, err)
   409  	require.Nil(t, instancesRes)
   410  }
   411  
   412  func TestTCPClientWriteTimedMetricSuccess(t *testing.T) {
   413  	ctrl := gomock.NewController(t)
   414  	defer ctrl.Finish()
   415  
   416  	var (
   417  		instancesRes []placement.Instance
   418  		shardRes     uint32
   419  		payloadRes   payloadUnion
   420  	)
   421  	writerMgr := NewMockinstanceWriterManager(ctrl)
   422  	writerMgr.EXPECT().
   423  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   424  		DoAndReturn(func(
   425  			instance placement.Instance,
   426  			shardID uint32,
   427  			payload payloadUnion,
   428  		) error {
   429  			instancesRes = append(instancesRes, instance)
   430  			shardRes = shardID
   431  			payloadRes = payload
   432  			return nil
   433  		}).
   434  		MinTimes(1)
   435  	watcher := placement.NewMockWatcher(ctrl)
   436  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   437  	c := mustNewTestTCPClient(t, testOptions())
   438  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   439  	c.writerMgr = writerMgr
   440  	c.placementWatcher = watcher
   441  
   442  	expectedInstances := []placement.Instance{
   443  		testPlacementInstances[0],
   444  		testPlacementInstances[2],
   445  	}
   446  	testMetric := testTimed
   447  	testMetric.TimeNanos = testNowNanos
   448  	err := c.WriteTimed(testMetric, testTimedMetadata)
   449  	require.NoError(t, err)
   450  	require.Equal(t, expectedInstances, instancesRes)
   451  	require.Equal(t, uint32(1), shardRes)
   452  	require.Equal(t, timedType, payloadRes.payloadType)
   453  	require.Equal(t, testMetric, payloadRes.timed.metric)
   454  	require.Equal(t, testTimedMetadata, payloadRes.timed.metadata)
   455  }
   456  
   457  func TestTCPClientWriteTimedMetricPartialError(t *testing.T) {
   458  	ctrl := gomock.NewController(t)
   459  	defer ctrl.Finish()
   460  
   461  	var (
   462  		instancesRes     []placement.Instance
   463  		shardRes         uint32
   464  		payloadRes       payloadUnion
   465  		errInstanceWrite = errors.New("instance write error")
   466  	)
   467  	writerMgr := NewMockinstanceWriterManager(ctrl)
   468  	writerMgr.EXPECT().
   469  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   470  		DoAndReturn(func(
   471  			instance placement.Instance,
   472  			shardID uint32,
   473  			payload payloadUnion,
   474  		) error {
   475  			if instance.ID() == testPlacementInstances[0].ID() {
   476  				return errInstanceWrite
   477  			}
   478  			instancesRes = append(instancesRes, instance)
   479  			shardRes = shardID
   480  			payloadRes = payload
   481  			return nil
   482  		}).
   483  		MinTimes(1)
   484  	watcher := placement.NewMockWatcher(ctrl)
   485  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   486  	c := mustNewTestTCPClient(t, testOptions())
   487  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   488  	c.writerMgr = writerMgr
   489  	c.placementWatcher = watcher
   490  
   491  	expectedInstances := []placement.Instance{
   492  		testPlacementInstances[2],
   493  	}
   494  	testMetric := testTimed
   495  	testMetric.TimeNanos = testNowNanos
   496  	err := c.WriteTimed(testMetric, testTimedMetadata)
   497  	require.Error(t, err)
   498  	require.True(t, strings.Contains(err.Error(), errInstanceWrite.Error()))
   499  	require.Equal(t, expectedInstances, instancesRes)
   500  	require.Equal(t, uint32(1), shardRes)
   501  	require.Equal(t, timedType, payloadRes.payloadType)
   502  	require.Equal(t, testMetric, payloadRes.timed.metric)
   503  	require.Equal(t, testTimedMetadata, payloadRes.timed.metadata)
   504  }
   505  
   506  func TestTCPClientWriteForwardedMetricSuccess(t *testing.T) {
   507  	ctrl := gomock.NewController(t)
   508  	defer ctrl.Finish()
   509  
   510  	var (
   511  		instancesRes []placement.Instance
   512  		shardRes     uint32
   513  		payloadRes   payloadUnion
   514  	)
   515  	writerMgr := NewMockinstanceWriterManager(ctrl)
   516  	writerMgr.EXPECT().
   517  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   518  		DoAndReturn(func(
   519  			instance placement.Instance,
   520  			shardID uint32,
   521  			payload payloadUnion,
   522  		) error {
   523  			instancesRes = append(instancesRes, instance)
   524  			shardRes = shardID
   525  			payloadRes = payload
   526  			return nil
   527  		}).
   528  		MinTimes(1)
   529  	watcher := placement.NewMockWatcher(ctrl)
   530  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   531  	c := mustNewTestTCPClient(t, testOptions())
   532  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   533  	c.writerMgr = writerMgr
   534  	c.placementWatcher = watcher
   535  
   536  	expectedInstances := []placement.Instance{
   537  		testPlacementInstances[0],
   538  		testPlacementInstances[2],
   539  	}
   540  	testMetric := testForwarded
   541  	testMetric.TimeNanos = testNowNanos
   542  	err := c.WriteForwarded(testMetric, testForwardMetadata)
   543  	require.NoError(t, err)
   544  	require.Equal(t, expectedInstances, instancesRes)
   545  	require.Equal(t, uint32(1), shardRes)
   546  	require.Equal(t, forwardedType, payloadRes.payloadType)
   547  	require.Equal(t, testMetric, payloadRes.forwarded.metric)
   548  	require.Equal(t, testForwardMetadata, payloadRes.forwarded.metadata)
   549  }
   550  
   551  func TestTCPClientWriteForwardedMetricPartialError(t *testing.T) {
   552  	ctrl := gomock.NewController(t)
   553  	defer ctrl.Finish()
   554  
   555  	var (
   556  		instancesRes     []placement.Instance
   557  		shardRes         uint32
   558  		payloadRes       payloadUnion
   559  		errInstanceWrite = errors.New("instance write error")
   560  	)
   561  	writerMgr := NewMockinstanceWriterManager(ctrl)
   562  	writerMgr.EXPECT().
   563  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   564  		DoAndReturn(func(
   565  			instance placement.Instance,
   566  			shardID uint32,
   567  			payload payloadUnion,
   568  		) error {
   569  			if instance.ID() == testPlacementInstances[0].ID() {
   570  				return errInstanceWrite
   571  			}
   572  			instancesRes = append(instancesRes, instance)
   573  			shardRes = shardID
   574  			payloadRes = payload
   575  			return nil
   576  		}).
   577  		MinTimes(1)
   578  	watcher := placement.NewMockWatcher(ctrl)
   579  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   580  	c := mustNewTestTCPClient(t, testOptions())
   581  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   582  	c.writerMgr = writerMgr
   583  	c.placementWatcher = watcher
   584  
   585  	expectedInstances := []placement.Instance{
   586  		testPlacementInstances[2],
   587  	}
   588  	testMetric := testForwarded
   589  	testMetric.TimeNanos = testNowNanos
   590  	err := c.WriteForwarded(testMetric, testForwardMetadata)
   591  	require.Error(t, err)
   592  	require.True(t, strings.Contains(err.Error(), errInstanceWrite.Error()))
   593  	require.Equal(t, expectedInstances, instancesRes)
   594  	require.Equal(t, uint32(1), shardRes)
   595  	require.Equal(t, forwardedType, payloadRes.payloadType)
   596  	require.Equal(t, testMetric, payloadRes.forwarded.metric)
   597  	require.Equal(t, testForwardMetadata, payloadRes.forwarded.metadata)
   598  }
   599  
   600  func TestTCPClientWritePassthroughMetricSuccess(t *testing.T) {
   601  	ctrl := gomock.NewController(t)
   602  	defer ctrl.Finish()
   603  
   604  	var (
   605  		instancesRes []placement.Instance
   606  		shardRes     uint32
   607  		payloadRes   payloadUnion
   608  	)
   609  	writerMgr := NewMockinstanceWriterManager(ctrl)
   610  	writerMgr.EXPECT().
   611  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   612  		DoAndReturn(func(
   613  			instance placement.Instance,
   614  			shardID uint32,
   615  			payload payloadUnion,
   616  		) error {
   617  			instancesRes = append(instancesRes, instance)
   618  			shardRes = shardID
   619  			payloadRes = payload
   620  			return nil
   621  		}).
   622  		MinTimes(1)
   623  	watcher := placement.NewMockWatcher(ctrl)
   624  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   625  	c := mustNewTestTCPClient(t, testOptions())
   626  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   627  	c.writerMgr = writerMgr
   628  	c.placementWatcher = watcher
   629  
   630  	expectedInstances := []placement.Instance{
   631  		testPlacementInstances[0],
   632  		testPlacementInstances[2],
   633  	}
   634  	testMetric := testPassthrough
   635  	testMetric.TimeNanos = testNowNanos
   636  	err := c.WritePassthrough(testMetric, testPassthroughMetadata)
   637  	require.NoError(t, err)
   638  	require.Equal(t, expectedInstances, instancesRes)
   639  	require.Equal(t, uint32(1), shardRes)
   640  	require.Equal(t, passthroughType, payloadRes.payloadType)
   641  	require.Equal(t, testMetric, payloadRes.passthrough.metric)
   642  	require.Equal(t, testPassthroughMetadata, payloadRes.passthrough.storagePolicy)
   643  }
   644  
   645  func TestTCPClientWritePassthroughMetricPartialError(t *testing.T) {
   646  	ctrl := gomock.NewController(t)
   647  	defer ctrl.Finish()
   648  
   649  	var (
   650  		instancesRes     []placement.Instance
   651  		shardRes         uint32
   652  		payloadRes       payloadUnion
   653  		errInstanceWrite = errors.New("instance write error")
   654  	)
   655  	writerMgr := NewMockinstanceWriterManager(ctrl)
   656  	writerMgr.EXPECT().
   657  		Write(gomock.Any(), gomock.Any(), gomock.Any()).
   658  		DoAndReturn(func(
   659  			instance placement.Instance,
   660  			shardID uint32,
   661  			payload payloadUnion,
   662  		) error {
   663  			if instance.ID() == testPlacementInstances[0].ID() {
   664  				return errInstanceWrite
   665  			}
   666  			instancesRes = append(instancesRes, instance)
   667  			shardRes = shardID
   668  			payloadRes = payload
   669  			return nil
   670  		}).
   671  		MinTimes(1)
   672  	watcher := placement.NewMockWatcher(ctrl)
   673  	watcher.EXPECT().Get().Return(testPlacement, nil).MinTimes(1)
   674  	c := mustNewTestTCPClient(t, testOptions())
   675  	c.nowFn = func() time.Time { return time.Unix(0, testNowNanos) }
   676  	c.writerMgr = writerMgr
   677  	c.placementWatcher = watcher
   678  
   679  	expectedInstances := []placement.Instance{
   680  		testPlacementInstances[2],
   681  	}
   682  	testMetric := testPassthrough
   683  	testMetric.TimeNanos = testNowNanos
   684  	err := c.WritePassthrough(testMetric, testPassthroughMetadata)
   685  	require.Error(t, err)
   686  	require.True(t, strings.Contains(err.Error(), errInstanceWrite.Error()))
   687  	require.Equal(t, expectedInstances, instancesRes)
   688  	require.Equal(t, uint32(1), shardRes)
   689  	require.Equal(t, passthroughType, payloadRes.payloadType)
   690  	require.Equal(t, testMetric, payloadRes.passthrough.metric)
   691  	require.Equal(t, testPassthroughMetadata, payloadRes.passthrough.storagePolicy)
   692  }
   693  
   694  func TestTCPClientFlushClosed(t *testing.T) {
   695  	c := mustNewTestTCPClient(t, testOptions())
   696  	require.NoError(t, c.Close())
   697  	require.Equal(t, errInstanceWriterManagerClosed, c.Flush())
   698  }
   699  
   700  func TestTCPClientFlushError(t *testing.T) {
   701  	ctrl := gomock.NewController(t)
   702  	defer ctrl.Finish()
   703  
   704  	errTestFlush := errors.New("test flush error")
   705  	writerMgr := NewMockinstanceWriterManager(ctrl)
   706  	writerMgr.EXPECT().Flush().Return(errTestFlush).MinTimes(1)
   707  	c := mustNewTestTCPClient(t, testOptions())
   708  	c.writerMgr = writerMgr
   709  	require.Equal(t, errTestFlush, c.Flush())
   710  }
   711  
   712  func TestTCPClientFlushSuccess(t *testing.T) {
   713  	ctrl := gomock.NewController(t)
   714  	defer ctrl.Finish()
   715  
   716  	writerMgr := NewMockinstanceWriterManager(ctrl)
   717  	writerMgr.EXPECT().Flush().Return(nil).MinTimes(1)
   718  	c := mustNewTestTCPClient(t, testOptions())
   719  	c.writerMgr = writerMgr
   720  	require.NoError(t, c.Flush())
   721  }
   722  
   723  func TestTCPClientClosed(t *testing.T) {
   724  	c := mustNewTestTCPClient(t, testOptions())
   725  
   726  	require.NoError(t, c.Close())
   727  	require.Equal(t, errInstanceWriterManagerClosed, c.Close())
   728  }
   729  
   730  func TestTCPClientCloseSuccess(t *testing.T) {
   731  	c := mustNewTestTCPClient(t, testOptions())
   732  	require.NoError(t, c.Close())
   733  }
   734  
   735  func TestTCPClientWriteTimeRangeFor(t *testing.T) {
   736  	c := mustNewTestTCPClient(t, testOptions())
   737  	testShard := shard.NewShard(0).SetState(shard.Initializing)
   738  	for _, input := range []struct {
   739  		cutoverNanos     int64
   740  		cutoffNanos      int64
   741  		expectedEarliest int64
   742  		expectedLatest   int64
   743  	}{
   744  		{
   745  			cutoverNanos:     0,
   746  			cutoffNanos:      int64(math.MaxInt64),
   747  			expectedEarliest: 0,
   748  			expectedLatest:   int64(math.MaxInt64),
   749  		},
   750  		{
   751  			cutoverNanos:     testNowNanos,
   752  			cutoffNanos:      int64(math.MaxInt64),
   753  			expectedEarliest: testNowNanos - int64(time.Minute),
   754  			expectedLatest:   int64(math.MaxInt64),
   755  		},
   756  		{
   757  			cutoverNanos:     0,
   758  			cutoffNanos:      testNowNanos,
   759  			expectedEarliest: 0,
   760  			expectedLatest:   testNowNanos + int64(10*time.Minute),
   761  		},
   762  	} {
   763  		testShard = testShard.SetCutoverNanos(input.cutoverNanos).SetCutoffNanos(input.cutoffNanos)
   764  		earliest, latest := c.writeTimeRangeFor(testShard)
   765  		require.Equal(t, input.expectedEarliest, earliest)
   766  		require.Equal(t, input.expectedLatest, latest)
   767  	}
   768  }
   769  
   770  func TestTCPClientActivePlacement(t *testing.T) {
   771  	var (
   772  		c       = mustNewTestTCPClient(t, testOptions())
   773  		emptyPl = placement.NewPlacement()
   774  		ctrl    = gomock.NewController(t)
   775  		mockPl  = placement.NewMockPlacement(ctrl)
   776  		watcher = placement.NewMockWatcher(ctrl)
   777  	)
   778  
   779  	c.placementWatcher = watcher
   780  	watcher.EXPECT().Get().Return(mockPl, nil).Times(2)
   781  	mockPl.EXPECT().Version().Return(42).Times(2)
   782  	mockPl.EXPECT().Clone().Return(emptyPl)
   783  
   784  	pl, v, err := c.ActivePlacement()
   785  	assert.NoError(t, err)
   786  	assert.Equal(t, 42, v)
   787  	assert.Equal(t, emptyPl, pl)
   788  
   789  	v, err = c.ActivePlacementVersion()
   790  	assert.NoError(t, err)
   791  	assert.Equal(t, 42, v)
   792  }
   793  
   794  func TestTCPClientInitAndClose(t *testing.T) {
   795  	c := mustNewTestTCPClient(t, testOptions())
   796  	require.NoError(t, c.Init())
   797  	require.NoError(t, c.Close())
   798  }
   799  
   800  func mustNewTestTCPClient(t *testing.T, opts Options) *TCPClient {
   801  	c, err := NewClient(opts)
   802  	require.NoError(t, err)
   803  	value, ok := c.(*TCPClient)
   804  	require.True(t, ok)
   805  	return value
   806  }
   807  
   808  // TODO: clean this up as it's in use by other test files
   809  func testOptions() Options {
   810  	return testTCPClientOptions()
   811  }
   812  
   813  func testTCPClientOptions() Options {
   814  	const placementKey = "placement"
   815  	pl, err := placement.NewPlacement().Proto()
   816  	if err != nil {
   817  		panic(err.Error())
   818  	}
   819  
   820  	plSnapshots := &placementpb.PlacementSnapshots{
   821  		Snapshots: []*placementpb.Placement{
   822  			pl,
   823  		},
   824  	}
   825  
   826  	store := mem.NewStore()
   827  	if _, err := store.Set(placementKey, plSnapshots); err != nil {
   828  		panic(err.Error())
   829  	}
   830  
   831  	plOpts := placement.NewWatcherOptions().
   832  		SetStagedPlacementStore(store).
   833  		SetStagedPlacementKey(placementKey).
   834  		SetInitWatchTimeout(time.Millisecond)
   835  	return NewOptions().
   836  		SetClockOptions(clock.NewOptions()).
   837  		SetConnectionOptions(testConnectionOptions()).
   838  		SetInstrumentOptions(instrument.NewOptions()).
   839  		SetShardFn(func([]byte, uint32) uint32 { return 1 }).
   840  		SetInstanceQueueSize(10).
   841  		SetMaxTimerBatchSize(140).
   842  		SetShardCutoverWarmupDuration(time.Minute).
   843  		SetShardCutoffLingerDuration(10 * time.Minute).
   844  		SetAggregatorClientType(TCPAggregatorClient).
   845  		SetWatcherOptions(plOpts).
   846  		SetForceFlushEvery(0).
   847  		SetFlushWorkerCount(8)
   848  }