github.com/m3db/m3@v1.5.0/src/cmd/tools/dtest/config/config.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  package config
    22  
    23  import (
    24  	"fmt"
    25  	"io/ioutil"
    26  	"time"
    27  
    28  	etcdclient "github.com/m3db/m3/src/cluster/client/etcd"
    29  	"github.com/m3db/m3/src/cluster/placement"
    30  	m3emnode "github.com/m3db/m3/src/dbnode/x/m3em/node"
    31  	"github.com/m3db/m3/src/m3em/cluster"
    32  	"github.com/m3db/m3/src/m3em/generated/proto/m3em"
    33  	"github.com/m3db/m3/src/m3em/node"
    34  	"github.com/m3db/m3/src/m3em/x/grpc"
    35  	xconfig "github.com/m3db/m3/src/x/config"
    36  
    37  	"go.uber.org/zap"
    38  	"google.golang.org/grpc"
    39  	"google.golang.org/grpc/credentials"
    40  )
    41  
    42  // Configuration is a collection of knobs to control test behavior
    43  type Configuration struct {
    44  	DTest DTestConfig              `yaml:"dtest"`
    45  	M3EM  M3EMConfig               `yaml:"m3em"`
    46  	KV    etcdclient.Configuration `yaml:"kv"`
    47  }
    48  
    49  // DTestConfig is a collection of DTest configs
    50  type DTestConfig struct {
    51  	DebugPort               int                 `yaml:"debugPort" validate:"nonzero"`
    52  	BootstrapTimeout        time.Duration       `yaml:"bootstrapTimeout" validate:"nonzero"`
    53  	BootstrapReportInterval time.Duration       `yaml:"bootstrapReportInterval" validate:"nonzero"`
    54  	NodePort                int                 `yaml:"nodePort" validate:"nonzero"`
    55  	ServiceID               string              `yaml:"serviceID" validate:"nonzero"`
    56  	DataDir                 string              `yaml:"dataDir" validate:"nonzero"` // path relative to m3em agent working directory
    57  	Seeds                   []SeedConfig        `yaml:"seeds"`
    58  	Instances               []PlacementInstance `yaml:"instances" validate:"min=1"`
    59  }
    60  
    61  // SeedConfig is a collection of Seed Data configurations
    62  type SeedConfig struct {
    63  	Namespace     string        `yaml:"namespace" validate:"nonzero"`
    64  	LocalShardNum uint32        `yaml:"localShardNum" validate:"nonzero"`
    65  	Retention     time.Duration `yaml:"retention" validate:"nonzero"`
    66  	BlockSize     time.Duration `yaml:"blockSize" validate:"nonzero"`
    67  	Delay         time.Duration `yaml:"delay"`
    68  }
    69  
    70  // PlacementInstance is a config for a placement instance.
    71  type PlacementInstance struct {
    72  	ID       string `yaml:"id" validate:"nonzero"`
    73  	Rack     string `yaml:"rack" validate:"nonzero"`
    74  	Zone     string `yaml:"zone" validate:"nonzero"`
    75  	Weight   uint32 `yaml:"weight" validate:"nonzero"`
    76  	Hostname string `yaml:"hostname" validate:"nonzero"`
    77  }
    78  
    79  // M3EMConfig is a list of m3em environment settings
    80  type M3EMConfig struct {
    81  	AgentPort     int                   `yaml:"agentPort" validate:"nonzero"`
    82  	AgentTLS      *TLSConfiguration     `yaml:"agentTLS"`
    83  	HeartbeatPort int                   `yaml:"heartbeatPort" validate:"nonzero"`
    84  	Node          node.Configuration    `yaml:"node"`
    85  	Cluster       cluster.Configuration `yaml:"cluster"`
    86  }
    87  
    88  // TLSConfiguration are the resources required for TLS Communication
    89  type TLSConfiguration struct {
    90  	ServerName    string `yaml:"serverName" validate:"nonzero"`
    91  	CACrtPath     string `yaml:"caCrt" validate:"nonzero"`
    92  	ClientCrtPath string `yaml:"clientCrt" validate:"nonzero"`
    93  	ClientKeyPath string `yaml:"clientKey" validate:"nonzero"`
    94  }
    95  
    96  // Credentials returns the TransportCredentials corresponding to the provided struct
    97  func (t TLSConfiguration) Credentials() (credentials.TransportCredentials, error) {
    98  	caCrt, err := ioutil.ReadFile(t.CACrtPath)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	clientCrt, err := ioutil.ReadFile(t.ClientCrtPath)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	clientKey, err := ioutil.ReadFile(t.ClientKeyPath)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return xgrpc.NewClientCredentials(t.ServerName, caCrt, clientCrt, clientKey)
   114  }
   115  
   116  // New constructs a Configuration object from the path specified
   117  func New(m3emConfigPath string) (*Configuration, error) {
   118  	var conf Configuration
   119  	if err := xconfig.LoadFile(&conf, m3emConfigPath, xconfig.Options{}); err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return &conf, nil
   124  }
   125  
   126  // Zone returns the zone configured for kv, and the instances if they are all the same;
   127  // it returns an error if they're not.
   128  func (c *Configuration) Zone() (string, error) {
   129  	kvZone := c.KV.Zone
   130  	for _, inst := range c.DTest.Instances {
   131  		if kvZone != inst.Zone {
   132  			return "", fmt.Errorf("instance has zone %s which differs from kv zone %s", kvZone, inst.Zone)
   133  		}
   134  	}
   135  	return kvZone, nil
   136  }
   137  
   138  // Nodes returns a slice of m3emnode.Nodes per the config provided
   139  func (c *Configuration) Nodes(opts node.Options, numNodes int) ([]m3emnode.Node, error) {
   140  	// use all nodes if numNodes is zero
   141  	if numNodes <= 0 {
   142  		numNodes = len(c.DTest.Instances)
   143  	}
   144  
   145  	var (
   146  		logger  = opts.InstrumentOptions().Logger()
   147  		nodes   = make([]m3emnode.Node, 0, len(c.DTest.Instances))
   148  		nodeNum = 0
   149  	)
   150  
   151  	for _, inst := range c.DTest.Instances {
   152  		if nodeNum >= numNodes {
   153  			break
   154  		}
   155  
   156  		pi := inst.newServicesPlacementInstance(c.DTest.NodePort)
   157  		clientFn, err := inst.operatorClientFn(c.M3EM.AgentPort, c.M3EM.AgentTLS)
   158  		if err != nil {
   159  			return nil, fmt.Errorf("unable to create operationClientFn for %+v, error: %v", inst, err)
   160  		}
   161  
   162  		newOpts := opts.
   163  			SetOperatorClientFn(clientFn).
   164  			SetInstrumentOptions(opts.InstrumentOptions().SetLogger(
   165  				logger.With(zap.String("host", inst.Hostname))))
   166  
   167  		svcNode, err := node.New(pi, newOpts)
   168  		if err != nil {
   169  			return nil, fmt.Errorf("unable to create service node for %+v, error: %v", inst, err)
   170  		}
   171  
   172  		nodeOpts := m3emnode.NewOptions(newOpts.InstrumentOptions()).SetNodeOptions(newOpts)
   173  		n, err := m3emnode.New(svcNode, nodeOpts)
   174  		if err != nil {
   175  			return nil, fmt.Errorf("unable to create m3emnode for %+v, error: %v", inst, err)
   176  		}
   177  		nodes = append(nodes, n)
   178  
   179  		nodeNum++
   180  	}
   181  
   182  	return nodes, nil
   183  }
   184  
   185  func (pi *PlacementInstance) operatorClientFn(agentPort int, tlsConfig *TLSConfiguration) (node.OperatorClientFn, error) {
   186  	agentEndpoint := fmt.Sprintf("%s:%d", pi.Hostname, agentPort)
   187  
   188  	dialOpt := grpc.WithInsecure()
   189  	if tlsConfig != nil {
   190  		tc, err := tlsConfig.Credentials()
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		dialOpt = grpc.WithTransportCredentials(tc)
   195  	}
   196  
   197  	return func() (*grpc.ClientConn, m3em.OperatorClient, error) {
   198  		conn, err := grpc.Dial(agentEndpoint, dialOpt)
   199  		if err != nil {
   200  			return nil, nil, err
   201  		}
   202  		return conn, m3em.NewOperatorClient(conn), nil
   203  	}, nil
   204  }
   205  
   206  func (pi *PlacementInstance) newServicesPlacementInstance(nodePort int) placement.Instance {
   207  	endpoint := fmt.Sprintf("%s:%d", pi.Hostname, nodePort)
   208  	return placement.NewInstance().
   209  		SetID(pi.ID).
   210  		SetIsolationGroup(pi.Rack).
   211  		SetZone(pi.Zone).
   212  		SetEndpoint(endpoint).
   213  		SetWeight(pi.Weight)
   214  }