Skip to content

Commit 08f3272

Browse files
authored
Merge pull request kubernetes#131311 from gnufied/fix-csi-json-file-removal
Fix error handling and csi json file removal interaction
2 parents af53af1 + c704025 commit 08f3272

File tree

7 files changed

+181
-45
lines changed

7 files changed

+181
-45
lines changed

pkg/volume/csi/csi_attacher.go

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -350,25 +350,6 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
350350
data[volDataKey.seLinuxMountContext] = deviceMounterArgs.SELinuxLabel
351351
}
352352

353-
err = saveVolumeData(dataDir, volDataFileName, data)
354-
defer func() {
355-
// Only if there was an error and volume operation was considered
356-
// finished, we should remove the directory.
357-
if err != nil && volumetypes.IsOperationFinishedError(err) {
358-
// clean up metadata
359-
klog.Error(log("attacher.MountDevice failed: %v", err))
360-
if err := removeMountDir(c.plugin, deviceMountPath); err != nil {
361-
klog.Error(log("attacher.MountDevice failed to remove mount dir after error [%s]: %v", deviceMountPath, err))
362-
}
363-
}
364-
}()
365-
366-
if err != nil {
367-
errMsg := log("failed to save volume info data: %v", err)
368-
klog.Error(errMsg)
369-
return errors.New(errMsg)
370-
}
371-
372353
if !stageUnstageSet {
373354
klog.Info(log("attacher.MountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice..."))
374355
// defer does *not* remove the metadata file and it's correct - UnmountDevice needs it there.
@@ -387,13 +368,23 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
387368
return volumetypes.NewTransientOperationFailure(log("attacher.MountDevice failed to determine if the node service has VOLUME_MOUNT_GROUP capability: %v", err))
388369
}
389370

371+
err = saveVolumeData(dataDir, volDataFileName, data)
372+
if err != nil {
373+
errMsg := log("failed to save volume info data: %v", err)
374+
klog.Error(errMsg)
375+
if err := removeMountDir(c.plugin, deviceMountPath); err != nil {
376+
klog.Error(log("attacher.MountDevice failed to remove mount dir after error [%s]: %v", deviceMountPath, err))
377+
}
378+
return errors.New(errMsg)
379+
}
380+
390381
if driverSupportsCSIVolumeMountGroup {
391382
klog.V(3).Infof("Driver %s supports applying FSGroup (has VOLUME_MOUNT_GROUP node capability). Delegating FSGroup application to the driver through NodeStageVolume.", csiSource.Driver)
392383
nodeStageFSGroupArg = deviceMounterArgs.FsGroup
393384
}
394385

395386
fsType := csiSource.FSType
396-
err = csi.NodeStageVolume(ctx,
387+
csiRPCError := csi.NodeStageVolume(ctx,
397388
csiSource.VolumeHandle,
398389
publishContext,
399390
deviceMountPath,
@@ -404,12 +395,19 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
404395
mountOptions,
405396
nodeStageFSGroupArg)
406397

407-
if err != nil {
408-
return err
398+
if csiRPCError != nil {
399+
if volumetypes.IsOperationFinishedError(csiRPCError) {
400+
// clean up metadata
401+
klog.Error(log("attacher.MountDevice failed: %v", csiRPCError))
402+
if err := removeMountDir(c.plugin, deviceMountPath); err != nil {
403+
klog.Error(log("attacher.MountDevice failed to remove mount dir after error [%s]: %v", deviceMountPath, err))
404+
}
405+
}
406+
return csiRPCError
409407
}
410408

411409
klog.V(4).Info(log("attacher.MountDevice successfully requested NodeStageVolume [%s]", deviceMountPath))
412-
return err
410+
return nil
413411
}
414412

415413
var _ volume.Detacher = &csiAttacher{}

pkg/volume/csi/csi_mounter.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,16 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
281281
}
282282

