Skip to content

Commit 070f826

Browse files
authored
Merge pull request kubernetes-csi#66 from darkowlzz/46-nodestagetest
sanity: Add tests for NodeStageVolume/NodeUnstageVolume
2 parents 9d42c8d + 0f094bc commit 070f826

File tree

2 files changed

+251
-3
lines changed

2 files changed

+251
-3
lines changed

mock/service/node.go

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,86 @@ func (s *service) NodeStageVolume(
1616
req *csi.NodeStageVolumeRequest) (
1717
*csi.NodeStageVolumeResponse, error) {
1818

19-
return nil, status.Error(codes.Unimplemented, "")
19+
device, ok := req.PublishInfo["device"]
20+
if !ok {
21+
return nil, status.Error(
22+
codes.InvalidArgument,
23+
"stage volume info 'device' key required")
24+
}
25+
26+
if len(req.GetVolumeId()) == 0 {
27+
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
28+
}
29+
30+
if len(req.GetStagingTargetPath()) == 0 {
31+
return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty")
32+
}
33+
34+
if req.GetVolumeCapability() == nil {
35+
return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty")
36+
}
37+
38+
s.volsRWL.Lock()
39+
defer s.volsRWL.Unlock()
40+
41+
i, v := s.findVolNoLock("id", req.VolumeId)
42+
if i < 0 {
43+
return nil, status.Error(codes.NotFound, req.VolumeId)
44+
}
45+
46+
// nodeStgPathKey is the key in the volume's attributes that is set to a
47+
// mock stage path if the volume has been published by the node
48+
nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath)
49+
50+
// Check to see if the volume has already been staged.
51+
if v.Attributes[nodeStgPathKey] != "" {
52+
// TODO: Check for the capabilities to be equal. Return "ALREADY_EXISTS"
53+
// if the capabilities don't match.
54+
return &csi.NodeStageVolumeResponse{}, nil
55+
}
56+
57+
// Stage the volume.
58+
v.Attributes[nodeStgPathKey] = device
59+
s.vols[i] = v
60+
61+
return &csi.NodeStageVolumeResponse{}, nil
2062
}
2163

2264
func (s *service) NodeUnstageVolume(
2365
ctx context.Context,
2466
req *csi.NodeUnstageVolumeRequest) (
2567
*csi.NodeUnstageVolumeResponse, error) {
2668

27-
return nil, status.Error(codes.Unimplemented, "")
69+
if len(req.GetVolumeId()) == 0 {
70+
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
71+
}
72+
73+
if len(req.GetStagingTargetPath()) == 0 {
74+
return nil, status.Error(codes.InvalidArgument, "Staging Target Path cannot be empty")
75+
}
76+
77+
s.volsRWL.Lock()
78+
defer s.volsRWL.Unlock()
79+
80+
i, v := s.findVolNoLock("id", req.VolumeId)
81+
if i < 0 {
82+
return nil, status.Error(codes.NotFound, req.VolumeId)
83+
}
84+
85+
// nodeStgPathKey is the key in the volume's attributes that is set to a
86+
// mock stage path if the volume has been published by the node
87+
nodeStgPathKey := path.Join(s.nodeID, req.StagingTargetPath)
88+
89+
// Check to see if the volume has already been unstaged.
90+
if v.Attributes[nodeStgPathKey] == "" {
91+
return &csi.NodeUnstageVolumeResponse{}, nil
92+
}
93+
94+
// Unpublish the volume.
95+
delete(v.Attributes, nodeStgPathKey)
96+
s.vols[i] = v
97+
98+
return &csi.NodeUnstageVolumeResponse{}, nil
2899
}
29100

30101
func (s *service) NodePublishVolume(
@@ -39,6 +110,18 @@ func (s *service) NodePublishVolume(
39110
"publish volume info 'device' key required")
40111
}
41112

113+
if len(req.GetVolumeId()) == 0 {
114+
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
115+
}
116+
117+
if len(req.GetTargetPath()) == 0 {
118+
return nil, status.Error(codes.InvalidArgument, "Target Path cannot be empty")
119+
}
120+
121+
if req.GetVolumeCapability() == nil {
122+
return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot be empty")
123+
}
124+
42125
s.volsRWL.Lock()
43126
defer s.volsRWL.Unlock()
44127

@@ -64,7 +147,11 @@ func (s *service) NodePublishVolume(
64147
}
65148

66149
// Publish the volume.
67-
v.Attributes[nodeMntPathKey] = device
150+
if req.GetStagingTargetPath() != "" {
151+
v.Attributes[nodeMntPathKey] = req.GetStagingTargetPath()
152+
} else {
153+
v.Attributes[nodeMntPathKey] = device
154+
}
68155
s.vols[i] = v
69156

70157
return &csi.NodePublishVolumeResponse{}, nil
@@ -130,6 +217,13 @@ func (s *service) NodeGetCapabilities(
130217
},
131218
},
132219
},
220+
{
221+
Type: &csi.NodeServiceCapability_Rpc{
222+
Rpc: &csi.NodeServiceCapability_RPC{
223+
Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
224+
},
225+
},
226+
},
133227
},
134228
}, nil
135229
}

