Skip to content

Commit 8e1f88d

Browse files
author
Jose Villalta
committed
adds daemon-bridge netns
1 parent 8ada03e commit 8e1f88d

File tree

12 files changed

+506
-39
lines changed

12 files changed

+506
-39
lines changed

ecs-agent/netlib/common_test.go

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,10 @@ func getSingleNetNSAWSVPCTestData(testTaskID string) (*ecsacs.Task, tasknetworkc
6767

6868
netNSName := fmt.Sprintf(netNSNamePattern, testTaskID, eniName)
6969
netNSPath := netNSPathDir + netNSName
70+
netNS, _ := tasknetworkconfig.NewNetworkNamespace(netNSName, netNSPath, 0, nil, &netIfs[0])
7071
taskNetConfig := tasknetworkconfig.TaskNetworkConfig{
7172
NetworkMode: types.NetworkModeAwsvpc,
72-
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
73-
{
74-
Name: netNSName,
75-
Path: netNSPath,
76-
Index: 0,
77-
NetworkInterfaces: []*networkinterface.NetworkInterface{
78-
&netIfs[0],
79-
},
80-
KnownState: status.NetworkNone,
81-
DesiredState: status.NetworkReadyPull,
82-
},
83-
},
73+
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{netNS},
8474
}
8575

8676
return taskPayload, taskNetConfig
@@ -152,30 +142,12 @@ func getMultiNetNSMultiIfaceAWSVPCTestData(testTaskID string) (*ecsacs.Task, tas
152142
secondaryNetNSName := fmt.Sprintf(netNSNamePattern, testTaskID, ifName2)
153143
secondaryNetNSPath := netNSPathDir + secondaryNetNSName
154144

145+
primaryNetNS, _ := tasknetworkconfig.NewNetworkNamespace(primaryNetNSName, primaryNetNSPath, 0, nil, &netIfs[0])
146+
secondaryNetNS, _ := tasknetworkconfig.NewNetworkNamespace(secondaryNetNSName, secondaryNetNSPath, 1, nil, &netIfs[1])
147+
155148
taskNetConfig := tasknetworkconfig.TaskNetworkConfig{
156149
NetworkMode: types.NetworkModeAwsvpc,
157-
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
158-
{
159-
Name: primaryNetNSName,
160-
Path: primaryNetNSPath,
161-
Index: 0,
162-
NetworkInterfaces: []*networkinterface.NetworkInterface{
163-
&netIfs[0],
164-
},
165-
KnownState: status.NetworkNone,
166-
DesiredState: status.NetworkReadyPull,
167-
},
168-
{
169-
Name: secondaryNetNSName,
170-
Path: secondaryNetNSPath,
171-
Index: 1,
172-
NetworkInterfaces: []*networkinterface.NetworkInterface{
173-
&netIfs[1],
174-
},
175-
KnownState: status.NetworkNone,
176-
DesiredState: status.NetworkReadyPull,
177-
},
178-
},
150+
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{primaryNetNS, secondaryNetNS},
179151
}
180152

181153
return taskPayload, taskNetConfig
@@ -323,6 +295,7 @@ func getV2NTestData(testTaskID string) (*ecsacs.Task, tasknetworkconfig.TaskNetw
323295
Name: netNSName,
324296
Path: netNSPath,
325297
Index: 0,
298+
NetworkMode: types.NetworkModeAwsvpc,
326299
NetworkInterfaces: netIfs,
327300
KnownState: status.NetworkNone,
328301
DesiredState: status.NetworkReadyPull,

ecs-agent/netlib/model/tasknetworkconfig/network_namespace.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
2323
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/serviceconnect"
2424
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/status"
25+
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
2526
)
2627

