Skip to content

Commit 17779fc

Browse files
samjkonYiyuanzzz
authored andcommitted
Create DNS files for second ENI (#4087)
On firecracker, DNS files have to be created for second task ENI.
1 parent b7e9650 commit 17779fc

File tree

6 files changed

+199
-8
lines changed

6 files changed

+199
-8
lines changed

ecs-agent/netlib/platform/cniconf_linux_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,17 @@ func getTestV2NInterface() *networkinterface.NetworkInterface {
202202
IPV4Addresses: []*networkinterface.IPV4Address{
203203
{
204204
Address: networkinterface.DefaultGeneveInterfaceIPAddress,
205+
Primary: true,
205206
},
206207
},
207208
TunnelProperties: &networkinterface.TunnelProperties{
208209
ID: vni,
209210
DestinationIPAddress: destinationIP,
210211
DestinationPort: destinationPort,
211212
},
212-
DeviceName: fmt.Sprintf(networkinterface.GeneveInterfaceNamePattern, vni, destinationPort),
213+
DeviceName: fmt.Sprintf(networkinterface.GeneveInterfaceNamePattern, vni, destinationPort),
214+
Name: secondaryENIName,
215+
DomainNameServers: []string{nameServer},
216+
DomainNameSearchList: []string{searchDomainName},
213217
}
214218
}

