github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/nomad/volumewatcher/volume_watcher_test.go (about) 1 package volumewatcher 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/hashicorp/nomad/helper/testlog" 9 "github.com/hashicorp/nomad/nomad/mock" 10 "github.com/hashicorp/nomad/nomad/state" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/stretchr/testify/require" 13 ) 14 15 // TestVolumeWatch_OneReap tests one pass through the reaper 16 func TestVolumeWatch_OneReap(t *testing.T) { 17 t.Parallel() 18 require := require.New(t) 19 20 cases := []struct { 21 Name string 22 Volume *structs.CSIVolume 23 Node *structs.Node 24 ControllerRequired bool 25 ExpectedErr string 26 ExpectedClaimsCount int 27 ExpectedNodeDetachCount int 28 ExpectedControllerDetachCount int 29 ExpectedUpdateClaimsCount int 30 srv *MockRPCServer 31 }{ 32 { 33 Name: "No terminal allocs", 34 Volume: mock.CSIVolume(mock.CSIPlugin()), 35 ControllerRequired: true, 36 srv: &MockRPCServer{ 37 state: state.TestStateStore(t), 38 nextCSINodeDetachError: fmt.Errorf("should never see this"), 39 }, 40 }, 41 { 42 Name: "NodeDetachVolume fails", 43 ControllerRequired: true, 44 ExpectedErr: "some node plugin error", 45 ExpectedNodeDetachCount: 1, 46 srv: &MockRPCServer{ 47 state: state.TestStateStore(t), 48 nextCSINodeDetachError: fmt.Errorf("some node plugin error"), 49 }, 50 }, 51 { 52 Name: "NodeDetachVolume node-only happy path", 53 ControllerRequired: false, 54 ExpectedNodeDetachCount: 1, 55 ExpectedUpdateClaimsCount: 2, 56 srv: &MockRPCServer{ 57 state: state.TestStateStore(t), 58 }, 59 }, 60 { 61 Name: "ControllerDetachVolume no controllers available", 62 Node: mock.Node(), 63 ControllerRequired: true, 64 ExpectedErr: "Unknown node", 65 ExpectedNodeDetachCount: 1, 66 ExpectedUpdateClaimsCount: 1, 67 srv: &MockRPCServer{ 68 state: state.TestStateStore(t), 69 }, 70 }, 71 { 72 Name: "ControllerDetachVolume controller error", 73 ControllerRequired: true, 74 ExpectedErr: "some controller error", 75 ExpectedNodeDetachCount: 1, 76 ExpectedControllerDetachCount: 1, 77 ExpectedUpdateClaimsCount: 1, 78 srv: &MockRPCServer{ 79 state: state.TestStateStore(t), 80 nextCSIControllerDetachError: fmt.Errorf("some controller error"), 81 }, 82 }, 83 { 84 Name: "ControllerDetachVolume happy path", 85 ControllerRequired: true, 86 ExpectedNodeDetachCount: 1, 87 ExpectedControllerDetachCount: 1, 88 ExpectedUpdateClaimsCount: 2, 89 srv: &MockRPCServer{ 90 state: state.TestStateStore(t), 91 }, 92 }, 93 } 94 95 for _, tc := range cases { 96 t.Run(tc.Name, func(t *testing.T) { 97 98 plugin := mock.CSIPlugin() 99 plugin.ControllerRequired = tc.ControllerRequired 100 node := testNode(tc.Node, plugin, tc.srv.State()) 101 alloc := mock.Alloc() 102 alloc.NodeID = node.ID 103 alloc.ClientStatus = structs.AllocClientStatusComplete 104 vol := testVolume(tc.Volume, plugin, alloc, node.ID) 105 ctx, exitFn := context.WithCancel(context.Background()) 106 w := &volumeWatcher{ 107 v: vol, 108 rpc: tc.srv, 109 state: tc.srv.State(), 110 updateClaims: tc.srv.UpdateClaims, 111 ctx: ctx, 112 exitFn: exitFn, 113 logger: testlog.HCLogger(t), 114 } 115 116 err := w.volumeReapImpl(vol) 117 if tc.ExpectedErr != "" { 118 require.Error(err, fmt.Sprintf("expected: %q", tc.ExpectedErr)) 119 require.Contains(err.Error(), tc.ExpectedErr) 120 } else { 121 require.NoError(err) 122 } 123 require.Equal(tc.ExpectedNodeDetachCount, 124 tc.srv.countCSINodeDetachVolume, "node detach RPC count") 125 require.Equal(tc.ExpectedControllerDetachCount, 126 tc.srv.countCSIControllerDetachVolume, "controller detach RPC count") 127 require.Equal(tc.ExpectedUpdateClaimsCount, 128 tc.srv.countUpdateClaims, "update claims count") 129 }) 130 } 131 } 132 133 // TestVolumeWatch_OldVolume_OneReap tests one pass through the reaper 134 // COMPAT(1.0): the claim fields were added after 0.11.1; this test 135 // can be removed for 1.0 136 func TestVolumeWatch_OldVolume_OneReap(t *testing.T) { 137 t.Parallel() 138 require := require.New(t) 139 140 cases := []struct { 141 Name string 142 Volume *structs.CSIVolume 143 Node *structs.Node 144 ControllerRequired bool 145 ExpectedErr string 146 ExpectedClaimsCount int 147 ExpectedNodeDetachCount int 148 ExpectedControllerDetachCount int 149 ExpectedUpdateClaimsCount int 150 srv *MockRPCServer 151 }{ 152 { 153 Name: "No terminal allocs", 154 Volume: mock.CSIVolume(mock.CSIPlugin()), 155 ControllerRequired: true, 156 srv: &MockRPCServer{ 157 state: state.TestStateStore(t), 158 nextCSINodeDetachError: fmt.Errorf("should never see this"), 159 }, 160 }, 161 { 162 Name: "NodeDetachVolume fails", 163 ControllerRequired: true, 164 ExpectedErr: "some node plugin error", 165 ExpectedNodeDetachCount: 1, 166 srv: &MockRPCServer{ 167 state: state.TestStateStore(t), 168 nextCSINodeDetachError: fmt.Errorf("some node plugin error"), 169 }, 170 }, 171 { 172 Name: "NodeDetachVolume node-only happy path", 173 ControllerRequired: false, 174 ExpectedNodeDetachCount: 1, 175 ExpectedUpdateClaimsCount: 2, 176 srv: &MockRPCServer{ 177 state: state.TestStateStore(t), 178 }, 179 }, 180 { 181 Name: "ControllerDetachVolume no controllers available", 182 Node: mock.Node(), 183 ControllerRequired: true, 184 ExpectedErr: "Unknown node", 185 ExpectedNodeDetachCount: 1, 186 ExpectedUpdateClaimsCount: 1, 187 srv: &MockRPCServer{ 188 state: state.TestStateStore(t), 189 }, 190 }, 191 { 192 Name: "ControllerDetachVolume controller error", 193 ControllerRequired: true, 194 ExpectedErr: "some controller error", 195 ExpectedNodeDetachCount: 1, 196 ExpectedControllerDetachCount: 1, 197 ExpectedUpdateClaimsCount: 1, 198 srv: &MockRPCServer{ 199 state: state.TestStateStore(t), 200 nextCSIControllerDetachError: fmt.Errorf("some controller error"), 201 }, 202 }, 203 { 204 Name: "ControllerDetachVolume happy path", 205 ControllerRequired: true, 206 ExpectedNodeDetachCount: 1, 207 ExpectedControllerDetachCount: 1, 208 ExpectedUpdateClaimsCount: 2, 209 srv: &MockRPCServer{ 210 state: state.TestStateStore(t), 211 }, 212 }, 213 } 214 215 for _, tc := range cases { 216 t.Run(tc.Name, func(t *testing.T) { 217 218 plugin := mock.CSIPlugin() 219 plugin.ControllerRequired = tc.ControllerRequired 220 node := testNode(tc.Node, plugin, tc.srv.State()) 221 alloc := mock.Alloc() 222 alloc.ClientStatus = structs.AllocClientStatusComplete 223 alloc.NodeID = node.ID 224 vol := testOldVolume(tc.Volume, plugin, alloc, node.ID) 225 ctx, exitFn := context.WithCancel(context.Background()) 226 w := &volumeWatcher{ 227 v: vol, 228 rpc: tc.srv, 229 state: tc.srv.State(), 230 updateClaims: tc.srv.UpdateClaims, 231 ctx: ctx, 232 exitFn: exitFn, 233 logger: testlog.HCLogger(t), 234 } 235 236 err := w.volumeReapImpl(vol) 237 if tc.ExpectedErr != "" { 238 require.Error(err, fmt.Sprintf("expected: %q", tc.ExpectedErr)) 239 require.Contains(err.Error(), tc.ExpectedErr) 240 } else { 241 require.NoError(err) 242 } 243 require.Equal(tc.ExpectedNodeDetachCount, 244 tc.srv.countCSINodeDetachVolume, "node detach RPC count") 245 require.Equal(tc.ExpectedControllerDetachCount, 246 tc.srv.countCSIControllerDetachVolume, "controller detach RPC count") 247 require.Equal(tc.ExpectedUpdateClaimsCount, 248 tc.srv.countUpdateClaims, "update claims count") 249 }) 250 } 251 } 252 253 // TestVolumeWatch_OneReap tests multiple passes through the reaper, 254 // updating state after each one 255 func TestVolumeWatch_ReapStates(t *testing.T) { 256 t.Parallel() 257 require := require.New(t) 258 259 srv := &MockRPCServer{state: state.TestStateStore(t)} 260 plugin := mock.CSIPlugin() 261 node := testNode(nil, plugin, srv.State()) 262 alloc := mock.Alloc() 263 alloc.ClientStatus = structs.AllocClientStatusComplete 264 vol := testVolume(nil, plugin, alloc, node.ID) 265 266 w := &volumeWatcher{ 267 v: vol, 268 rpc: srv, 269 state: srv.State(), 270 updateClaims: srv.UpdateClaims, 271 logger: testlog.HCLogger(t), 272 } 273 274 srv.nextCSINodeDetachError = fmt.Errorf("some node plugin error") 275 err := w.volumeReapImpl(vol) 276 require.Error(err) 277 require.Equal(structs.CSIVolumeClaimStateTaken, vol.PastClaims[alloc.ID].State) 278 require.Equal(1, srv.countCSINodeDetachVolume) 279 require.Equal(0, srv.countCSIControllerDetachVolume) 280 require.Equal(0, srv.countUpdateClaims) 281 282 srv.nextCSINodeDetachError = nil 283 srv.nextCSIControllerDetachError = fmt.Errorf("some controller plugin error") 284 err = w.volumeReapImpl(vol) 285 require.Error(err) 286 require.Equal(structs.CSIVolumeClaimStateNodeDetached, vol.PastClaims[alloc.ID].State) 287 require.Equal(1, srv.countUpdateClaims) 288 289 srv.nextCSIControllerDetachError = nil 290 err = w.volumeReapImpl(vol) 291 require.NoError(err) 292 require.Equal(0, len(vol.PastClaims)) 293 require.Equal(2, srv.countUpdateClaims) 294 }