github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/nomad/volumewatcher/volumes_watcher_test.go (about) 1 package volumewatcher 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 memdb "github.com/hashicorp/go-memdb" 9 "github.com/hashicorp/nomad/helper/testlog" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/state" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/stretchr/testify/require" 14 ) 15 16 // TestVolumeWatch_EnableDisable tests the watcher registration logic that needs 17 // to happen during leader step-up/step-down 18 func TestVolumeWatch_EnableDisable(t *testing.T) { 19 t.Parallel() 20 require := require.New(t) 21 22 srv := &MockRPCServer{} 23 srv.state = state.TestStateStore(t) 24 index := uint64(100) 25 26 watcher := NewVolumesWatcher(testlog.HCLogger(t), 27 srv, srv, 28 LimitStateQueriesPerSecond, 29 CrossVolumeUpdateBatchDuration) 30 31 watcher.SetEnabled(true, srv.State()) 32 33 plugin := mock.CSIPlugin() 34 node := testNode(nil, plugin, srv.State()) 35 alloc := mock.Alloc() 36 alloc.ClientStatus = structs.AllocClientStatusComplete 37 vol := testVolume(nil, plugin, alloc, node.ID) 38 39 index++ 40 err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol}) 41 require.NoError(err) 42 43 claim := &structs.CSIVolumeClaim{Mode: structs.CSIVolumeClaimRelease} 44 index++ 45 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 46 require.NoError(err) 47 require.Eventually(func() bool { 48 return 1 == len(watcher.watchers) 49 }, time.Second, 10*time.Millisecond) 50 51 watcher.SetEnabled(false, srv.State()) 52 require.Equal(0, len(watcher.watchers)) 53 } 54 55 // TestVolumeWatch_Checkpoint tests the checkpointing of progress across 56 // leader leader step-up/step-down 57 func TestVolumeWatch_Checkpoint(t *testing.T) { 58 t.Parallel() 59 require := require.New(t) 60 61 srv := &MockRPCServer{} 62 srv.state = state.TestStateStore(t) 63 index := uint64(100) 64 65 watcher := NewVolumesWatcher(testlog.HCLogger(t), 66 srv, srv, 67 LimitStateQueriesPerSecond, 68 CrossVolumeUpdateBatchDuration) 69 70 plugin := mock.CSIPlugin() 71 node := testNode(nil, plugin, srv.State()) 72 alloc := mock.Alloc() 73 alloc.ClientStatus = structs.AllocClientStatusComplete 74 vol := testVolume(nil, plugin, alloc, node.ID) 75 76 watcher.SetEnabled(true, srv.State()) 77 78 index++ 79 err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol}) 80 require.NoError(err) 81 82 // we should get or start up a watcher when we get an update for 83 // the volume from the state store 84 require.Eventually(func() bool { 85 return 1 == len(watcher.watchers) 86 }, time.Second, 10*time.Millisecond) 87 88 // step-down (this is sync, but step-up is async) 89 watcher.SetEnabled(false, srv.State()) 90 require.Equal(0, len(watcher.watchers)) 91 92 // step-up again 93 watcher.SetEnabled(true, srv.State()) 94 require.Eventually(func() bool { 95 return 1 == len(watcher.watchers) && 96 !watcher.watchers[vol.ID+vol.Namespace].isRunning() 97 }, time.Second, 10*time.Millisecond) 98 } 99 100 // TestVolumeWatch_StartStop tests the start and stop of the watcher when 101 // it receives notifcations and has completed its work 102 func TestVolumeWatch_StartStop(t *testing.T) { 103 t.Parallel() 104 require := require.New(t) 105 106 ctx, exitFn := context.WithCancel(context.Background()) 107 defer exitFn() 108 109 srv := &MockStatefulRPCServer{} 110 srv.state = state.TestStateStore(t) 111 index := uint64(100) 112 srv.volumeUpdateBatcher = NewVolumeUpdateBatcher( 113 CrossVolumeUpdateBatchDuration, srv, ctx) 114 115 watcher := NewVolumesWatcher(testlog.HCLogger(t), 116 srv, srv, 117 LimitStateQueriesPerSecond, 118 CrossVolumeUpdateBatchDuration) 119 120 watcher.SetEnabled(true, srv.State()) 121 require.Equal(0, len(watcher.watchers)) 122 123 plugin := mock.CSIPlugin() 124 node := testNode(nil, plugin, srv.State()) 125 alloc1 := mock.Alloc() 126 alloc1.ClientStatus = structs.AllocClientStatusRunning 127 alloc2 := mock.Alloc() 128 alloc2.Job = alloc1.Job 129 alloc2.ClientStatus = structs.AllocClientStatusRunning 130 index++ 131 err := srv.State().UpsertJob(index, alloc1.Job) 132 require.NoError(err) 133 index++ 134 err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc1, alloc2}) 135 require.NoError(err) 136 137 // register a volume 138 vol := testVolume(nil, plugin, alloc1, node.ID) 139 index++ 140 err = srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol}) 141 require.NoError(err) 142 143 // assert we get a watcher; there are no claims so it should immediately stop 144 require.Eventually(func() bool { 145 return 1 == len(watcher.watchers) && 146 !watcher.watchers[vol.ID+vol.Namespace].isRunning() 147 }, time.Second*2, 10*time.Millisecond) 148 149 // claim the volume for both allocs 150 claim := &structs.CSIVolumeClaim{ 151 AllocationID: alloc1.ID, 152 NodeID: node.ID, 153 Mode: structs.CSIVolumeClaimRead, 154 } 155 index++ 156 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 157 require.NoError(err) 158 claim.AllocationID = alloc2.ID 159 index++ 160 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 161 require.NoError(err) 162 163 // reap the volume and assert nothing has happened 164 claim = &structs.CSIVolumeClaim{ 165 AllocationID: alloc1.ID, 166 NodeID: node.ID, 167 Mode: structs.CSIVolumeClaimRelease, 168 } 169 index++ 170 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 171 require.NoError(err) 172 173 ws := memdb.NewWatchSet() 174 vol, _ = srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID) 175 require.Equal(2, len(vol.ReadAllocs)) 176 177 // alloc becomes terminal 178 alloc1.ClientStatus = structs.AllocClientStatusComplete 179 index++ 180 err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc1}) 181 require.NoError(err) 182 index++ 183 claim.State = structs.CSIVolumeClaimStateReadyToFree 184 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 185 require.NoError(err) 186 187 // 1 claim has been released and watcher stops 188 require.Eventually(func() bool { 189 ws := memdb.NewWatchSet() 190 vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID) 191 return len(vol.ReadAllocs) == 1 && len(vol.PastClaims) == 0 192 }, time.Second*2, 10*time.Millisecond) 193 194 require.Eventually(func() bool { 195 return !watcher.watchers[vol.ID+vol.Namespace].isRunning() 196 }, time.Second*5, 10*time.Millisecond) 197 198 // the watcher will have incremented the index so we need to make sure 199 // our inserts will trigger new events 200 index, _ = srv.State().LatestIndex() 201 202 // remaining alloc's job is stopped (alloc is not marked terminal) 203 alloc2.Job.Stop = true 204 index++ 205 err = srv.State().UpsertJob(index, alloc2.Job) 206 require.NoError(err) 207 208 // job deregistration write a claim with no allocations or nodes 209 claim = &structs.CSIVolumeClaim{ 210 Mode: structs.CSIVolumeClaimRelease, 211 } 212 index++ 213 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim) 214 require.NoError(err) 215 216 // all claims have been released and watcher has stopped again 217 require.Eventually(func() bool { 218 ws := memdb.NewWatchSet() 219 vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID) 220 return len(vol.ReadAllocs) == 0 && len(vol.PastClaims) == 0 221 }, time.Second*2, 10*time.Millisecond) 222 223 require.Eventually(func() bool { 224 return !watcher.watchers[vol.ID+vol.Namespace].isRunning() 225 }, time.Second*5, 10*time.Millisecond) 226 227 // the watcher will have incremented the index so we need to make sure 228 // our inserts will trigger new events 229 index, _ = srv.State().LatestIndex() 230 231 // create a new claim 232 alloc3 := mock.Alloc() 233 alloc3.ClientStatus = structs.AllocClientStatusRunning 234 index++ 235 err = srv.State().UpsertAllocs(index, []*structs.Allocation{alloc3}) 236 require.NoError(err) 237 claim3 := &structs.CSIVolumeClaim{ 238 AllocationID: alloc3.ID, 239 NodeID: node.ID, 240 Mode: structs.CSIVolumeClaimRelease, 241 } 242 index++ 243 err = srv.State().CSIVolumeClaim(index, vol.Namespace, vol.ID, claim3) 244 require.NoError(err) 245 246 // a stopped watcher should restore itself on notification 247 require.Eventually(func() bool { 248 return watcher.watchers[vol.ID+vol.Namespace].isRunning() 249 }, time.Second*5, 10*time.Millisecond) 250 } 251 252 // TestVolumeWatch_RegisterDeregister tests the start and stop of 253 // watchers around registration 254 func TestVolumeWatch_RegisterDeregister(t *testing.T) { 255 t.Parallel() 256 require := require.New(t) 257 258 ctx, exitFn := context.WithCancel(context.Background()) 259 defer exitFn() 260 261 srv := &MockStatefulRPCServer{} 262 srv.state = state.TestStateStore(t) 263 srv.volumeUpdateBatcher = NewVolumeUpdateBatcher( 264 CrossVolumeUpdateBatchDuration, srv, ctx) 265 266 index := uint64(100) 267 268 watcher := NewVolumesWatcher(testlog.HCLogger(t), 269 srv, srv, 270 LimitStateQueriesPerSecond, 271 CrossVolumeUpdateBatchDuration) 272 273 watcher.SetEnabled(true, srv.State()) 274 require.Equal(0, len(watcher.watchers)) 275 276 plugin := mock.CSIPlugin() 277 node := testNode(nil, plugin, srv.State()) 278 alloc := mock.Alloc() 279 alloc.ClientStatus = structs.AllocClientStatusComplete 280 281 // register a volume 282 vol := testVolume(nil, plugin, alloc, node.ID) 283 index++ 284 err := srv.State().CSIVolumeRegister(index, []*structs.CSIVolume{vol}) 285 require.NoError(err) 286 287 require.Eventually(func() bool { 288 return 1 == len(watcher.watchers) 289 }, time.Second, 10*time.Millisecond) 290 291 // reap the volume and assert we've cleaned up 292 w := watcher.watchers[vol.ID+vol.Namespace] 293 w.Notify(vol) 294 295 require.Eventually(func() bool { 296 ws := memdb.NewWatchSet() 297 vol, _ := srv.State().CSIVolumeByID(ws, vol.Namespace, vol.ID) 298 return len(vol.ReadAllocs) == 0 && len(vol.PastClaims) == 0 299 }, time.Second*2, 10*time.Millisecond) 300 301 require.Eventually(func() bool { 302 return !watcher.watchers[vol.ID+vol.Namespace].isRunning() 303 }, time.Second*1, 10*time.Millisecond) 304 305 require.Equal(1, srv.countCSINodeDetachVolume, "node detach RPC count") 306 require.Equal(1, srv.countCSIControllerDetachVolume, "controller detach RPC count") 307 require.Equal(2, srv.countUpsertVolumeClaims, "upsert claims count") 308 309 // deregistering the volume doesn't cause an update that triggers 310 // a watcher; we'll clean up this watcher in a GC later 311 err = srv.State().CSIVolumeDeregister(index, vol.Namespace, []string{vol.ID}) 312 require.NoError(err) 313 require.Equal(1, len(watcher.watchers)) 314 require.False(watcher.watchers[vol.ID+vol.Namespace].isRunning()) 315 }