Skip to content

Commit 2a3bcad

Browse files
authored
Merge pull request #4821 from aviral92/managed-daemons-networking
Managed Daemons networking changes.
2 parents ec978b0 + f966279 commit 2a3bcad

19 files changed

+1092
-20
lines changed

ecs-agent/netlib/common_test.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ func getSingleNetNSAWSVPCTestData(testTaskID string) (*ecsacs.Task, tasknetworkc
7171
NetworkMode: types.NetworkModeAwsvpc,
7272
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
7373
{
74-
Name: netNSName,
75-
Path: netNSPath,
76-
Index: 0,
74+
Name: netNSName,
75+
Path: netNSPath,
76+
Index: 0,
77+
NetworkMode: types.NetworkModeAwsvpc,
7778
NetworkInterfaces: []*networkinterface.NetworkInterface{
7879
&netIfs[0],
7980
},
@@ -156,19 +157,21 @@ func getMultiNetNSMultiIfaceAWSVPCTestData(testTaskID string) (*ecsacs.Task, tas
156157
NetworkMode: types.NetworkModeAwsvpc,
157158
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
158159
{
159-
Name: primaryNetNSName,
160-
Path: primaryNetNSPath,
161-
Index: 0,
160+
Name: primaryNetNSName,
161+
Path: primaryNetNSPath,
162+
Index: 0,
163+
NetworkMode: types.NetworkModeAwsvpc,
162164
NetworkInterfaces: []*networkinterface.NetworkInterface{
163165
&netIfs[0],
164166
},
165167
KnownState: status.NetworkNone,
166168
DesiredState: status.NetworkReadyPull,
167169
},
168170
{
169-
Name: secondaryNetNSName,
170-
Path: secondaryNetNSPath,
171-
Index: 1,
171+
Name: secondaryNetNSName,
172+
Path: secondaryNetNSPath,
173+
Index: 1,
174+
NetworkMode: types.NetworkModeAwsvpc,
172175
NetworkInterfaces: []*networkinterface.NetworkInterface{
173176
&netIfs[1],
174177
},
@@ -323,6 +326,7 @@ func getV2NTestData(testTaskID string) (*ecsacs.Task, tasknetworkconfig.TaskNetw
323326
Name: netNSName,
324327
Path: netNSPath,
325328
Index: 0,
329+
NetworkMode: types.NetworkModeAwsvpc,
326330
NetworkInterfaces: netIfs,
327331
KnownState: status.NetworkNone,
328332
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), host(managed-instances only), 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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package tasknetworkconfig
1919
import (
2020
"testing"
2121

22+
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
2223
"github.com/stretchr/testify/assert"
2324
"github.com/stretchr/testify/require"
2425
)
@@ -47,6 +48,7 @@ func TestNewNetworkNamespace(t *testing.T) {
4748
assert.Equal(t, primaryNetNSName, netns.Name)
4849
assert.Equal(t, primaryNetNSPath, netns.Path)
4950
assert.Equal(t, 0, netns.Index)
51+
assert.Equal(t, types.NetworkModeAwsvpc, netns.NetworkMode)
5052
assert.Empty(t, netns.AppMeshConfig)
5153
assert.Equal(t, *netIFs[0], *netns.NetworkInterfaces[0])
5254
assert.Equal(t, *netIFs[1], *netns.NetworkInterfaces[1])
@@ -78,3 +80,41 @@ func TestNetworkNamespace_IsPrimary(t *testing.T) {
7880
require.Equal(t, tc.isPrimary, tc.netNS.IsPrimary())
7981
}
8082
}
83+
84+
func TestNetworkNamespace_WithNetworkMode(t *testing.T) {
85+
testCases := []struct {
86+
name string
87+
networkMode types.NetworkMode
88+
}{
89+
{
90+
name: "awsvpc mode",
91+
networkMode: types.NetworkModeAwsvpc,
92+
},
93+
{
94+
name: "host mode",
95+
networkMode: types.NetworkModeHost,
96+
},
97+
{
98+
name: "bridge mode",
99+
networkMode: types.NetworkModeBridge,
100+
},
101+
}
102+
103+
for _, tc := range testCases {
104+
t.Run(tc.name, func(t *testing.T) {
105+
netns := &NetworkNamespace{
106+
Name: "test-netns",
107+
Path: "/test/path",
108+
Index: 0,
109+
NetworkMode: types.NetworkModeAwsvpc, // default
110+
}
111+
112+
result := netns.WithNetworkMode(tc.networkMode)
113+
114+
// Verify the method returns the same instance
115+
assert.Same(t, netns, result)
116+
// Verify the NetworkMode was updated
117+
assert.Equal(t, tc.networkMode, netns.NetworkMode)
118+
})
119+
}
120+
}

ecs-agent/netlib/network_builder.go

Lines changed: 4 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,8 @@ 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+
err = nb.platformAPI.StopDaemonNetNS(ctx, netNS)
135139
default:
136140
err = errors.New("invalid network mode: " + string(mode))
137141
}

ecs-agent/netlib/network_builder_linux_test.go

Lines changed: 64 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,65 @@ 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 for daemon-bridge network mode.
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+
netNS := &tasknetworkconfig.NetworkNamespace{
402+
Name: "daemon-test",
403+
Path: "/var/run/netns/daemon-test",
404+
}
405+
406+
gomock.InOrder(
407+
metricsFactory.EXPECT().New(metrics.BuildNetworkNamespaceMetricName).Return(mockEntry).Times(1),
408+
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
409+
platformAPI.EXPECT().ConfigureDaemonNetNS(netNS).Return(nil).Times(1),
410+
mockEntry.EXPECT().Done(nil).Times(1),
411+
)
412+
413+
err := netBuilder.Start(ctx, "daemon-bridge", taskID, netNS)
414+
require.NoError(t, err)
415+
}
416+
417+
// testNetworkBuilder_StopDaemonBridge verifies that the expected platform API calls
418+
// are made by the network builder for stopping daemon-bridge network mode.
419+
func testNetworkBuilder_StopDaemonBridge(t *testing.T) {
420+
ctrl := gomock.NewController(t)
421+
defer ctrl.Finish()
422+
423+
ctx := context.TODO()
424+
platformAPI := mock_platform.NewMockAPI(ctrl)
425+
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
426+
mockEntry := mock_metrics.NewMockEntry(ctrl)
427+
netBuilder := &networkBuilder{
428+
platformAPI: platformAPI,
429+
metricsFactory: metricsFactory,
430+
}
431+
432+
netNS := &tasknetworkconfig.NetworkNamespace{
433+
Name: "daemon-test",
434+
Path: "/var/run/netns/daemon-test",
435+
}
436+
437+
gomock.InOrder(
438+
metricsFactory.EXPECT().New(metrics.DeleteNetworkNamespaceMetricName).Return(mockEntry).Times(1),
439+
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
440+
platformAPI.EXPECT().StopDaemonNetNS(ctx, netNS).Return(nil).Times(1),
441+
mockEntry.EXPECT().Done(nil).Times(1),
442+
)
443+
444+
err := netBuilder.Stop(ctx, "daemon-bridge", taskID, netNS)
445+
require.NoError(t, err)
446+
}

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.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const (
2121
ECSSubNet = "169.254.172.0/22"
2222
AgentEndpoint = "169.254.170.2/32"
2323

24+
// Daemon-bridge networking constants
25+
DaemonBridgeGatewayIP = "169.254.172.1"
26+
DefaultRouteDestination = "0.0.0.0/0"
27+
2428
CNIPluginLogFileEnv = "ECS_CNI_LOG_FILE"
2529
VPCCNIPluginLogFileEnv = "VPC_CNI_LOG_FILE"
2630
IPAMDataPathEnv = "IPAM_DB_PATH"

ecs-agent/netlib/platform/cniconf_linux.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,54 @@ func createBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
121121
return bridgeConfig
122122
}
123123

124+
// createDaemonBridgePluginConfig constructs the configuration object for bridge plugin in daemon-bridge mode
125+
// It includes routes for ECS agent endpoint and default route for external traffic (including DNS resolution)
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+
// Add routes for daemon-bridge mode
139+
var routes []*types.Route
140+
routes = append(routes, route) // ECS agent endpoint route
141+
142+
// Add default route for external traffic, which goes through the bridge gateway to reach host's trunk ENI
143+
_, defaultNet, _ := net.ParseCIDR(DefaultRouteDestination)
144+
bridgeGW := net.ParseIP(DaemonBridgeGatewayIP)
145+
defaultRoute := &types.Route{
146+
Dst: *defaultNet,
147+
GW: bridgeGW,
148+
}
149+
routes = append(routes, defaultRoute)
150+
151+
ipamConfig := &ecscni.IPAMConfig{
152+
CNIConfig: ecscni.CNIConfig{
153+
NetNSPath: netNSPath,
154+
CNISpecVersion: cniSpecVersion,
155+
CNIPluginName: IPAMPluginName,
156+
},
157+
IPV4Subnet: ECSSubNet,
158+
IPV4Routes: routes,
159+
ID: netNSPath,
160+
}
161+
162+
// Invoke the bridge plugin and ipam plugin
163+
bridgeConfig := &ecscni.BridgeConfig{
164+
CNIConfig: cniConfig,
165+
Name: BridgeInterfaceName,
166+
IPAM: *ipamConfig,
167+
}
168+
169+
return bridgeConfig
170+
}
171+
124172
func createAppMeshPluginConfig(
125173
netNSPath string,
126174
cfg *appmesh.AppMesh,

ecs-agent/netlib/platform/cniconf_linux_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,47 @@ func getTestV2NInterface() *networkinterface.NetworkInterface {
357357
DomainNameSearchList: []string{searchDomainName},
358358
}
359359
}
360+
361+
func TestCreateDaemonBridgePluginConfig(t *testing.T) {
362+
cniConfig := ecscni.CNIConfig{
363+
NetNSPath: netNSPath,
364+
CNISpecVersion: cniSpecVersion,
365+
CNIPluginName: BridgePluginName,
366+
}
367+
368+
_, agentRouteIPNet, _ := net.ParseCIDR(AgentEndpoint)
369+
agentRoute := &types.Route{
370+
Dst: *agentRouteIPNet,
371+
}
372+
373+
_, defaultNet, _ := net.ParseCIDR(DefaultRouteDestination)
374+
bridgeGW := net.ParseIP(DaemonBridgeGatewayIP)
375+
defaultRoute := &types.Route{
376+
Dst: *defaultNet,
377+
GW: bridgeGW,
378+
}
379+
380+
ipamConfig := &ecscni.IPAMConfig{
381+
CNIConfig: ecscni.CNIConfig{
382+
NetNSPath: netNSPath,
383+
CNISpecVersion: cniSpecVersion,
384+
CNIPluginName: IPAMPluginName,
385+
},
386+
IPV4Subnet: ECSSubNet,
387+
IPV4Routes: []*types.Route{agentRoute, defaultRoute},
388+
ID: netNSPath,
389+
}
390+
391+
bridgeConfig := &ecscni.BridgeConfig{
392+
CNIConfig: cniConfig,
393+
Name: BridgeInterfaceName,
394+
IPAM: *ipamConfig,
395+
}
396+
397+
expected, err := json.Marshal(bridgeConfig)
398+
require.NoError(t, err)
399+
actual, err := json.Marshal(createDaemonBridgePluginConfig(netNSPath))
400+
require.NoError(t, err)
401+
402+
require.Equal(t, expected, actual)
403+
}

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)