From b28a993c1d4223a7a87a7c0177022f31435559b4 Mon Sep 17 00:00:00 2001 From: Sharanya Devaraj Date: Wed, 27 Sep 2017 23:37:49 +0000 Subject: [PATCH] ecs_client/model, functional_tests: devices and init support This commit contains the model changes and functional tests for the new Task Definition fields-devices and initProcessEnabled. This addresses the following issues: * https://github.com/aws/amazon-ecs-agent/issues/433 * https://github.com/aws/amazon-ecs-agent/issues/852 --- agent/ecs_client/model/api/api-2.json | 29 ++++- agent/ecs_client/model/ecs/api.go | 104 ++++++++++++++++++ .../testdata/simpletests_unix/devices.json | 10 ++ .../simpletests_unix/init-process.json | 10 ++ .../devices/task-definition.json | 19 ++++ .../init-process/task-definition.json | 13 +++ .../simpletests_generated_unix_test.go | 80 ++++++++++++++ 7 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 agent/functional_tests/testdata/simpletests_unix/devices.json create mode 100644 agent/functional_tests/testdata/simpletests_unix/init-process.json create mode 100644 agent/functional_tests/testdata/taskdefinitions/devices/task-definition.json create mode 100644 agent/functional_tests/testdata/taskdefinitions/init-process/task-definition.json diff --git a/agent/ecs_client/model/api/api-2.json b/agent/ecs_client/model/api/api-2.json index 50875b1bec8..ac5b43197c1 100644 --- a/agent/ecs_client/model/api/api-2.json +++ b/agent/ecs_client/model/api/api-2.json @@ -565,6 +565,18 @@ "type":"integer", "box":true }, + "DeviceCgroupPermission":{ + "type":"string", + "enum":[ + "read", + "write", + "mknod" + ] + }, + "DeviceCgroupPermissions":{ + "type":"list", + "member":{"shape":"DeviceCgroupPermission"} + }, "ClientException":{ "type":"structure", "members":{ @@ -942,6 +954,19 @@ "STOPPED" ] }, + "Device":{ + "type":"structure", + "required":["hostPath"], + "members":{ + "hostPath":{"shape":"String"}, + "containerPath":{"shape":"String"}, + "permissions":{"shape":"DeviceCgroupPermissions"} + } + }, + "DevicesList":{ + "type":"list", + "member":{"shape":"Device"} + }, "DiscoverPollEndpointRequest":{ "type":"structure", "members":{ @@ -1022,7 +1047,9 @@ "LinuxParameters":{ "type":"structure", "members":{ - "capabilities":{"shape":"KernelCapabilities"} + "capabilities":{"shape":"KernelCapabilities"}, + "devices":{"shape":"DevicesList"}, + "initProcessEnabled":{"shape":"BoxedBoolean"} } }, "ListAttributesRequest":{ diff --git a/agent/ecs_client/model/ecs/api.go b/agent/ecs_client/model/ecs/api.go index 5c014efda11..a7596f976ce 100644 --- a/agent/ecs_client/model/ecs/api.go +++ b/agent/ecs_client/model/ecs/api.go @@ -4262,6 +4262,11 @@ func (s *ContainerDefinition) Validate() error { } } } + if s.LinuxParameters != nil { + if err := s.LinuxParameters.Validate(); err != nil { + invalidParams.AddNested("LinuxParameters", err.(request.ErrInvalidParams)) + } + } if s.LogConfiguration != nil { if err := s.LogConfiguration.Validate(); err != nil { invalidParams.AddNested("LogConfiguration", err.(request.ErrInvalidParams)) @@ -5926,6 +5931,58 @@ func (s *DescribeTasksOutput) SetTasks(v []*Task) *DescribeTasksOutput { return s } +type Device struct { + _ struct{} `type:"structure"` + + ContainerPath *string `locationName:"containerPath" type:"string"` + + // HostPath is a required field + HostPath *string `locationName:"hostPath" type:"string" required:"true"` + + Permissions []*string `locationName:"permissions" type:"list"` +} + +// String returns the string representation +func (s Device) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Device) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *Device) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "Device"} + if s.HostPath == nil { + invalidParams.Add(request.NewErrParamRequired("HostPath")) + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + +// SetContainerPath sets the ContainerPath field's value. +func (s *Device) SetContainerPath(v string) *Device { + s.ContainerPath = &v + return s +} + +// SetHostPath sets the HostPath field's value. +func (s *Device) SetHostPath(v string) *Device { + s.HostPath = &v + return s +} + +// SetPermissions sets the Permissions field's value. +func (s *Device) SetPermissions(v []*string) *Device { + s.Permissions = v + return s +} + type DiscoverPollEndpointInput struct { _ struct{} `type:"structure"` @@ -6219,6 +6276,10 @@ type LinuxParameters struct { // The Linux capabilities for the container that are added to or dropped from // the default configuration provided by Docker. Capabilities *KernelCapabilities `locationName:"capabilities" type:"structure"` + + Devices []*Device `locationName:"devices" type:"list"` + + InitProcessEnabled *bool `locationName:"initProcessEnabled" type:"boolean"` } // String returns the string representation @@ -6231,12 +6292,44 @@ func (s LinuxParameters) GoString() string { return s.String() } +// Validate inspects the fields of the type to determine if they are valid. +func (s *LinuxParameters) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "LinuxParameters"} + if s.Devices != nil { + for i, v := range s.Devices { + if v == nil { + continue + } + if err := v.Validate(); err != nil { + invalidParams.AddNested(fmt.Sprintf("%s[%v]", "Devices", i), err.(request.ErrInvalidParams)) + } + } + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + // SetCapabilities sets the Capabilities field's value. func (s *LinuxParameters) SetCapabilities(v *KernelCapabilities) *LinuxParameters { s.Capabilities = v return s } +// SetDevices sets the Devices field's value. +func (s *LinuxParameters) SetDevices(v []*Device) *LinuxParameters { + s.Devices = v + return s +} + +// SetInitProcessEnabled sets the InitProcessEnabled field's value. +func (s *LinuxParameters) SetInitProcessEnabled(v bool) *LinuxParameters { + s.InitProcessEnabled = &v + return s +} + type ListAttributesInput struct { _ struct{} `type:"structure"` @@ -9708,6 +9801,17 @@ const ( DesiredStatusStopped = "STOPPED" ) +const ( + // DeviceCgroupPermissionRead is a DeviceCgroupPermission enum value + DeviceCgroupPermissionRead = "read" + + // DeviceCgroupPermissionWrite is a DeviceCgroupPermission enum value + DeviceCgroupPermissionWrite = "write" + + // DeviceCgroupPermissionMknod is a DeviceCgroupPermission enum value + DeviceCgroupPermissionMknod = "mknod" +) + const ( // LogDriverJsonFile is a LogDriver enum value LogDriverJsonFile = "json-file" diff --git a/agent/functional_tests/testdata/simpletests_unix/devices.json b/agent/functional_tests/testdata/simpletests_unix/devices.json new file mode 100644 index 00000000000..b2762beb282 --- /dev/null +++ b/agent/functional_tests/testdata/simpletests_unix/devices.json @@ -0,0 +1,10 @@ +{ + "Name": "Devices", + "Description": "checks that adding devices works", + "TaskDefinition": "devices", + "Version": ">=1.0.0", + "Timeout": "2m", + "ExitCodes": { + "exit": 42 + } +} diff --git a/agent/functional_tests/testdata/simpletests_unix/init-process.json b/agent/functional_tests/testdata/simpletests_unix/init-process.json new file mode 100644 index 00000000000..fecf2027b47 --- /dev/null +++ b/agent/functional_tests/testdata/simpletests_unix/init-process.json @@ -0,0 +1,10 @@ +{ + "Name": "InitProcessEnabled", + "Description": "checks that enabling init process works", + "TaskDefinition": "init-process", + "Version": ">=1.14.5", + "Timeout": "2m", + "ExitCodes": { + "exit": 42 + } +} diff --git a/agent/functional_tests/testdata/taskdefinitions/devices/task-definition.json b/agent/functional_tests/testdata/taskdefinitions/devices/task-definition.json new file mode 100644 index 00000000000..7d8958195bc --- /dev/null +++ b/agent/functional_tests/testdata/taskdefinitions/devices/task-definition.json @@ -0,0 +1,19 @@ +{ + "family": "ecsftest-devices", + "containerDefinitions": [{ + "image": "127.0.0.1:51670/ubuntu:latest", + "name": "exit", + "cpu": 10, + "memory": 10, + "linuxParameters": { + "devices":[ + { + "hostPath": "/dev/xvda", + "containerPath": "/dev/sda", + "permissions": ["read"] + } + ] + }, + "command": ["sh", "-c", "if ls /dev/sda && ! fdisk /dev/sda; then exit 42; else exit 1; fi"] + }] +} diff --git a/agent/functional_tests/testdata/taskdefinitions/init-process/task-definition.json b/agent/functional_tests/testdata/taskdefinitions/init-process/task-definition.json new file mode 100644 index 00000000000..a0cedd16449 --- /dev/null +++ b/agent/functional_tests/testdata/taskdefinitions/init-process/task-definition.json @@ -0,0 +1,13 @@ +{ + "family": "ecsftest-init-process", + "containerDefinitions": [{ + "image": "127.0.0.1:51670/ubuntu:latest", + "name": "exit", + "cpu": 10, + "memory": 10, + "linuxParameters": { + "initProcessEnabled":true + }, + "command": ["sh", "-c", "if pidof init == 1; then exit 42; else exit 1; fi"] + }] +} diff --git a/agent/functional_tests/tests/generated/simpletests_unix/simpletests_generated_unix_test.go b/agent/functional_tests/tests/generated/simpletests_unix/simpletests_generated_unix_test.go index aebef7905d5..798986c25d0 100644 --- a/agent/functional_tests/tests/generated/simpletests_unix/simpletests_generated_unix_test.go +++ b/agent/functional_tests/tests/generated/simpletests_unix/simpletests_generated_unix_test.go @@ -147,6 +147,46 @@ func TestDataVolume2(t *testing.T) { } +// TestDevices checks that adding devices works +func TestDevices(t *testing.T) { + + // Parallel is opt in because resource constraints could cause test failures + // on smaller instances + if os.Getenv("ECS_FUNCTIONAL_PARALLEL") != "" { + t.Parallel() + } + agent := RunAgent(t, nil) + defer agent.Cleanup() + agent.RequireVersion(">=1.0.0") + + td, err := GetTaskDefinition("devices") + if err != nil { + t.Fatalf("Could not register task definition: %v", err) + } + testTasks, err := agent.StartMultipleTasks(t, td, 1) + if err != nil { + t.Fatalf("Could not start task: %v", err) + } + timeout, err := time.ParseDuration("2m") + if err != nil { + t.Fatalf("Could not parse timeout: %#v", err) + } + + for _, testTask := range testTasks { + err = testTask.WaitStopped(timeout) + if err != nil { + t.Fatalf("Timed out waiting for task to reach stopped. Error %#v, task %#v", err, testTask) + } + + if exit, ok := testTask.ContainerExitcode("exit"); !ok || exit != 42 { + t.Errorf("Expected exit to exit with 42; actually exited (%v) with %v", ok, exit) + } + + defer agent.SweepTask(testTask) + } + +} + // TestDisableNetworking Check that disable networking works func TestDisableNetworking(t *testing.T) { @@ -347,6 +387,46 @@ func TestHostname(t *testing.T) { } +// TestInitProcessEnabled checks that enabling init process works +func TestInitProcessEnabled(t *testing.T) { + + // Parallel is opt in because resource constraints could cause test failures + // on smaller instances + if os.Getenv("ECS_FUNCTIONAL_PARALLEL") != "" { + t.Parallel() + } + agent := RunAgent(t, nil) + defer agent.Cleanup() + agent.RequireVersion(">=1.14.5") + + td, err := GetTaskDefinition("init-process") + if err != nil { + t.Fatalf("Could not register task definition: %v", err) + } + testTasks, err := agent.StartMultipleTasks(t, td, 1) + if err != nil { + t.Fatalf("Could not start task: %v", err) + } + timeout, err := time.ParseDuration("2m") + if err != nil { + t.Fatalf("Could not parse timeout: %#v", err) + } + + for _, testTask := range testTasks { + err = testTask.WaitStopped(timeout) + if err != nil { + t.Fatalf("Timed out waiting for task to reach stopped. Error %#v, task %#v", err, testTask) + } + + if exit, ok := testTask.ContainerExitcode("exit"); !ok || exit != 42 { + t.Errorf("Expected exit to exit with 42; actually exited (%v) with %v", ok, exit) + } + + defer agent.SweepTask(testTask) + } + +} + // TestLinkVolumeDependencies Tests that the dependency graph of task definitions is resolved correctly func TestLinkVolumeDependencies(t *testing.T) {