283283
err = saveVolumeData(parentDir, volDataFileName, volData)
284-
defer func() {
285-
// Only if there was an error and volume operation was considered
286-
// finished, we should remove the directory.
287-
if err != nil && volumetypes.IsOperationFinishedError(err) {
288-
// attempt to cleanup volume mount dir
289-
if removeerr := removeMountDir(c.plugin, dir); removeerr != nil {
290-
klog.Error(log("mounter.SetUpAt failed to remove mount dir after error [%s]: %v", dir, removeerr))
291-
}
292-
}
293-
}()
294284
if err != nil {
295285
errorMsg := log("mounter.SetUpAt failed to save volume info data: %v", err)
296286
klog.Error(errorMsg)
297-
return volumetypes.NewTransientOperationFailure(errorMsg)
287+
if removeerr := removeMountDir(c.plugin, dir); removeerr != nil {
288+
klog.Error(log("mounter.SetUpAt failed to remove mount dir after error [%s]: %v", dir, removeerr))
289+
}
290+
return err
298291
}
299292

300-
err = csi.NodePublishVolume(
293+
csiRPCError := csi.NodePublishVolume(
301294
ctx,
302295
volumeHandle,
303296
readOnly,
@@ -312,14 +305,14 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
312305
nodePublishFSGroupArg,
313306
)
314307

315-
if err != nil {
308+
if csiRPCError != nil {
316309
// If operation finished with error then we can remove the mount directory.
317-
if volumetypes.IsOperationFinishedError(err) {
310+
if volumetypes.IsOperationFinishedError(csiRPCError) {
318311
if removeMountDirErr := removeMountDir(c.plugin, dir); removeMountDirErr != nil {
319312
klog.Error(log("mounter.SetupAt failed to remove mount dir after a NodePublish() error [%s]: %v", dir, removeMountDirErr))
320313
}
321314
}
322-
return err
315+
return csiRPCError
323316
}
324317

325318
if !selinuxLabelMount {
@@ -332,9 +325,13 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
332325

333326
if !driverSupportsCSIVolumeMountGroup && c.supportsFSGroup(fsType, mounterArgs.FsGroup, fsGroupPolicy) {
334327
// Driver doesn't support applying FSGroup. Kubelet must apply it instead.
335-
328+
var ownershipChanger volume.VolumeOwnershipChanger
329+
if mounterArgs.VolumeOwnershipApplicator != nil {
330+
ownershipChanger = mounterArgs.VolumeOwnershipApplicator
331+
} else {
332+
ownershipChanger = volume.NewVolumeOwnership(c, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(c.plugin, c.spec))
333+
}
336334
// fullPluginName helps to distinguish different driver from csi plugin
337-
ownershipChanger := volume.NewVolumeOwnership(c, dir, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(c.plugin, c.spec))
338335
ownershipChanger.AddProgressNotifier(c.pod, mounterArgs.Recorder)
339336
err = ownershipChanger.ChangePermissions()
340337
if err != nil {

pkg/volume/csi/csi_mounter_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
utilfeature "k8s.io/apiserver/pkg/util/feature"
3939
fakeclient "k8s.io/client-go/kubernetes/fake"
4040
clitesting "k8s.io/client-go/testing"
41+
"k8s.io/client-go/tools/record"
4142
featuregatetesting "k8s.io/component-base/featuregate/testing"
4243
pkgauthenticationv1 "k8s.io/kubernetes/pkg/apis/authentication/v1"
4344
pkgcorev1 "k8s.io/kubernetes/pkg/apis/core/v1"
@@ -360,6 +361,132 @@ func TestMounterSetUp(t *testing.T) {
360361
}
361362
}
362363

364+
type mockVolumeOwnershipChanger struct {
365+
triggerError bool
366+
}
367+
368+
func (m *mockVolumeOwnershipChanger) ChangePermissions() error {
369+
if m.triggerError {
370+
return fmt.Errorf("mock error")
371+
}
372+
return nil
373+
}
374+
375+
func (m *mockVolumeOwnershipChanger) AddProgressNotifier(pod *corev1.Pod, recorder record.EventRecorder) volume.VolumeOwnershipChanger {
376+
return m
377+
}
378+
379+
func TestMounterSetupJsonFileHandling(t *testing.T) {
380+
testCases := []struct {
381+
name string
382+
volumeID string
383+
setupShouldFail bool
384+
errorType error
385+
failOwnershipChange bool
386+
shouldRemoveFile bool
387+
}{
388+
{
389+
name: "transient error should not remove json file",
390+
volumeID: fakecsi.NodePublishTimeOut_VolumeID,
391+
setupShouldFail: true,
392+
shouldRemoveFile: false,
393+
},
394+
{
395+
name: "final error should remove json file",
396+
volumeID: fakecsi.NodePublishFinalError_VolumeID,
397+
setupShouldFail: true,
398+
shouldRemoveFile: true,
399+
},
400+
{
401+
name: "error in fsgroup permission change, should not remove json file",
402+
volumeID: testVol,
403+
failOwnershipChange: true,
404+
setupShouldFail: true,
405+
shouldRemoveFile: false,
406+
},
407+
}
408+
409+
for _, tc := range testCases {
410+
t.Run(tc.name, func(t *testing.T) {
411+
modes := []storage.VolumeLifecycleMode{
412+
storage.VolumeLifecyclePersistent,
413+
}
414+
csiDriver := getTestCSIDriver("file-driver", nil, nil, modes)
415+
fileFsPolicy := storage.FileFSGroupPolicy
416+
csiDriver.Spec.FSGroupPolicy = &fileFsPolicy
417+
418+
fakeClient := fakeclient.NewSimpleClientset(csiDriver)
419+
plug, tmpDir := newTestPlugin(t, fakeClient)
420+
defer os.RemoveAll(tmpDir)
421+
422+
registerFakePlugin("test-driver", "endpoint", []string{"1.0.0"}, t)
423+
pv := makeTestPV("test-vol", 10, "file-driver", tc.volumeID)
424+
pv.Spec.MountOptions = []string{"foo=bar", "baz=qux"}
425+
426+
mounter, err := plug.NewMounter(
427+
volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly),
428+
&corev1.Pod{
429+
ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns, Name: testPod},
430+
Spec: corev1.PodSpec{
431+
ServiceAccountName: testAccount,
432+
},
433+
},
434+
)
435+
if err != nil {
436+
t.Fatalf("failed to make a new Mounter: %v", err)
437+
}
438+
439+
if mounter == nil {
440+
t.Fatal("failed to create CSI mounter")
441+
}
442+
443+
csiMounter := mounter.(*csiMountMgr)
444+
csiMounter.csiClient = setupClient(t, true)
445+
446+
attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName()))
447+
attachment := makeTestAttachment(attachID, "test-node", csiMounter.spec.Name())
448+
_, err = csiMounter.k8s.StorageV1().VolumeAttachments().Create(context.TODO(), attachment, meta.CreateOptions{})
449+
if err != nil {
450+
t.Fatalf("failed to setup VolumeAttachment: %v", err)
451+
}
452+
453+
var mounterArgs volume.MounterArgs
454+
fsGroup := int64(2000)
455+
mounterArgs.FsGroup = &fsGroup
456+
if tc.failOwnershipChange {
457+
mounterArgs.VolumeOwnershipApplicator = &mockVolumeOwnershipChanger{triggerError: true}
458+
}
459+
460+
dataDir := filepath.Dir(mounter.GetPath())
461+
462+
// Mounter.SetUp()
463+
err = csiMounter.SetUp(mounterArgs)
464+
if tc.setupShouldFail {
465+
if err == nil {
466+
t.Error("test should fail, but no error occurred")
467+
}
468+
} else if err != nil {
469+
t.Fatal("unexpected error:", err)
470+
}
471+
472+
// Check if the json file exists or not
473+
dataFile := filepath.Join(dataDir, volDataFileName)
474+
_, err = os.Stat(dataFile)
475+
if tc.shouldRemoveFile {
476+
if !os.IsNotExist(err) {
477+
t.Errorf("Expected json file to be removed, but it still exists: %v", err)
478+
}
479+
} else {
480+
if os.IsNotExist(err) {
481+
t.Errorf("Expected json file to exist, but it was removed")
482+
} else if err != nil {
483+
t.Errorf("Unexpected error while checking json file: %v", err)
484+
}
485+
}
486+
})
487+
}
488+
}
489+
363490
func TestMounterSetUpSimple(t *testing.T) {
364491
fakeClient := fakeclient.NewSimpleClientset()
365492
plug, tmpDir := newTestPlugin(t, fakeClient)

pkg/volume/csi/fake/fake_client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import (
3131

3232
const (
3333
// NodePublishTimeOut_VolumeID is volume id that will result in NodePublish operation to timeout
34-
NodePublishTimeOut_VolumeID = "node-publish-timeout"
34+
NodePublishTimeOut_VolumeID = "node-publish-timeout"
35+
NodePublishFinalError_VolumeID = "node-publish-final-error"
3536

3637
// NodeStageTimeOut_VolumeID is a volume id that will result in NodeStage operation to timeout
3738
NodeStageTimeOut_VolumeID = "node-stage-timeout"
@@ -206,6 +207,10 @@ func (f *NodeClient) NodePublishVolume(ctx context.Context, req *csipb.NodePubli
206207
return nil, timeoutErr
207208
}
208209

210+
if req.GetVolumeId() == NodePublishFinalError_VolumeID {
211+
return nil, status.Errorf(codes.Internal, "final error")
212+
}
213+
209214
// "Creation of target_path is the responsibility of the SP."
210215
// Our plugin depends on it.
211216
if req.VolumeCapability.GetBlock() != nil {

pkg/volume/volume.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ type MounterArgs struct {
134134
DesiredSize *resource.Quantity
135135
SELinuxLabel string
136136
Recorder record.EventRecorder
137+
138+
// Optional interface that will be used to change the ownership of the volume, if specified.
139+
// mainly used by unit tests
140+
VolumeOwnershipApplicator VolumeOwnershipChanger
141+
}
142+
143+
type VolumeOwnershipChanger interface {
144+
AddProgressNotifier(pod *v1.Pod, recorder record.EventRecorder) VolumeOwnershipChanger
145+
ChangePermissions() error
137146
}
138147

139148
type VolumeOwnership struct {

pkg/volume/volume_linux.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ var (
5151
)
5252

5353
// NewVolumeOwnership returns an interface that can be used to recursively change volume permissions and ownership
54-
func NewVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy, completeFunc func(types.CompleteFuncParam)) *VolumeOwnership {
54+
func NewVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy, completeFunc func(types.CompleteFuncParam)) VolumeOwnershipChanger {
5555
vo := &VolumeOwnership{
5656
mounter: mounter,
5757
dir: dir,
@@ -63,7 +63,7 @@ func NewVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChan
6363
return vo
6464
}
6565

66-
func (vo *VolumeOwnership) AddProgressNotifier(pod *v1.Pod, recorder record.EventRecorder) *VolumeOwnership {
66+
func (vo *VolumeOwnership) AddProgressNotifier(pod *v1.Pod, recorder record.EventRecorder) VolumeOwnershipChanger {
6767
vo.pod = pod
6868
vo.recorder = recorder
6969
return vo

pkg/volume/volume_unsupported.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ import (
2626
)
2727

2828
// NewVolumeOwnership returns an interface that can be used to recursively change volume permissions and ownership
29-
func NewVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy, completeFunc func(types.CompleteFuncParam)) *VolumeOwnership {
29+
func NewVolumeOwnership(mounter Mounter, dir string, fsGroup *int64, fsGroupChangePolicy *v1.PodFSGroupChangePolicy, completeFunc func(types.CompleteFuncParam)) VolumeOwnershipChanger {
3030
return nil
3131
}
3232

33-
func (vo *VolumeOwnership) AddProgressNotifier(pod *v1.Pod, recorder record.EventRecorder) *VolumeOwnership {
33+
func (vo *VolumeOwnership) AddProgressNotifier(pod *v1.Pod, recorder record.EventRecorder) VolumeOwnershipChanger {
3434
return vo
3535
}
3636

0 commit comments

Comments
 (0)