Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions pkg/driver/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest)
}
if err == cloud.ErrNotFound {
klog.V(5).Infof("DeleteVolume: Access Point %v not found, returning success", accessPointId)
deleteCompleted = true
return &csi.DeleteVolumeResponse{}, nil
}
return nil, status.Errorf(codes.Internal, "Could not get describe Access Point: %v , error: %v", accessPointId, err)
Expand Down Expand Up @@ -521,14 +522,11 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest)
}
}

// Before removing, ensure the removal path exists and is a directory
apRootPath := fsRoot + accessPoint.AccessPointRootDir
if pathInfo, err := d.mounter.Stat(apRootPath); err == nil && !os.IsNotExist(err) && pathInfo.IsDir() {
err = os.RemoveAll(apRootPath)
}
deleteCompleted, err = d.removeAccessPointRootDir(fsRoot + accessPoint.AccessPointRootDir)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not delete access point root directory %q: %v", accessPoint.AccessPointRootDir, err)
return nil, err
}

err = d.mounter.Unmount(fsRoot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not unmount %q: %v", fsRoot, err)
Expand Down Expand Up @@ -557,6 +555,34 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest)
return &csi.DeleteVolumeResponse{}, nil
}

func (d *Driver) removeAccessPointRootDir(path string) (bool, error) {
// Before removing, ensure the removal path exists and is a directory
pathInfo, err := d.mounter.Stat(path)
if pathInfo != nil {
// Only remove directory if stat call successful and path is indeed a directory
if pathInfo.IsDir() {
err = os.RemoveAll(path)
if err != nil {
return false, status.Errorf(codes.Internal, "Could not delete access point root directory %q: %v", path, err)
}
return true, nil
}

// Not a directory, ignore
return true, nil
}

if err != nil {
// Does not exist ignore
if os.IsNotExist(err) {
return true, nil
}
}

// Legitimate error
return false, status.Errorf(codes.Internal, "Cannot read access point root directory %q information: %v", path, err)
}