2728
// NetworkNamespace is model representing each network namespace.
@@ -30,6 +31,10 @@ type NetworkNamespace struct {
3031
Path string
3132
Index int
3233

34+
// NetworkMode represents the network mode for this namespace.
35+
// Supported values: awsvpc (default), daemon-bridge (managed-instances only).
36+
NetworkMode types.NetworkMode
37+
3338
// NetworkInterfaces represents ENIs or any kind of network interface associated the particular netns.
3439
NetworkInterfaces []*networkinterface.NetworkInterface
3540

@@ -58,6 +63,7 @@ func NewNetworkNamespace(
5863
NetworkInterfaces: networkInterfaces,
5964
KnownState: status.NetworkNone,
6065
DesiredState: status.NetworkReadyPull,
66+
NetworkMode: types.NetworkModeAwsvpc,
6167
}
6268

6369
// Sort interfaces as per their index values in ascending order.
@@ -104,3 +110,9 @@ func (ns *NetworkNamespace) GetInterfaceByIndex(idx int64) *networkinterface.Net
104110

105111
return nil
106112
}
113+
114+
// WithNetworkMode sets the NetworkMode field
115+
func (ns *NetworkNamespace) WithNetworkMode(mode types.NetworkMode) *NetworkNamespace {
116+
ns.NetworkMode = mode
117+
return ns
118+
}

ecs-agent/netlib/model/tasknetworkconfig/network_namespace_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func TestNewNetworkNamespace(t *testing.T) {
4747
assert.Equal(t, primaryNetNSName, netns.Name)
4848
assert.Equal(t, primaryNetNSPath, netns.Path)
4949
assert.Equal(t, 0, netns.Index)
50+
assert.Equal(t, "awsvpc", string(netns.NetworkMode))
5051
assert.Empty(t, netns.AppMeshConfig)
5152
assert.Equal(t, *netIFs[0], *netns.NetworkInterfaces[0])
5253
assert.Equal(t, *netIFs[1], *netns.NetworkInterfaces[1])
@@ -78,3 +79,10 @@ func TestNetworkNamespace_IsPrimary(t *testing.T) {
7879
require.Equal(t, tc.isPrimary, tc.netNS.IsPrimary())
7980
}
8081
}
82+
83+
func TestNetworkNamespace_WithNetworkMode(t *testing.T) {
84+
netns := &NetworkNamespace{}
85+
result := netns.WithNetworkMode("daemon-bridge")
86+
assert.Equal(t, "daemon-bridge", string(result.NetworkMode))
87+
assert.Equal(t, netns, result) // Should return same instance
88+
}

ecs-agent/netlib/network_builder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ func (nb *networkBuilder) Start(
102102
err = nb.startAWSVPC(ctx, taskID, netNS)
103103
case types.NetworkModeHost:
104104
err = nb.platformAPI.HandleHostMode()
105+
case "daemon-bridge":
106+
err = nb.platformAPI.ConfigureDaemonNetNS(netNS)
105107
default:
106108
err = errors.New("invalid network mode: " + string(mode))
107109
}
@@ -132,6 +134,10 @@ func (nb *networkBuilder) Stop(ctx context.Context, mode types.NetworkMode, task
132134
err = nb.stopAWSVPC(ctx, netNS)
133135
case types.NetworkModeHost:
134136
err = nb.platformAPI.HandleHostMode()
137+
case "daemon-bridge":
138+
// Adding extra logging to help with debug TODO remove later.
139+
logger.Info("Stopping Daemon network namespace setup", logFields)
140+
err = nb.platformAPI.StopDaemonNetNS(ctx, netNS)
135141
default:
136142
err = errors.New("invalid network mode: " + string(mode))
137143
}

