github.com/m3db/m3@v1.5.0/src/dbnode/topology/dynamic.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 topology
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  
    27  	"github.com/m3db/m3/src/cluster/kv"
    28  	"github.com/m3db/m3/src/cluster/placement"
    29  	"github.com/m3db/m3/src/cluster/services"
    30  	"github.com/m3db/m3/src/cluster/shard"
    31  	"github.com/m3db/m3/src/dbnode/sharding"
    32  	xwatch "github.com/m3db/m3/src/x/watch"
    33  
    34  	"go.uber.org/zap"
    35  )
    36  
    37  var (
    38  	errInvalidService            = errors.New("service topology is invalid")
    39  	errUnexpectedShard           = errors.New("shard is unexpected")
    40  	errMissingShard              = errors.New("shard is missing")
    41  	errNotEnoughReplicasForShard = errors.New("replicas of shard is less than expected")
    42  	errInvalidTopology           = errors.New("could not parse latest value from config service")
    43  )
    44  
    45  type dynamicInitializer struct {
    46  	sync.Mutex
    47  	opts DynamicOptions
    48  	topo Topology
    49  }
    50  
    51  // NewDynamicInitializer returns a dynamic topology initializer
    52  func NewDynamicInitializer(opts DynamicOptions) Initializer {
    53  	return &dynamicInitializer{opts: opts}
    54  }
    55  
    56  func (i *dynamicInitializer) Init() (Topology, error) {
    57  	i.Lock()
    58  	defer i.Unlock()
    59  
    60  	if i.topo != nil {
    61  		return i.topo, nil
    62  	}
    63  
    64  	if err := i.opts.Validate(); err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	topo, err := newDynamicTopology(i.opts)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	i.topo = topo
    74  	return i.topo, nil
    75  }
    76  
    77  func (i *dynamicInitializer) TopologyIsSet() (bool, error) {
    78  	services, err := i.opts.ConfigServiceClient().Services(i.opts.ServicesOverrideOptions())
    79  	if err != nil {
    80  		return false, err
    81  	}
    82  
    83  	_, err = services.Query(i.opts.ServiceID(), i.opts.QueryOptions())
    84  	if err != nil {
    85  		if err == kv.ErrNotFound {
    86  			// Valid, just means topology is not set
    87  			return false, nil
    88  		}
    89  
    90  		return false, err
    91  	}
    92  
    93  	return true, nil
    94  }
    95  
    96  type dynamicTopology struct {
    97  	sync.RWMutex
    98  	opts      DynamicOptions
    99  	services  services.Services
   100  	watch     services.Watch
   101  	watchable xwatch.Watchable
   102  	closed    bool
   103  	hashGen   sharding.HashGen
   104  	logger    *zap.Logger
   105  }
   106  
   107  func newDynamicTopology(opts DynamicOptions) (DynamicTopology, error) {
   108  	services, err := opts.ConfigServiceClient().Services(opts.ServicesOverrideOptions())
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	logger := opts.InstrumentOptions().Logger()
   114  	logger.Info("waiting for dynamic topology initialization, " +
   115  		"if this takes a long time, make sure that a topology/placement is configured")
   116  	watch, err := services.Watch(opts.ServiceID(), opts.QueryOptions())
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	<-watch.C()
   121  	logger.Info("initial topology / placement value received")
   122  
   123  	m, err := getMapFromUpdate(watch.Get(), opts.HashGen())
   124  	if err != nil {
   125  		logger.Error("dynamic topology received invalid initial value", zap.Error(err))
   126  		return nil, err
   127  	}
   128  
   129  	watchable := xwatch.NewWatchable()
   130  	watchable.Update(m)
   131  
   132  	dt := &dynamicTopology{
   133  		opts:      opts,
   134  		services:  services,
   135  		watch:     watch,
   136  		watchable: watchable,
   137  		hashGen:   opts.HashGen(),
   138  		logger:    logger,
   139  	}
   140  	go dt.run()
   141  	return dt, nil
   142  }
   143  
   144  func (t *dynamicTopology) isClosed() bool {
   145  	t.RLock()
   146  	closed := t.closed
   147  	t.RUnlock()
   148  	return closed
   149  }
   150  
   151  func (t *dynamicTopology) run() {
   152  	for !t.isClosed() {
   153  		if _, ok := <-t.watch.C(); !ok {
   154  			t.Close()
   155  			break
   156  		}
   157  
   158  		m, err := getMapFromUpdate(t.watch.Get(), t.hashGen)
   159  		if err != nil {
   160  			t.logger.Warn("dynamic topology received invalid update", zap.Error(err))
   161  			continue
   162  		}
   163  		t.watchable.Update(m)
   164  	}
   165  }
   166  
   167  func (t *dynamicTopology) Get() Map {
   168  	return t.watchable.Get().(Map)
   169  }
   170  
   171  func (t *dynamicTopology) Watch() (MapWatch, error) {
   172  	_, w, err := t.watchable.Watch()
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return NewMapWatch(w), err
   177  }
   178  
   179  func (t *dynamicTopology) Close() {
   180  	t.Lock()
   181  	defer t.Unlock()
   182  
   183  	if t.closed {
   184  		return
   185  	}
   186  
   187  	t.closed = true
   188  
   189  	t.watch.Close()
   190  	t.watchable.Close()
   191  }
   192  
   193  func (t *dynamicTopology) MarkShardsAvailable(
   194  	instanceID string,
   195  	shardIDs ...uint32,
   196  ) error {
   197  	opts := placement.NewOptions()
   198  	ps, err := t.services.PlacementService(t.opts.ServiceID(), opts)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	_, err = ps.MarkShardsAvailable(instanceID, shardIDs...)
   203  	return err
   204  }
   205  
   206  func getMapFromUpdate(data interface{}, hashGen sharding.HashGen) (Map, error) {
   207  	service, ok := data.(services.Service)
   208  	if !ok {
   209  		return nil, errInvalidTopology
   210  	}
   211  	to, err := getStaticOptions(service, hashGen)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	if err := to.Validate(); err != nil {
   216  		return nil, err
   217  	}
   218  	return NewStaticMap(to), nil
   219  }
   220  
   221  func getStaticOptions(service services.Service, hashGen sharding.HashGen) (StaticOptions, error) {
   222  	if service.Replication() == nil || service.Sharding() == nil || service.Instances() == nil {
   223  		return nil, errInvalidService
   224  	}
   225  	replicas := service.Replication().Replicas()
   226  	instances := service.Instances()
   227  	numShards := service.Sharding().NumShards()
   228  
   229  	allShardIDs, err := validateInstances(instances, replicas, numShards)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	allShards := make([]shard.Shard, len(allShardIDs))
   235  	for i, id := range allShardIDs {
   236  		allShards[i] = shard.NewShard(id).SetState(shard.Available)
   237  	}
   238  
   239  	fn := hashGen(numShards)
   240  	allShardSet, err := sharding.NewShardSet(allShards, fn)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	hostShardSets := make([]HostShardSet, len(instances))
   246  	for i, instance := range instances {
   247  		hs, err := NewHostShardSetFromServiceInstance(instance, fn)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		hostShardSets[i] = hs
   252  	}
   253  
   254  	return NewStaticOptions().
   255  		SetReplicas(replicas).
   256  		SetShardSet(allShardSet).
   257  		SetHostShardSets(hostShardSets), nil
   258  }
   259  
   260  func validateInstances(
   261  	instances []services.ServiceInstance,
   262  	replicas, numShards int,
   263  ) ([]uint32, error) {
   264  	m := make(map[uint32]int)
   265  	for _, i := range instances {
   266  		if i.Shards() == nil {
   267  			return nil, errInstanceHasNoShardsAssignment
   268  		}
   269  		for _, s := range i.Shards().All() {
   270  			m[s.ID()] = m[s.ID()] + 1
   271  		}
   272  	}
   273  	s := make([]uint32, numShards)
   274  	for i := range s {
   275  		expectShard := uint32(i)
   276  		count, exist := m[expectShard]
   277  		if !exist {
   278  			return nil, errMissingShard
   279  		}
   280  		if count < replicas {
   281  			return nil, errNotEnoughReplicasForShard
   282  		}
   283  		delete(m, expectShard)
   284  		s[i] = expectShard
   285  	}
   286  
   287  	if len(m) > 0 {
   288  		return nil, errUnexpectedShard
   289  	}
   290  	return s, nil
   291  }