github.com/m3db/m3@v1.5.0/src/cluster/changeset/manager.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 changeset 22 23 import ( 24 "errors" 25 "fmt" 26 27 "github.com/m3db/m3/src/cluster/generated/proto/changesetpb" 28 "github.com/m3db/m3/src/cluster/kv" 29 30 "github.com/golang/protobuf/proto" 31 "go.uber.org/zap" 32 ) 33 34 var ( 35 // ErrAlreadyCommitted is returned when attempting to commit an already 36 // committed ChangeSet 37 ErrAlreadyCommitted = errors.New("change list already committed") 38 39 // ErrCommitInProgress is returned when attempting to commit a change set 40 // that is already being committed 41 ErrCommitInProgress = errors.New("commit in progress") 42 43 // ErrChangeSetClosed is returned when attempting to make a change to a 44 // closed (committed / commit in progress) ChangeSet 45 ErrChangeSetClosed = errors.New("change set closed") 46 47 // ErrUnknownVersion is returned when attempting to commit a change for 48 // a version that doesn't exist 49 ErrUnknownVersion = errors.New("unknown version") 50 51 errOptsNotSet = errors.New("opts must not be nil") 52 errKVNotSet = errors.New("KV must be specified") 53 errConfigKeyNotSet = errors.New("configKey must be specified") 54 errConfigTypeNotSet = errors.New("configType must be specified") 55 errChangeTypeNotSet = errors.New("changesType must be specified") 56 ) 57 58 // ManagerOptions are options used in creating a new ChangeSet Manager 59 type ManagerOptions interface { 60 // KV is the KVStore holding the configuration 61 SetKV(kv kv.Store) ManagerOptions 62 KV() kv.Store 63 64 // ConfigKey is the key holding the configuration object 65 SetConfigKey(key string) ManagerOptions 66 ConfigKey() string 67 68 // Logger is the logger to use 69 SetLogger(logger *zap.Logger) ManagerOptions 70 Logger() *zap.Logger 71 72 // ConfigType is a proto.Message defining the structure of the configuration 73 // object. Clones of this proto will be used to unmarshal configuration 74 // instances 75 SetConfigType(config proto.Message) ManagerOptions 76 ConfigType() proto.Message 77 78 // ChangesType is a proto.Message defining the structure of the changes 79 // object. Clones of this protol will be used to unmarshal change list 80 // instances. 81 SetChangesType(changes proto.Message) ManagerOptions 82 ChangesType() proto.Message 83 84 // Validate validates the options 85 Validate() error 86 } 87 88 // NewManagerOptions creates an empty ManagerOptions 89 func NewManagerOptions() ManagerOptions { return managerOptions{} } 90 91 // A ChangeFn adds a change to an existing set of changes 92 type ChangeFn func(config, changes proto.Message) error 93 94 // An ApplyFn applies a set of changes to a configuration, resulting in a new 95 // configuration 96 type ApplyFn func(config, changes proto.Message) error 97 98 // A Manager manages sets of changes in a version friendly manager. Changes to 99 // a given version of a configuration object are stored under 100 // <key>/_changes/<version>. Multiple changes can be added, then committed all 101 // at once. Committing transforms the configuration according to the changes, 102 // then writes the configuration back. CAS operations are used to ensure that 103 // commits are not applied more than once, and to avoid conflicts on the change 104 // object itself. 105 type Manager interface { 106 // Change creates a new change against the latest configuration, adding it 107 // to the set of pending changes for that configuration 108 Change(change ChangeFn) error 109 110 // GetPendingChanges gets the latest uncommitted changes 111 GetPendingChanges() (int, proto.Message, proto.Message, error) 112 113 // Commit commits the specified ChangeSet, transforming the configuration on 114 // which they are based into a new configuration, and storing that new 115 // configuration as a next versions. Ensures that changes are applied as a 116 // batch, are not applied more than once, and that new changes are not 117 // started while a commit is underway 118 Commit(version int, apply ApplyFn) error 119 } 120 121 // NewManager creates a new change list Manager 122 func NewManager(opts ManagerOptions) (Manager, error) { 123 if opts == nil { 124 return nil, errOptsNotSet 125 } 126 127 if err := opts.Validate(); err != nil { 128 return nil, err 129 } 130 131 logger := opts.Logger() 132 if logger == nil { 133 logger = zap.NewNop() 134 } 135 136 return manager{ 137 key: opts.ConfigKey(), 138 kv: opts.KV(), 139 configType: proto.Clone(opts.ConfigType()), 140 changesType: proto.Clone(opts.ChangesType()), 141 log: logger, 142 }, nil 143 } 144 145 type manager struct { 146 key string 147 kv kv.Store 148 configType proto.Message 149 changesType proto.Message 150 log *zap.Logger 151 } 152 153 func (m manager) Change(change ChangeFn) error { 154 for { 155 // Retrieve the current configuration, creating an empty config if one does 156 // not exist 157 config := proto.Clone(m.configType) 158 configVersion, err := m.getOrCreate(m.key, config) 159 if err != nil { 160 return err 161 } 162 163 // Retrieve the changes for the current configuration, creating an empty 164 // change set if one does not exist 165 changeset := &changesetpb.ChangeSet{ 166 ForVersion: int32(configVersion), 167 State: changesetpb.ChangeSetState_OPEN, 168 } 169 170 changeSetKey := fmtChangeSetKey(m.key, configVersion) 171 csVersion, err := m.getOrCreate(changeSetKey, changeset) 172 if err != nil { 173 return err 174 } 175 176 // Only allow changes to an open change list 177 if changeset.State != changesetpb.ChangeSetState_OPEN { 178 return ErrChangeSetClosed 179 } 180 181 // Apply the new changes... 182 changes := proto.Clone(m.changesType) 183 if err := proto.Unmarshal(changeset.Changes, changes); err != nil { 184 return err 185 } 186 187 if err := change(config, changes); err != nil { 188 return err 189 } 190 191 changeBytes, err := proto.Marshal(changes) 192 if err != nil { 193 return err 194 } 195 196 // ...and update the stored changes 197 changeset.Changes = changeBytes 198 if _, err := m.kv.CheckAndSet(changeSetKey, csVersion, changeset); err != nil { 199 if err == kv.ErrVersionMismatch { 200 // Someone else updated the changes first - try again 201 continue 202 } 203 204 return err 205 } 206 207 return nil 208 } 209 } 210 211 func (m manager) GetPendingChanges() (int, proto.Message, proto.Message, error) { 212 // Get the current configuration, but don't bother trying to create it if it doesn't exist 213 configVal, err := m.kv.Get(m.key) 214 if err != nil { 215 return 0, nil, nil, err 216 } 217 218 // Retrieve the config data so that we can transform it appropriately 219 config := proto.Clone(m.configType) 220 if err := configVal.Unmarshal(config); err != nil { 221 return 0, nil, nil, err 222 } 223 224 // Get the change set for the current configuration, again not bothering to 225 // create if it doesn't exist 226 changeSetKey := fmtChangeSetKey(m.key, configVal.Version()) 227 changeSetVal, err := m.kv.Get(changeSetKey) 228 if err != nil { 229 if err == kv.ErrNotFound { 230 // It's ok, just means no pending changes 231 return configVal.Version(), config, nil, nil 232 } 233 234 return 0, nil, nil, err 235 } 236 237 var changeset changesetpb.ChangeSet 238 if err := changeSetVal.Unmarshal(&changeset); err != nil { 239 return 0, nil, nil, err 240 } 241 242 // Retrieve the changes 243 changes := proto.Clone(m.changesType) 244 if err := proto.Unmarshal(changeset.Changes, changes); err != nil { 245 return 0, nil, nil, err 246 } 247 248 return configVal.Version(), config, changes, nil 249 } 250 251 func (m manager) Commit(version int, apply ApplyFn) error { 252 // Get the current configuration, but don't bother trying to create it if it doesn't exist 253 configVal, err := m.kv.Get(m.key) 254 if err != nil { 255 return err 256 } 257 258 // Confirm the version does exist... 259 if configVal.Version() < version { 260 return ErrUnknownVersion 261 } 262 263 // ...and that it hasn't already been committed 264 if configVal.Version() > version { 265 return ErrAlreadyCommitted 266 } 267 268 // Retrieve the config data so that we can transform it appropriately 269 config := proto.Clone(m.configType) 270 if err := configVal.Unmarshal(config); err != nil { 271 return err 272 } 273 274 // Get the change set for the current configuration, again not bothering to create 275 // if it doesn't exist 276 changeSetKey := fmtChangeSetKey(m.key, configVal.Version()) 277 changeSetVal, err := m.kv.Get(changeSetKey) 278 if err != nil { 279 return err 280 } 281 282 var changeset changesetpb.ChangeSet 283 if err := changeSetVal.Unmarshal(&changeset); err != nil { 284 return err 285 } 286 287 // If the change set is not already CLOSED, mark it as such to prevent new 288 // changes from being recorded while the commit is underway 289 if changeset.State != changesetpb.ChangeSetState_CLOSED { 290 changeset.State = changesetpb.ChangeSetState_CLOSED 291 if _, err := m.kv.CheckAndSet(changeSetKey, changeSetVal.Version(), &changeset); err != nil { 292 if err == kv.ErrVersionMismatch { 293 return ErrCommitInProgress 294 } 295 296 return err 297 } 298 } 299 300 // Transform the current configuration according to the change list 301 changes := proto.Clone(m.changesType) 302 if err := proto.Unmarshal(changeset.Changes, changes); err != nil { 303 return err 304 } 305 306 if err := apply(config, changes); err != nil { 307 return err 308 } 309 310 // Save the updated config. This updates the version number for the config, so 311 // attempting to commit the current version again will fail 312 if _, err := m.kv.CheckAndSet(m.key, configVal.Version(), config); err != nil { 313 if err == kv.ErrVersionMismatch { 314 return ErrAlreadyCommitted 315 } 316 317 return err 318 } 319 320 return nil 321 } 322 323 func (m manager) getOrCreate(k string, v proto.Message) (int, error) { 324 for { 325 val, err := m.kv.Get(k) 326 if err == kv.ErrNotFound { 327 // Attempt to create 328 version, err := m.kv.SetIfNotExists(k, v) 329 if err == nil { 330 return version, nil 331 } 332 333 if err == kv.ErrAlreadyExists { 334 // Someone got there first...try again 335 continue 336 } 337 338 return 0, err 339 } 340 341 if err != nil { 342 // Some other error occurred 343 return 0, err 344 } 345 346 // Unmarshall the current value 347 if err := val.Unmarshal(v); err != nil { 348 return 0, err 349 } 350 351 return val.Version(), nil 352 } 353 } 354 355 func fmtChangeSetKey(configKey string, configVers int) string { 356 return fmt.Sprintf("%s/_changes/%d", configKey, configVers) 357 } 358 359 type managerOptions struct { 360 kv kv.Store 361 logger *zap.Logger 362 configKey string 363 configType proto.Message 364 changesType proto.Message 365 } 366 367 func (opts managerOptions) KV() kv.Store { return opts.kv } 368 func (opts managerOptions) Logger() *zap.Logger { return opts.logger } 369 func (opts managerOptions) ConfigKey() string { return opts.configKey } 370 func (opts managerOptions) ConfigType() proto.Message { return opts.configType } 371 func (opts managerOptions) ChangesType() proto.Message { return opts.changesType } 372 373 func (opts managerOptions) SetKV(kv kv.Store) ManagerOptions { 374 opts.kv = kv 375 return opts 376 } 377 func (opts managerOptions) SetLogger(logger *zap.Logger) ManagerOptions { 378 opts.logger = logger 379 return opts 380 } 381 func (opts managerOptions) SetConfigKey(k string) ManagerOptions { 382 opts.configKey = k 383 return opts 384 } 385 func (opts managerOptions) SetConfigType(ct proto.Message) ManagerOptions { 386 opts.configType = ct 387 return opts 388 } 389 func (opts managerOptions) SetChangesType(ct proto.Message) ManagerOptions { 390 opts.changesType = ct 391 return opts 392 } 393 394 func (opts managerOptions) Validate() error { 395 if opts.ConfigKey() == "" { 396 return errConfigKeyNotSet 397 } 398 399 if opts.KV() == nil { 400 return errKVNotSet 401 } 402 403 if opts.ConfigType() == nil { 404 return errConfigTypeNotSet 405 } 406 407 if opts.ChangesType() == nil { 408 return errChangeTypeNotSet 409 } 410 411 return nil 412 }