ecs-agent/netlib/network_builder_linux_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ func TestNetworkBuilder_BuildTaskNetworkConfiguration(t *testing.T) {
7070

7171
func TestNetworkBuilder_Start(t *testing.T) {
7272
t.Run("awsvpc", testNetworkBuilder_StartAWSVPC)
73+
t.Run("daemon-bridge", testNetworkBuilder_StartDaemonBridge)
7374
}
7475

7576
// TestNetworkBuilder_Stop verifies stop workflow for AWSVPC mode.
7677
func TestNetworkBuilder_Stop(t *testing.T) {
7778
t.Run("awsvpc", testNetworkBuilder_StopAWSVPC)
79+
t.Run("daemon-bridge", testNetworkBuilder_StopDaemonBridge)
7880
}
7981

8082
// getTestFunc returns a test function that verifies the capability of the networkBuilder
@@ -380,3 +382,66 @@ func getExpectedCalls_StopAWSVPC(
380382
platformAPI.EXPECT().DeleteDNSConfig(netNS.Name).Return(nil).Times(1),
381383
platformAPI.EXPECT().DeleteNetNS(netNS.Path).Return(nil).Times(1))
382384
}
385+
386+
// testNetworkBuilder_StartDaemonBridge verifies that the expected platform API calls
387+
// are made by the network builder while configuring daemon-bridge network namespace.
388+
func testNetworkBuilder_StartDaemonBridge(t *testing.T) {
389+
ctrl := gomock.NewController(t)
390+
defer ctrl.Finish()
391+
392+
ctx := context.TODO()
393+
platformAPI := mock_platform.NewMockAPI(ctrl)
394+
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
395+
mockEntry := mock_metrics.NewMockEntry(ctrl)
396+
netBuilder := &networkBuilder{
397+
platformAPI: platformAPI,
398+
metricsFactory: metricsFactory,
399+
}
400+
401+
// Create a daemon-bridge network namespace
402+
netNS, _ := tasknetworkconfig.NewNetworkNamespace("daemon-ns", "/var/run/netns/daemon-ns", 0, nil)
403+
netNS = netNS.WithNetworkMode("daemon-bridge")
404+
netNS.KnownState = status.NetworkNone
405+
netNS.DesiredState = status.NetworkReadyPull
406+
407+
t.Run("daemon-bridge-start", func(*testing.T) {
408+
gomock.InOrder(
409+
metricsFactory.EXPECT().New(metrics.BuildNetworkNamespaceMetricName).Return(mockEntry).Times(1),
410+
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
411+
platformAPI.EXPECT().ConfigureDaemonNetNS(netNS).Return(nil).Times(1),
412+
mockEntry.EXPECT().Done(nil).Times(1),
413+
)
414+
netBuilder.Start(ctx, "daemon-bridge", taskID, netNS)
415+
})
416+
}
417+
418+
// testNetworkBuilder_StopDaemonBridge verifies that the cleanup of daemon-bridge
419+
// network namespace works as expected.
420+
func testNetworkBuilder_StopDaemonBridge(t *testing.T) {
421+
ctrl := gomock.NewController(t)
422+
defer ctrl.Finish()
423+
424+
ctx := context.TODO()
425+
platformAPI := mock_platform.NewMockAPI(ctrl)
426+
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
427+
mockEntry := mock_metrics.NewMockEntry(ctrl)
428+
netBuilder := &networkBuilder{
429+
platformAPI: platformAPI,
430+
metricsFactory: metricsFactory,
431+
}
432+
433+
// Create a daemon-bridge network namespace
434+
netNS, _ := tasknetworkconfig.NewNetworkNamespace("daemon-ns", "/var/run/netns/daemon-ns", 0, nil)
435+
netNS = netNS.WithNetworkMode("daemon-bridge")
436+
netNS.DesiredState = status.NetworkDeleted
437+
438+
t.Run("daemon-bridge-stop", func(*testing.T) {
439+
gomock.InOrder(
440+
metricsFactory.EXPECT().New(metrics.DeleteNetworkNamespaceMetricName).Return(mockEntry).Times(1),
441+
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
442+
platformAPI.EXPECT().StopDaemonNetNS(ctx, netNS).Return(nil).Times(1),
443+
mockEntry.EXPECT().Done(nil).Times(1),
444+
)
445+
netBuilder.Stop(ctx, "daemon-bridge", taskID, netNS)
446+
})
447+
}

ecs-agent/netlib/platform/api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ type API interface {
7878
primaryIf *networkinterface.NetworkInterface,
7979
scConfig *serviceconnect.ServiceConnectConfig,
8080
) error
81+
82+
// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
83+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
84+
ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error
85+
86+
// StopDaemonNetNS stops and cleans up a daemon network namespace.
87+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
88+
StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error
8189
}
8290

8391
// Config contains platform-specific data.

