Skip to content

Commit

Permalink
feat: support for efs discovery
Browse files Browse the repository at this point in the history
discovering efs with tags support

add new mocks

added mount_utils interface to the MockMounter

discovering efs with single AWS call
  • Loading branch information
avanish23 committed Nov 1, 2024
1 parent c620c05 commit 65af3c8
Show file tree
Hide file tree
Showing 8 changed files with 730 additions and 39 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
module github.com/kubernetes-sigs/aws-efs-csi-driver

require (
github.com/aws/aws-sdk-go v1.50.3
github.com/aws/aws-sdk-go-v2 v1.31.0
github.com/aws/aws-sdk-go-v2/config v1.27.35
github.com/aws/aws-sdk-go-v2/credentials v1.17.33
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13
github.com/aws/aws-sdk-go-v2/service/ec2 v1.178.0
github.com/aws/aws-sdk-go-v2/service/efs v1.31.8
github.com/aws/aws-sdk-go-v2/service/sts v1.30.8
github.com/aws/smithy-go v1.21.0
github.com/container-storage-interface/spec v1.7.0
github.com/golang/mock v1.6.0
Expand All @@ -26,16 +29,13 @@ require (
)

require (
github.com/aws/aws-sdk-go v1.50.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.8 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
Expand Down
43 changes: 40 additions & 3 deletions pkg/cloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ var (
)

type FileSystem struct {
FileSystemId string
FileSystemId string
FileSystemArn string
Tags map[string]string
}

type AccessPoint struct {
Expand Down Expand Up @@ -105,7 +107,8 @@ type Cloud interface {
DescribeAccessPoint(ctx context.Context, accessPointId string) (accessPoint *AccessPoint, err error)
FindAccessPointByClientToken(ctx context.Context, clientToken, fileSystemId string) (accessPoint *AccessPoint, err error)
ListAccessPoints(ctx context.Context, fileSystemId string) (accessPoints []*AccessPoint, err error)
DescribeFileSystem(ctx context.Context, fileSystemId string) (fs *FileSystem, err error)
DescribeFileSystemById(ctx context.Context, fileSystemId string) (fs *FileSystem, err error)
DescribeFileSystemByToken(ctx context.Context, creationToken string) (fs []*FileSystem, err error)
DescribeMountTargets(ctx context.Context, fileSystemId, az string) (fs *MountTarget, err error)
}

Expand Down Expand Up @@ -322,7 +325,41 @@ func (c *cloud) ListAccessPoints(ctx context.Context, fileSystemId string) (acce
return
}

func (c *cloud) DescribeFileSystem(ctx context.Context, fileSystemId string) (fs *FileSystem, err error) {
func (c *cloud) DescribeFileSystemByToken(ctx context.Context, creationToken string) (fs []*FileSystem, err error) {
var describeFsInput *efs.DescribeFileSystemsInput
if creationToken == "" {
describeFsInput = &efs.DescribeFileSystemsInput{}
} else {
describeFsInput = &efs.DescribeFileSystemsInput{CreationToken: &creationToken}
}
klog.V(5).Infof("Calling DescribeFileSystems with input: %+v", *describeFsInput)
res, err := c.efs.DescribeFileSystems(ctx, describeFsInput)
if err != nil {
if isAccessDenied(err) {
return nil, ErrAccessDenied
}
if isFileSystemNotFound(err) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("Describe File System failed: %v", err)
}

var efsList = make([]*FileSystem, 0)
for _, fileSystem := range res.FileSystems {
var tagsList = make(map[string]string, 0)
for _, tag := range fileSystem.Tags {
tagsList[*tag.Key] = *tag.Value
}
efsList = append(efsList, &FileSystem{
FileSystemId: *fileSystem.FileSystemId,
FileSystemArn: *fileSystem.FileSystemArn,
Tags: tagsList,
})
}
return efsList, nil
}

func (c *cloud) DescribeFileSystemById(ctx context.Context, fileSystemId string) (fs *FileSystem, err error) {
describeFsInput := &efs.DescribeFileSystemsInput{FileSystemId: &fileSystemId}
klog.V(5).Infof("Calling DescribeFileSystems with input: %+v", *describeFsInput)
res, err := c.efs.DescribeFileSystems(ctx, describeFsInput)
Expand Down
220 changes: 214 additions & 6 deletions pkg/cloud/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ func TestDescribeFileSystem(t *testing.T) {

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(output, nil)
res, err := c.DescribeFileSystem(ctx, fsId)
res, err := c.DescribeFileSystemById(ctx, fsId)
if err != nil {
t.Fatalf("Describe File System failed: %v", err)
}
Expand Down Expand Up @@ -813,7 +813,7 @@ func TestDescribeFileSystem(t *testing.T) {

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(output, nil)
_, err := c.DescribeFileSystem(ctx, fsId)
_, err := c.DescribeFileSystemById(ctx, fsId)
if err == nil {
t.Fatalf("DescribeFileSystem did not fail")
}
Expand Down Expand Up @@ -848,7 +848,7 @@ func TestDescribeFileSystem(t *testing.T) {

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(output, nil)
_, err := c.DescribeFileSystem(ctx, fsId)
_, err := c.DescribeFileSystemById(ctx, fsId)
if err == nil {
t.Fatalf("DescribeFileSystem did not fail")
}
Expand All @@ -867,7 +867,7 @@ func TestDescribeFileSystem(t *testing.T) {
&types.FileSystemNotFound{
Message: aws.String("File System not found"),
})
_, err := c.DescribeFileSystem(ctx, fsId)
_, err := c.DescribeFileSystemById(ctx, fsId)
if err == nil {
t.Fatalf("DescribeFileSystem did not fail")
}
Expand All @@ -890,7 +890,7 @@ func TestDescribeFileSystem(t *testing.T) {
Code: AccessDeniedException,
Message: "Access Denied",
})
_, err := c.DescribeFileSystem(ctx, fsId)
_, err := c.DescribeFileSystemById(ctx, fsId)
if err == nil {
t.Fatalf("DescribeFileSystem did not fail")
}
Expand All @@ -909,7 +909,7 @@ func TestDescribeFileSystem(t *testing.T) {

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(nil, errors.New("DescribeFileSystem failed"))
_, err := c.DescribeFileSystem(ctx, fsId)
_, err := c.DescribeFileSystemById(ctx, fsId)
if err == nil {
t.Fatalf("DescribeFileSystem did not fail")
}
Expand Down Expand Up @@ -1050,6 +1050,214 @@ func TestDescribeMountTargets(t *testing.T) {
}
}

func TestDescribeFileSystemByToken(t *testing.T) {
var (
fsId = []string{"fs-abcd1234", "fs-efgh5678"}
fsArn = []string{"arn:aws:elasticfilesystem:us-west-2:1111333322228888:file-system/fs-0123456789abcdef8", "arn:aws:elasticfilesystem:us-west-2:1111333322228888:file-system/fs-987654321abcdef0"}
creationToken = "efs-for-discovery"
az = "us-east-1a"
)

testCases := []struct {
name string
testFunc func(t *testing.T)
}{
{
name: "Success: Normal flow",
testFunc: func(t *testing.T) {
mockctl := gomock.NewController(t)
mockEfs := mocks.NewMockEfs(mockctl)
c := &cloud{efs: mockEfs}

fs := &efs.DescribeFileSystemsOutput{
FileSystems: []types.FileSystemDescription{
{
FileSystemId: aws.String(fsId[0]),
FileSystemArn: aws.String(fsArn[0]),
Encrypted: aws.Bool(true),
CreationToken: aws.String("efs-for-discovery"),
AvailabilityZoneName: aws.String(az),
Tags: []types.Tag{
{
Key: aws.String("env"),
Value: aws.String("prod"),
},
{
Key: aws.String("owner"),
Value: aws.String("[email protected]"),
},
},
},
{
FileSystemId: aws.String(fsId[1]),
FileSystemArn: aws.String(fsArn[1]),
Encrypted: aws.Bool(true),
CreationToken: aws.String("efs-not-for-discovery"),
AvailabilityZoneName: aws.String(az),
Tags: []types.Tag{
{
Key: aws.String("env"),
Value: aws.String("prod"),
},
{
Key: aws.String("owner"),
Value: aws.String("[email protected]"),
},
},
},
},
}

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).DoAndReturn(func(ctx context.Context, input *efs.DescribeFileSystemsInput, opts ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error) {
res := &efs.DescribeFileSystemsOutput{}
for _, fileSystem := range fs.FileSystems {
if input.CreationToken != nil && *fileSystem.CreationToken == *input.CreationToken {
res.FileSystems = append(res.FileSystems, fileSystem)
} else if input.CreationToken == nil {
res.FileSystems = append(res.FileSystems, fileSystem)
}
}
return res, nil
})

efsList, err := c.DescribeFileSystemByToken(ctx, creationToken)
if err != nil {
t.Fatalf("DescribeFileSystem failed")
}
if len(efsList) != 1 {
t.Fatalf("Expected 1 fileSystems got %d", len(efsList))
}
mockctl.Finish()
},
},
{
name: "Success: Normal flow without creation token",
testFunc: func(t *testing.T) {
mockctl := gomock.NewController(t)
mockEfs := mocks.NewMockEfs(mockctl)
c := &cloud{efs: mockEfs}

fs := &efs.DescribeFileSystemsOutput{
FileSystems: []types.FileSystemDescription{
{
FileSystemId: aws.String(fsId[0]),
FileSystemArn: aws.String(fsArn[0]),
Encrypted: aws.Bool(true),
CreationToken: aws.String("efs-for-discovery"),
AvailabilityZoneName: aws.String(az),
Tags: []types.Tag{
{
Key: aws.String("env"),
Value: aws.String("prod"),
},
{
Key: aws.String("owner"),
Value: aws.String("[email protected]"),
},
},
},
{
FileSystemId: aws.String(fsId[1]),
FileSystemArn: aws.String(fsArn[1]),
Encrypted: aws.Bool(true),
CreationToken: aws.String("efs-not-for-discovery"),
AvailabilityZoneName: aws.String(az),
Tags: []types.Tag{
{
Key: aws.String("env"),
Value: aws.String("prod"),
},
{
Key: aws.String("owner"),
Value: aws.String("[email protected]"),
},
},
},
},
}

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).DoAndReturn(func(ctx context.Context, input *efs.DescribeFileSystemsInput, opts ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error) {
res := &efs.DescribeFileSystemsOutput{}
for _, fileSystem := range fs.FileSystems {
if input.CreationToken != nil && *fileSystem.CreationToken == *input.CreationToken {
res.FileSystems = append(res.FileSystems, fileSystem)
} else if input.CreationToken == nil {
res.FileSystems = append(res.FileSystems, fileSystem)
}
}
return res, nil
})

efsList, err := c.DescribeFileSystemByToken(ctx, "")
if err != nil {
t.Fatalf("DescribeFileSystem failed")
}
if len(efsList) != len(fs.FileSystems) {
t.Fatalf("Expected 1 fileSystems got %d", len(efsList))
}
for i, fileSystem := range fs.FileSystems {
for _, v := range fileSystem.Tags {
if val, exists := efsList[i].Tags[*v.Key]; !exists || val != *v.Value {
t.Fatalf("Tags list is corrupted, expected %s for %s but got %s", *v.Value, *v.Key, val)
}
}
}
mockctl.Finish()
},
},
{
name: "Fail: Access Denied",
testFunc: func(t *testing.T) {
mockctl := gomock.NewController(t)
mockEfs := mocks.NewMockEfs(mockctl)
c := &cloud{efs: mockEfs}

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(nil, &smithy.GenericAPIError{
Code: AccessDeniedException,
Message: "Access Denied",
})

_, err := c.DescribeFileSystemByToken(ctx, "efs-discovery")
if err == nil {
t.Fatalf("DescribeFileSystemByToken did not fail")
}
if err != ErrAccessDenied {
t.Fatalf("Failed. Expected: %v, Actual:%v", ErrAccessDenied, err)
}
mockctl.Finish()
},
},
{
name: "Fail: File System not found",
testFunc: func(t *testing.T) {
mockctl := gomock.NewController(t)
mockEfs := mocks.NewMockEfs(mockctl)
c := &cloud{efs: mockEfs}

ctx := context.Background()
mockEfs.EXPECT().DescribeFileSystems(gomock.Eq(ctx), gomock.Any()).Return(nil, &types.FileSystemNotFound{
Message: aws.String("File System not found"),
})

_, err := c.DescribeFileSystemByToken(ctx, "efs-discovery")
if err == nil {
t.Fatalf("DescribeFileSystemByToken did not fail")
}
if err != ErrNotFound {
t.Fatalf("Failed. Expected: %v, Actual:%v", ErrNotFound, err)
}
mockctl.Finish()
},
},
}
for _, tc := range testCases {
t.Run(tc.name, tc.testFunc)
}
}

func testResult(t *testing.T, funcName string, ret interface{}, err error, expectError errtyp) {
if expectError.message == "" {
if err != nil {
Expand Down
Loading

0 comments on commit 65af3c8

Please sign in to comment.