@@ -18,12 +18,18 @@ package sanity
18
18
19
19
import (
20
20
"context"
21
+ "fmt"
21
22
"log"
22
23
"sync"
23
24
25
+ "google.golang.org/grpc"
26
+ "google.golang.org/grpc/codes"
27
+ "google.golang.org/grpc/status"
28
+
24
29
"github.com/container-storage-interface/spec/lib/go/csi"
25
30
26
31
. "github.com/onsi/ginkgo"
32
+ . "github.com/onsi/gomega"
27
33
)
28
34
29
35
// VolumeInfo keeps track of the information needed to delete a volume.
@@ -36,110 +42,284 @@ type VolumeInfo struct {
36
42
VolumeID string
37
43
}
38
44
39
- // Cleanup keeps track of resources, in particular volumes, which need
40
- // to be freed when testing is done. All methods can be called concurrently.
45
+ // Cleanup keeps track of resources, in particular volumes and snapshots, that
46
+ // need to be freed when testing is done. It implements both ControllerClient
47
+ // and NodeClient and should be used as the only interaction point to either
48
+ // APIs. That way, Cleanup can ensure that resources are marked for cleanup as
49
+ // necessary.
50
+ // All methods can be called concurrently.
41
51
type Cleanup struct {
42
- Context * TestContext
43
- ControllerClient csi.ControllerClient
44
- NodeClient csi.NodeClient
52
+ Context * TestContext
53
+ // ControllerClient is meant for struct-internal use only
54
+ csi.ControllerClient
55
+ // NodeClient is meant for struct-internal use only
56
+ csi.NodeClient
45
57
ControllerPublishSupported bool
46
58
NodeStageSupported bool
47
59
48
- // Maps from volume name to the node ID for which the volume
49
- // is published and the volume ID.
50
- volumes map [string ]VolumeInfo
51
- mutex sync.Mutex
60
+ // mutex protects access to the volumes and snapshots maps.
61
+ mutex sync.Mutex
62
+ // volumes maps from volume IDs to VolumeInfo structs and records if a given
63
+ // volume must be cleaned up.
64
+ volumes map [string ]* VolumeInfo
65
+ // snapshots is keyed by snapshot IDs and records if a given snapshot must
66
+ // be cleaned up.
67
+ snapshots map [string ]bool
68
+ }
69
+
70
+ // ControllerClient interface wrappers
71
+
72
+ // CreateVolume proxies to a Controller service implementation and registers the
73
+ // volume for cleanup.
74
+ func (cl * Cleanup ) CreateVolume (ctx context.Context , in * csi.CreateVolumeRequest , _ ... grpc.CallOption ) (* csi.CreateVolumeResponse , error ) {
75
+ return cl .createVolume (ctx , in )
76
+ }
77
+
78
+ // DeleteVolume proxies to a Controller service implementation and unregisters
79
+ // the volume from cleanup.
80
+ func (cl * Cleanup ) DeleteVolume (ctx context.Context , in * csi.DeleteVolumeRequest , _ ... grpc.CallOption ) (* csi.DeleteVolumeResponse , error ) {
81
+ return cl .deleteVolume (ctx , in )
82
+ }
83
+
84
+ // ControllerPublishVolume proxies to a Controller service implementation and
85
+ // adds the node ID to the corresponding volume for cleanup.
86
+ func (cl * Cleanup ) ControllerPublishVolume (ctx context.Context , in * csi.ControllerPublishVolumeRequest , _ ... grpc.CallOption ) (* csi.ControllerPublishVolumeResponse , error ) {
87
+ return cl .controllerPublishVolume (ctx , in )
88
+ }
89
+
90
+ // CreateSnapshot proxies to a Controller service implementation and registers
91
+ // the snapshot for cleanup.
92
+ func (cl * Cleanup ) CreateSnapshot (ctx context.Context , in * csi.CreateSnapshotRequest , _ ... grpc.CallOption ) (* csi.CreateSnapshotResponse , error ) {
93
+ return cl .createSnapshot (ctx , in )
94
+ }
95
+
96
+ // DeleteSnapshot proxies to a Controller service implementation and unregisters
97
+ // the snapshot from cleanup.
98
+ func (cl * Cleanup ) DeleteSnapshot (ctx context.Context , in * csi.DeleteSnapshotRequest , _ ... grpc.CallOption ) (* csi.DeleteSnapshotResponse , error ) {
99
+ return cl .deleteSnapshot (ctx , in )
100
+ }
101
+
102
+ // MustCreateVolume is like CreateVolume but asserts that the volume was
103
+ // successfully created.
104
+ func (cl * Cleanup ) MustCreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) * csi.CreateVolumeResponse {
105
+ vol , err := cl .createVolume (ctx , req )
106
+ Expect (err ).NotTo (HaveOccurred ())
107
+ Expect (vol ).NotTo (BeNil ())
108
+ Expect (vol .GetVolume ()).NotTo (BeNil ())
109
+ Expect (vol .GetVolume ().GetVolumeId ()).NotTo (BeEmpty ())
110
+ return vol
111
+ }
112
+
113
+ func (cl * Cleanup ) createVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
114
+ vol , err := cl .ControllerClient .CreateVolume (ctx , req )
115
+ if err == nil && vol != nil && vol .GetVolume ().GetVolumeId () != "" {
116
+ cl .registerVolume (VolumeInfo {VolumeID : vol .GetVolume ().GetVolumeId ()})
117
+ }
118
+ return vol , err
52
119
}
53
120
54
- // RegisterVolume adds or updates an entry for the volume with the
55
- // given name.
56
- func (cl * Cleanup ) RegisterVolume (name string , info VolumeInfo ) {
121
+ func (cl * Cleanup ) deleteVolume (ctx context.Context , req * csi.DeleteVolumeRequest ) (* csi.DeleteVolumeResponse , error ) {
122
+ vol , err := cl .ControllerClient .DeleteVolume (ctx , req )
123
+ if err == nil {
124
+ cl .unregisterVolume (req .VolumeId )
125
+ }
126
+ return vol , err
127
+ }
128
+
129
+ // MustControllerPublishVolume is like ControllerPublishVolume but asserts that
130
+ // the volume was successfully controller-published.
131
+ func (cl * Cleanup ) MustControllerPublishVolume (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) * csi.ControllerPublishVolumeResponse {
132
+ conpubvol , err := cl .controllerPublishVolume (ctx , req )
133
+ Expect (err ).NotTo (HaveOccurred ())
134
+ Expect (conpubvol ).NotTo (BeNil ())
135
+ return conpubvol
136
+ }
137
+
138
+ func (cl * Cleanup ) controllerPublishVolume (ctx context.Context , req * csi.ControllerPublishVolumeRequest ) (* csi.ControllerPublishVolumeResponse , error ) {
139
+ conpubvol , err := cl .ControllerClient .ControllerPublishVolume (ctx , req )
140
+ if err == nil && req .VolumeId != "" && req .NodeId != "" {
141
+ cl .registerVolume (VolumeInfo {VolumeID : req .VolumeId , NodeID : req .NodeId })
142
+ }
143
+ return conpubvol , err
144
+ }
145
+
146
+ // registerVolume adds or updates an entry for given volume.
147
+ func (cl * Cleanup ) registerVolume (info VolumeInfo ) {
148
+ Expect (info ).NotTo (BeNil ())
149
+ Expect (info .VolumeID ).NotTo (BeEmpty ())
57
150
cl .mutex .Lock ()
58
151
defer cl .mutex .Unlock ()
59
152
if cl .volumes == nil {
60
- cl .volumes = make (map [string ]VolumeInfo )
153
+ cl .volumes = make (map [string ]* VolumeInfo )
61
154
}
62
- cl .volumes [name ] = info
155
+ cl .volumes [info . VolumeID ] = & info
63
156
}
64
157
65
- // MaybeRegisterVolume adds or updates an entry for the volume with
66
- // the given name if CreateVolume was successful.
67
- func (cl * Cleanup ) MaybeRegisterVolume (name string , vol * csi.CreateVolumeResponse , err error ) {
68
- if err == nil && vol .GetVolume ().GetVolumeId () != "" {
69
- cl .RegisterVolume (name , VolumeInfo {VolumeID : vol .GetVolume ().GetVolumeId ()})
158
+ // unregisterVolume removes the entry for the volume with the
159
+ // given ID, thus preventing all cleanup operations for it.
160
+ func (cl * Cleanup ) unregisterVolume (id string ) {
161
+ cl .mutex .Lock ()
162
+ defer cl .mutex .Unlock ()
163
+ cl .unregisterVolumeNoLock (id )
164
+ }
165
+
166
+ func (cl * Cleanup ) unregisterVolumeNoLock (id string ) {
167
+ Expect (id ).NotTo (BeEmpty ())
168
+ if cl .volumes != nil {
169
+ delete (cl .volumes , id )
70
170
}
71
171
}
72
172
73
- // UnregisterVolume removes the entry for the volume with the
74
- // given name, thus preventing all cleanup operations for it.
75
- func (cl * Cleanup ) UnregisterVolume (name string ) {
173
+ // MustCreateSnapshot is like CreateSnapshot but asserts that the snapshot was
174
+ // successfully created.
175
+ func (cl * Cleanup ) MustCreateSnapshot (ctx context.Context , req * csi.CreateSnapshotRequest ) * csi.CreateSnapshotResponse {
176
+ snap , err := cl .createSnapshot (ctx , req )
177
+ Expect (err ).NotTo (HaveOccurred ())
178
+ Expect (snap ).NotTo (BeNil ())
179
+ verifySnapshotInfo (snap .GetSnapshot ())
180
+ return snap
181
+ }
182
+
183
+ // MustCreateSnapshotFromVolumeRequest creates a volume from the given
184
+ // CreateVolumeRequest and a snapshot subsequently. It registers the volume and
185
+ // snapshot and asserts that both were created successfully.
186
+ func (cl * Cleanup ) MustCreateSnapshotFromVolumeRequest (ctx context.Context , req * csi.CreateVolumeRequest , snapshotName string ) (* csi.CreateSnapshotResponse , * csi.CreateVolumeResponse ) {
187
+ vol := cl .MustCreateVolume (ctx , req )
188
+ snap := cl .MustCreateSnapshot (ctx , MakeCreateSnapshotReq (cl .Context , snapshotName , vol .Volume .VolumeId ))
189
+ return snap , vol
190
+ }
191
+
192
+ func (cl * Cleanup ) createSnapshot (ctx context.Context , req * csi.CreateSnapshotRequest ) (* csi.CreateSnapshotResponse , error ) {
193
+ snap , err := cl .ControllerClient .CreateSnapshot (ctx , req )
194
+ if err == nil && snap .GetSnapshot ().GetSnapshotId () != "" {
195
+ cl .registerSnapshot (snap .Snapshot .SnapshotId )
196
+ }
197
+ return snap , err
198
+ }
199
+
200
+ func (cl * Cleanup ) deleteSnapshot (ctx context.Context , req * csi.DeleteSnapshotRequest ) (* csi.DeleteSnapshotResponse , error ) {
201
+ snap , err := cl .ControllerClient .DeleteSnapshot (ctx , req )
202
+ if err == nil && req .SnapshotId != "" {
203
+ cl .unregisterSnapshot (req .SnapshotId )
204
+ }
205
+ return snap , err
206
+ }
207
+
208
+ func (cl * Cleanup ) registerSnapshot (id string ) {
76
209
cl .mutex .Lock ()
77
210
defer cl .mutex .Unlock ()
78
- cl .unregisterVolume ( name )
211
+ cl .registerSnapshotNoLock ( id )
79
212
}
80
- func (cl * Cleanup ) unregisterVolume (name string ) {
81
- if cl .volumes != nil {
82
- delete (cl .volumes , name )
213
+
214
+ func (cl * Cleanup ) registerSnapshotNoLock (id string ) {
215
+ Expect (id ).NotTo (BeEmpty ())
216
+ if cl .snapshots == nil {
217
+ cl .snapshots = make (map [string ]bool )
83
218
}
219
+ cl .snapshots [id ] = true
84
220
}
85
221
86
- // DeleteVolumes stops using the registered volumes and tries to delete all of them.
87
- func (cl * Cleanup ) DeleteVolumes () {
222
+ func (cl * Cleanup ) unregisterSnapshot (id string ) {
88
223
cl .mutex .Lock ()
89
224
defer cl .mutex .Unlock ()
90
- if cl .volumes == nil {
91
- return
225
+ cl .unregisterSnapshotNoLock (id )
226
+ }
227
+
228
+ func (cl * Cleanup ) unregisterSnapshotNoLock (id string ) {
229
+ Expect (id ).NotTo (BeEmpty ())
230
+ if cl .snapshots != nil {
231
+ delete (cl .snapshots , id )
92
232
}
93
- logger := log .New (GinkgoWriter , "cleanup: " , 0 )
233
+ }
234
+
235
+ // Cleanup calls unpublish methods as needed and deletes all volumes and
236
+ // snapshots.
237
+ func (cl * Cleanup ) Cleanup () {
238
+ cl .mutex .Lock ()
239
+ defer cl .mutex .Unlock ()
94
240
ctx := context .Background ()
95
241
96
- for name , info := range cl .volumes {
97
- logger .Printf ("deleting %s = %s" , name , info .VolumeID )
98
- if _ , err := cl .NodeClient .NodeUnpublishVolume (
99
- ctx ,
100
- & csi.NodeUnpublishVolumeRequest {
101
- VolumeId : info .VolumeID ,
102
- TargetPath : cl .Context .TargetPath + "/target" ,
103
- },
104
- ); err != nil {
105
- logger .Printf ("warning: NodeUnpublishVolume: %s" , err )
106
- }
242
+ cl .deleteVolumes (ctx )
243
+ cl .deleteSnapshots (ctx )
244
+ }
245
+
246
+ func (cl * Cleanup ) deleteVolumes (ctx context.Context ) {
247
+ logger := log .New (GinkgoWriter , "cleanup volumes: " , 0 )
107
248
108
- if cl .NodeStageSupported {
109
- if _ , err := cl .NodeClient .NodeUnstageVolume (
249
+ for volumeID , info := range cl .volumes {
250
+ logger .Printf ("deleting %s" , volumeID )
251
+ if cl .NodeClient != nil {
252
+ if _ , err := cl .NodeUnpublishVolume (
110
253
ctx ,
111
- & csi.NodeUnstageVolumeRequest {
112
- VolumeId : info . VolumeID ,
113
- StagingTargetPath : cl .Context .StagingPath ,
254
+ & csi.NodeUnpublishVolumeRequest {
255
+ VolumeId : volumeID ,
256
+ TargetPath : cl .Context .TargetPath + "/target" ,
114
257
},
115
258
); err != nil {
116
- logger .Printf ("warning: NodeUnstageVolume: %s" , err )
259
+ if status .Code (err ) != codes .NotFound {
260
+ Fail (fmt .Sprintf ("NodeUnpublishVolume failed: %s" , err ))
261
+ }
262
+ }
263
+
264
+ if cl .NodeStageSupported {
265
+ if _ , err := cl .NodeUnstageVolume (
266
+ ctx ,
267
+ & csi.NodeUnstageVolumeRequest {
268
+ VolumeId : volumeID ,
269
+ StagingTargetPath : cl .Context .StagingPath ,
270
+ },
271
+ ); err != nil {
272
+ if status .Code (err ) != codes .NotFound {
273
+ Fail (fmt .Sprintf ("NodeUnstageVolume failed: %s" , err ))
274
+ }
275
+ }
117
276
}
118
277
}
119
278
120
279
if cl .ControllerPublishSupported && info .NodeID != "" {
121
280
if _ , err := cl .ControllerClient .ControllerUnpublishVolume (
122
281
ctx ,
123
282
& csi.ControllerUnpublishVolumeRequest {
124
- VolumeId : info . VolumeID ,
283
+ VolumeId : volumeID ,
125
284
NodeId : info .NodeID ,
126
285
Secrets : cl .Context .Secrets .ControllerUnpublishVolumeSecret ,
127
286
},
128
287
); err != nil {
129
- logger . Printf ( "warning: ControllerUnpublishVolume: %s" , err )
288
+ Fail ( fmt . Sprintf ( " ControllerUnpublishVolume failed : %s" , err ) )
130
289
}
131
290
}
132
291
133
292
if _ , err := cl .ControllerClient .DeleteVolume (
134
293
ctx ,
135
294
& csi.DeleteVolumeRequest {
136
- VolumeId : info . VolumeID ,
295
+ VolumeId : volumeID ,
137
296
Secrets : cl .Context .Secrets .DeleteVolumeSecret ,
138
297
},
139
298
); err != nil {
140
- logger .Printf ("error: DeleteVolume: %s" , err )
299
+ if status .Code (err ) != codes .NotFound {
300
+ Fail (fmt .Sprintf ("DeleteVolume failed: %s" , err ))
301
+ }
141
302
}
142
303
143
- cl .unregisterVolume (name )
304
+ cl .unregisterVolumeNoLock (volumeID )
305
+ }
306
+ }
307
+
308
+ func (cl * Cleanup ) deleteSnapshots (ctx context.Context ) {
309
+ logger := log .New (GinkgoWriter , "cleanup snapshots: " , 0 )
310
+
311
+ for id := range cl .snapshots {
312
+ logger .Printf ("deleting %s" , id )
313
+ _ , err := cl .ControllerClient .DeleteSnapshot (
314
+ ctx ,
315
+ & csi.DeleteSnapshotRequest {
316
+ SnapshotId : id ,
317
+ Secrets : cl .Context .Secrets .DeleteSnapshotSecret ,
318
+ },
319
+ )
320
+ if err != nil {
321
+ Fail (fmt .Sprintf ("DeleteSnapshot failed: %s" , err ))
322
+ }
323
+ cl .unregisterSnapshotNoLock (id )
144
324
}
145
325
}
0 commit comments