Skip to content

Commit e75627d

Browse files
committed
Updating agent state to include task default interface name and task network namespace
1 parent 1215fc2 commit e75627d

File tree

9 files changed

+280
-28
lines changed

9 files changed

+280
-28
lines changed

agent/api/task/task.go

+28-1
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,17 @@ type Task struct {
297297

298298
IsInternal bool `json:"IsInternal,omitempty"`
299299

300-
// TODO: Will need to initialize/set the value in a follow PR
301300
NetworkNamespace string `json:"NetworkNamespace,omitempty"`
302301

303302
// TODO: Will need to initialize/set the value in a follow PR
304303
FaultInjectionEnabled bool `json:"FaultInjectionEnabled,omitempty"`
304+
305+
// DefaultIfname is used to reference the default network interface name on the task network namespace
306+
// For AWSVPC mode, it can be eth0 which corresponds to the interface name on the task ENI
307+
// For Host mode, it can vary based on the hardware/network config on the host instance (e.g. eth0, ens5, etc.) and will need to be obtained on the host.
308+
// For all other network modes (i.e. bridge, none, etc.), DefaultIfname is currently not being initialized/set. In order to use this task field for these
309+
// network modes, changes will need to be made in the corresponding task provisioning workflows.
310+
DefaultIfname string `json:"DefaultIfname,omitempty"`
305311
}
306312

307313
// TaskFromACS translates ecsacs.Task to apitask.Task by first marshaling the received
@@ -3773,3 +3779,24 @@ func (task *Task) GetNetworkNamespace() string {
37733779

37743780
return task.NetworkNamespace
37753781
}
3782+
3783+
func (task *Task) SetNetworkNamespace(netNs string) {
3784+
task.lock.Lock()
3785+
defer task.lock.Unlock()
3786+
3787+
task.NetworkNamespace = netNs
3788+
}
3789+
3790+
func (task *Task) GetDefaultIfname() string {
3791+
task.lock.RLock()
3792+
defer task.lock.RUnlock()
3793+
3794+
return task.DefaultIfname
3795+
}
3796+
3797+
func (task *Task) SetDefaultIfname(ifname string) {
3798+
task.lock.Lock()
3799+
defer task.lock.Unlock()
3800+
3801+
task.DefaultIfname = ifname
3802+
}

agent/engine/docker_task_engine.go

+6
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,12 @@ func (engine *DockerTaskEngine) provisionContainerResourcesAwsvpc(task *apitask.
23582358
field.TaskID: task.GetID(),
23592359
"ip": taskIP,
23602360
})
2361+
task.SetNetworkNamespace(cniConfig.ContainerNetNS)
2362+
// Note: By default, the interface name is set to eth0 within the CNI configs. We can also always assume that the first entry of the CNI network config to be
2363+
// the task ENI. Otherwise this means that there weren't any task ENIs passed down to agent from the task payload.
2364+
if len(cniConfig.NetworkConfigs) > 0 {
2365+
task.SetDefaultIfname(cniConfig.NetworkConfigs[0].IfName)
2366+
}
23612367
engine.state.AddTaskIPAddress(taskIP, task.Arn)
23622368
task.SetLocalIPAddress(taskIP)
23632369
engine.saveTaskData(task)

agent/engine/docker_task_engine_linux_test.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ import (
7373
)
7474

7575
const (
76-
cgroupMountPath = "/sys/fs/cgroup"
77-
testTaskDefFamily = "testFamily"
78-
testTaskDefVersion = "1"
79-
containerNetNS = "none"
76+
cgroupMountPath = "/sys/fs/cgroup"
77+
testTaskDefFamily = "testFamily"
78+
testTaskDefVersion = "1"
79+
containerNetNS = "none"
80+
ExpectedNetworkNamespace = "/host/proc/123/ns/net"
8081
)
8182

