Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EFS functional testing #2247

Merged
merged 6 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 4 additions & 1 deletion agent/Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"family": "ecsftest-task-efs-volume",
"containerDefinitions": [
{
"name": "task-efs-vol-read",
"image": "127.0.0.1:51670/busybox:latest",
"cpu": 10,
"command": ["sh", "-c", "while true; do sleep 1; [ -f /ecs/success ] && if grep -q 'can you read me' /ecs/success; then exit 42; fi done"],
"memory": 256,
"memoryReservation": 128,
"mountPoints": [
{
"sourceVolume": "task-efs-shared",
"containerPath": "/ecs/"
}
]
}
],
"volumes":[
{
"name": "task-efs-shared",
"host": null,
"dockerVolumeConfiguration" : {
"scope": "shared",
"autoprovision": true,
"driver": "local",
"labels": {
"mylabels": "test"
},
"driverOpts": {
"type": "nfs",
"device": ":/",
"o": "addr=FILESYSTEM_ID.efs.TEST_REGION.amazonaws.com,nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"family": "ecsftest-task-efs-volume",
"containerDefinitions": [
{
"name": "task-efs-vol-write",
"image": "127.0.0.1:51670/busybox:latest",
"cpu": 10,
"command": ["sh", "-c", "echo 'can you read me'> /ecs/success; [ -f /ecs/success ] && exit 42 || exit 1"],
"memory": 256,
"memoryReservation": 128,
"mountPoints": [
{
"sourceVolume": "task-efs-shared",
"containerPath": "/ecs/"
}
]
}
],
"volumes":[
{
"name": "task-efs-shared",
"host": null,
"dockerVolumeConfiguration" : {
"scope": "shared",
"autoprovision": true,
"driver": "local",
"labels": {
"mylabels": "test"
},
"driverOpts": {
"type": "nfs",
"device": ":/",
"o": "addr=FILESYSTEM_ID.efs.TEST_REGION.amazonaws.com,nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport"
}
}
}
]
}
123 changes: 122 additions & 1 deletion agent/functional_tests/tests/functionaltests_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/efs"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/aws/aws-sdk-go/service/secretsmanager"
Expand Down Expand Up @@ -1359,6 +1360,126 @@ func TestASMSecretsARN(t *testing.T) {
assert.Equal(t, 42, exitCode, fmt.Sprintf("Expected exit code of 42; got %d", exitCode))
}

// TestRunEFSVolumeTask does the following:
// 1. creates an EFS filesystem with a mount target.
// 2. spins up a task to mount and write to the filesystem.
// 3. spins up a task to mount and read from the filesystem.
// 4. TODO: do this via EFSVolumeConfiguration instead of via NFS/Docker.
func TestRunEFSVolumeTask(t *testing.T) {
if os.Getenv("TEST_DISABLE_EXECUTION_ROLE") == "true" {
t.Skip("TEST_DISABLE_EXECUTION_ROLE was set to true")
}

if IsCNPartition() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this URL will not work in the CN partition: addr=FILESYSTEM_ID.efs.TEST_REGION.amazonaws.com, there is currently a TODO in the feature branch (and design doc) to deal with this in EFSVolumeConfiguration (see https://github.com/aws/amazon-ecs-agent/pull/2234/files#diff-a617e6edf02b5af84b984ccb06ce315fR547)

t.Skip("Skip TestSSMSecretsEncryptedParameter in China partition")
}

agent := RunAgent(t, nil)
defer agent.Cleanup()

efsClient := efs.New(session.New(), aws.NewConfig().WithRegion(*ECS.Config.Region))
fsID := createEFSFileSystem(t, efsClient)
createMountTarget(t, efsClient, fsID)

// start writer task first
overrides := map[string]string{
"FILESYSTEM_ID": fsID,
"TEST_REGION": *ECS.Config.Region,
}
wTask, err := agent.StartTaskWithTaskDefinitionOverrides(t, "task-efs-vol-write", overrides)
require.NoError(t, err, "Register task definition failed")
// Wait for the first task to create the volume
wErr := wTask.WaitStopped(waitTaskStateChangeDuration)
require.NoError(t, wErr, "Error waiting for task to transition to STOPPED")
wExitCode, ok := wTask.ContainerExitcode("task-efs-vol-write")
require.True(t, ok, "Error code for container [task-efs-vol-write] not found, check the logs")
assert.Equal(t, 42, wExitCode, fmt.Sprintf("Expected exit code of 42; got %d", wExitCode))

// then reader task try to read from the volume
rTask, err := agent.StartTaskWithTaskDefinitionOverrides(t, "task-efs-vol-read", overrides)
require.NoError(t, err, "Register task definition failed")

rErr := rTask.WaitStopped(waitTaskStateChangeDuration)
require.NoError(t, rErr, "Error waiting for task to transition to STOPPED")
rExitCode, ok := rTask.ContainerExitcode("task-efs-vol-read")
require.True(t, ok, "Error code for container [task-efs-vol-read] not found, check the logs")
assert.Equal(t, 42, rExitCode, fmt.Sprintf("Expected exit code of 42; got %d", rExitCode))
return
}

// createEFSFileSystem creates a new EFS file system and returns the FileSystemId.
// will ignore already-created filesystems
// also will wait until filesystem is "available" before returning
func createEFSFileSystem(t *testing.T, efsClient *efs.EFS) string {
creationToken := "efs-func-tests"
_, err := efsClient.CreateFileSystem(&efs.CreateFileSystemInput{
CreationToken: aws.String(creationToken),
})

if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case efs.ErrCodeFileSystemAlreadyExists:
t.Logf("EFS filesystem already exists")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect to reuse the same file system created once for the tests? I don't see any cleanup logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we expect to reuse the filesystem, that's why we check for ErrCodeFileSystemAlreadyExists (filesystem already exists) and ErrCodeMountTargetConflict (mount target already exists)

The main reason for this is speed, and the EFS api makes it easy with the CreationToken: https://docs.aws.amazon.com/efs/latest/ug/API_CreateFileSystem.html#API_CreateFileSystem_RequestSyntax

out, err := efsClient.DescribeFileSystems(&efs.DescribeFileSystemsInput{
CreationToken: aws.String(creationToken),
})
require.NoError(t, err, "Unexpected error from DescribeFileSystems: %s", err)
return *out.FileSystems[0].FileSystemId
default:
require.NoError(t, err, "Error creating EFS Filesystem")
}
}
}

// Wait for filesystem to be "available"
for true {
out, err := efsClient.DescribeFileSystems(&efs.DescribeFileSystemsInput{
CreationToken: aws.String(creationToken),
})
require.NoError(t, err, "Unexpected error from DescribeFileSystems: %s", err)
if *out.FileSystems[0].LifeCycleState == efs.LifeCycleStateAvailable {
return *out.FileSystems[0].FileSystemId
}
time.Sleep(time.Second * 30)
}

return ""
}

