Skip to content

Commit

Permalink
Refactor task ENI setup
Browse files Browse the repository at this point in the history
ACS payload message can send a list of network interface devices and
a list of ipv4 addresses per network interface to the agent. The
current implementation considers a message with where there's a
plurality of interfaces and ipv4 addresses to be invalid messages. This
might not always be the case. This commit introduces modifications to
this logic where such messages are gracefully handled.

This commit also refactors the `ecscni` package, where all of the config
generation logic is moved out of `plugin.go` into `netconfig.go`. This
ensures that all of the logic that deals with various CNI plugins gets
centralized in the same place instead of being spread out over different
files.
  • Loading branch information
ofiliz authored and sharanyad committed Aug 23, 2019
1 parent 6ebb625 commit 6ca1b3b
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 488 deletions.
12 changes: 8 additions & 4 deletions agent/acs/handler/payload_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func (payloadHandler *payloadRequestHandler) addPayloadTasks(payload *ecsacs.Pay
allTasksOK = false
continue
}

if task.RoleCredentials != nil {
// The payload from ACS for the task has credentials for the
// task. Add those to the credentials manager and set the
Expand All @@ -211,16 +212,17 @@ func (payloadHandler *payloadRequestHandler) addPayloadTasks(payload *ecsacs.Pay
apiTask.SetCredentialsID(taskIAMRoleCredentials.CredentialsID)
}

// Adding the eni information to the task struct
if len(task.ElasticNetworkInterfaces) != 0 {
eni, err := apieni.ENIFromACS(task.ElasticNetworkInterfaces)
// Add ENI information to the task struct.
for _, acsENI := range task.ElasticNetworkInterfaces {
eni, err := apieni.ENIFromACS(acsENI)
if err != nil {
payloadHandler.handleUnrecognizedTask(task, err, payload)
allTasksOK = false
continue
}
apiTask.SetTaskENI(eni)
apiTask.AddTaskENI(eni)
}

// Add the app mesh information to task struct
if task.ProxyConfiguration != nil {
appmesh, err := apiappmesh.AppMeshFromACS(task.ProxyConfiguration)
Expand All @@ -231,6 +233,7 @@ func (payloadHandler *payloadRequestHandler) addPayloadTasks(payload *ecsacs.Pay
}
apiTask.SetAppMesh(appmesh)
}

if task.ExecutionRoleCredentials != nil {
// The payload message contains execution credentials for the task.
// Add the credentials to the credentials manager and set the
Expand All @@ -251,6 +254,7 @@ func (payloadHandler *payloadRequestHandler) addPayloadTasks(payload *ecsacs.Pay

validTasks = append(validTasks, apiTask)
}

// Add 'stop' transitions first to allow seqnum ordering to work out
// Because a 'start' sequence number should only be proceeded if all 'stop's
// of the same sequence number have completed, the 'start' events need to be
Expand Down
4 changes: 2 additions & 2 deletions agent/acs/handler/payload_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ func TestPayloadHandlerAddedENIToTask(t *testing.T) {

// Validate the added task has the eni information as expected
expectedENI := payloadMessage.Tasks[0].ElasticNetworkInterfaces[0]
taskeni := addedTask.GetTaskENI()
taskeni := addedTask.GetPrimaryENI()
assert.Equal(t, aws.StringValue(expectedENI.Ec2Id), taskeni.ID)
assert.Equal(t, aws.StringValue(expectedENI.MacAddress), taskeni.MacAddress)
assert.Equal(t, 1, len(taskeni.IPV4Addresses))
Expand Down Expand Up @@ -784,7 +784,7 @@ func TestPayloadHandlerAddedENITrunkToTask(t *testing.T) {
err := tester.payloadHandler.handleSingleMessage(payloadMessage)
assert.NoError(t, err)

taskeni := addedTask.GetTaskENI()
taskeni := addedTask.GetPrimaryENI()

assert.Equal(t, taskeni.InterfaceAssociationProtocol, eni.VLANInterfaceAssociationProtocol)
assert.Equal(t, taskeni.InterfaceVlanProperties.TrunkInterfaceMacAddress, "mac")
Expand Down
114 changes: 59 additions & 55 deletions agent/api/eni/eni.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ type ENI struct {
IPV6Addresses []*ENIIPV6Address
// MacAddress is the mac address of the eni
MacAddress string
// DomainNameServers specifies the nameserver IP addresses for
// the eni
// DomainNameServers specifies the nameserver IP addresses for the eni
DomainNameServers []string `json:",omitempty"`
// DomainNameSearchList specifies the search list for the domain
// name lookup, for the eni
Expand All @@ -45,8 +44,7 @@ type ENI struct {
InterfaceVlanProperties *InterfaceVlanProperties `json:",omitempty"`
// PrivateDNSName is the dns name assigned by the vpc to this eni
PrivateDNSName string `json:",omitempty"`
// SubnetGatewayIPV4Address is the address to the subnet gateway for
// the eni
// SubnetGatewayIPV4Address is the address to the subnet gateway for the eni
SubnetGatewayIPV4Address string `json:",omitempty"`
}

Expand Down Expand Up @@ -75,6 +73,19 @@ func (eni *ENI) GetIPV4Addresses() []string {
return addresses
}

// GetPrimaryIPv4Address returns the primary IPv4 address associated with the ENI.
func (eni *ENI) GetPrimaryIPv4Address() string {
var primaryAddr string
for _, addr := range eni.IPV4Addresses {
if addr.Primary {
primaryAddr = addr.Address
break
}
}

return primaryAddr
}

// GetIPV6Addresses returns a list of ipv6 addresses allocated to the ENI
func (eni *ENI) GetIPV6Addresses() []string {
var addresses []string
Expand Down Expand Up @@ -139,95 +150,88 @@ type ENIIPV6Address struct {
Address string
}

// ENIFromACS validates the information from acs message and create the ENI object
func ENIFromACS(acsenis []*ecsacs.ElasticNetworkInterface) (*ENI, error) {
err := ValidateTaskENI(acsenis)
// ENIFromACS validates the given ACS ENI information and creates an ENI object from it.
func ENIFromACS(acsENI *ecsacs.ElasticNetworkInterface) (*ENI, error) {
err := ValidateTaskENI(acsENI)
if err != nil {
return nil, err
}

var ipv4 []*ENIIPV4Address
var ipv6 []*ENIIPV6Address
var ipv4Addrs []*ENIIPV4Address
var ipv6Addrs []*ENIIPV6Address

// Read ipv4 address information of the eni
for _, ec2Ipv4 := range acsenis[0].Ipv4Addresses {
ipv4 = append(ipv4, &ENIIPV4Address{
// Read IPv4 address information of the ENI.
for _, ec2Ipv4 := range acsENI.Ipv4Addresses {
ipv4Addrs = append(ipv4Addrs, &ENIIPV4Address{
Primary: aws.BoolValue(ec2Ipv4.Primary),
Address: aws.StringValue(ec2Ipv4.PrivateAddress),
})
}

// Read ipv6 address information of the eni
for _, ec2Ipv6 := range acsenis[0].Ipv6Addresses {
ipv6 = append(ipv6, &ENIIPV6Address{
// Read IPv6 address information of the ENI.
for _, ec2Ipv6 := range acsENI.Ipv6Addresses {
ipv6Addrs = append(ipv6Addrs, &ENIIPV6Address{
Address: aws.StringValue(ec2Ipv6.Address),
})
}

var interfaceVlanProperties InterfaceVlanProperties

if aws.StringValue(acsenis[0].InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
interfaceVlanProperties.TrunkInterfaceMacAddress = aws.StringValue(acsenis[0].InterfaceVlanProperties.TrunkInterfaceMacAddress)
interfaceVlanProperties.VlanID = aws.StringValue(acsenis[0].InterfaceVlanProperties.VlanId)
if aws.StringValue(acsENI.InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
interfaceVlanProperties.TrunkInterfaceMacAddress = aws.StringValue(acsENI.InterfaceVlanProperties.TrunkInterfaceMacAddress)
interfaceVlanProperties.VlanID = aws.StringValue(acsENI.InterfaceVlanProperties.VlanId)
}

eni := &ENI{
ID: aws.StringValue(acsenis[0].Ec2Id),
IPV4Addresses: ipv4,
IPV6Addresses: ipv6,
MacAddress: aws.StringValue(acsenis[0].MacAddress),
PrivateDNSName: aws.StringValue(acsenis[0].PrivateDnsName),
SubnetGatewayIPV4Address: aws.StringValue(acsenis[0].SubnetGatewayIpv4Address),
InterfaceAssociationProtocol: aws.StringValue(acsenis[0].InterfaceAssociationProtocol),
ID: aws.StringValue(acsENI.Ec2Id),
IPV4Addresses: ipv4Addrs,
IPV6Addresses: ipv6Addrs,
MacAddress: aws.StringValue(acsENI.MacAddress),
PrivateDNSName: aws.StringValue(acsENI.PrivateDnsName),
SubnetGatewayIPV4Address: aws.StringValue(acsENI.SubnetGatewayIpv4Address),
InterfaceAssociationProtocol: aws.StringValue(acsENI.InterfaceAssociationProtocol),
InterfaceVlanProperties: &interfaceVlanProperties,
}

for _, nameserverIP := range acsenis[0].DomainNameServers {
for _, nameserverIP := range acsENI.DomainNameServers {
eni.DomainNameServers = append(eni.DomainNameServers, aws.StringValue(nameserverIP))
}
for _, nameserverDomain := range acsenis[0].DomainName {
for _, nameserverDomain := range acsENI.DomainName {
eni.DomainNameSearchList = append(eni.DomainNameSearchList, aws.StringValue(nameserverDomain))
}

return eni, nil
}

// ValidateTaskENI validates the eni informaiton sent from acs
func ValidateTaskENI(acsenis []*ecsacs.ElasticNetworkInterface) error {
// Only one eni should be associated with the task
// Only one ipv4 should be associated with the eni
// No more than one ipv6 should be associated with the eni
if len(acsenis) != 1 {
return errors.Errorf("eni message validation: more than one ENIs in the message(%d)", len(acsenis))
} else if len(acsenis[0].Ipv4Addresses) != 1 {
return errors.Errorf("eni message validation: more than one ipv4 addresses in the message(%d)",
len(acsenis[0].Ipv4Addresses))
} else if len(acsenis[0].Ipv6Addresses) > 1 {
return errors.Errorf("eni message validation: more than one ipv6 addresses in the message(%d)",
len(acsenis[0].Ipv6Addresses))
}

if acsenis[0].MacAddress == nil {
// ValidateTaskENI validates the ENI information sent from ACS.
func ValidateTaskENI(acsENI *ecsacs.ElasticNetworkInterface) error {
// At least one IPv4 address should be associated with the ENI.
if len(acsENI.Ipv4Addresses) < 1 {
return errors.Errorf("eni message validation: no ipv4 addresses in the message")
}

if acsENI.MacAddress == nil {
return errors.Errorf("eni message validation: empty eni mac address in the message")
}

if acsenis[0].Ec2Id == nil {
if acsENI.Ec2Id == nil {
return errors.Errorf("eni message validation: empty eni id in the message")
}

if (acsenis[0].InterfaceAssociationProtocol != nil) && (aws.StringValue(acsenis[0].InterfaceAssociationProtocol) !=
VLANInterfaceAssociationProtocol) && (aws.StringValue(acsenis[0].InterfaceAssociationProtocol) !=
DefaultInterfaceAssociationProtocol) {
return errors.Errorf("invalid ENI interface type: %s",
aws.StringValue(acsenis[0].InterfaceAssociationProtocol))
// The association protocol, if specified, must be a supported value.
if (acsENI.InterfaceAssociationProtocol != nil) &&
(aws.StringValue(acsENI.InterfaceAssociationProtocol) != VLANInterfaceAssociationProtocol) &&
(aws.StringValue(acsENI.InterfaceAssociationProtocol) != DefaultInterfaceAssociationProtocol) {
return errors.Errorf("invalid interface association protocol: %s",
aws.StringValue(acsENI.InterfaceAssociationProtocol))
}

// If ENI type is vlan, InterfaceVlanProperties must be not nil.
if aws.StringValue(acsenis[0].InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
if acsenis[0].InterfaceVlanProperties == nil ||
len(aws.StringValue(acsenis[0].InterfaceVlanProperties.VlanId)) == 0 ||
len(aws.StringValue(acsenis[0].InterfaceVlanProperties.TrunkInterfaceMacAddress)) == 0 {
return errors.New("vlan interface properties missing.")
// If the interface association protocol is vlan, InterfaceVlanProperties must be specified.
if aws.StringValue(acsENI.InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
if acsENI.InterfaceVlanProperties == nil ||
len(aws.StringValue(acsENI.InterfaceVlanProperties.VlanId)) == 0 ||
len(aws.StringValue(acsENI.InterfaceVlanProperties.TrunkInterfaceMacAddress)) == 0 {
return errors.New("vlan interface properties missing")
}
}

Expand Down
Loading

0 comments on commit 6ca1b3b

Please sign in to comment.