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 }