github.com/m3db/m3@v1.5.0/src/cluster/placement/placements_watcher.go (about)

     1  // Copyright (c) 2021 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 placement
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  
    27  	"go.uber.org/atomic"
    28  
    29  	"github.com/m3db/m3/src/cluster/generated/proto/placementpb"
    30  	"github.com/m3db/m3/src/cluster/kv"
    31  	"github.com/m3db/m3/src/cluster/kv/util/runtime"
    32  )
    33  
    34  var (
    35  	errNilValue             = errors.New("nil value received")
    36  	errWatcherIsNotWatching = errors.New("placement watcher is not watching")
    37  	errWatcherIsWatching    = errors.New("placement watcher is watching")
    38  	errWatcherCastError     = errors.New("interface cast failed, unexpected placement type")
    39  
    40  	_ Watcher = (*placementsWatcher)(nil) // enforce interface compliance
    41  )
    42  
    43  // placementsWatcher implements watcher of staged placement.
    44  // Currently, the aggregator placement is stored as staged placement in etcd using
    45  // protobuf type `*placementpb.PlacementSnapshots` as a remnant of now deprecated concept
    46  // of "staged placement". This Watcher abstracts that detail from the clients while
    47  // maintaining backward compatibility.
    48  // TODO: Consider migrating to storing protobuf type `*placementpb.Placement` in etcd.
    49  type placementsWatcher struct {
    50  	mtx                sync.Mutex
    51  	valuePayload       atomic.Value
    52  	value              runtime.Value
    53  	watching           atomic.Bool
    54  	onPlacementChanged OnPlacementChangedFn
    55  }
    56  
    57  // payload is a wrapper for type-safe interface storage in atomic.Value,
    58  // as the concrete type has to be the same for each .Store() call.
    59  type payload struct {
    60  	placement Placement
    61  }
    62  
    63  // NewPlacementsWatcher creates a new staged placement watcher.
    64  func NewPlacementsWatcher(opts WatcherOptions) Watcher {
    65  	watcher := &placementsWatcher{
    66  		onPlacementChanged: opts.OnPlacementChangedFn(),
    67  	}
    68  
    69  	valueOpts := runtime.NewOptions().
    70  		SetInstrumentOptions(opts.InstrumentOptions()).
    71  		SetInitWatchTimeout(opts.InitWatchTimeout()).
    72  		SetKVStore(opts.StagedPlacementStore()).
    73  		SetUnmarshalFn(watcher.unmarshalAsPlacementSnapshots).
    74  		SetProcessFn(watcher.process)
    75  	watcher.value = runtime.NewValue(opts.StagedPlacementKey(), valueOpts)
    76  	return watcher
    77  }
    78  
    79  func (t *placementsWatcher) Watch() error {
    80  	if !t.watching.CAS(false, true) {
    81  		return errWatcherIsWatching
    82  	}
    83  
    84  	return t.value.Watch()
    85  }
    86  
    87  func (t *placementsWatcher) Get() (Placement, error) {
    88  	if !t.watching.Load() {
    89  		return nil, errWatcherIsNotWatching
    90  	}
    91  
    92  	vp := t.valuePayload.Load()
    93  	pl, ok := vp.(payload)
    94  	if !ok {
    95  		return nil, errWatcherCastError
    96  	}
    97  
    98  	return pl.placement, nil
    99  }
   100  
   101  func (t *placementsWatcher) Unwatch() error {
   102  	if !t.watching.CAS(true, false) {
   103  		return errWatcherIsNotWatching
   104  	}
   105  
   106  	t.value.Unwatch()
   107  	return nil
   108  }
   109  
   110  func (t *placementsWatcher) unmarshalAsPlacementSnapshots(value kv.Value) (interface{}, error) {
   111  	if !t.watching.Load() {
   112  		return nil, errWatcherIsNotWatching
   113  	}
   114  	if value == nil {
   115  		return nil, errNilValue
   116  	}
   117  
   118  	var proto placementpb.PlacementSnapshots
   119  	if err := value.Unmarshal(&proto); err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	snapshots, err := NewPlacementsFromProto(&proto)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	latest := snapshots.Latest()
   129  	latest.SetVersion(value.Version())
   130  
   131  	return latest, nil
   132  }
   133  
   134  // process is called upon update of value, the value is already unmarshalled.
   135  func (t *placementsWatcher) process(newValue interface{}) error {
   136  	t.mtx.Lock() // serialize value processing
   137  	defer t.mtx.Unlock()
   138  
   139  	if !t.watching.Load() {
   140  		return errWatcherIsNotWatching
   141  	}
   142  
   143  	newPlacement := newValue.(Placement)
   144  	var oldPlacement Placement
   145  	vp := t.valuePayload.Load()
   146  	old, ok := vp.(payload)
   147  	if ok && old.placement != nil {
   148  		oldPlacement = old.placement
   149  	}
   150  
   151  	t.valuePayload.Store(payload{placement: newPlacement})
   152  
   153  	if t.onPlacementChanged != nil {
   154  		t.onPlacementChanged(oldPlacement, newPlacement)
   155  	}
   156  
   157  	return nil
   158  }