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 }