-
Notifications
You must be signed in to change notification settings - Fork 618
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add functionality in Agent to confirm Trunk ENI attachment.
A high level workflow to for this is as follow: 1. An ACS handler, AttachInstanceENIHandler receives an AttachNetworkInterfacesMessage from ACS which contains the information of the trunk ENI. upon receiving this message the handler adds the trunk eni attachment to the attachment table in Agent's state; 2. Udev Watcher is periodically scanning through attached network devices on the instances and also listens to network device's attached event. Once it found that the trunk ENI is attached (it knows a device is the trunk ENI from information in attachment table added in step 1), it sends an AttachmentStateChange event to the global state change event channel; 3. An event handler, AttachmentEventHandler handles the AttachmentStateChange event from the event channel by sending it to backend.
- Loading branch information
Showing
37 changed files
with
2,168 additions
and
775 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package handler | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
"github.com/aws/amazon-ecs-agent/agent/acs/model/ecsacs" | ||
apieni "github.com/aws/amazon-ecs-agent/agent/api/eni" | ||
"github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" | ||
"github.com/aws/amazon-ecs-agent/agent/statemanager" | ||
"github.com/aws/amazon-ecs-agent/agent/wsclient" | ||
"github.com/aws/aws-sdk-go/aws" | ||
|
||
"github.com/cihub/seelog" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// ackTimeoutHandler remove ENI attachment from agent state after the ENI ack timeout | ||
type ackTimeoutHandler struct { | ||
mac string | ||
state dockerstate.TaskEngineState | ||
} | ||
|
||
func (handler *ackTimeoutHandler) handle() { | ||
eniAttachment, ok := handler.state.ENIByMac(handler.mac) | ||
if !ok { | ||
seelog.Warnf("Ignoring unmanaged ENI attachment with MAC address: %s", handler.mac) | ||
return | ||
} | ||
if !eniAttachment.IsSent() { | ||
seelog.Warnf("Timed out waiting for ENI ack; removing ENI attachment record with MAC address: %s", handler.mac) | ||
handler.state.RemoveENIAttachment(handler.mac) | ||
} | ||
} | ||
|
||
// sendAck sends ack for a certain ACS message | ||
func sendAck(acsClient wsclient.ClientServer, clusterArn *string, containerInstanceArn *string, messageId *string) { | ||
if err := acsClient.MakeRequest(&ecsacs.AckRequest{ | ||
Cluster: clusterArn, | ||
ContainerInstance: containerInstanceArn, | ||
MessageId: messageId, | ||
}); err != nil { | ||
seelog.Warnf("Failed to ack request with messageId: %s, error: %v", aws.StringValue(messageId), err) | ||
} | ||
} | ||
|
||
// handleENIAttachment handles an ENI attachment via the following: | ||
// 1. Check whether we already have this attachment in state, if so, start its ack timer and return | ||
// 2. Otherwise add the attachment to state, start its ack timer, and save the state | ||
// These are common tasks for handling a task ENI attachment and an instance ENI attachment, so they are put | ||
// into this function to be shared by both attachment handlers | ||
func handleENIAttachment(attachmentType, attachmentARN, taskARN, mac string, | ||
expiresAt time.Time, | ||
state dockerstate.TaskEngineState, | ||
saver statemanager.Saver) error { | ||
seelog.Infof("Handling ENI attachment: %s", attachmentARN) | ||
|
||
if eniAttachment, ok := state.ENIByMac(mac); ok { | ||
seelog.Infof("Duplicate %s attachment message for ENI with MAC address: %s", attachmentType, mac) | ||
eniAckTimeoutHandler := ackTimeoutHandler{mac: mac, state: state} | ||
return eniAttachment.StartTimer(eniAckTimeoutHandler.handle) | ||
} | ||
if err := addENIAttachmentToState(attachmentType, attachmentARN, taskARN, mac, expiresAt, state); err != nil { | ||
return errors.Wrapf(err, fmt.Sprintf("attach %s message handler: unable to add eni attachment to engine state", attachmentType)) | ||
} | ||
if err := saver.Save(); err != nil { | ||
return errors.Wrapf(err, fmt.Sprintf("attach %s message handler: unable to save agent state", attachmentType)) | ||
} | ||
return nil | ||
} | ||
|
||
// addENIAttachmentToState adds an ENI attachment to state, and start its ack timer | ||
func addENIAttachmentToState(attachmentType, attachmentARN, taskARN, mac string, expiresAt time.Time, state dockerstate.TaskEngineState) error { | ||
eniAttachment := &apieni.ENIAttachment{ | ||
TaskARN: taskARN, | ||
AttachmentType: attachmentType, | ||
AttachmentARN: attachmentARN, | ||
AttachStatusSent: false, | ||
MACAddress: mac, | ||
ExpiresAt: expiresAt, // Stop tracking the eni attachment after timeout | ||
} | ||
eniAckTimeoutHandler := ackTimeoutHandler{mac: mac, state: state} | ||
if err := eniAttachment.StartTimer(eniAckTimeoutHandler.handle); err != nil { | ||
return err | ||
} | ||
|
||
switch attachmentType { | ||
case apieni.ENIAttachmentTypeTaskENI: | ||
seelog.Infof("Adding task eni attachment info for task '%s' to state, attachment=%s mac=%s", | ||
taskARN, attachmentARN, mac) | ||
case apieni.ENIAttachmentTypeInstanceENI: | ||
seelog.Infof("Adding instance eni attachment info to state, attachment=%s mac=%s", attachmentARN, mac) | ||
default: | ||
return fmt.Errorf("unrecognized eni attachment type: %s", attachmentType) | ||
} | ||
|
||
state.AddENIAttachment(eniAttachment) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// +build unit | ||
|
||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"). You may | ||
// not use this file except in compliance with the License. A copy of the | ||
// License is located at | ||
// | ||
// http://aws.amazon.com/apache2.0/ | ||
// | ||
// or in the "license" file accompanying this file. This file is distributed | ||
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
// express or implied. See the License for the specific language governing | ||
// permissions and limitations under the License. | ||
|
||
package handler | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
apieni "github.com/aws/amazon-ecs-agent/agent/api/eni" | ||
"github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" | ||
"github.com/aws/amazon-ecs-agent/agent/statemanager" | ||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
const ( | ||
attachmentArn = "attachmentarn" | ||
) | ||
|
||
// TestTaskENIAckTimeout tests acknowledge timeout for a task eni before submit the state change | ||
func TestTaskENIAckTimeout(t *testing.T) { | ||
testENIAckTimeout(t, apieni.ENIAttachmentTypeTaskENI) | ||
} | ||
|
||
// TestInstanceENIAckTimeout tests acknowledge timeout for an instance level eni before submit the state change | ||
func TestInstanceENIAckTimeout(t *testing.T) { | ||
testENIAckTimeout(t, apieni.ENIAttachmentTypeInstanceENI) | ||
} | ||
|
||
func testENIAckTimeout(t *testing.T, attachmentType string) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
taskEngineState := dockerstate.NewTaskEngineState() | ||
|
||
expiresAt := time.Now().Add(time.Duration(waitTimeoutMillis) * time.Millisecond) | ||
err := addENIAttachmentToState(attachmentType, attachmentArn, taskArn, randomMAC, expiresAt, taskEngineState) | ||
assert.NoError(t, err) | ||
assert.Len(t, taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments(), 1) | ||
for { | ||
time.Sleep(time.Millisecond * waitTimeoutMillis) | ||
if len(taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments()) == 0 { | ||
break | ||
} | ||
} | ||
} | ||
|
||
// TestTaskENIAckWithinTimeout tests the eni state change was reported before the timeout, for a task eni | ||
func TestTaskENIAckWithinTimeout(t *testing.T) { | ||
testENIAckWithinTimeout(t, apieni.ENIAttachmentTypeTaskENI) | ||
} | ||
|
||
// TestInstanceENIAckWithinTimeout tests the eni state change was reported before the timeout, for an instance eni | ||
func TestInstanceENIAckWithinTimeout(t *testing.T) { | ||
testENIAckWithinTimeout(t, apieni.ENIAttachmentTypeInstanceENI) | ||
} | ||
|
||
func testENIAckWithinTimeout(t *testing.T, attachmentType string) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
taskEngineState := dockerstate.NewTaskEngineState() | ||
expiresAt := time.Now().Add(time.Duration(waitTimeoutMillis) * time.Millisecond) | ||
err := addENIAttachmentToState(attachmentType, attachmentArn, taskArn, randomMAC, expiresAt, taskEngineState) | ||
assert.NoError(t, err) | ||
assert.Len(t, taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments(), 1) | ||
eniAttachment, ok := taskEngineState.(*dockerstate.DockerTaskEngineState).ENIByMac(randomMAC) | ||
assert.True(t, ok) | ||
eniAttachment.SetSentStatus() | ||
|
||
time.Sleep(time.Millisecond * waitTimeoutMillis) | ||
|
||
assert.Len(t, taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments(), 1) | ||
} | ||
|
||
// TestHandleENIAttachmentTaskENI tests handling a new task eni | ||
func TestHandleENIAttachmentTaskENI(t *testing.T) { | ||
testHandleENIAttachment(t, apieni.ENIAttachmentTypeTaskENI, taskArn) | ||
} | ||
|
||
// TestHandleENIAttachmentInstanceENI tests handling a new instance eni | ||
func TestHandleENIAttachmentInstanceENI(t *testing.T) { | ||
testHandleENIAttachment(t, apieni.ENIAttachmentTypeInstanceENI, "") | ||
} | ||
|
||
func testHandleENIAttachment(t *testing.T, attachmentType, taskArn string) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
taskEngineState := dockerstate.NewTaskEngineState() | ||
expiresAt := time.Now().Add(time.Duration(waitTimeoutMillis) * time.Millisecond) | ||
stateManager := statemanager.NewNoopStateManager() | ||
err := handleENIAttachment(attachmentType, attachmentArn, taskArn, randomMAC, expiresAt, taskEngineState, stateManager) | ||
assert.NoError(t, err) | ||
assert.Len(t, taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments(), 1) | ||
eniAttachment, ok := taskEngineState.(*dockerstate.DockerTaskEngineState).ENIByMac(randomMAC) | ||
assert.True(t, ok) | ||
eniAttachment.SetSentStatus() | ||
|
||
time.Sleep(time.Millisecond * waitTimeoutMillis) | ||
|
||
assert.Len(t, taskEngineState.(*dockerstate.DockerTaskEngineState).AllENIAttachments(), 1) | ||
} |
Oops, something went wrong.