github.com/m3db/m3@v1.5.0/src/query/storage/m3/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 m3
    22  
    23  import (
    24  	goerrors "errors"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/client"
    30  	"github.com/m3db/m3/src/dbnode/encoding"
    31  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    32  	"github.com/m3db/m3/src/query/stores/m3db"
    33  	xerrors "github.com/m3db/m3/src/x/errors"
    34  	"github.com/m3db/m3/src/x/ident"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  )
    37  
    38  var (
    39  	errNotAggregatedClusterNamespace = goerrors.New("not an aggregated cluster namespace")
    40  	errNoNamespaceInitializerSet     = goerrors.New("no namespace initializer set")
    41  )
    42  
    43  // ClustersStaticConfiguration is a set of static cluster configurations.
    44  type ClustersStaticConfiguration []ClusterStaticConfiguration
    45  
    46  // NewClientFromConfig is a method that can be set on
    47  // ClusterStaticConfiguration to allow overriding the client initialization.
    48  type NewClientFromConfig func(
    49  	cfg client.Configuration,
    50  	params client.ConfigurationParameters,
    51  	custom ...client.CustomAdminOption,
    52  ) (client.Client, error)
    53  
    54  // ClusterStaticConfiguration is a static cluster configuration.
    55  type ClusterStaticConfiguration struct {
    56  	NewClientFromConfig NewClientFromConfig                   `yaml:"-"`
    57  	Namespaces          []ClusterStaticNamespaceConfiguration `yaml:"namespaces"`
    58  	Client              client.Configuration                  `yaml:"client"`
    59  }
    60  
    61  func (c ClusterStaticConfiguration) newClient(
    62  	params client.ConfigurationParameters,
    63  	custom ...client.CustomAdminOption,
    64  ) (client.Client, error) {
    65  	if c.NewClientFromConfig != nil {
    66  		return c.NewClientFromConfig(c.Client, params, custom...)
    67  	}
    68  	return c.Client.NewAdminClient(params, custom...)
    69  }
    70  
    71  // ClusterStaticNamespaceConfiguration describes the namespaces in a
    72  // static cluster.
    73  type ClusterStaticNamespaceConfiguration struct {
    74  	// Namespace is namespace in the cluster that is specified.
    75  	Namespace string `yaml:"namespace"`
    76  
    77  	// Type is the type of values stored by the namespace, current
    78  	// supported values are "unaggregated" or "aggregated".
    79  	Type storagemetadata.MetricsType `yaml:"type"`
    80  
    81  	// Retention is the length of which values are stored by the namespace.
    82  	Retention time.Duration `yaml:"retention" validate:"nonzero"`
    83  
    84  	// Resolution is the frequency of which values are stored by the namespace.
    85  	Resolution time.Duration `yaml:"resolution" validate:"min=0"`
    86  
    87  	// Downsample is the configuration for downsampling options to use with
    88  	// the namespace.
    89  	Downsample *DownsampleClusterStaticNamespaceConfiguration `yaml:"downsample"`
    90  
    91  	// ReadOnly prevents any writes to this namespace.
    92  	ReadOnly bool `yaml:"readOnly"`
    93  
    94  	// DataLatency is the duration after which the data is available in this namespace.
    95  	DataLatency time.Duration `yaml:"dataLatency"`
    96  }
    97  
    98  func (c ClusterStaticNamespaceConfiguration) metricsType() (storagemetadata.MetricsType, error) {
    99  	unset := storagemetadata.MetricsType(0)
   100  
   101  	if c.Type != unset {
   102  		// New field value set
   103  		return c.Type, nil
   104  	}
   105  
   106  	// Both are unset
   107  	return storagemetadata.DefaultMetricsType, nil
   108  }
   109  
   110  func (c ClusterStaticNamespaceConfiguration) downsampleOptions() (
   111  	ClusterNamespaceDownsampleOptions,
   112  	error,
   113  ) {
   114  	nsType, err := c.metricsType()
   115  	if err != nil {
   116  		return ClusterNamespaceDownsampleOptions{}, err
   117  	}
   118  	if nsType != storagemetadata.AggregatedMetricsType {
   119  		return ClusterNamespaceDownsampleOptions{}, errNotAggregatedClusterNamespace
   120  	}
   121  	if c.Downsample == nil {
   122  		return DefaultClusterNamespaceDownsampleOptions, nil
   123  	}
   124  
   125  	return c.Downsample.downsampleOptions(), nil
   126  }
   127  
   128  // DownsampleClusterStaticNamespaceConfiguration is configuration
   129  // specified for downsampling options on an aggregated cluster namespace.
   130  type DownsampleClusterStaticNamespaceConfiguration struct {
   131  	All bool `yaml:"all"`
   132  }
   133  
   134  func (c DownsampleClusterStaticNamespaceConfiguration) downsampleOptions() ClusterNamespaceDownsampleOptions {
   135  	return ClusterNamespaceDownsampleOptions(c)
   136  }
   137  
   138  type unaggregatedClusterNamespaceConfiguration struct {
   139  	client    client.Client
   140  	namespace ClusterStaticNamespaceConfiguration
   141  	result    clusterConnectResult
   142  }
   143  
   144  type aggregatedClusterNamespacesConfiguration struct {
   145  	client     client.Client
   146  	namespaces []ClusterStaticNamespaceConfiguration
   147  	result     clusterConnectResult
   148  }
   149  
   150  type clusterConnectResult struct {
   151  	session client.Session
   152  	err     error
   153  }
   154  
   155  // ClustersStaticConfigurationOptions are options to use when
   156  // constructing clusters from config.
   157  type ClustersStaticConfigurationOptions struct {
   158  	AsyncSessions      bool
   159  	ProvidedSession    client.Session
   160  	CustomAdminOptions []client.CustomAdminOption
   161  	EncodingOptions    encoding.Options
   162  }
   163  
   164  // NewStaticClusters instantiates a new Clusters instance based on
   165  // static configuration.
   166  func (c ClustersStaticConfiguration) NewStaticClusters(
   167  	instrumentOpts instrument.Options,
   168  	opts ClustersStaticConfigurationOptions,
   169  	clusterNamespacesWatcher ClusterNamespacesWatcher,
   170  ) (Clusters, error) {
   171  	var (
   172  		numUnaggregatedClusterNamespaces int
   173  		numAggregatedClusterNamespaces   int
   174  		unaggregatedClusterNamespaceCfg  = &unaggregatedClusterNamespaceConfiguration{}
   175  		aggregatedClusterNamespacesCfgs  []*aggregatedClusterNamespacesConfiguration
   176  		unaggregatedClusterNamespace     UnaggregatedClusterNamespaceDefinition
   177  		aggregatedClusterNamespaces      []AggregatedClusterNamespaceDefinition
   178  	)
   179  	for _, clusterCfg := range c {
   180  		var (
   181  			result client.Client
   182  			err    error
   183  		)
   184  
   185  		if opts.ProvidedSession == nil {
   186  			// NB(r): Only create client session if not already provided.
   187  			result, err = clusterCfg.newClient(client.ConfigurationParameters{
   188  				InstrumentOptions: instrumentOpts,
   189  				EncodingOptions:   opts.EncodingOptions,
   190  			}, opts.CustomAdminOptions...)
   191  			if err != nil {
   192  				return nil, err
   193  			}
   194  		}
   195  
   196  		aggregatedClusterNamespacesCfg := &aggregatedClusterNamespacesConfiguration{
   197  			client: result,
   198  		}
   199  
   200  		for _, n := range clusterCfg.Namespaces {
   201  			nsType, err := n.metricsType()
   202  			if err != nil {
   203  				return nil, err
   204  			}
   205  
   206  			switch nsType {
   207  			case storagemetadata.UnaggregatedMetricsType:
   208  				numUnaggregatedClusterNamespaces++
   209  				if numUnaggregatedClusterNamespaces > 1 {
   210  					return nil, fmt.Errorf("only one unaggregated cluster namespace  "+
   211  						"can be specified: specified %d", numUnaggregatedClusterNamespaces)
   212  				}
   213  
   214  				unaggregatedClusterNamespaceCfg.client = result
   215  				unaggregatedClusterNamespaceCfg.namespace = n
   216  
   217  			case storagemetadata.AggregatedMetricsType:
   218  				numAggregatedClusterNamespaces++
   219  
   220  				aggregatedClusterNamespacesCfg.namespaces =
   221  					append(aggregatedClusterNamespacesCfg.namespaces, n)
   222  
   223  			default:
   224  				return nil, fmt.Errorf("unknown storage metrics type: %v", nsType)
   225  			}
   226  		}
   227  
   228  		if len(aggregatedClusterNamespacesCfg.namespaces) > 0 {
   229  			aggregatedClusterNamespacesCfgs =
   230  				append(aggregatedClusterNamespacesCfgs, aggregatedClusterNamespacesCfg)
   231  		}
   232  	}
   233  
   234  	if numUnaggregatedClusterNamespaces != 1 {
   235  		return nil, fmt.Errorf("one unaggregated cluster namespace  "+
   236  			"must be specified: specified %d", numUnaggregatedClusterNamespaces)
   237  	}
   238  
   239  	// Connect to all clusters in parallel.
   240  	var wg sync.WaitGroup
   241  	wg.Add(1)
   242  	go func() {
   243  		defer wg.Done()
   244  		cfg := unaggregatedClusterNamespaceCfg
   245  		if opts.ProvidedSession != nil {
   246  			cfg.result.session = opts.ProvidedSession
   247  		} else if !opts.AsyncSessions {
   248  			cfg.result.session, cfg.result.err = cfg.client.DefaultSession()
   249  		} else {
   250  			cfg.result.session = m3db.NewAsyncSession(func() (client.Client, error) {
   251  				return cfg.client, nil
   252  			}, nil)
   253  		}
   254  	}()
   255  	for _, cfg := range aggregatedClusterNamespacesCfgs {
   256  		cfg := cfg // Capture var
   257  		wg.Add(1)
   258  		go func() {
   259  			defer wg.Done()
   260  			if opts.ProvidedSession != nil {
   261  				cfg.result.session = opts.ProvidedSession
   262  			} else if !opts.AsyncSessions {
   263  				cfg.result.session, cfg.result.err = cfg.client.DefaultSession()
   264  			} else {
   265  				cfg.result.session = m3db.NewAsyncSession(func() (client.Client, error) {
   266  					return cfg.client, nil
   267  				}, nil)
   268  			}
   269  		}()
   270  	}
   271  
   272  	// Wait for connections.
   273  	wg.Wait()
   274  
   275  	if unaggregatedClusterNamespaceCfg.result.err != nil {
   276  		return nil, fmt.Errorf("could not connect to unaggregated cluster: %v",
   277  			unaggregatedClusterNamespaceCfg.result.err)
   278  	}
   279  
   280  	unaggregatedClusterNamespace = UnaggregatedClusterNamespaceDefinition{
   281  		NamespaceID: ident.StringID(unaggregatedClusterNamespaceCfg.namespace.Namespace),
   282  		Session:     unaggregatedClusterNamespaceCfg.result.session,
   283  		Retention:   unaggregatedClusterNamespaceCfg.namespace.Retention,
   284  	}
   285  
   286  	for i, cfg := range aggregatedClusterNamespacesCfgs {
   287  		if cfg.result.err != nil {
   288  			return nil, fmt.Errorf("could not connect to aggregated cluster #%d: %v",
   289  				i, cfg.result.err)
   290  		}
   291  
   292  		for _, n := range cfg.namespaces {
   293  			downsampleOpts, err := n.downsampleOptions()
   294  			if err != nil {
   295  				return nil, fmt.Errorf("error parse downsample options for cluster #%d namespace %s: %v",
   296  					i, n.Namespace, err)
   297  			}
   298  
   299  			def := AggregatedClusterNamespaceDefinition{
   300  				NamespaceID: ident.StringID(n.Namespace),
   301  				Session:     cfg.result.session,
   302  				Retention:   n.Retention,
   303  				Resolution:  n.Resolution,
   304  				Downsample:  &downsampleOpts,
   305  				ReadOnly:    n.ReadOnly,
   306  				DataLatency: n.DataLatency,
   307  			}
   308  			aggregatedClusterNamespaces = append(aggregatedClusterNamespaces, def)
   309  		}
   310  	}
   311  
   312  	clusters, err := NewClusters(unaggregatedClusterNamespace,
   313  		aggregatedClusterNamespaces...)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	if err := clusterNamespacesWatcher.Update(clusters.ClusterNamespaces()); err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	return clusters, nil
   323  }
   324  
   325  // NB(nate): exists primarily for testing.
   326  type newClustersFn func(DynamicClusterOptions) (Clusters, error)
   327  
   328  // NewDynamicClusters instantiates a new Clusters instance that pulls
   329  // cluster information from etcd.
   330  func (c ClustersStaticConfiguration) NewDynamicClusters(
   331  	instrumentOpts instrument.Options,
   332  	opts ClustersStaticConfigurationOptions,
   333  	clusterNamespacesWatcher ClusterNamespacesWatcher,
   334  ) (Clusters, error) {
   335  	return c.newDynamicClusters(NewDynamicClusters, instrumentOpts, opts, clusterNamespacesWatcher)
   336  }
   337  
   338  func (c ClustersStaticConfiguration) newDynamicClusters(
   339  	newFn newClustersFn,
   340  	instrumentOpts instrument.Options,
   341  	opts ClustersStaticConfigurationOptions,
   342  	clusterNamespacesWatcher ClusterNamespacesWatcher,
   343  ) (Clusters, error) {
   344  	clients := make([]client.Client, 0, len(c))
   345  	for _, clusterCfg := range c {
   346  		clusterClient, err := clusterCfg.newClient(client.ConfigurationParameters{
   347  			InstrumentOptions: instrumentOpts,
   348  			EncodingOptions:   opts.EncodingOptions,
   349  		}, opts.CustomAdminOptions...)
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  		clients = append(clients, clusterClient)
   354  	}
   355  
   356  	// Connect to all clusters in parallel
   357  	var (
   358  		wg   sync.WaitGroup
   359  		cfgs = make([]DynamicClusterNamespaceConfiguration, len(clients))
   360  
   361  		errLock  sync.Mutex
   362  		multiErr xerrors.MultiError
   363  	)
   364  	for i, clusterClient := range clients {
   365  		i := i
   366  		clusterClient := clusterClient
   367  		nsInit := clusterClient.Options().NamespaceInitializer()
   368  
   369  		// TODO(nate): move this validation to client.Options once static configuration of namespaces
   370  		// is no longer allowed.
   371  		if nsInit == nil {
   372  			return nil, errNoNamespaceInitializerSet
   373  		}
   374  
   375  		wg.Add(1)
   376  		go func() {
   377  			defer wg.Done()
   378  
   379  			cfgs[i].nsInitializer = nsInit
   380  			if opts.ProvidedSession != nil {
   381  				cfgs[i].session = opts.ProvidedSession
   382  			} else if !opts.AsyncSessions {
   383  				var err error
   384  				session, err := clusterClient.DefaultSession()
   385  				if err != nil {
   386  					errLock.Lock()
   387  					multiErr = multiErr.Add(err)
   388  					errLock.Unlock()
   389  				}
   390  				cfgs[i].session = session
   391  			} else {
   392  				cfgs[i].session = m3db.NewAsyncSession(func() (client.Client, error) {
   393  					return clusterClient, nil
   394  				}, nil)
   395  			}
   396  		}()
   397  	}
   398  
   399  	wg.Wait()
   400  
   401  	if !multiErr.Empty() {
   402  		// Close any created sessions on failure.
   403  		for _, cfg := range cfgs {
   404  			if cfg.session != nil {
   405  				// Returns an error if session is already closed which, in this case,
   406  				// is fine
   407  				_ = cfg.session.Close()
   408  			}
   409  		}
   410  		return nil, multiErr.FinalError()
   411  	}
   412  
   413  	dcOpts := NewDynamicClusterOptions().
   414  		SetDynamicClusterNamespaceConfiguration(cfgs).
   415  		SetClusterNamespacesWatcher(clusterNamespacesWatcher).
   416  		SetInstrumentOptions(instrumentOpts)
   417  
   418  	return newFn(dcOpts)
   419  }