Skip to content

Commit 5c624fc

Browse files
authored
Merge pull request #292 from pohly/volume-lifecycle-fixes
volume lifecycle fixes
2 parents 7a25259 + f2b48cd commit 5c624fc

File tree

5 files changed

+114
-22
lines changed

5 files changed

+114
-22
lines changed

pkg/hostpath/controllerserver.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,9 @@ func (hp *hostPath) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeReque
203203
return &csi.DeleteVolumeResponse{}, nil
204204
}
205205

206-
if vol.IsAttached || vol.IsPublished || vol.IsStaged {
206+
if vol.Attached || !vol.Published.Empty() || !vol.Staged.Empty() {
207207
return nil, status.Errorf(codes.Internal, "Volume '%s' is still used (attached: %v, staged: %v, published: %v) by '%s' node",
208-
vol.VolID, vol.IsAttached, vol.IsStaged, vol.IsPublished, vol.NodeID)
208+
vol.VolID, vol.Attached, vol.Staged, vol.Published, vol.NodeID)
209209
}
210210

211211
if err := hp.deleteVolume(volId); err != nil {
@@ -287,7 +287,7 @@ func (hp *hostPath) ControllerPublishVolume(ctx context.Context, req *csi.Contro
287287
}
288288

289289
// Check to see if the volume is already published.
290-
if vol.IsAttached {
290+
if vol.Attached {
291291
// Check if readonly flag is compatible with the publish request.
292292
if req.GetReadonly() != vol.ReadOnlyAttach {
293293
return nil, status.Error(codes.AlreadyExists, "Volume published but has incompatible readonly flag")
@@ -303,7 +303,7 @@ func (hp *hostPath) ControllerPublishVolume(ctx context.Context, req *csi.Contro
303303
return nil, status.Errorf(codes.ResourceExhausted, "Cannot attach any more volumes to this node ('%s')", hp.config.NodeID)
304304
}
305305

306-
vol.IsAttached = true
306+
vol.Attached = true
307307
vol.ReadOnlyAttach = req.GetReadonly()
308308
if err := hp.state.UpdateVolume(vol); err != nil {
309309
return nil, err
@@ -339,12 +339,12 @@ func (hp *hostPath) ControllerUnpublishVolume(ctx context.Context, req *csi.Cont
339339
}
340340

341341
// Check to see if the volume is staged/published on a node
342-
if vol.IsPublished || vol.IsStaged {
342+
if !vol.Published.Empty() || !vol.Staged.Empty() {
343343
return nil, status.Errorf(codes.Internal, "Volume '%s' is still used (staged: %v, published: %v) by '%s' node",
344-
vol.VolID, vol.IsStaged, vol.IsPublished, vol.NodeID)
344+
vol.VolID, vol.Staged, vol.Published, vol.NodeID)
345345
}
346346

347-
vol.IsAttached = false
347+
vol.Attached = false
348348
if err := hp.state.UpdateVolume(vol); err != nil {
349349
return nil, status.Errorf(codes.Internal, "could not update volume %s: %v", vol.VolID, err)
350350
}

pkg/hostpath/hostpath.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ func loadFromBlockVolume(hostPathVolume state.Volume, destPath string) error {
368368
func (hp *hostPath) getAttachCount() int64 {
369369
count := int64(0)
370370
for _, vol := range hp.state.GetVolumes() {
371-
if vol.IsAttached {
371+
if vol.Attached {
372372
count++
373373
}
374374
}

pkg/hostpath/nodeserver.go

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ func (hp *hostPath) NodePublishVolume(ctx context.Context, req *csi.NodePublishV
8080
return nil, status.Error(codes.NotFound, err.Error())
8181
}
8282

83-
if !ephemeralVolume && !vol.IsStaged {
84-
return nil, status.Errorf(codes.FailedPrecondition, "Volume ('%s') must be staged before publishing.", vol.VolID)
83+
if !ephemeralVolume {
84+
if vol.Staged.Empty() {
85+
return nil, status.Errorf(codes.FailedPrecondition, "volume %q must be staged before publishing", vol.VolID)
86+
}
87+
if !vol.Staged.Has(req.GetStagingTargetPath()) {
88+
return nil, status.Errorf(codes.InvalidArgument, "volume %q was staged at %v, not %q", vol.VolID, vol.Staged, req.GetStagingTargetPath())
89+
}
8590
}
8691

8792
if req.GetVolumeCapability().GetBlock() != nil {
@@ -184,7 +189,7 @@ func (hp *hostPath) NodePublishVolume(ctx context.Context, req *csi.NodePublishV
184189
}
185190

186191
vol.NodeID = hp.config.NodeID
187-
vol.IsPublished = true
192+
vol.Published.Add(targetPath)
188193
if err := hp.state.UpdateVolume(vol); err != nil {
189194
return nil, err
190195
}
@@ -213,6 +218,11 @@ func (hp *hostPath) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpubl
213218
return nil, err
214219
}
215220

221+
if !vol.Published.Has(targetPath) {
222+
glog.V(4).Infof("Volume %q is not published at %q, nothing to do.", volumeID, targetPath)
223+
return &csi.NodeUnpublishVolumeResponse{}, nil
224+
}
225+
216226
// Unmount only if the target path is really a mount point.
217227
if notMnt, err := mount.IsNotMountPoint(mount.New(""), targetPath); err != nil {
218228
if !os.IsNotExist(err) {
@@ -238,7 +248,7 @@ func (hp *hostPath) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpubl
238248
return nil, fmt.Errorf("failed to delete volume: %w", err)
239249
}
240250
} else {
241-
vol.IsPublished = false
251+
vol.Published.Remove(targetPath)
242252
if err := hp.state.UpdateVolume(vol); err != nil {
243253
return nil, err
244254
}
@@ -253,24 +263,39 @@ func (hp *hostPath) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolum
253263
if len(req.GetVolumeId()) == 0 {
254264
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
255265
}
256-
if len(req.GetStagingTargetPath()) == 0 {
266+
stagingTargetPath := req.GetStagingTargetPath()
267+
if stagingTargetPath == "" {
257268
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
258269
}
259270
if req.GetVolumeCapability() == nil {
260271
return nil, status.Error(codes.InvalidArgument, "Volume Capability missing in request")
261272
}
262273

274+
// Lock before acting on global state. A production-quality
275+
// driver might use more fine-grained locking.
276+
hp.mutex.Lock()
277+
defer hp.mutex.Unlock()
278+
263279
vol, err := hp.state.GetVolumeByID(req.VolumeId)
264280
if err != nil {
265281
return nil, err
266282
}
267283

268-
if hp.config.EnableAttach && !vol.IsAttached {
284+
if hp.config.EnableAttach && !vol.Attached {
269285
return nil, status.Errorf(codes.Internal, "ControllerPublishVolume must be called on volume '%s' before staging on node",
270286
vol.VolID)
271287
}
272288

273-
vol.IsStaged = true
289+
if vol.Staged.Has(stagingTargetPath) {
290+
glog.V(4).Infof("Volume %q is already staged at %q, nothing to do.", req.VolumeId, stagingTargetPath)
291+
return &csi.NodeStageVolumeResponse{}, nil
292+
}
293+
294+
if !vol.Staged.Empty() {
295+
return nil, status.Errorf(codes.FailedPrecondition, "volume %q is already staged at %v", req.VolumeId, vol.Staged)
296+
}
297+
298+
vol.Staged.Add(stagingTargetPath)
274299
if err := hp.state.UpdateVolume(vol); err != nil {
275300
return nil, err
276301
}
@@ -284,19 +309,30 @@ func (hp *hostPath) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageV
284309
if len(req.GetVolumeId()) == 0 {
285310
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
286311
}
287-
if len(req.GetStagingTargetPath()) == 0 {
312+
stagingTargetPath := req.GetStagingTargetPath()
313+
if stagingTargetPath == "" {
288314
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
289315
}
290316

317+
// Lock before acting on global state. A production-quality
318+
// driver might use more fine-grained locking.
319+
hp.mutex.Lock()
320+
defer hp.mutex.Unlock()
321+
291322
vol, err := hp.state.GetVolumeByID(req.VolumeId)
292323
if err != nil {
293324
return nil, err
294325
}
295326

296-
if vol.IsPublished {
297-
return nil, status.Errorf(codes.Internal, "Volume '%s' is still pulished on '%s' node", vol.VolID, vol.NodeID)
327+
if !vol.Staged.Has(stagingTargetPath) {
328+
glog.V(4).Infof("Volume %q is not staged at %q, nothing to do.", req.VolumeId, stagingTargetPath)
329+
return &csi.NodeUnstageVolumeResponse{}, nil
330+
}
331+
332+
if !vol.Published.Empty() {
333+
return nil, status.Errorf(codes.Internal, "volume %q is still published at %q on node %q", vol.VolID, vol.Published, vol.NodeID)
298334
}
299-
vol.IsStaged = false
335+
vol.Staged.Remove(stagingTargetPath)
300336
if err := hp.state.UpdateVolume(vol); err != nil {
301337
return nil, err
302338
}

pkg/state/state.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,14 @@ type Volume struct {
4848
NodeID string
4949
Kind string
5050
ReadOnlyAttach bool
51-
IsAttached bool
52-
IsStaged bool
53-
IsPublished bool
51+
Attached bool
52+
// Staged contains the staging target path at which the volume
53+
// was staged. A set of paths is used for consistency
54+
// with Published.
55+
Staged Strings
56+
// Published contains the target paths where the volume
57+
// was published.
58+
Published Strings
5459
}
5560

5661
type Snapshot struct {

pkg/state/strings.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package state
18+
19+
// Strings is an ordered set of strings with helper functions for
20+
// adding, searching and removing entries.
21+
type Strings []string
22+
23+
// Add appends at the end.
24+
func (s *Strings) Add(str string) {
25+
*s = append(*s, str)
26+
}
27+
28+
// Has checks whether the string is already present.
29+
func (s *Strings) Has(str string) bool {
30+
for _, str2 := range *s {
31+
if str == str2 {
32+
return true
33+
}
34+
}
35+
return false
36+
}
37+
38+
// Empty returns true if the list is empty.
39+
func (s *Strings) Empty() bool {
40+
return len(*s) == 0
41+
}
42+
43+
// Remove removes the first occurence of the string, if present.
44+
func (s *Strings) Remove(str string) {
45+
for i, str2 := range *s {
46+
if str == str2 {
47+
*s = append((*s)[:i], (*s)[i+1:]...)
48+
return
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)