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 }