8283
func init() {

agent/engine/docker_task_engine_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ const (
114114
containerNetworkMode = "none"
115115
serviceConnectContainerName = "service-connect"
116116
mediaTypeManifestV2 = "application/vnd.docker.distribution.manifest.v2+json"
117+
defaultIfname = "eth0"
117118
)
118119

119120
var (
@@ -1098,6 +1099,8 @@ func TestProvisionContainerResourcesAwsvpcSetPausePIDInVolumeResources(t *testin
10981099
require.Nil(t, taskEngine.(*DockerTaskEngine).provisionContainerResources(testTask, pauseContainer).Error)
10991100
assert.Equal(t, strconv.Itoa(containerPid), volRes.GetPauseContainerPID())
11001101
assert.Equal(t, taskIP, testTask.GetLocalIPAddress())
1102+
assert.Equal(t, defaultIfname, testTask.GetDefaultIfname())
1103+
assert.Equal(t, ExpectedNetworkNamespace, testTask.GetNetworkNamespace())
11011104
savedTasks, err := dataClient.GetTasks()
11021105
require.NoError(t, err)
11031106
assert.Len(t, savedTasks, 1)

agent/engine/docker_task_engine_windows_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ import (
5353
)
5454

5555
const (
56-
containerNetNS = "container:abcd"
56+
containerNetNS = "container:abcd"
57+
ExpectedNetworkNamespace = "none"
5758
)
5859

5960
func TestDeleteTask(t *testing.T) {

agent/handlers/task_server_setup_test.go

+189
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ const (
113113
taskCredentialsID = "taskCredentialsId"
114114
endpointId = "endpointId"
115115
networkNamespace = "/path"
116+
hostNetworkNamespace = "host"
117+
defaultIfname = "eth0"
116118

117119
port = 1234
118120
protocol = "tcp"
@@ -270,6 +272,7 @@ var (
270272
PullStoppedAtUnsafe: now,
271273
ExecutionStoppedAtUnsafe: now,
272274
LaunchType: "EC2",
275+
NetworkMode: bridgeMode,
273276
}
274277
container1 = &apicontainer.Container{
275278
Name: containerName,
@@ -404,6 +407,30 @@ var (
404407
Type: containerType,
405408
},
406409
}
410+
expectedV4HostContainerResponse = v4.ContainerResponse{
411+
ContainerResponse: &v2.ContainerResponse{
412+
ID: containerID,
413+
Name: containerName,
414+
DockerName: containerName,
415+
Image: imageName,
416+
ImageID: imageID,
417+
DesiredStatus: statusRunning,
418+
KnownStatus: statusRunning,
419+
ContainerARN: "arn:aws:ecs:ap-northnorth-1:NNN:container/NNNNNNNN-aaaa-4444-bbbb-00000000000",
420+
Limits: v2.LimitsResponse{
421+
CPU: aws.Float64(cpu),
422+
Memory: aws.Int64(memory),
423+
},
424+
Type: containerType,
425+
Labels: labels,
426+
Ports: []tmdsresponse.PortResponse{
427+
{
428+
ContainerPort: containerPort,
429+
Protocol: containerPortProtocol,
430+
},
431+
},
432+
},
433+
}
407434
expectedV4BridgeContainerResponse = v4ContainerResponseFromV2(expectedBridgeContainerResponse, []v4.Network{{
408435
Network: tmdsresponse.Network{
409436
NetworkMode: bridgeMode,
@@ -423,6 +450,7 @@ var (
423450
task.FaultInjectionEnabled = faultInjectionEnabled
424451
task.NetworkMode = networkMode
425452
task.NetworkNamespace = networkNamespace
453+
task.DefaultIfname = defaultIfname
426454
gomock.InOrder(
427455
state.EXPECT().TaskARNByV3EndpointID(endpointId).Return(taskARN, true),
428456
state.EXPECT().TaskByArn(taskARN).Return(task, true).Times(2),
@@ -466,6 +494,13 @@ func standardTask() *apitask.Task {
466494
return &task
467495
}
468496

497+
func standardHostTask() *apitask.Task {
498+
task := standardTask()
499+
task.ENIs = nil
500+
task.NetworkMode = apitask.HostNetworkMode
501+
return task
502+
}
503+
469504
// Returns a standard v2 task response. This getter function protects against tests mutating the
470505
// response.
471506
func expectedTaskResponse() v2.TaskResponse {
@@ -524,6 +559,34 @@ func expectedV4TaskResponse() v4.TaskResponse {
524559
)
525560
}
526561

562+
func expectedV4TaskNetworkConfig(faultInjectionEnabled bool, networkMode, path, deviceName string) *v4.TaskNetworkConfig {
563+
return v4.NewTaskNetworkConfig(networkMode, path, deviceName)
564+
}
565+
566+
func expectedV4TaskResponseHostMode() v4.TaskResponse {
567+
return v4TaskResponseFromV2(
568+
v2.TaskResponse{
569+
Cluster: clusterName,
570+
TaskARN: taskARN,
571+
Family: family,
572+
Revision: version,
573+
DesiredStatus: statusRunning,
574+
KnownStatus: statusRunning,
575+
Limits: &v2.LimitsResponse{
576+
CPU: aws.Float64(cpu),
577+
Memory: aws.Int64(memory),
578+
},
579+
PullStartedAt: aws.Time(now.UTC()),
580+
PullStoppedAt: aws.Time(now.UTC()),
581+
ExecutionStoppedAt: aws.Time(now.UTC()),
582+
AvailabilityZone: availabilityzone,
583+
LaunchType: "EC2",
584+
},
585+
[]v4.ContainerResponse{expectedV4HostContainerResponse},
586+
vpcID,
587+
)
588+
}
589+
527590
// Returns a standard v4 task response including pulled containers response. This getter function
528591
// protects against tests mutating the response.
529592
func expectedV4PulledTaskResponse() v4.TaskResponse {
@@ -1994,6 +2057,51 @@ func TestV4TaskMetadata(t *testing.T) {
19942057
expectedResponseBody: expectedV4PulledTaskResponse(),
19952058
})
19962059
})
2060+
2061+
t.Run("happy case with fault injection enabled using awsvpc mode", func(t *testing.T) {
2062+
testTMDSRequest(t, TMDSTestCase[v4.TaskResponse]{
2063+
path: v4BasePath + v3EndpointID + "/task",
2064+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
2065+
task.FaultInjectionEnabled = true
2066+
task.NetworkNamespace = networkNamespace
2067+
task.DefaultIfname = defaultIfname
2068+
gomock.InOrder(
2069+
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
2070+
state.EXPECT().TaskByArn(taskARN).Return(task, true).Times(2),
2071+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2072+
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2073+
state.EXPECT().TaskByArn(taskARN).Return(task, true),
2074+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2075+
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
2076+
)
2077+
},
2078+
expectedStatusCode: http.StatusOK,
2079+
expectedResponseBody: expectedV4TaskResponse(),
2080+
})
2081+
})
2082+
2083+
t.Run("happy case with fault injection enabled using host mode", func(t *testing.T) {
2084+
testTMDSRequest(t, TMDSTestCase[v4.TaskResponse]{
2085+
path: v4BasePath + v3EndpointID + "/task",
2086+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
2087+
hostTask := standardHostTask()
2088+
hostTask.FaultInjectionEnabled = true
2089+
hostTask.NetworkNamespace = networkNamespace
2090+
hostTask.DefaultIfname = defaultIfname
2091+
gomock.InOrder(
2092+
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
2093+
state.EXPECT().TaskByArn(taskARN).Return(hostTask, true).Times(2),
2094+
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
2095+
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
2096+
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
2097+
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
2098+
)
2099+
},
2100+
expectedStatusCode: http.StatusOK,
2101+
expectedResponseBody: expectedV4TaskResponseHostMode(),
2102+
})
2103+
})
2104+
19972105
t.Run("bridge mode container not found", func(t *testing.T) {
19982106
testTMDSRequest(t, TMDSTestCase[v4.TaskResponse]{
19992107
path: v4BasePath + v3EndpointID + "/task",
@@ -3804,3 +3912,84 @@ func testRegisterFaultHandler(t *testing.T, tcs []blackholePortFaultTestCase, me
38043912
})
38053913
}
38063914
}
3915+
3916+
func TestV4GetTaskMetadataWithTaskNetworkConfig(t *testing.T) {
3917+
3918+
tcs := []struct {
3919+
name string
3920+
setStateExpectations func(state *mock_dockerstate.MockTaskEngineState)
3921+
expectedTaskNetworkConfig *v4.TaskNetworkConfig
3922+
}{
3923+
{
3924+
name: "happy case with awsvpc mode",
3925+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
3926+
task := standardTask()
3927+
task.FaultInjectionEnabled = true
3928+
task.NetworkNamespace = networkNamespace
3929+
task.DefaultIfname = defaultIfname
3930+
gomock.InOrder(
3931+
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
3932+
state.EXPECT().TaskByArn(taskARN).Return(task, true).Times(2),
3933+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
3934+
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
3935+
state.EXPECT().TaskByArn(taskARN).Return(task, true),
3936+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
3937+
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
3938+
)
3939+
},
3940+
expectedTaskNetworkConfig: expectedV4TaskNetworkConfig(true, apitask.AWSVPCNetworkMode, networkNamespace, defaultIfname),
3941+
},
3942+
{
3943+
name: "happy case with host mode",
3944+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
3945+
hostTask := standardHostTask()
3946+
hostTask.FaultInjectionEnabled = true
3947+
hostTask.NetworkNamespace = networkNamespace
3948+
hostTask.DefaultIfname = defaultIfname
3949+
gomock.InOrder(
3950+
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
3951+
state.EXPECT().TaskByArn(taskARN).Return(hostTask, true).Times(2),
3952+
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToDockerContainer, true),
3953+
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
3954+
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
3955+
state.EXPECT().ContainerByID(containerID).Return(nil, false).AnyTimes(),
3956+
)
3957+
},
3958+
expectedTaskNetworkConfig: expectedV4TaskNetworkConfig(true, apitask.HostNetworkMode, hostNetworkNamespace, defaultIfname),
3959+
},
3960+
{
3961+
name: "happy bridge mode",
3962+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
3963+
gomock.InOrder(
3964+
state.EXPECT().TaskARNByV3EndpointID(v3EndpointID).Return(taskARN, true),
3965+
state.EXPECT().TaskByArn(taskARN).Return(bridgeTask, true).Times(2),
3966+
state.EXPECT().ContainerMapByArn(taskARN).Return(containerNameToBridgeContainer, true),
3967+
state.EXPECT().ContainerByID(containerID).Return(bridgeContainer, true).AnyTimes(),
3968+
state.EXPECT().PulledContainerMapByArn(taskARN).Return(nil, true),
3969+
state.EXPECT().ContainerByID(containerID).Return(bridgeContainer, true).AnyTimes(),
3970+
)
3971+
},
3972+
expectedTaskNetworkConfig: expectedV4TaskNetworkConfig(true, bridgeMode, "", ""),
3973+
},
3974+
}
3975+
3976+
for _, tc := range tcs {
3977+
t.Run(tc.name, func(t *testing.T) {
3978+
ctrl := gomock.NewController(t)
3979+
defer ctrl.Finish()
3980+
3981+
state := mock_dockerstate.NewMockTaskEngineState(ctrl)
3982+
statsEngine := mock_stats.NewMockEngine(ctrl)
3983+
ecsClient := mock_ecs.NewMockECSClient(ctrl)
3984+
3985+
if tc.setStateExpectations != nil {
3986+
tc.setStateExpectations(state)
3987+
}
3988+
tmdsAgentState := agentV4.NewTMDSAgentState(state, statsEngine, ecsClient, clusterName, availabilityzone, vpcID, containerInstanceArn)
3989+
actualTaskResponse, err := tmdsAgentState.GetTaskMetadata(v3EndpointID)
3990+
3991+
assert.NoError(t, err)
3992+
assert.Equal(t, tc.expectedTaskNetworkConfig, actualTaskResponse.TaskNetworkConfig)
3993+
})
3994+
}
3995+
}

agent/handlers/v4/tmdsstate.go

+15-22
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
tmdsv4 "github.com/aws/amazon-ecs-agent/ecs-agent/tmds/handlers/v4/state"
2424
)
2525

26+
const (
27+
defaultHostNetworkNamespace = "host"
28+
)
29+
2630
// Implements AgentState interface for TMDS v4.
2731
type TMDSAgentState struct {
2832
state dockerstate.TaskEngineState
@@ -151,30 +155,19 @@ func (s *TMDSAgentState) getTaskMetadata(v3EndpointID string, includeTags bool)
151155
NewPulledContainerResponse(dockerContainer, task.GetPrimaryENI()))
152156
}
153157

154-
if task.IsFaultInjectionEnabled() {
155-
// TODO: The correct values for the task network config will need to be set/initialized
156-
taskResponse.FaultInjectionEnabled = task.IsFaultInjectionEnabled()
157-
taskNetworkConfig := tmdsv4.TaskNetworkConfig{
158-
NetworkMode: task.GetNetworkMode(),
159-
NetworkNamespaces: []*tmdsv4.NetworkNamespace{
160-
{
161-
Path: task.GetNetworkNamespace(),
162-
NetworkInterfaces: []*tmdsv4.NetworkInterface{
163-
{
164-
// TODO: fetch the correct device name.
165-
// We are exposing this information via AgentState to facilitate the fault injection
166-
// handler to start/stop/check network faults.
167-
// Use 'eth0'(a fake value) for existing fault injection related unit tests for now and
168-
// it will be updated in the future.
169-
DeviceName: "eth0",
170-
},
171-
},
172-
},
173-
},
174-
}
175-
taskResponse.TaskNetworkConfig = &taskNetworkConfig
158+
taskResponse.FaultInjectionEnabled = task.IsFaultInjectionEnabled()
159+
var taskNetworkConfig *tmdsv4.TaskNetworkConfig
160+
if task.IsNetworkModeHost() {
161+
// For host most, we don't really need the network namespace in order to do anything within the host instance network namespace
162+
// and so we will set this to an arbitrary value such as "host".
163+
// TODO: Will need to find/obtain the interface name of the default network interface on the host instance
164+
taskNetworkConfig = tmdsv4.NewTaskNetworkConfig(task.GetNetworkMode(), defaultHostNetworkNamespace, task.GetDefaultIfname())
165+
} else {
166+
taskNetworkConfig = tmdsv4.NewTaskNetworkConfig(task.GetNetworkMode(), task.GetNetworkNamespace(), task.GetDefaultIfname())
176167
}
177168

169+
taskResponse.TaskNetworkConfig = taskNetworkConfig
170+
178171
return *taskResponse, nil
179172
}
180173

0 commit comments

Comments
 (0)