github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/server/rawtcp/server_test.go (about)

     1  // Copyright (c) 2016 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 rawtcp
    22  
    23  import (
    24  	"errors"
    25  	"net"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/aggregator/aggregator"
    31  	"github.com/m3db/m3/src/aggregator/aggregator/capture"
    32  	"github.com/m3db/m3/src/metrics/aggregation"
    33  	"github.com/m3db/m3/src/metrics/encoding"
    34  	"github.com/m3db/m3/src/metrics/encoding/protobuf"
    35  	"github.com/m3db/m3/src/metrics/metadata"
    36  	"github.com/m3db/m3/src/metrics/metric"
    37  	"github.com/m3db/m3/src/metrics/metric/aggregated"
    38  	"github.com/m3db/m3/src/metrics/metric/unaggregated"
    39  	"github.com/m3db/m3/src/metrics/pipeline"
    40  	"github.com/m3db/m3/src/metrics/pipeline/applied"
    41  	"github.com/m3db/m3/src/metrics/policy"
    42  	"github.com/m3db/m3/src/x/clock"
    43  	"github.com/m3db/m3/src/x/instrument"
    44  	"github.com/m3db/m3/src/x/retry"
    45  	xserver "github.com/m3db/m3/src/x/server"
    46  	xtest "github.com/m3db/m3/src/x/test"
    47  	xtime "github.com/m3db/m3/src/x/time"
    48  
    49  	"github.com/golang/mock/gomock"
    50  	"github.com/google/go-cmp/cmp"
    51  	"github.com/google/go-cmp/cmp/cmpopts"
    52  	"github.com/stretchr/testify/require"
    53  	"go.uber.org/zap"
    54  	"go.uber.org/zap/zapcore"
    55  	"go.uber.org/zap/zaptest/observer"
    56  )
    57  
    58  const (
    59  	testListenAddress = "127.0.0.1:0"
    60  )
    61  
    62  var (
    63  	testNowNanos = time.Now().UnixNano()
    64  	testCounter  = unaggregated.MetricUnion{
    65  		Type:       metric.CounterType,
    66  		ID:         []byte("testCounter"),
    67  		CounterVal: 123,
    68  	}
    69  	testBatchTimer = unaggregated.MetricUnion{
    70  		Type:          metric.TimerType,
    71  		ID:            []byte("testBatchTimer"),
    72  		BatchTimerVal: []float64{1.0, 2.0, 3.0},
    73  	}
    74  	testGauge = unaggregated.MetricUnion{
    75  		Type:     metric.GaugeType,
    76  		ID:       []byte("testGauge"),
    77  		GaugeVal: 456.780,
    78  	}
    79  	testTimed = aggregated.Metric{
    80  		Type:      metric.CounterType,
    81  		ID:        []byte("testTimed"),
    82  		TimeNanos: 12345,
    83  		Value:     -13,
    84  	}
    85  	testForwarded = aggregated.ForwardedMetric{
    86  		Type:      metric.CounterType,
    87  		ID:        []byte("testForwarded"),
    88  		TimeNanos: 12345,
    89  		Values:    []float64{908, -13},
    90  	}
    91  	testPassthrough = aggregated.Metric{
    92  		Type:      metric.CounterType,
    93  		ID:        []byte("testPassthrough"),
    94  		TimeNanos: 12345,
    95  		Value:     -13,
    96  	}
    97  	testDefaultPoliciesList = policy.DefaultPoliciesList
    98  	testCustomPoliciesList  = policy.PoliciesList{
    99  		policy.NewStagedPolicies(
   100  			testNowNanos,
   101  			false,
   102  			[]policy.Policy{
   103  				policy.NewPolicy(policy.NewStoragePolicy(10*time.Second, xtime.Second, 6*time.Hour), aggregation.MustCompressTypes(aggregation.Min)),
   104  				policy.NewPolicy(policy.NewStoragePolicy(time.Minute, xtime.Minute, 2*24*time.Hour), aggregation.DefaultID),
   105  				policy.NewPolicy(policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 30*24*time.Hour), aggregation.DefaultID),
   106  			},
   107  		),
   108  	}
   109  	testDefaultMetadatas = metadata.DefaultStagedMetadatas
   110  	testCustomMetadatas  = metadata.StagedMetadatas{
   111  		{
   112  			CutoverNanos: testNowNanos,
   113  			Tombstoned:   false,
   114  			Metadata: metadata.Metadata{
   115  				Pipelines: []metadata.PipelineMetadata{
   116  					{
   117  						AggregationID: aggregation.MustCompressTypes(aggregation.Min),
   118  						StoragePolicies: []policy.StoragePolicy{
   119  							policy.NewStoragePolicy(10*time.Second, xtime.Second, 6*time.Hour),
   120  						},
   121  					},
   122  					{
   123  						AggregationID: aggregation.DefaultID,
   124  						StoragePolicies: []policy.StoragePolicy{
   125  							policy.NewStoragePolicy(time.Minute, xtime.Minute, 2*24*time.Hour),
   126  							policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 30*24*time.Hour),
   127  						},
   128  					},
   129  				},
   130  			},
   131  		},
   132  	}
   133  	testTimedMetadata = metadata.TimedMetadata{
   134  		AggregationID: aggregation.DefaultID,
   135  		StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour),
   136  	}
   137  	testForwardMetadata = metadata.ForwardMetadata{
   138  		AggregationID: aggregation.DefaultID,
   139  		StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour),
   140  		Pipeline: applied.NewPipeline([]applied.OpUnion{
   141  			{
   142  				Type: pipeline.RollupOpType,
   143  				Rollup: applied.RollupOp{
   144  					ID:            []byte("foo"),
   145  					AggregationID: aggregation.MustCompressTypes(aggregation.Count),
   146  				},
   147  			},
   148  		}),
   149  		SourceID:          1234,
   150  		NumForwardedTimes: 3,
   151  	}
   152  	testPassthroughStoragePolicy   = policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour)
   153  	testBatchTimerWithPoliciesList = unaggregated.BatchTimerWithPoliciesList{
   154  		BatchTimer:   testBatchTimer.BatchTimer(),
   155  		PoliciesList: testCustomPoliciesList,
   156  	}
   157  	testGaugeWithPoliciesList = unaggregated.GaugeWithPoliciesList{
   158  		Gauge:        testGauge.Gauge(),
   159  		PoliciesList: testDefaultPoliciesList,
   160  	}
   161  	testCounterWithMetadatas = unaggregated.CounterWithMetadatas{
   162  		Counter:         testCounter.Counter(),
   163  		StagedMetadatas: testDefaultMetadatas,
   164  	}
   165  	testBatchTimerWithMetadatas = unaggregated.BatchTimerWithMetadatas{
   166  		BatchTimer:      testBatchTimer.BatchTimer(),
   167  		StagedMetadatas: testCustomMetadatas,
   168  	}
   169  	testGaugeWithMetadatas = unaggregated.GaugeWithMetadatas{
   170  		Gauge:           testGauge.Gauge(),
   171  		StagedMetadatas: testDefaultMetadatas,
   172  	}
   173  	testTimedMetricWithMetadata = aggregated.TimedMetricWithMetadata{
   174  		Metric:        testTimed,
   175  		TimedMetadata: testTimedMetadata,
   176  	}
   177  	testTimedMetricWithMetadatas = aggregated.TimedMetricWithMetadatas{
   178  		Metric:          testTimed,
   179  		StagedMetadatas: testDefaultMetadatas,
   180  	}
   181  	testForwardedMetricWithMetadata = aggregated.ForwardedMetricWithMetadata{
   182  		ForwardedMetric: testForwarded,
   183  		ForwardMetadata: testForwardMetadata,
   184  	}
   185  	testPassthroughMetricWithMetadata = aggregated.PassthroughMetricWithMetadata{
   186  		Metric:        testPassthrough,
   187  		StoragePolicy: testPassthroughStoragePolicy,
   188  	}
   189  	testCmpOpts = []cmp.Option{
   190  		cmpopts.EquateEmpty(),
   191  		cmp.AllowUnexported(policy.StoragePolicy{}),
   192  	}
   193  )
   194  
   195  func TestRawTCPServerHandleUnaggregatedProtobufEncoding(t *testing.T) {
   196  	agg := capture.NewAggregator()
   197  	h := NewHandler(agg, testServerOptions())
   198  
   199  	var (
   200  		numClients     = 9
   201  		wgClient       sync.WaitGroup
   202  		expectedResult capture.SnapshotResult
   203  	)
   204  
   205  	listener, err := net.Listen("tcp", testListenAddress)
   206  	require.NoError(t, err)
   207  
   208  	s := xserver.NewServer(testListenAddress, h, xserver.NewOptions())
   209  	// Start server.
   210  	require.NoError(t, s.Serve(listener))
   211  
   212  	// Now establish multiple connections and send data to the server.
   213  	var expectedTotalMetrics int
   214  	for i := 0; i < numClients; i++ {
   215  		wgClient.Add(1)
   216  
   217  		// Add test metrics to expected result.
   218  		expectedResult.CountersWithMetadatas = append(expectedResult.CountersWithMetadatas, testCounterWithMetadatas)
   219  		expectedResult.BatchTimersWithMetadatas = append(expectedResult.BatchTimersWithMetadatas, testBatchTimerWithMetadatas)
   220  		expectedResult.GaugesWithMetadatas = append(expectedResult.GaugesWithMetadatas, testGaugeWithMetadatas)
   221  		expectedResult.TimedMetricWithMetadata = append(expectedResult.TimedMetricWithMetadata, testTimedMetricWithMetadata)
   222  		expectedResult.PassthroughMetricWithMetadata = append(expectedResult.PassthroughMetricWithMetadata, testPassthroughMetricWithMetadata)
   223  		expectedResult.ForwardedMetricsWithMetadata = append(expectedResult.ForwardedMetricsWithMetadata, testForwardedMetricWithMetadata)
   224  		expectedTotalMetrics += 5
   225  
   226  		go func() {
   227  			defer wgClient.Done()
   228  
   229  			conn, err := net.Dial("tcp", listener.Addr().String())
   230  			require.NoError(t, err)
   231  
   232  			encoder := protobuf.NewUnaggregatedEncoder(protobuf.NewUnaggregatedOptions())
   233  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   234  				Type:                 encoding.CounterWithMetadatasType,
   235  				CounterWithMetadatas: testCounterWithMetadatas,
   236  			}))
   237  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   238  				Type:                    encoding.BatchTimerWithMetadatasType,
   239  				BatchTimerWithMetadatas: testBatchTimerWithMetadatas,
   240  			}))
   241  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   242  				Type:               encoding.GaugeWithMetadatasType,
   243  				GaugeWithMetadatas: testGaugeWithMetadatas,
   244  			}))
   245  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   246  				Type:                    encoding.TimedMetricWithMetadataType,
   247  				TimedMetricWithMetadata: testTimedMetricWithMetadata,
   248  			}))
   249  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   250  				Type:                          encoding.PassthroughMetricWithMetadataType,
   251  				PassthroughMetricWithMetadata: testPassthroughMetricWithMetadata,
   252  			}))
   253  			require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{
   254  				Type:                        encoding.ForwardedMetricWithMetadataType,
   255  				ForwardedMetricWithMetadata: testForwardedMetricWithMetadata,
   256  			}))
   257  
   258  			_, err = conn.Write(encoder.Relinquish().Bytes())
   259  			require.NoError(t, err)
   260  		}()
   261  	}
   262  
   263  	// Wait for all metrics to be processed.
   264  	wgClient.Wait()
   265  	for agg.NumMetricsAdded() < expectedTotalMetrics {
   266  		time.Sleep(50 * time.Millisecond)
   267  	}
   268  
   269  	// Close the server.
   270  	s.Close()
   271  
   272  	// Assert the snapshot match expectations.
   273  	snapshot := agg.Snapshot()
   274  	require.True(t, cmp.Equal(expectedResult, snapshot, testCmpOpts...), expectedResult, snapshot)
   275  }
   276  
   277  func TestHandle_Errors(t *testing.T) {
   278  	cases := []struct {
   279  		name   string
   280  		msg    encoding.UnaggregatedMessageUnion
   281  		logMsg string
   282  	}{
   283  		{
   284  			name: "timed with staged metadata",
   285  			msg: encoding.UnaggregatedMessageUnion{
   286  				Type:                     encoding.TimedMetricWithMetadatasType,
   287  				TimedMetricWithMetadatas: testTimedMetricWithMetadatas,
   288  			},
   289  			logMsg: "error adding timed metric",
   290  		},
   291  		{
   292  			name: "timed with metadata",
   293  			msg: encoding.UnaggregatedMessageUnion{
   294  				Type:                    encoding.TimedMetricWithMetadataType,
   295  				TimedMetricWithMetadata: testTimedMetricWithMetadata,
   296  			},
   297  			logMsg: "error adding timed metric",
   298  		},
   299  		{
   300  			name: "gauge untimed",
   301  			msg: encoding.UnaggregatedMessageUnion{
   302  				Type:               encoding.GaugeWithMetadatasType,
   303  				GaugeWithMetadatas: testGaugeWithMetadatas,
   304  			},
   305  			logMsg: "error adding untimed metric",
   306  		},
   307  		{
   308  			name: "counter untimed",
   309  			msg: encoding.UnaggregatedMessageUnion{
   310  				Type:                 encoding.CounterWithMetadatasType,
   311  				CounterWithMetadatas: testCounterWithMetadatas,
   312  			},
   313  			logMsg: "error adding untimed metric",
   314  		},
   315  		{
   316  			name: "timer untimed",
   317  			msg: encoding.UnaggregatedMessageUnion{
   318  				Type:                    encoding.BatchTimerWithMetadatasType,
   319  				BatchTimerWithMetadatas: testBatchTimerWithMetadatas,
   320  			},
   321  			logMsg: "error adding untimed metric",
   322  		},
   323  		{
   324  			name: "forward",
   325  			msg: encoding.UnaggregatedMessageUnion{
   326  				Type:                        encoding.ForwardedMetricWithMetadataType,
   327  				ForwardedMetricWithMetadata: testForwardedMetricWithMetadata,
   328  			},
   329  			logMsg: "error adding forwarded metric",
   330  		},
   331  		{
   332  			name: "passthrough",
   333  			msg: encoding.UnaggregatedMessageUnion{
   334  				Type:                          encoding.PassthroughMetricWithMetadataType,
   335  				PassthroughMetricWithMetadata: testPassthroughMetricWithMetadata,
   336  			},
   337  			logMsg: "error adding passthrough metric",
   338  		},
   339  	}
   340  
   341  	ctrl := xtest.NewController(t)
   342  	defer ctrl.Finish()
   343  	agg := aggregator.NewMockAggregator(ctrl)
   344  
   345  	aggErr := errors.New("boom")
   346  	agg.EXPECT().AddTimedWithStagedMetadatas(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes()
   347  	agg.EXPECT().AddUntimed(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes()
   348  	agg.EXPECT().AddForwarded(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes()
   349  	agg.EXPECT().AddTimed(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes()
   350  	agg.EXPECT().AddPassthrough(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes()
   351  
   352  	for _, tc := range cases {
   353  		tc := tc
   354  		t.Run(tc.name, func(t *testing.T) {
   355  			core, recorded := observer.New(zapcore.InfoLevel)
   356  			listener, err := net.Listen("tcp", testListenAddress)
   357  			require.NoError(t, err)
   358  
   359  			h := NewHandler(agg, testServerOptions().SetInstrumentOptions(instrument.NewOptions().
   360  				SetLogger(zap.New(core))))
   361  			s := xserver.NewServer(testListenAddress, h, xserver.NewOptions())
   362  			// Start server.
   363  			require.NoError(t, s.Serve(listener))
   364  
   365  			conn, err := net.Dial("tcp", listener.Addr().String())
   366  			encoder := protobuf.NewUnaggregatedEncoder(protobuf.NewUnaggregatedOptions())
   367  			require.NoError(t, err)
   368  
   369  			require.NoError(t, encoder.EncodeMessage(tc.msg))
   370  
   371  			_, err = conn.Write(encoder.Relinquish().Bytes())
   372  			require.NoError(t, err)
   373  			res := clock.WaitUntil(func() bool {
   374  				return len(recorded.FilterMessage(tc.logMsg).All()) == 1
   375  			}, time.Second*5)
   376  			if !res {
   377  				require.Fail(t,
   378  					"failed to find expected log message",
   379  					"expected=%v logs=%v", tc.logMsg, recorded.All())
   380  			}
   381  		})
   382  	}
   383  }
   384  
   385  func testServerOptions() Options {
   386  	opts := NewOptions()
   387  	instrumentOpts := opts.InstrumentOptions().SetReportInterval(time.Second)
   388  	serverOpts := xserver.NewOptions().SetRetryOptions(retry.NewOptions().SetMaxRetries(2))
   389  	return opts.SetInstrumentOptions(instrumentOpts).SetServerOptions(serverOpts)
   390  }