github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/resources/resources.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 resources
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"net"
    27  	"strconv"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    31  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    32  	"github.com/m3db/m3/src/dbnode/generated/proto/namespace"
    33  	"github.com/m3db/m3/src/msg/generated/proto/topicpb"
    34  	"github.com/m3db/m3/src/query/generated/proto/admin"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  
    37  	protobuftypes "github.com/gogo/protobuf/types"
    38  	"go.uber.org/zap"
    39  )
    40  
    41  const (
    42  	retention = "6h"
    43  
    44  	// AggName is the name of the aggregated namespace.
    45  	AggName = "aggregated"
    46  	// UnaggName is the name of the unaggregated namespace.
    47  	UnaggName = "default"
    48  	// ColdWriteNsName is the name for cold write namespace.
    49  	ColdWriteNsName = "coldWritesRepairAndNoIndex"
    50  	// AggregatorInputTopic is the m3msg topic name for coordinator->aggregator traffic.
    51  	AggregatorInputTopic = "aggregator_ingest"
    52  	// AggregatorOutputTopic is the m3msg topic name for aggregator->coordinator traffic.
    53  	AggregatorOutputTopic = "aggregated_metrics"
    54  )
    55  
    56  // SetupCluster setups m3 cluster on provided docker containers.
    57  func SetupCluster(
    58  	cluster M3Resources,
    59  	opts ClusterOptions,
    60  ) error { // nolint: gocyclo
    61  	coordinator := cluster.Coordinator()
    62  	iOpts := instrument.NewOptions()
    63  	logger := iOpts.Logger().With(zap.String("source", "harness"))
    64  	hosts := make([]*admin.Host, 0, len(cluster.Nodes()))
    65  	ids := make([]string, 0, len(cluster.Nodes()))
    66  	for i, n := range cluster.Nodes() {
    67  		h, err := n.HostDetails(9000)
    68  		if err != nil {
    69  			logger.Error("could not get host details", zap.Error(err))
    70  			return err
    71  		}
    72  		if opts.DBNode != nil && opts.DBNode.NumIsolationGroups > 0 {
    73  			h.IsolationGroup = fmt.Sprintf("isogroup-%d", int32(i)%opts.DBNode.NumIsolationGroups)
    74  		}
    75  
    76  		hosts = append(hosts, h)
    77  		ids = append(ids, h.GetId())
    78  	}
    79  
    80  	replicationFactor := int32(1)
    81  	numShards := int32(4)
    82  	if opts.DBNode != nil {
    83  		if opts.DBNode.RF > 0 {
    84  			replicationFactor = opts.DBNode.RF
    85  		}
    86  		if opts.DBNode.NumShards > 0 {
    87  			numShards = opts.DBNode.NumShards
    88  		}
    89  	}
    90  
    91  	var (
    92  		unaggDatabase = admin.DatabaseCreateRequest{
    93  			Type:              "cluster",
    94  			NamespaceName:     UnaggName,
    95  			RetentionTime:     retention,
    96  			NumShards:         numShards,
    97  			ReplicationFactor: replicationFactor,
    98  			Hosts:             hosts,
    99  		}
   100  
   101  		aggNamespace = admin.NamespaceAddRequest{
   102  			Name: AggName,
   103  			Options: &namespace.NamespaceOptions{
   104  				BootstrapEnabled:  true,
   105  				FlushEnabled:      true,
   106  				WritesToCommitLog: true,
   107  				CleanupEnabled:    true,
   108  				SnapshotEnabled:   true,
   109  				IndexOptions: &namespace.IndexOptions{
   110  					Enabled:        true,
   111  					BlockSizeNanos: int64(30 * time.Minute),
   112  				},
   113  				RetentionOptions: &namespace.RetentionOptions{
   114  					RetentionPeriodNanos:                     int64(6 * time.Hour),
   115  					BlockSizeNanos:                           int64(30 * time.Minute),
   116  					BufferFutureNanos:                        int64(2 * time.Minute),
   117  					BufferPastNanos:                          int64(10 * time.Minute),
   118  					BlockDataExpiry:                          true,
   119  					BlockDataExpiryAfterNotAccessPeriodNanos: int64(time.Minute * 5),
   120  				},
   121  				AggregationOptions: &namespace.AggregationOptions{
   122  					Aggregations: []*namespace.Aggregation{
   123  						{
   124  							Aggregated: true,
   125  							Attributes: &namespace.AggregatedAttributes{
   126  								ResolutionNanos: int64(5 * time.Second),
   127  							},
   128  						},
   129  					},
   130  				},
   131  			},
   132  		}
   133  
   134  		coldWriteNamespace = admin.NamespaceAddRequest{
   135  			Name: ColdWriteNsName,
   136  			Options: &namespace.NamespaceOptions{
   137  				BootstrapEnabled:      true,
   138  				FlushEnabled:          true,
   139  				WritesToCommitLog:     true,
   140  				CleanupEnabled:        true,
   141  				SnapshotEnabled:       true,
   142  				RepairEnabled:         true,
   143  				ColdWritesEnabled:     true,
   144  				CacheBlocksOnRetrieve: &protobuftypes.BoolValue{Value: true},
   145  				RetentionOptions: &namespace.RetentionOptions{
   146  					RetentionPeriodNanos:                     int64(4 * time.Hour),
   147  					BlockSizeNanos:                           int64(time.Hour),
   148  					BufferFutureNanos:                        int64(time.Minute * 10),
   149  					BufferPastNanos:                          int64(time.Minute * 10),
   150  					BlockDataExpiry:                          true,
   151  					BlockDataExpiryAfterNotAccessPeriodNanos: int64(time.Minute * 5),
   152  				},
   153  			},
   154  		}
   155  	)
   156  
   157  	logger.Info("waiting for coordinator")
   158  	if err := coordinator.WaitForNamespace(""); err != nil {
   159  		return err
   160  	}
   161  
   162  	logger.Info("creating database", zap.Any("request", unaggDatabase))
   163  	if _, err := coordinator.CreateDatabase(unaggDatabase); err != nil {
   164  		return err
   165  	}
   166  
   167  	logger.Info("waiting for placements", zap.Strings("placement ids", ids))
   168  	if err := coordinator.WaitForInstances(ids); err != nil {
   169  		return err
   170  	}
   171  
   172  	logger.Info("waiting for namespace", zap.String("name", UnaggName))
   173  	if err := coordinator.WaitForNamespace(UnaggName); err != nil {
   174  		return err
   175  	}
   176  
   177  	logger.Info("creating namespace", zap.Any("request", aggNamespace))
   178  	if _, err := coordinator.AddNamespace(aggNamespace); err != nil {
   179  		return err
   180  	}
   181  
   182  	logger.Info("waiting for namespace", zap.String("name", AggName))
   183  	if err := coordinator.WaitForNamespace(AggName); err != nil {
   184  		return err
   185  	}
   186  
   187  	logger.Info("creating namespace", zap.Any("request", coldWriteNamespace))
   188  	if _, err := coordinator.AddNamespace(coldWriteNamespace); err != nil {
   189  		return err
   190  	}
   191  
   192  	logger.Info("waiting for namespace", zap.String("name", ColdWriteNsName))
   193  	if err := coordinator.WaitForNamespace(ColdWriteNsName); err != nil {
   194  		return err
   195  	}
   196  
   197  	logger.Info("waiting for healthy")
   198  	if err := cluster.Nodes().WaitForHealthy(); err != nil {
   199  		return err
   200  	}
   201  
   202  	logger.Info("waiting for shards ready")
   203  	if err := coordinator.WaitForShardsReady(); err != nil {
   204  		return err
   205  	}
   206  
   207  	logger.Info("waiting for cluster to be ready")
   208  	if err := coordinator.WaitForClusterReady(); err != nil {
   209  		return err
   210  	}
   211  
   212  	if opts.Aggregator != nil {
   213  		aggregators := cluster.Aggregators()
   214  		if len(aggregators) == 0 {
   215  			return errors.New("no aggregators have been initiazted")
   216  		}
   217  
   218  		if err := aggregators.WaitForHealthy(); err != nil {
   219  			return err
   220  		}
   221  	}
   222  
   223  	logger.Info("all healthy")
   224  
   225  	return nil
   226  }
   227  
   228  // SetupPlacement configures the placement for the provided coordinators
   229  // and aggregators.
   230  func SetupPlacement(
   231  	coordAPI Coordinator,
   232  	coordHost InstanceInfo,
   233  	aggs Aggregators,
   234  	opts AggregatorClusterOptions,
   235  ) error {
   236  	// Setup aggregator placement.
   237  	var env, zone string
   238  	instances := make([]*placementpb.Instance, 0, len(aggs))
   239  	for i, agg := range aggs {
   240  		info, err := agg.HostDetails()
   241  		if err != nil {
   242  			return err
   243  		}
   244  
   245  		if i == 0 {
   246  			env = info.Env
   247  			zone = info.Zone
   248  		}
   249  
   250  		instance := &placementpb.Instance{
   251  			Id:             info.ID,
   252  			IsolationGroup: fmt.Sprintf("isogroup-%02d", i%int(opts.NumIsolationGroups)),
   253  			Zone:           info.Zone,
   254  			Weight:         1,
   255  			Endpoint:       net.JoinHostPort(info.M3msgAddress, strconv.Itoa(int(info.M3msgPort))),
   256  			Hostname:       info.ID,
   257  			Port:           info.M3msgPort,
   258  		}
   259  
   260  		instances = append(instances, instance)
   261  	}
   262  
   263  	aggPlacementRequestOptions := PlacementRequestOptions{
   264  		Service: ServiceTypeM3Aggregator,
   265  		Zone:    zone,
   266  		Env:     env,
   267  	}
   268  
   269  	_, err := coordAPI.InitPlacement(
   270  		aggPlacementRequestOptions,
   271  		admin.PlacementInitRequest{
   272  			NumShards:         opts.NumShards,
   273  			ReplicationFactor: opts.RF,
   274  			Instances:         instances,
   275  			OptionOverride: &placementpb.Options{
   276  				SkipPortMirroring: &protobuftypes.BoolValue{Value: true},
   277  			},
   278  		},
   279  	)
   280  	if err != nil {
   281  		return fmt.Errorf("failed to init aggregator placement: %w", err)
   282  	}
   283  
   284  	// Setup coordinator placement.
   285  	coordPlacementRequestOptions := PlacementRequestOptions{
   286  		Service: ServiceTypeM3Coordinator,
   287  		Zone:    coordHost.Zone,
   288  		Env:     coordHost.Env,
   289  	}
   290  	_, err = coordAPI.InitPlacement(
   291  		coordPlacementRequestOptions,
   292  		admin.PlacementInitRequest{
   293  			Instances: []*placementpb.Instance{
   294  				{
   295  					Id:       coordHost.ID,
   296  					Zone:     coordHost.Zone,
   297  					Endpoint: net.JoinHostPort(coordHost.M3msgAddress, strconv.Itoa(int(coordHost.M3msgPort))),
   298  					Hostname: coordHost.ID,
   299  					Port:     coordHost.M3msgPort,
   300  				},
   301  			},
   302  		},
   303  	)
   304  	if err != nil {
   305  		return fmt.Errorf("failed to init coordinator placement: %w", err)
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  // SetupM3MsgTopics sets up the m3msg topics for the provided coordinator
   312  // and aggregator.
   313  func SetupM3MsgTopics(
   314  	coord Coordinator,
   315  	aggInstanceInfo InstanceInfo,
   316  	opts ClusterOptions,
   317  ) error {
   318  	aggInputTopicOpts := M3msgTopicOptions{
   319  		Zone:      aggInstanceInfo.Zone,
   320  		Env:       aggInstanceInfo.Env,
   321  		TopicName: AggregatorInputTopic,
   322  	}
   323  
   324  	aggOutputTopicOpts := M3msgTopicOptions{
   325  		Zone:      aggInstanceInfo.Zone,
   326  		Env:       aggInstanceInfo.Env,
   327  		TopicName: AggregatorOutputTopic,
   328  	}
   329  
   330  	_, err := coord.InitM3msgTopic(
   331  		aggInputTopicOpts,
   332  		admin.TopicInitRequest{NumberOfShards: uint32(opts.Aggregator.NumShards)},
   333  	)
   334  	if err != nil {
   335  		return fmt.Errorf("failed to init aggregator input m3msg topic: %w", err)
   336  	}
   337  
   338  	_, err = coord.AddM3msgTopicConsumer(aggInputTopicOpts, admin.TopicAddRequest{
   339  		ConsumerService: &topicpb.ConsumerService{
   340  			ServiceId: &topicpb.ServiceID{
   341  				Name:        handleroptions.M3AggregatorServiceName,
   342  				Environment: aggInputTopicOpts.Env,
   343  				Zone:        aggInputTopicOpts.Zone,
   344  			},
   345  			ConsumptionType: topicpb.ConsumptionType_REPLICATED,
   346  			MessageTtlNanos: 600000000000, // 10 mins
   347  		},
   348  	})
   349  	if err != nil {
   350  		return fmt.Errorf("failed to add aggregator input m3msg topic consumer: %w", err)
   351  	}
   352  
   353  	_, err = coord.InitM3msgTopic(
   354  		aggOutputTopicOpts,
   355  		admin.TopicInitRequest{NumberOfShards: uint32(opts.DBNode.NumShards)},
   356  	)
   357  	if err != nil {
   358  		return fmt.Errorf("failed to init aggregator output m3msg topic: %w", err)
   359  	}
   360  
   361  	_, err = coord.AddM3msgTopicConsumer(aggOutputTopicOpts, admin.TopicAddRequest{
   362  		ConsumerService: &topicpb.ConsumerService{
   363  			ServiceId: &topicpb.ServiceID{
   364  				Name:        handleroptions.M3CoordinatorServiceName,
   365  				Environment: aggInputTopicOpts.Env,
   366  				Zone:        aggInputTopicOpts.Zone,
   367  			},
   368  			ConsumptionType: topicpb.ConsumptionType_SHARED,
   369  			MessageTtlNanos: 600000000000, // 10 mins
   370  		},
   371  	})
   372  	if err != nil {
   373  		return fmt.Errorf("failed to add aggregator output m3msg topic consumer: %w", err)
   374  	}
   375  
   376  	return nil
   377  }