github.com/m3db/m3@v1.5.0/src/cluster/placement/service/service.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 service
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"github.com/m3db/m3/src/cluster/placement"
    27  	"github.com/m3db/m3/src/cluster/placement/algo"
    28  	"github.com/m3db/m3/src/cluster/placement/selector"
    29  	"github.com/m3db/m3/src/cluster/shard"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  type placementService struct {
    34  	placement.Storage
    35  	*placementServiceImpl
    36  }
    37  
    38  // NewPlacementService returns an instance of placement service.
    39  func NewPlacementService(s placement.Storage, opts ...Option) placement.Service {
    40  	return &placementService{
    41  		Storage: s,
    42  		placementServiceImpl: newPlacementServiceImpl(
    43  			s,
    44  			opts...,
    45  		),
    46  	}
    47  }
    48  
    49  type options struct {
    50  	placementAlgorithm placement.Algorithm
    51  	placementOpts      placement.Options
    52  }
    53  
    54  // Option is an interface for PlacementService options.
    55  type Option interface {
    56  	apply(*options)
    57  }
    58  
    59  // WithAlgorithm sets the algorithm implementation that will be used by PlacementService.
    60  func WithAlgorithm(algo placement.Algorithm) Option {
    61  	return &algorithmOption{placementAlgorithm: algo}
    62  }
    63  
    64  type algorithmOption struct {
    65  	placementAlgorithm placement.Algorithm
    66  }
    67  
    68  func (a *algorithmOption) apply(opts *options) {
    69  	opts.placementAlgorithm = a.placementAlgorithm
    70  }
    71  
    72  type placementOptionsOption struct {
    73  	opts placement.Options
    74  }
    75  
    76  func (a *placementOptionsOption) apply(opts *options) {
    77  	opts.placementOpts = a.opts
    78  }
    79  
    80  // WithPlacementOptions sets the placement options for PlacementService.
    81  func WithPlacementOptions(opts placement.Options) Option {
    82  	return &placementOptionsOption{opts: opts}
    83  }
    84  
    85  func newPlacementServiceImpl(
    86  	storage minimalPlacementStorage,
    87  	opts ...Option,
    88  ) *placementServiceImpl {
    89  	o := options{
    90  		placementOpts: placement.NewOptions(),
    91  	}
    92  
    93  	for _, opt := range opts {
    94  		opt.apply(&o)
    95  	}
    96  
    97  	if o.placementAlgorithm == nil {
    98  		o.placementAlgorithm = algo.NewAlgorithm(o.placementOpts)
    99  	}
   100  
   101  	instanceSelector := o.placementOpts.InstanceSelector()
   102  	if instanceSelector == nil {
   103  		instanceSelector = selector.NewInstanceSelector(o.placementOpts)
   104  	}
   105  
   106  	return &placementServiceImpl{
   107  		store:    storage,
   108  		opts:     o.placementOpts,
   109  		algo:     o.placementAlgorithm,
   110  		selector: instanceSelector,
   111  		logger:   o.placementOpts.InstrumentOptions().Logger(),
   112  	}
   113  }
   114  
   115  // minimalPlacementStorage is the subset of the placement.Storage interface used by placement.Service
   116  // directly.
   117  type minimalPlacementStorage interface {
   118  
   119  	// Set writes a placement.
   120  	Set(p placement.Placement) (placement.Placement, error)
   121  
   122  	// CheckAndSet writes a placement.Placement if the current version
   123  	// matches the expected version.
   124  	CheckAndSet(p placement.Placement, version int) (placement.Placement, error)
   125  
   126  	// SetIfNotExist writes a placement.Placement.
   127  	SetIfNotExist(p placement.Placement) (placement.Placement, error)
   128  
   129  	// Placement reads placement.Placement.
   130  	Placement() (placement.Placement, error)
   131  }
   132  
   133  // type assertion
   134  var _ minimalPlacementStorage = placement.Storage(nil)
   135  
   136  type placementServiceImpl struct {
   137  	store minimalPlacementStorage
   138  
   139  	opts     placement.Options
   140  	algo     placement.Algorithm
   141  	selector placement.InstanceSelector
   142  	logger   *zap.Logger
   143  }
   144  
   145  func (ps *placementServiceImpl) BuildInitialPlacement(
   146  	candidates []placement.Instance,
   147  	numShards int,
   148  	rf int,
   149  ) (placement.Placement, error) {
   150  	if numShards < 0 {
   151  		return nil, fmt.Errorf("could not build initial placement, invalid numShards %d", numShards)
   152  	}
   153  
   154  	if rf <= 0 {
   155  		return nil, fmt.Errorf("could not build initial placement, invalid replica factor %d", rf)
   156  	}
   157  
   158  	instances, err := ps.selector.SelectInitialInstances(candidates, rf)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	ids := make([]uint32, numShards)
   164  	for i := 0; i < numShards; i++ {
   165  		ids[i] = uint32(i)
   166  	}
   167  
   168  	tempPlacement, err := ps.algo.InitialPlacement(instances, ids, rf)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	if err := placement.Validate(tempPlacement); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	return ps.store.SetIfNotExist(tempPlacement)
   178  }
   179  
   180  func (ps *placementServiceImpl) AddReplica() (placement.Placement, error) {
   181  	curPlacement, err := ps.store.Placement()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	tempPlacement, err := ps.algo.AddReplica(curPlacement)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	if err := placement.Validate(tempPlacement); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   200  }
   201  
   202  func (ps *placementServiceImpl) AddInstances(
   203  	candidates []placement.Instance,
   204  ) (placement.Placement, []placement.Instance, error) {
   205  	curPlacement, err := ps.store.Placement()
   206  	if err != nil {
   207  		return nil, nil, err
   208  	}
   209  
   210  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   211  		return nil, nil, err
   212  	}
   213  
   214  	addingInstances, err := ps.selector.SelectAddingInstances(candidates, curPlacement)
   215  	if err != nil {
   216  		return nil, nil, err
   217  	}
   218  
   219  	tempPlacement, err := ps.algo.AddInstances(curPlacement, addingInstances)
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  
   224  	if err := placement.Validate(tempPlacement); err != nil {
   225  		return nil, nil, err
   226  	}
   227  
   228  	for i, instance := range addingInstances {
   229  		addingInstance, ok := tempPlacement.Instance(instance.ID())
   230  		if !ok {
   231  			return nil, nil, fmt.Errorf("unable to find added instance [%s] in new placement", instance.ID())
   232  		}
   233  		addingInstances[i] = addingInstance
   234  	}
   235  
   236  	newPlacement, err := ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   237  	if err != nil {
   238  		return nil, nil, err
   239  	}
   240  	return newPlacement, addingInstances, nil
   241  }
   242  
   243  func (ps *placementServiceImpl) RemoveInstances(instanceIDs []string) (placement.Placement, error) {
   244  	curPlacement, err := ps.store.Placement()
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	tempPlacement, err := ps.algo.RemoveInstances(curPlacement, instanceIDs)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	if err := placement.Validate(tempPlacement); err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   263  }
   264  
   265  func (ps *placementServiceImpl) ReplaceInstances(
   266  	leavingInstanceIDs []string,
   267  	candidates []placement.Instance,
   268  ) (placement.Placement, []placement.Instance, error) {
   269  	curPlacement, err := ps.store.Placement()
   270  	if err != nil {
   271  		return nil, nil, err
   272  	}
   273  
   274  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   275  		return nil, nil, err
   276  	}
   277  
   278  	addingInstances, err := ps.selector.SelectReplaceInstances(candidates, leavingInstanceIDs, curPlacement)
   279  	if err != nil {
   280  		return nil, nil, err
   281  	}
   282  
   283  	tempPlacement, err := ps.algo.ReplaceInstances(curPlacement, leavingInstanceIDs, addingInstances)
   284  	if err != nil {
   285  		return nil, nil, err
   286  	}
   287  
   288  	if err := placement.Validate(tempPlacement); err != nil {
   289  		return nil, nil, err
   290  	}
   291  
   292  	addedInstances := make([]placement.Instance, 0, len(addingInstances))
   293  	for _, inst := range addingInstances {
   294  		addedInstance, ok := tempPlacement.Instance(inst.ID())
   295  		if !ok {
   296  			return nil, nil, fmt.Errorf("unable to find added instance [%+v] in new placement [%+v]", inst, curPlacement)
   297  		}
   298  		addedInstances = append(addedInstances, addedInstance)
   299  	}
   300  
   301  	newPlacement, err := ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   302  	if err != nil {
   303  		return nil, nil, err
   304  	}
   305  	return newPlacement, addedInstances, nil
   306  }
   307  
   308  func (ps *placementServiceImpl) MarkShardsAvailable(instanceID string, shardIDs ...uint32) (placement.Placement, error) {
   309  	curPlacement, err := ps.store.Placement()
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   315  		return nil, err
   316  	}
   317  
   318  	tempPlacement, err := ps.algo.MarkShardsAvailable(curPlacement, instanceID, shardIDs...)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	if err := placement.Validate(tempPlacement); err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   328  }
   329  
   330  func (ps *placementServiceImpl) MarkInstanceAvailable(instanceID string) (placement.Placement, error) {
   331  	curPlacement, err := ps.store.Placement()
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	instance, exist := curPlacement.Instance(instanceID)
   341  	if !exist {
   342  		return nil, fmt.Errorf("could not find instance %s in placement", instanceID)
   343  	}
   344  
   345  	shards := instance.Shards().ShardsForState(shard.Initializing)
   346  	shardIDs := make([]uint32, len(shards))
   347  	for i, s := range shards {
   348  		shardIDs[i] = s.ID()
   349  	}
   350  
   351  	tempPlacement, err := ps.algo.MarkShardsAvailable(curPlacement, instanceID, shardIDs...)
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	if err := placement.Validate(tempPlacement); err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   361  }
   362  
   363  func (ps *placementServiceImpl) MarkAllShardsAvailable() (placement.Placement, error) {
   364  	curPlacement, err := ps.store.Placement()
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  
   369  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	tempPlacement, updated, err := ps.algo.MarkAllShardsAvailable(curPlacement)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	if !updated {
   378  		return curPlacement, nil
   379  	}
   380  
   381  	if err := placement.Validate(tempPlacement); err != nil {
   382  		return nil, err
   383  	}
   384  
   385  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   386  }
   387  
   388  func (ps *placementServiceImpl) BalanceShards() (placement.Placement, error) {
   389  	curPlacement, err := ps.store.Placement()
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	if err := ps.opts.ValidateFnBeforeUpdate()(curPlacement); err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	tempPlacement, err := ps.algo.BalanceShards(curPlacement)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  
   403  	if err := placement.Validate(tempPlacement); err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	return ps.store.CheckAndSet(tempPlacement, curPlacement.Version())
   408  }