ecs-agent/netlib/platform/common_linux.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,7 @@ func (c *common) createDNSConfig(
407407

408408
// Next, copy these files into a task volume, which can be used by containers as well, to
409409
// configure their network.
410-
configFiles := []string{HostsFileName, ResolveConfFileName, HostnameFileName}
411-
if err := c.copyNetworkConfigFilesToTask(taskID, netNS.Name, configFiles); err != nil {
410+
if err := c.copyNetworkConfigFilesToTask(taskID, netNS.Name); err != nil {
412411
return err
413412
}
414413
return nil
@@ -454,7 +453,8 @@ func (c *common) createNetworkConfigFiles(netNSName string, primaryIF *networkin
454453

455454
// copyNetworkConfigFilesToTask copies the contents of the DNS config files for a
456455
// task into the task volume.
457-
func (c *common) copyNetworkConfigFilesToTask(taskID, netNSName string, configFiles []string) error {
456+
func (c *common) copyNetworkConfigFilesToTask(taskID, netNSName string) error {
457+
configFiles := []string{HostsFileName, ResolveConfFileName, HostnameFileName}
458458
for _, file := range configFiles {
459459
source := filepath.Join(networkConfigFileDirectory, netNSName, file)
460460
err := c.dnsVolumeAccessor.CopyToVolume(taskID, source, file, networkConfigFileMode)
@@ -583,8 +583,11 @@ func (c *common) configureInterface(
583583
err = c.configureBranchENI(ctx, netNSPath, iface)
584584
case networkinterface.V2NInterfaceAssociationProtocol:
585585
err = c.configureGENEVEInterface(ctx, netNSPath, iface, netDAO)
586+
case networkinterface.VETHInterfaceAssociationProtocol:
587+
// Do nothing.
588+
return nil
586589
default:
587-
err = errors.New("invalid interface association protocol %s" + iface.InterfaceAssociationProtocol)
590+
err = errors.New("invalid interface association protocol " + iface.InterfaceAssociationProtocol)
588591
}
589592
return err
590593
}

ecs-agent/netlib/platform/common_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const (
3535
deviceName = "eth1"
3636
eniMAC = "f0:5c:89:a3:ab:01"
3737
subnetGatewayCIDR = "10.1.0.1/24"
38+
primaryENIName = "primary-eni"
39+
secondaryENIName = "secondary-eni"
3840
)
3941

4042
func getTestInterface() *networkinterface.NetworkInterface {
@@ -69,5 +71,6 @@ func getTestInterface() *networkinterface.NetworkInterface {
6971
InterfaceAssociationProtocol: networkinterface.DefaultInterfaceAssociationProtocol,
7072
KnownStatus: status.NetworkNone,
7173
DesiredStatus: status.NetworkReadyPull,
74+
Name: primaryENIName,
7275
}
7376
}

ecs-agent/netlib/platform/firecracker_debug_linux.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@ type firecrackerDebug struct {
77
}
88

99
func (fc *firecrackerDebug) CreateDNSConfig(taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
10-
return fc.common.createDNSConfig(taskID, true, netNS)
10+
err := fc.common.createDNSConfig(taskID, true, netNS)
11+
if err != nil {
12+
return err
13+
}
14+
15+
return fc.configureSecondaryDNSConfig(taskID, netNS)
1116
}

ecs-agent/netlib/platform/firecracker_linux.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ package platform
1515

1616
import (
1717
"context"
18-
"errors"
1918
"fmt"
2019

2120
netlibdata "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/data"
@@ -27,6 +26,7 @@ import (
2726
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"
2827

2928
"github.com/aws/aws-sdk-go/aws"
29+
"github.com/pkg/errors"
3030
)
3131

3232
type firecraker struct {
@@ -51,7 +51,12 @@ func (f *firecraker) BuildTaskNetworkConfiguration(
5151
}
5252

5353
func (f *firecraker) CreateDNSConfig(taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
54-
return f.common.createDNSConfig(taskID, false, netNS)
54+
err := f.common.createDNSConfig(taskID, false, netNS)
55+
if err != nil {
56+
return err
57+
}
58+
59+
return f.configureSecondaryDNSConfig(taskID, netNS)
5560
}
5661

5762
func (f *firecraker) ConfigureInterface(
@@ -76,6 +81,33 @@ func (f *firecraker) ConfigureServiceConnect(
7681
return errors.New("not implemented")
7782
}
7883

84+
// configureSecondaryDNSConfig creates DNS config files for secondary interfaces. This is required because
85+
// on FoF, secondary interfaces reside in their own network namespace inside the microVM. The DNS config
86+
// inside the namespace will need to be the secondary interface DNS config.
87+
func (f *firecraker) configureSecondaryDNSConfig(taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
88+
for _, iface := range netNS.NetworkInterfaces {
89+
// Omit primary interface and veth interfaces.
90+
if iface.IsPrimary() || iface.VETHProperties != nil {
91+
continue
92+
}
93+
94+
// Create DNS files.
95+
dnsDirName := networkinterface.NetNSName(taskID, iface.Name)
96+
err := f.common.createNetworkConfigFiles(dnsDirName, iface)
97+
if err != nil {
98+
return errors.Wrapf(err, "failed to create DNS config for interface %s", iface.Name)
99+
}
100+
101+
// Copy to task volume.
102+
err = f.common.copyNetworkConfigFilesToTask(taskID, dnsDirName)
103+
if err != nil {
104+
return errors.Wrapf(err, "failed to create DNS config for interface %s", iface.Name)
105+
}
106+
}
107+
108+
return nil
109+
}
110+
79111
// assignInterfacesToNamespaces computes how many network namespaces the task needs and assigns
80112
// each network interface to a network namespace.
81113
func assignInterfacesToNamespaces(taskPayload *ecsacs.Task) (map[string]string, error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//go:build !windows && unit
2+
// +build !windows,unit
3+
4+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
7+
// not use this file except in compliance with the License. A copy of the
8+
// License is located at
9+
//
10+
// http://aws.amazon.com/apache2.0/
11+
//
12+
// or in the "license" file accompanying this file. This file is distributed
13+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14+
// express or implied. See the License for the specific language governing
15+
// permissions and limitations under the License.
16+
17+
package platform
18+
19+
import (
20+
"fmt"
21+
"io/fs"
22+
"os"
23+
"testing"
24+
25+
mock_ecscni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/ecscni/mocks_nsutil"
26+
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
27+
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"
28+
mock_ioutilwrapper "github.com/aws/amazon-ecs-agent/ecs-agent/utils/ioutilwrapper/mocks"
29+
mock_oswrapper "github.com/aws/amazon-ecs-agent/ecs-agent/utils/oswrapper/mocks"
30+
mock_volume "github.com/aws/amazon-ecs-agent/ecs-agent/volume/mocks"
31+
32+
"github.com/golang/mock/gomock"
33+
"github.com/stretchr/testify/require"
34+
)
35+
36+
// TestFirecracker_CreateDNSConfig checks if DNS config files gets created for
37+
// both primary and secondary interfaces.
38+
func TestFirecracker_CreateDNSConfig(t *testing.T) {
39+
ctrl := gomock.NewController(t)
40+
defer ctrl.Finish()
41+
42+
taskID := "task-id"
43+
iface := getTestInterface()
44+
primaryNetNSName := networkinterface.NetNSName(taskID, iface.Name)
45+
primaryNetNSPath := "/etc/netns/" + primaryNetNSName
46+
47+
v2nIface := getTestV2NInterface()
48+
secondaryNetNSName := networkinterface.NetNSName(taskID, v2nIface.Name)
49+
secondaryNetNSPath := "/etc/netns/" + secondaryNetNSName
50+
51+
netns := &tasknetworkconfig.NetworkNamespace{
52+
Name: primaryNetNSName,
53+
Path: primaryNetNSPath,
54+
NetworkInterfaces: []*networkinterface.NetworkInterface{iface, v2nIface},
55+
}
56+
57+
ioutil := mock_ioutilwrapper.NewMockIOUtil(ctrl)
58+
nsUtil := mock_ecscni.NewMockNetNSUtil(ctrl)
59+
osWrapper := mock_oswrapper.NewMockOS(ctrl)
60+
mockFile := mock_oswrapper.NewMockFile(ctrl)
61+
volumeAccessor := mock_volume.NewMockTaskVolumeAccessor(ctrl)
62+
commonPlatform := common{
63+
ioutil: ioutil,
64+
nsUtil: nsUtil,
65+
os: osWrapper,
66+
dnsVolumeAccessor: volumeAccessor,
67+
}
68+
69+
fc := &firecraker{
70+
common: commonPlatform,
71+
}
72+
73+
// Test creation of hosts file.
74+
primaryHostsData := fmt.Sprintf("%s\n%s %s\n%s %s\n%s %s\n",
75+
HostsLocalhostEntry,
76+
ipv4Addr, dnsName,
77+
addr, hostName,
78+
addr2, hostName2,
79+
)
80+
primaryResolvData := fmt.Sprintf("nameserver %s\nnameserver %s\nsearch %s\n",
81+
nameServer,
82+
nameServer2,
83+
searchDomainName+" "+searchDomainName2,
84+
)
85+
primaryHostnameData := fmt.Sprintf("%s\n", iface.GetHostname())
86+
87+
secondaryHostsData := fmt.Sprintf("%s\n%s %s\n",
88+
HostsLocalhostEntry,
89+
networkinterface.DefaultGeneveInterfaceIPAddress, "",
90+
)
91+
secondaryResolvData := fmt.Sprintf("nameserver %s\nsearch %s\n",
92+
nameServer,
93+
searchDomainName,
94+
)
95+
secondaryHostnameData := "\n"
96+
97+
gomock.InOrder(
98+
// Creation of netns path.
99+
osWrapper.EXPECT().Stat(primaryNetNSPath).Return(nil, os.ErrNotExist).Times(1),
100+
osWrapper.EXPECT().IsNotExist(os.ErrNotExist).Return(true).Times(1),
101+
osWrapper.EXPECT().MkdirAll(primaryNetNSPath, fs.FileMode(0644)),
102+
103+
// Creation of resolv.conf file for primary interface.
104+
nsUtil.EXPECT().BuildResolvConfig(iface.DomainNameServers, iface.DomainNameSearchList).Return(primaryResolvData).Times(1),
105+
ioutil.EXPECT().WriteFile(primaryNetNSPath+"/resolv.conf", []byte(primaryResolvData), fs.FileMode(0644)),
106+
107+
// Creation of hostname file for primary interface.
108+
ioutil.EXPECT().WriteFile(primaryNetNSPath+"/hostname", []byte(primaryHostnameData), fs.FileMode(0644)),
109+
osWrapper.EXPECT().OpenFile("/etc/hostname", os.O_RDONLY|os.O_CREATE, fs.FileMode(0644)).Return(mockFile, nil).Times(1),
110+
111+
// Creation of hosts file for primary interface.
112+
mockFile.EXPECT().Close().Times(1),
113+
ioutil.EXPECT().WriteFile(primaryNetNSPath+"/hosts", []byte(primaryHostsData), fs.FileMode(0644)),
114+
115+
// CopyToVolume created files into task volume for primary interface.
116+
volumeAccessor.EXPECT().CopyToVolume(taskID, primaryNetNSPath+"/hosts", "hosts", fs.FileMode(0644)).Return(nil).Times(1),
117+
volumeAccessor.EXPECT().CopyToVolume(taskID, primaryNetNSPath+"/resolv.conf", "resolv.conf", fs.FileMode(0644)).Return(nil).Times(1),
118+
volumeAccessor.EXPECT().CopyToVolume(taskID, primaryNetNSPath+"/hostname", "hostname", fs.FileMode(0644)).Return(nil).Times(1),
119+
120+
// Creation of secondary netns path.
121+
osWrapper.EXPECT().Stat(secondaryNetNSPath).Return(nil, os.ErrNotExist).Times(1),
122+
osWrapper.EXPECT().IsNotExist(os.ErrNotExist).Return(true).Times(1),
123+
osWrapper.EXPECT().MkdirAll(secondaryNetNSPath, fs.FileMode(0644)),
124+
125+
// Creation of resolv.conf file for secondary interface.
126+
nsUtil.EXPECT().BuildResolvConfig(v2nIface.DomainNameServers, v2nIface.DomainNameSearchList).Return(secondaryResolvData).Times(1),
127+
ioutil.EXPECT().WriteFile(secondaryNetNSPath+"/resolv.conf", []byte(secondaryResolvData), fs.FileMode(0644)),
128+
129+
// Creation of hostname file for secondary interface.
130+
ioutil.EXPECT().WriteFile(secondaryNetNSPath+"/hostname", []byte(secondaryHostnameData), fs.FileMode(0644)),
131+
osWrapper.EXPECT().OpenFile("/etc/hostname", os.O_RDONLY|os.O_CREATE, fs.FileMode(0644)).Return(mockFile, nil).Times(1),
132+
133+
// Creation of hosts file for secondary interface.
134+
mockFile.EXPECT().Close().Times(1),
135+
ioutil.EXPECT().WriteFile(secondaryNetNSPath+"/hosts", []byte(secondaryHostsData), fs.FileMode(0644)),
136+
137+
// CopyToVolume created files into task volume for secondary interface.
138+
volumeAccessor.EXPECT().CopyToVolume(taskID, secondaryNetNSPath+"/hosts", "hosts", fs.FileMode(0644)).Return(nil).Times(1),
139+
volumeAccessor.EXPECT().CopyToVolume(taskID, secondaryNetNSPath+"/resolv.conf", "resolv.conf", fs.FileMode(0644)).Return(nil).Times(1),
140+
volumeAccessor.EXPECT().CopyToVolume(taskID, secondaryNetNSPath+"/hostname", "hostname", fs.FileMode(0644)).Return(nil).Times(1),
141+
)
142+
err := fc.CreateDNSConfig(taskID, netns)
143+
require.NoError(t, err)
144+
}

0 commit comments

Comments
 (0)