github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/resources/inprocess/aggregator_test.go (about)

     1  // +build test_harness
     2  // Copyright (c) 2021  Uber Technologies, Inc.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy
     5  // of this software and associated documentation files (the "Software"), to deal
     6  // in the Software without restriction, including without limitation the rights
     7  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  // copies of the Software, and to permit persons to whom the Software is
     9  // furnished to do so, subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in
    12  // all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20  // THE SOFTWARE.
    21  
    22  package inprocess
    23  
    24  import (
    25  	"errors"
    26  	"fmt"
    27  	"testing"
    28  	"time"
    29  
    30  	m3agg "github.com/m3db/m3/src/aggregator/aggregator"
    31  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    32  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    33  	"github.com/m3db/m3/src/integration/resources"
    34  	"github.com/m3db/m3/src/msg/generated/proto/topicpb"
    35  	"github.com/m3db/m3/src/query/generated/proto/admin"
    36  	"github.com/m3db/m3/src/query/generated/proto/prompb"
    37  	"github.com/m3db/m3/src/query/storage"
    38  	xtime "github.com/m3db/m3/src/x/time"
    39  	"github.com/prometheus/common/model"
    40  
    41  	"github.com/stretchr/testify/assert"
    42  	"github.com/stretchr/testify/require"
    43  )
    44  
    45  func TestNewAggregator(t *testing.T) {
    46  	coord, closer := setupCoordinator(t)
    47  	defer closer()
    48  	require.NoError(t, coord.WaitForNamespace(""))
    49  
    50  	agg, err := NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{})
    51  	require.NoError(t, err)
    52  	setupPlacement(t, coord, resources.Aggregators{agg})
    53  	setupM3msgTopic(t, coord)
    54  	agg.Start()
    55  	require.NoError(t, resources.Retry(agg.IsHealthy))
    56  	require.NoError(t, agg.Close())
    57  
    58  	// re-construct and restart an aggregator instance
    59  	agg, err = NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{Start: true})
    60  	require.NoError(t, err)
    61  	require.NoError(t, resources.Retry(agg.IsHealthy))
    62  	require.NoError(t, agg.Close())
    63  }
    64  
    65  func TestMultiAggregators(t *testing.T) {
    66  	coord, closer := setupCoordinator(t)
    67  	defer closer()
    68  	require.NoError(t, coord.WaitForNamespace(""))
    69  
    70  	aggOpts := AggregatorOptions{
    71  		GenerateHostID: true,
    72  		GeneratePorts:  true,
    73  		Start:          false,
    74  	}
    75  
    76  	agg1, err := NewAggregatorFromYAML(defaultAggregatorConfig, aggOpts)
    77  	defer func() {
    78  		assert.NoError(t, agg1.Close())
    79  	}()
    80  	require.NoError(t, err)
    81  
    82  	agg2, err := NewAggregatorFromYAML(defaultAggregatorConfig, aggOpts)
    83  	defer func() {
    84  		assert.NoError(t, agg2.Close())
    85  	}()
    86  	require.NoError(t, err)
    87  
    88  	setupPlacement(t, coord, resources.Aggregators{agg1, agg2})
    89  	setupM3msgTopic(t, coord)
    90  
    91  	agg1.Start()
    92  	require.NoError(t, resources.Retry(agg1.IsHealthy))
    93  
    94  	agg2.Start()
    95  	require.NoError(t, resources.Retry(agg2.IsHealthy))
    96  }
    97  
    98  func TestAggregatorStatus(t *testing.T) {
    99  	coord, closer := setupCoordinator(t)
   100  	defer closer()
   101  	require.NoError(t, coord.WaitForNamespace(""))
   102  
   103  	agg, err := NewAggregatorFromYAML(
   104  		defaultAggregatorConfig,
   105  		AggregatorOptions{GenerateHostID: true, GeneratePorts: true},
   106  	)
   107  	require.NoError(t, err)
   108  	defer func() {
   109  		assert.NoError(t, agg.Close())
   110  	}()
   111  
   112  	setupPlacement(t, coord, resources.Aggregators{agg})
   113  	setupM3msgTopic(t, coord)
   114  	agg.Start()
   115  	require.NoError(t, resources.Retry(agg.IsHealthy))
   116  
   117  	followerStatus := m3agg.RuntimeStatus{
   118  		FlushStatus: m3agg.FlushStatus{
   119  			ElectionState: m3agg.FollowerState,
   120  			CanLead:       false,
   121  		},
   122  	}
   123  
   124  	status, err := agg.Status()
   125  	require.NoError(t, err)
   126  	require.Equal(t, followerStatus, status)
   127  
   128  	// A follower remains a follower after resigning
   129  	require.NoError(t, agg.Resign())
   130  	status, err = agg.Status()
   131  	require.NoError(t, err)
   132  	require.Equal(t, followerStatus, status)
   133  }
   134  
   135  func TestAggregatorWriteWithCluster(t *testing.T) {
   136  	cfgs, err := NewClusterConfigsFromYAML(defaultDBNodeConfig, aggregatorCoordConfig, defaultAggregatorConfig)
   137  	require.NoError(t, err)
   138  
   139  	cluster, err := NewCluster(cfgs,
   140  		resources.ClusterOptions{
   141  			DBNode: resources.NewDBNodeClusterOptions(),
   142  		},
   143  	)
   144  	require.NoError(t, err)
   145  	defer func() {
   146  		assert.NoError(t, cluster.Cleanup())
   147  	}()
   148  
   149  	coord := cluster.Coordinator()
   150  	agg, err := NewAggregatorFromYAML(defaultAggregatorConfig, AggregatorOptions{Start: false})
   151  	defer func() {
   152  		assert.NoError(t, agg.Close())
   153  	}()
   154  	require.NoError(t, err)
   155  
   156  	setupPlacement(t, coord, resources.Aggregators{agg})
   157  	setupM3msgTopic(t, coord)
   158  
   159  	agg.Start()
   160  	require.NoError(t, resources.Retry(agg.IsHealthy))
   161  
   162  	testAggMetrics(t, coord)
   163  }
   164  
   165  func setupCoordinator(t *testing.T) (resources.Coordinator, func()) {
   166  	dbnode, err := NewDBNodeFromYAML(defaultDBNodeConfig, DBNodeOptions{Start: true})
   167  	require.NoError(t, err)
   168  
   169  	coord, err := NewCoordinatorFromYAML(aggregatorCoordConfig, CoordinatorOptions{Start: true})
   170  	require.NoError(t, err)
   171  
   172  	return coord, func() {
   173  		assert.NoError(t, coord.Close())
   174  		assert.NoError(t, dbnode.Close())
   175  	}
   176  }
   177  
   178  func setupM3msgTopic(t *testing.T, coord resources.Coordinator) {
   179  	m3msgTopicOpts := resources.M3msgTopicOptions{
   180  		Zone:      "embedded",
   181  		Env:       "default_env",
   182  		TopicName: "aggregator_ingest",
   183  	}
   184  
   185  	_, err := coord.InitM3msgTopic(m3msgTopicOpts, admin.TopicInitRequest{NumberOfShards: 4})
   186  	require.NoError(t, err)
   187  
   188  	_, err = coord.AddM3msgTopicConsumer(m3msgTopicOpts, admin.TopicAddRequest{
   189  		ConsumerService: &topicpb.ConsumerService{
   190  			ServiceId: &topicpb.ServiceID{
   191  				Name:        handleroptions.M3AggregatorServiceName,
   192  				Environment: m3msgTopicOpts.Env,
   193  				Zone:        m3msgTopicOpts.Zone,
   194  			},
   195  			ConsumptionType: topicpb.ConsumptionType_REPLICATED,
   196  			MessageTtlNanos: 600000000000, // 10 mins
   197  		},
   198  	})
   199  	require.NoError(t, err)
   200  
   201  	aggregatedTopicOpts := resources.M3msgTopicOptions{
   202  		Zone:      "embedded",
   203  		Env:       "default_env",
   204  		TopicName: "aggregated_metrics",
   205  	}
   206  	_, err = coord.InitM3msgTopic(aggregatedTopicOpts, admin.TopicInitRequest{NumberOfShards: 4})
   207  	require.NoError(t, err)
   208  
   209  	_, err = coord.AddM3msgTopicConsumer(aggregatedTopicOpts, admin.TopicAddRequest{
   210  		ConsumerService: &topicpb.ConsumerService{
   211  			ServiceId: &topicpb.ServiceID{
   212  				Name:        handleroptions.M3CoordinatorServiceName,
   213  				Environment: aggregatedTopicOpts.Env,
   214  				Zone:        aggregatedTopicOpts.Zone,
   215  			},
   216  			ConsumptionType: topicpb.ConsumptionType_SHARED,
   217  			MessageTtlNanos: 600000000000, // 10 mins
   218  		},
   219  	})
   220  	require.NoError(t, err)
   221  }
   222  
   223  func setupPlacement(t *testing.T, coord resources.Coordinator, aggs resources.Aggregators) {
   224  	instances := make([]*placementpb.Instance, 0, len(aggs))
   225  	for _, agg := range aggs {
   226  		info, err := agg.HostDetails()
   227  		require.NoError(t, err)
   228  		instance := &placementpb.Instance{
   229  			Id:             info.ID,
   230  			IsolationGroup: info.ID,
   231  			Zone:           info.Zone,
   232  			Weight:         1,
   233  			Endpoint:       fmt.Sprintf("%s:%d", info.M3msgAddress, info.M3msgPort),
   234  			Hostname:       info.ID,
   235  			Port:           info.M3msgPort,
   236  		}
   237  
   238  		instances = append(instances, instance)
   239  	}
   240  
   241  	_, err := coord.InitPlacement(
   242  		resources.PlacementRequestOptions{
   243  			Service: resources.ServiceTypeM3Aggregator,
   244  			Zone:    "embedded",
   245  			Env:     "default_env",
   246  		},
   247  		admin.PlacementInitRequest{
   248  			NumShards:         4,
   249  			ReplicationFactor: 1,
   250  			Instances:         instances,
   251  		},
   252  	)
   253  	require.NoError(t, err)
   254  
   255  	_, err = coord.InitPlacement(
   256  		resources.PlacementRequestOptions{
   257  			Service: resources.ServiceTypeM3Coordinator,
   258  			Zone:    "embedded",
   259  			Env:     "default_env",
   260  		},
   261  		admin.PlacementInitRequest{
   262  			Instances: []*placementpb.Instance{
   263  				{
   264  					Id:       "m3coordinator01",
   265  					Zone:     "embedded",
   266  					Endpoint: "0.0.0.0:7507",
   267  					Hostname: "m3coordinator01",
   268  					Port:     7507,
   269  				},
   270  			},
   271  		},
   272  	)
   273  	require.NoError(t, err)
   274  }
   275  
   276  func testAggMetrics(t *testing.T, coord resources.Coordinator) {
   277  	var (
   278  		ts      = time.Now()
   279  		ts1     = xtime.ToUnixNano(ts)
   280  		ts2     = xtime.ToUnixNano(ts.Add(1 * time.Millisecond))
   281  		ts3     = xtime.ToUnixNano(ts.Add(2 * time.Millisecond))
   282  		samples = []prompb.Sample{
   283  			{Value: 1, Timestamp: storage.TimeToPromTimestamp(ts1)},
   284  			{Value: 2, Timestamp: storage.TimeToPromTimestamp(ts2)},
   285  			{Value: 3, Timestamp: storage.TimeToPromTimestamp(ts3)},
   286  		}
   287  		// 6=1+2+3 is the sum of all three samples.
   288  		expectedValue = model.SampleValue(6)
   289  	)
   290  	assert.NoError(t, resources.Retry(func() error {
   291  		return coord.WriteProm("cpu", map[string]string{"host": "host1"}, samples, nil)
   292  	}))
   293  
   294  	queryHeaders := resources.Headers{"M3-Metrics-Type": {"aggregated"}, "M3-Storage-Policy": {"5s:6h"}}
   295  
   296  	// Instant Query
   297  	require.NoError(t, resources.Retry(func() error {
   298  		result, err := coord.InstantQuery(resources.QueryRequest{Query: "cpu"}, queryHeaders)
   299  		if err != nil {
   300  			return err
   301  		}
   302  		if len(result) != 1 {
   303  			return errors.New("wrong amount of datapoints")
   304  		}
   305  		if result[0].Value != expectedValue {
   306  			return errors.New("wrong data point value")
   307  		}
   308  		return nil
   309  	}))
   310  
   311  	// Range Query
   312  	require.NoError(t, resources.Retry(func() error {
   313  		result, err := coord.RangeQuery(
   314  			resources.RangeQueryRequest{
   315  				Query: "cpu",
   316  				Start: time.Now().Add(-30 * time.Second),
   317  				End:   time.Now(),
   318  				Step:  1 * time.Second,
   319  			},
   320  			queryHeaders,
   321  		)
   322  		if err != nil {
   323  			return err
   324  		}
   325  		if len(result) != 1 {
   326  			return errors.New("wrong amount of series in the range query result")
   327  		}
   328  		if len(result[0].Values) == 0 {
   329  			return errors.New("empty range query result")
   330  		}
   331  		if result[0].Values[0].Value != expectedValue {
   332  			return errors.New("wrong range query value")
   333  		}
   334  		return nil
   335  	}))
   336  }
   337  
   338  const defaultAggregatorConfig = `{}`
   339  
   340  const aggregatorCoordConfig = `
   341  clusters:
   342    - client:
   343        config:
   344          service:
   345            env: default_env
   346            zone: embedded
   347            service: m3db
   348            etcdClusters:
   349              - zone: embedded
   350                endpoints:
   351                  - 127.0.0.1:2379
   352  downsample:
   353    rules:
   354      mappingRules:
   355        - name: "agged metrics"
   356          filter: "host:*"
   357          aggregations: ["Sum"]
   358          storagePolicies:
   359            - resolution: 5s
   360              retention: 6h
   361  ingest:
   362    ingester:
   363      workerPoolSize: 10000
   364    m3msg:
   365      server:
   366        listenAddress: "0.0.0.0:7507"
   367  `