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  }