// createMountTarget attempts to create a mount target on the given filesystem ID
// if it already exists, the error code (MountTargetConflict) is ignored.
func createMountTarget(t *testing.T, efsClient *efs.EFS, fsID string) {
subnetID, err := GetSubnetID()
require.NoError(t, err)
mt, err := efsClient.CreateMountTarget(&efs.CreateMountTargetInput{
FileSystemId: aws.String(fsID),
SubnetId: aws.String(subnetID),
})
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case efs.ErrCodeMountTargetConflict:
t.Logf("EFS mount target already exists")
return
default:
require.NoError(t, err, "Error creating EFS mount target")
}
}
}
// Wait for mount target to be "available"
for true {
out, err := efsClient.DescribeMountTargets(&efs.DescribeMountTargetsInput{
MountTargetId: mt.MountTargetId,
})
require.NoError(t, err, "Unexpected error from DescribeMountTargets: %s", err)
if *out.MountTargets[0].LifeCycleState == efs.LifeCycleStateAvailable {
return
}
time.Sleep(time.Second * 30)
}
}

// Note: This functional test requires ECS GPU instance which has at least 1 GPU.
func TestRunGPUTask(t *testing.T) {
gpuInstances := []string{"p2", "p3", "g3", "g4dn"}
Expand Down Expand Up @@ -1766,7 +1887,7 @@ func testFirelens(t *testing.T, firelensConfigType, secretLogOptionKey, secretLo
},
TempDirOverride: tempDir,
}
if (isAWSVPC) {
if isAWSVPC {
agentOptions.EnableTaskENI = true
}
os.Setenv("ECS_FTEST_FORCE_NET_HOST", "true")
Expand Down
8 changes: 4 additions & 4 deletions agent/functional_tests/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory"
"github.com/aws/amazon-ecs-agent/agent/ec2"
"github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs"
"github.com/aws/amazon-ecs-agent/agent/handlers/v1"
v1 "github.com/aws/amazon-ecs-agent/agent/handlers/v1"
"github.com/aws/amazon-ecs-agent/agent/utils"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
Expand Down Expand Up @@ -275,7 +275,7 @@ func (agent *TestAgent) startAWSVPCTask(taskDefinition string) (*TestTask, error
agent.t.Logf("Task definition: %s", taskDefinition)
// Get the subnet ID, which is a required parameter for starting
// tasks in 'awsvpc' network mode
subnet, err := getSubnetID()
subnet, err := GetSubnetID()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -809,8 +809,8 @@ func AttributesToMap(attributes []*ecs.Attribute) map[string]string {
return attributeMap
}

// getSubnetID gets the subnet id for the instance from ec2 instance metadata
func getSubnetID() (string, error) {
// GetSubnetID gets the subnet id for the instance from ec2 instance metadata
func GetSubnetID() (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

ec2Metadata := ec2metadata.New(session.Must(session.NewSession()))
mac, err := ec2Metadata.GetMetadata("mac")
if err != nil {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading