github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/volumewatcher/volumes_watcher.go (about)

     1  package volumewatcher
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	log "github.com/hashicorp/go-hclog"
     8  	memdb "github.com/hashicorp/go-memdb"
     9  	"github.com/hashicorp/nomad/nomad/state"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  )
    12  
    13  // Watcher is used to watch volumes and their allocations created
    14  // by the scheduler and trigger the scheduler when allocation health
    15  // transitions.
    16  type Watcher struct {
    17  	enabled bool
    18  	logger  log.Logger
    19  
    20  	// rpc contains the set of Server methods that can be used by
    21  	// the volumes watcher for RPC
    22  	rpc CSIVolumeRPC
    23  
    24  	// the ACL needed to send RPCs
    25  	leaderAcl string
    26  
    27  	// state is the state that is watched for state changes.
    28  	state *state.StateStore
    29  
    30  	// watchers is the set of active watchers, one per volume
    31  	watchers map[string]*volumeWatcher
    32  
    33  	// ctx and exitFn are used to cancel the watcher
    34  	ctx    context.Context
    35  	exitFn context.CancelFunc
    36  
    37  	wlock sync.RWMutex
    38  }
    39  
    40  // NewVolumesWatcher returns a volumes watcher that is used to watch
    41  // volumes and trigger the scheduler as needed.
    42  func NewVolumesWatcher(logger log.Logger, rpc CSIVolumeRPC, leaderAcl string) *Watcher {
    43  
    44  	// the leader step-down calls SetEnabled(false) which is what
    45  	// cancels this context, rather than passing in its own shutdown
    46  	// context
    47  	ctx, exitFn := context.WithCancel(context.Background())
    48  
    49  	return &Watcher{
    50  		rpc:       rpc,
    51  		logger:    logger.Named("volumes_watcher"),
    52  		ctx:       ctx,
    53  		exitFn:    exitFn,
    54  		leaderAcl: leaderAcl,
    55  	}
    56  }
    57  
    58  // SetEnabled is used to control if the watcher is enabled. The
    59  // watcher should only be enabled on the active leader. When being
    60  // enabled the state is passed in as it is no longer valid once a
    61  // leader election has taken place.
    62  func (w *Watcher) SetEnabled(enabled bool, state *state.StateStore) {
    63  	w.wlock.Lock()
    64  	defer w.wlock.Unlock()
    65  
    66  	wasEnabled := w.enabled
    67  	w.enabled = enabled
    68  
    69  	if state != nil {
    70  		w.state = state
    71  	}
    72  
    73  	// Flush the state to create the necessary objects
    74  	w.flush(enabled)
    75  
    76  	// If we are starting now, launch the watch daemon
    77  	if enabled && !wasEnabled {
    78  		go w.watchVolumes(w.ctx)
    79  	}
    80  }
    81  
    82  // flush is used to clear the state of the watcher
    83  func (w *Watcher) flush(enabled bool) {
    84  	// Stop all the watchers and clear it
    85  	for _, watcher := range w.watchers {
    86  		watcher.Stop()
    87  	}
    88  
    89  	// Kill everything associated with the watcher
    90  	if w.exitFn != nil {
    91  		w.exitFn()
    92  	}
    93  
    94  	w.watchers = make(map[string]*volumeWatcher, 32)
    95  	w.ctx, w.exitFn = context.WithCancel(context.Background())
    96  }
    97  
    98  // watchVolumes is the long lived go-routine that watches for volumes to
    99  // add and remove watchers on.
   100  func (w *Watcher) watchVolumes(ctx context.Context) {
   101  	vIndex := uint64(1)
   102  	for {
   103  		volumes, idx, err := w.getVolumes(ctx, vIndex)
   104  		if err != nil {
   105  			if err == context.Canceled {
   106  				return
   107  			}
   108  			w.logger.Error("failed to retrieve volumes", "error", err)
   109  		}
   110  
   111  		vIndex = idx // last-seen index
   112  		for _, v := range volumes {
   113  			if err := w.add(v); err != nil {
   114  				w.logger.Error("failed to track volume", "volume_id", v.ID, "error", err)
   115  			}
   116  
   117  		}
   118  	}
   119  }
   120  
   121  // getVolumes retrieves all volumes blocking at the given index.
   122  func (w *Watcher) getVolumes(ctx context.Context, minIndex uint64) ([]*structs.CSIVolume, uint64, error) {
   123  	resp, index, err := w.state.BlockingQuery(w.getVolumesImpl, minIndex, ctx)
   124  	if err != nil {
   125  		return nil, 0, err
   126  	}
   127  
   128  	return resp.([]*structs.CSIVolume), index, nil
   129  }
   130  
   131  // getVolumesImpl retrieves all volumes from the passed state store.
   132  func (w *Watcher) getVolumesImpl(ws memdb.WatchSet, state *state.StateStore) (interface{}, uint64, error) {
   133  
   134  	iter, err := state.CSIVolumes(ws)
   135  	if err != nil {
   136  		return nil, 0, err
   137  	}
   138  
   139  	var volumes []*structs.CSIVolume
   140  	for {
   141  		raw := iter.Next()
   142  		if raw == nil {
   143  			break
   144  		}
   145  		volume := raw.(*structs.CSIVolume)
   146  		volumes = append(volumes, volume)
   147  	}
   148  
   149  	// Use the last index that affected the volume table
   150  	index, err := state.Index("csi_volumes")
   151  	if err != nil {
   152  		return nil, 0, err
   153  	}
   154  
   155  	return volumes, index, nil
   156  }
   157  
   158  // add adds a volume to the watch list
   159  func (w *Watcher) add(d *structs.CSIVolume) error {
   160  	w.wlock.Lock()
   161  	defer w.wlock.Unlock()
   162  	_, err := w.addLocked(d)
   163  	return err
   164  }
   165  
   166  // addLocked adds a volume to the watch list and should only be called when
   167  // locked. Creating the volumeWatcher starts a go routine to .watch() it
   168  func (w *Watcher) addLocked(v *structs.CSIVolume) (*volumeWatcher, error) {
   169  	// Not enabled so no-op
   170  	if !w.enabled {
   171  		return nil, nil
   172  	}
   173  
   174  	// Already watched so trigger an update for the volume
   175  	if watcher, ok := w.watchers[v.ID+v.Namespace]; ok {
   176  		watcher.Notify(v)
   177  		return nil, nil
   178  	}
   179  
   180  	watcher := newVolumeWatcher(w, v)
   181  	w.watchers[v.ID+v.Namespace] = watcher
   182  	return watcher, nil
   183  }