func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
Expand Down
154 changes: 150 additions & 4 deletions pkg/driver/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io/fs"
"math/rand"
"regexp"
"strconv"
Expand Down Expand Up @@ -3686,6 +3687,151 @@ func TestDeleteVolume(t *testing.T) {
mockCtl.Finish()
},
},
{
name: "Fail: cannot delete access point root directory",
testFunc: func(t *testing.T) {
mockCtl := gomock.NewController(t)
mockCloud := mocks.NewMockCloud(mockCtl)
mockMounter := mocks.NewMockMounter(mockCtl)

driver := &Driver{
endpoint: endpoint,
cloud: mockCloud,
mounter: mockMounter,
gidAllocator: NewGidAllocator(),
lockManager: NewLockManagerMap(),
deleteAccessPointRootDir: true,
}

req := &csi.DeleteVolumeRequest{
VolumeId: volumeId,
}

accessPoint := &cloud.AccessPoint{
AccessPointId: apId,
FileSystemId: fsId,
AccessPointRootDir: "/.",
CapacityGiB: 0,
}

dirPresent := mocks.NewMockFileInfo(
"testFile",
0,
0755,
time.Now(),
true,
nil,
)

ctx := context.Background()
mockMounter.EXPECT().MakeDir(gomock.Any()).Return(nil)
mockMounter.EXPECT().Mount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
mockMounter.EXPECT().Unmount(gomock.Any()).MaxTimes(1).Return(nil)
mockMounter.EXPECT().Stat(gomock.Any()).Return(dirPresent, nil)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Times(2).Return(true, nil)
mockCloud.EXPECT().DescribeAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(accessPoint, nil)
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Times(0)
_, err := driver.DeleteVolume(ctx, req)
if err == nil {
t.Fatalf("DeleteVolume did not fail")
}

mockCtl.Finish()
},
},
{
name: "Success: Normal flow with deleteAccessPointRootDir with directory does not exist",
testFunc: func(t *testing.T) {
mockCtl := gomock.NewController(t)
mockCloud := mocks.NewMockCloud(mockCtl)
mockMounter := mocks.NewMockMounter(mockCtl)

driver := &Driver{
endpoint: endpoint,
cloud: mockCloud,
mounter: mockMounter,
gidAllocator: NewGidAllocator(),
lockManager: NewLockManagerMap(),
deleteAccessPointRootDir: true,
}

req := &csi.DeleteVolumeRequest{
VolumeId: volumeId,
}

accessPoint := &cloud.AccessPoint{
AccessPointId: apId,
FileSystemId: fsId,
AccessPointRootDir: "/testDir",
CapacityGiB: 0,
}

ctx := context.Background()
mockMounter.EXPECT().MakeDir(gomock.Any()).Return(nil)
mockMounter.EXPECT().Mount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
mockMounter.EXPECT().Unmount(gomock.Any()).Return(nil)
mockMounter.EXPECT().Stat(gomock.Any()).Return(nil, fs.ErrNotExist)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil)
mockCloud.EXPECT().DescribeAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(accessPoint, nil)
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(nil)
_, err := driver.DeleteVolume(ctx, req)
if err != nil {
t.Fatalf("Delete Volume failed: %v", err)
}
mockCtl.Finish()
},
},
{
name: "Success: Normal flow with deleteAccessPointRootDir when rootDir is not a directory",
testFunc: func(t *testing.T) {
mockCtl := gomock.NewController(t)
mockCloud := mocks.NewMockCloud(mockCtl)
mockMounter := mocks.NewMockMounter(mockCtl)

driver := &Driver{
endpoint: endpoint,
cloud: mockCloud,
mounter: mockMounter,
gidAllocator: NewGidAllocator(),
lockManager: NewLockManagerMap(),
deleteAccessPointRootDir: true,
}

req := &csi.DeleteVolumeRequest{
VolumeId: volumeId,
}

accessPoint := &cloud.AccessPoint{
AccessPointId: apId,
FileSystemId: fsId,
AccessPointRootDir: "/testDir",
CapacityGiB: 0,
}

dirPresent := mocks.NewMockFileInfo(
"testFile",
0,
0644,
time.Now(),
false,
nil,
)

ctx := context.Background()
mockMounter.EXPECT().MakeDir(gomock.Any()).Return(nil)
mockMounter.EXPECT().Mount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
mockMounter.EXPECT().Unmount(gomock.Any()).Return(nil)
mockMounter.EXPECT().Stat(gomock.Any()).Return(dirPresent, nil)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil)
mockCloud.EXPECT().DescribeAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(accessPoint, nil)
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(nil)
_, err := driver.DeleteVolume(ctx, req)
if err != nil {
t.Fatalf("Delete Volume failed: %v", err)
}
mockCtl.Finish()
},
},
{
name: "Success: Race Delete with deleteAccessPointRootDir",
testFunc: func(t *testing.T) {
Expand Down Expand Up @@ -3731,7 +3877,7 @@ func TestDeleteVolume(t *testing.T) {
mockMounter.EXPECT().Stat(gomock.Any()).Return(dirPresent, nil).Times(1)
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(nil).Times(1)

mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(numGoRoutines)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(1)

// Expect the first describe call to see the access point, then subsequent calls to see it as deleted
var describeCallCount int32 = 0
Expand Down Expand Up @@ -3847,7 +3993,7 @@ func TestDeleteVolume(t *testing.T) {
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(nil).Times(1)
mockCloud.EXPECT().DeleteAccessPoint(gomock.Eq(ctx), gomock.Eq(apId2)).Return(nil).Times(1)

mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(2 * numGoRoutines)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(2)

// Expect the first describe call to see the access point, then subsequent calls to see it as deleted
describeCallCountAp1 := 0
Expand Down Expand Up @@ -4014,7 +4160,7 @@ func TestDeleteVolume(t *testing.T) {
}

ctx := context.Background()
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Times(0).Return(true, nil)
mockCloud.EXPECT().DescribeAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(nil, cloud.ErrNotFound)
_, err := driver.DeleteVolume(ctx, req)
if err != nil {
Expand Down Expand Up @@ -4201,7 +4347,7 @@ func TestDeleteVolume(t *testing.T) {
mockMounter.EXPECT().Mount(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
mockMounter.EXPECT().Unmount(gomock.Any()).Return(errors.New("Failed to unmount"))
mockMounter.EXPECT().Stat(gomock.Any()).Return(dirPresent, nil).Times(1)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(2)
mockMounter.EXPECT().IsLikelyNotMountPoint(gomock.Any()).Return(true, nil).Times(1)
mockCloud.EXPECT().DescribeAccessPoint(gomock.Eq(ctx), gomock.Eq(apId)).Return(accessPoint, nil)
_, err := driver.DeleteVolume(ctx, req)
if err == nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/driver/mounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (m *NodeMounter) IsLikelyNotMountPoint(target string) (bool, error) {
notMnt, err := m.MounterForceUnmounter.IsLikelyNotMountPoint(target)
if err != nil {
if os.IsNotExist(err) {
return false, nil
return true, nil
}
return false, err
}
Expand Down