ecs-agent/netlib/platform/cniconf_linux.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const (
5353
VPCTunnelInterfaceTypeGeneve = "geneve"
5454
VPCTunnelInterfaceTypeTap = "tap"
5555

56-
BridgeInterfaceName = "fargate-bridge"
56+
BridgeInterfaceName = "fargate-bridge"
57+
ManagedInstanceBridgeName = "mi-bridge"
5758

5859
IPAMDataFileName = "eni-ipam.db"
5960

@@ -121,6 +122,40 @@ func createBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
121122
return bridgeConfig
122123
}
123124

125+
// createBridgePluginConfig constructs the configuration object for bridge plugin
126+
func createDaemonBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
127+
cniConfig := ecscni.CNIConfig{
128+
NetNSPath: netNSPath,
129+
CNISpecVersion: cniSpecVersion,
130+
CNIPluginName: BridgePluginName,
131+
}
132+
133+
_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
134+
route := &types.Route{
135+
Dst: *routeIPNet,
136+
}
137+
138+
ipamConfig := &ecscni.IPAMConfig{
139+
CNIConfig: ecscni.CNIConfig{
140+
NetNSPath: netNSPath,
141+
CNISpecVersion: cniSpecVersion,
142+
CNIPluginName: IPAMPluginName,
143+
},
144+
IPV4Subnet: ECSSubNet,
145+
IPV4Routes: []*types.Route{route},
146+
ID: netNSPath,
147+
}
148+
149+
// Invoke the bridge plugin and ipam plugin
150+
bridgeConfig := &ecscni.BridgeConfig{
151+
CNIConfig: cniConfig,
152+
Name: ManagedInstanceBridgeName,
153+
IPAM: *ipamConfig,
154+
}
155+
156+
return bridgeConfig
157+
}
158+
124159
func createAppMeshPluginConfig(
125160
netNSPath string,
126161
cfg *appmesh.AppMesh,

ecs-agent/netlib/platform/cniconf_linux_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,44 @@ func TestCreateBridgeConfig(t *testing.T) {
7979
require.Equal(t, expected, actual)
8080
}
8181

82+
func TestCreateDaemonBridgeConfig(t *testing.T) {
83+
cniConfig := ecscni.CNIConfig{
84+
NetNSPath: netNSPath,
85+
CNISpecVersion: cniSpecVersion,
86+
CNIPluginName: BridgePluginName,
87+
}
88+
89+
_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
90+
route := &types.Route{
91+
Dst: *routeIPNet,
92+
}
93+
94+
ipamConfig := &ecscni.IPAMConfig{
95+
CNIConfig: ecscni.CNIConfig{
96+
NetNSPath: netNSPath,
97+
CNISpecVersion: cniSpecVersion,
98+
CNIPluginName: IPAMPluginName,
99+
},
100+
IPV4Subnet: ECSSubNet,
101+
IPV4Routes: []*types.Route{route},
102+
ID: netNSPath,
103+
}
104+
105+
// Invoke the bridge plugin and ipam plugin
106+
bridgeConfig := &ecscni.BridgeConfig{
107+
CNIConfig: cniConfig,
108+
Name: ManagedInstanceBridgeName,
109+
IPAM: *ipamConfig,
110+
}
111+
112+
expected, err := json.Marshal(bridgeConfig)
113+
require.NoError(t, err)
114+
actual, err := json.Marshal(createDaemonBridgePluginConfig(netNSPath))
115+
require.NoError(t, err)
116+
117+
require.Equal(t, expected, actual)
118+
}
119+
82120
func TestCreateENIConfig(t *testing.T) {
83121
for _, tc := range []struct {
84122
name string

ecs-agent/netlib/platform/common.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/ecscni"
22+
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"
2223

2324
"github.com/containernetworking/cni/pkg/types"
2425
)
@@ -92,3 +93,15 @@ func (c *common) interfacesMACToName() (map[string]string, error) {
9293
func (c *common) HandleHostMode() error {
9394
return errors.New("invalid platform for host mode")
9495
}
96+
97+
// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
98+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
99+
func (c *common) ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error {
100+
return errors.New("daemon network namespaces are not supported in this platform")
101+
}
102+
103+
// StopDaemonNetNS stops and cleans up a daemon network namespace.
104+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
105+
func (c *common) StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error {
106+
return errors.New("daemon network namespaces are not supported in this platform")
107+
}

0 commit comments

Comments
 (0)