pkg/sanity/node.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,157 @@ func testFullWorkflowSuccess(s csi.ControllerClient, c csi.NodeClient, controlle
364364
})
365365
Expect(err).NotTo(HaveOccurred())
366366
}
367+
368+
var _ = Describe("NodeStageVolume [Node Server]", func() {
369+
var (
370+
s csi.ControllerClient
371+
c csi.NodeClient
372+
controllerPublishSupported bool
373+
nodeStageSupported bool
374+
device string
375+
)
376+
377+
BeforeEach(func() {
378+
s = csi.NewControllerClient(conn)
379+
c = csi.NewNodeClient(conn)
380+
device = "/dev/mock"
381+
controllerPublishSupported = isControllerCapabilitySupported(
382+
s,
383+
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME)
384+
nodeStageSupported = isNodeCapabilitySupported(c, csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
385+
if nodeStageSupported {
386+
err := createMountTargetLocation(config.StagingPath)
387+
Expect(err).NotTo(HaveOccurred())
388+
} else {
389+
Skip("NodeStageVolume not supported")
390+
}
391+
})
392+
393+
It("should fail when no volume id is provided", func() {
394+
395+
_, err := c.NodeStageVolume(
396+
context.Background(),
397+
&csi.NodeStageVolumeRequest{
398+
StagingTargetPath: config.StagingPath,
399+
VolumeCapability: &csi.VolumeCapability{
400+
AccessType: &csi.VolumeCapability_Mount{
401+
Mount: &csi.VolumeCapability_MountVolume{},
402+
},
403+
AccessMode: &csi.VolumeCapability_AccessMode{
404+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
405+
},
406+
},
407+
PublishInfo: map[string]string{
408+
"device": device,
409+
},
410+
})
411+
Expect(err).To(HaveOccurred())
412+
413+
serverError, ok := status.FromError(err)
414+
Expect(ok).To(BeTrue())
415+
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
416+
})
417+
418+
It("should fail when no staging target path is provided", func() {
419+
420+
_, err := c.NodeStageVolume(
421+
context.Background(),
422+
&csi.NodeStageVolumeRequest{
423+
VolumeId: "id",
424+
VolumeCapability: &csi.VolumeCapability{
425+
AccessType: &csi.VolumeCapability_Mount{
426+
Mount: &csi.VolumeCapability_MountVolume{},
427+
},
428+
AccessMode: &csi.VolumeCapability_AccessMode{
429+
Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
430+
},
431+
},
432+
PublishInfo: map[string]string{
433+
"device": device,
434+
},
435+
})
436+
Expect(err).To(HaveOccurred())
437+
438+
serverError, ok := status.FromError(err)
439+
Expect(ok).To(BeTrue())
440+
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
441+
})
442+
443+
It("should fail when no volume capability is provided", func() {
444+
445+
_, err := c.NodeStageVolume(
446+
context.Background(),
447+
&csi.NodeStageVolumeRequest{
448+
VolumeId: "id",
449+
StagingTargetPath: config.StagingPath,
450+
PublishInfo: map[string]string{
451+
"device": device,
452+
},
453+
})
454+
Expect(err).To(HaveOccurred())
455+
456+
serverError, ok := status.FromError(err)
457+
Expect(ok).To(BeTrue())
458+
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
459+
})
460+
461+
It("should return appropriate values (no optional values added)", func() {
462+
testFullWorkflowSuccess(s, c, controllerPublishSupported, nodeStageSupported)
463+
})
464+
})
465+
466+
var _ = Describe("NodeUnstageVolume [Node Server]", func() {
467+
var (
468+
s csi.ControllerClient
469+
c csi.NodeClient
470+
controllerPublishSupported bool
471+
nodeStageSupported bool
472+
)
473+
474+
BeforeEach(func() {
475+
s = csi.NewControllerClient(conn)
476+
c = csi.NewNodeClient(conn)
477+
controllerPublishSupported = isControllerCapabilitySupported(
478+
s,
479+
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME)
480+
nodeStageSupported = isNodeCapabilitySupported(c, csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
481+
if nodeStageSupported {
482+
err := createMountTargetLocation(config.StagingPath)
483+
Expect(err).NotTo(HaveOccurred())
484+
} else {
485+
Skip("NodeUnstageVolume not supported")
486+
}
487+
})
488+
489+
It("should fail when no volume id is provided", func() {
490+
491+
_, err := c.NodeUnstageVolume(
492+
context.Background(),
493+
&csi.NodeUnstageVolumeRequest{
494+
StagingTargetPath: config.StagingPath,
495+
})
496+
Expect(err).To(HaveOccurred())
497+
498+
serverError, ok := status.FromError(err)
499+
Expect(ok).To(BeTrue())
500+
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
501+
})
502+
503+
It("should fail when no staging target path is provided", func() {
504+
505+
_, err := c.NodeUnstageVolume(
506+
context.Background(),
507+
&csi.NodeUnstageVolumeRequest{
508+
VolumeId: "id",
509+
})
510+
Expect(err).To(HaveOccurred())
511+
512+
serverError, ok := status.FromError(err)
513+
Expect(ok).To(BeTrue())
514+
Expect(serverError.Code()).To(Equal(codes.InvalidArgument))
515+
})
516+
517+
It("should return appropriate values (no optional values added)", func() {
518+
testFullWorkflowSuccess(s, c, controllerPublishSupported, nodeStageSupported)
519+
})
520+
})

0 commit comments

Comments
 (0)