Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 9 additions & 36 deletions ecs-agent/netlib/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,10 @@ func getSingleNetNSAWSVPCTestData(testTaskID string) (*ecsacs.Task, tasknetworkc

netNSName := fmt.Sprintf(netNSNamePattern, testTaskID, eniName)
netNSPath := netNSPathDir + netNSName
netNS, _ := tasknetworkconfig.NewNetworkNamespace(netNSName, netNSPath, 0, nil, &netIfs[0])
taskNetConfig := tasknetworkconfig.TaskNetworkConfig{
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
{
Name: netNSName,
Path: netNSPath,
Index: 0,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[0],
},
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
},
},
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{netNS},
}

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

primaryNetNS, _ := tasknetworkconfig.NewNetworkNamespace(primaryNetNSName, primaryNetNSPath, 0, nil, &netIfs[0])
secondaryNetNS, _ := tasknetworkconfig.NewNetworkNamespace(secondaryNetNSName, secondaryNetNSPath, 1, nil, &netIfs[1])

taskNetConfig := tasknetworkconfig.TaskNetworkConfig{
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
{
Name: primaryNetNSName,
Path: primaryNetNSPath,
Index: 0,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[0],
},
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
},
{
Name: secondaryNetNSName,
Path: secondaryNetNSPath,
Index: 1,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[1],
},
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
},
},
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{primaryNetNS, secondaryNetNS},
}

return taskPayload, taskNetConfig
Expand Down Expand Up @@ -323,6 +295,7 @@ func getV2NTestData(testTaskID string) (*ecsacs.Task, tasknetworkconfig.TaskNetw
Name: netNSName,
Path: netNSPath,
Index: 0,
NetworkMode: types.NetworkModeAwsvpc,
NetworkInterfaces: netIfs,
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
Expand Down
12 changes: 12 additions & 0 deletions ecs-agent/netlib/model/tasknetworkconfig/network_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/serviceconnect"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/status"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
)

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

// NetworkMode represents the network mode for this namespace.
// Supported values: awsvpc (default), daemon-bridge (managed-instances only).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there someplace we enforce this? like can a caller call with any mode possible or can a caller not using EMI call with daemon-bridge mode?

NetworkMode types.NetworkMode

// NetworkInterfaces represents ENIs or any kind of network interface associated the particular netns.
NetworkInterfaces []*networkinterface.NetworkInterface

Expand Down Expand Up @@ -58,6 +63,7 @@ func NewNetworkNamespace(
NetworkInterfaces: networkInterfaces,
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
NetworkMode: types.NetworkModeAwsvpc,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a comment here that we are defaulting to awsvpc if the caller does not explicity set the network mode.

}

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

return nil
}

// WithNetworkMode sets the NetworkMode field
func (ns *NetworkNamespace) WithNetworkMode(mode types.NetworkMode) *NetworkNamespace {
ns.NetworkMode = mode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a validation here that the caller should only send the supported modes?

return ns
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestNewNetworkNamespace(t *testing.T) {
assert.Equal(t, primaryNetNSName, netns.Name)
assert.Equal(t, primaryNetNSPath, netns.Path)
assert.Equal(t, 0, netns.Index)
assert.Equal(t, "awsvpc", string(netns.NetworkMode))
assert.Empty(t, netns.AppMeshConfig)
assert.Equal(t, *netIFs[0], *netns.NetworkInterfaces[0])
assert.Equal(t, *netIFs[1], *netns.NetworkInterfaces[1])
Expand Down Expand Up @@ -78,3 +79,10 @@ func TestNetworkNamespace_IsPrimary(t *testing.T) {
require.Equal(t, tc.isPrimary, tc.netNS.IsPrimary())
}
}

func TestNetworkNamespace_WithNetworkMode(t *testing.T) {
netns := &NetworkNamespace{}
result := netns.WithNetworkMode("daemon-bridge")
assert.Equal(t, "daemon-bridge", string(result.NetworkMode))
assert.Equal(t, netns, result) // Should return same instance
}
6 changes: 6 additions & 0 deletions ecs-agent/netlib/network_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (nb *networkBuilder) Start(
err = nb.startAWSVPC(ctx, taskID, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
case "daemon-bridge":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this hard-coded config and move this to the enum.

err = nb.platformAPI.ConfigureDaemonNetNS(netNS)
default:
err = errors.New("invalid network mode: " + string(mode))
}
Expand Down Expand Up @@ -132,6 +134,10 @@ func (nb *networkBuilder) Stop(ctx context.Context, mode types.NetworkMode, task
err = nb.stopAWSVPC(ctx, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
case "daemon-bridge":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here.

// Adding extra logging to help with debug TODO remove later.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove this comment.

logger.Info("Stopping Daemon network namespace setup", logFields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this log in production?

err = nb.platformAPI.StopDaemonNetNS(ctx, netNS)
default:
err = errors.New("invalid network mode: " + string(mode))
}
Expand Down
65 changes: 65 additions & 0 deletions ecs-agent/netlib/network_builder_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ func TestNetworkBuilder_BuildTaskNetworkConfiguration(t *testing.T) {

func TestNetworkBuilder_Start(t *testing.T) {
t.Run("awsvpc", testNetworkBuilder_StartAWSVPC)
t.Run("daemon-bridge", testNetworkBuilder_StartDaemonBridge)
}

// TestNetworkBuilder_Stop verifies stop workflow for AWSVPC mode.
func TestNetworkBuilder_Stop(t *testing.T) {
t.Run("awsvpc", testNetworkBuilder_StopAWSVPC)
t.Run("daemon-bridge", testNetworkBuilder_StopDaemonBridge)
}

// getTestFunc returns a test function that verifies the capability of the networkBuilder
Expand Down Expand Up @@ -380,3 +382,66 @@ func getExpectedCalls_StopAWSVPC(
platformAPI.EXPECT().DeleteDNSConfig(netNS.Name).Return(nil).Times(1),
platformAPI.EXPECT().DeleteNetNS(netNS.Path).Return(nil).Times(1))
}

// testNetworkBuilder_StartDaemonBridge verifies that the expected platform API calls
// are made by the network builder while configuring daemon-bridge network namespace.
func testNetworkBuilder_StartDaemonBridge(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx := context.TODO()
platformAPI := mock_platform.NewMockAPI(ctrl)
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
mockEntry := mock_metrics.NewMockEntry(ctrl)
netBuilder := &networkBuilder{
platformAPI: platformAPI,
metricsFactory: metricsFactory,
}

// Create a daemon-bridge network namespace
netNS, _ := tasknetworkconfig.NewNetworkNamespace("daemon-ns", "/var/run/netns/daemon-ns", 0, nil)
netNS = netNS.WithNetworkMode("daemon-bridge")
netNS.KnownState = status.NetworkNone
netNS.DesiredState = status.NetworkReadyPull

t.Run("daemon-bridge-start", func(*testing.T) {
gomock.InOrder(
metricsFactory.EXPECT().New(metrics.BuildNetworkNamespaceMetricName).Return(mockEntry).Times(1),
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
platformAPI.EXPECT().ConfigureDaemonNetNS(netNS).Return(nil).Times(1),
mockEntry.EXPECT().Done(nil).Times(1),
)
netBuilder.Start(ctx, "daemon-bridge", taskID, netNS)
})
}

// testNetworkBuilder_StopDaemonBridge verifies that the cleanup of daemon-bridge
// network namespace works as expected.
func testNetworkBuilder_StopDaemonBridge(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx := context.TODO()
platformAPI := mock_platform.NewMockAPI(ctrl)
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
mockEntry := mock_metrics.NewMockEntry(ctrl)
netBuilder := &networkBuilder{
platformAPI: platformAPI,
metricsFactory: metricsFactory,
}

// Create a daemon-bridge network namespace
netNS, _ := tasknetworkconfig.NewNetworkNamespace("daemon-ns", "/var/run/netns/daemon-ns", 0, nil)
netNS = netNS.WithNetworkMode("daemon-bridge")
netNS.DesiredState = status.NetworkDeleted

t.Run("daemon-bridge-stop", func(*testing.T) {
gomock.InOrder(
metricsFactory.EXPECT().New(metrics.DeleteNetworkNamespaceMetricName).Return(mockEntry).Times(1),
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
platformAPI.EXPECT().StopDaemonNetNS(ctx, netNS).Return(nil).Times(1),
mockEntry.EXPECT().Done(nil).Times(1),
)
netBuilder.Stop(ctx, "daemon-bridge", taskID, netNS)
})
}
8 changes: 8 additions & 0 deletions ecs-agent/netlib/platform/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ type API interface {
primaryIf *networkinterface.NetworkInterface,
scConfig *serviceconnect.ServiceConnectConfig,
) error

// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error

// StopDaemonNetNS stops and cleans up a daemon network namespace.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error
}

// Config contains platform-specific data.
Expand Down
37 changes: 36 additions & 1 deletion ecs-agent/netlib/platform/cniconf_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const (
VPCTunnelInterfaceTypeGeneve = "geneve"
VPCTunnelInterfaceTypeTap = "tap"

BridgeInterfaceName = "fargate-bridge"
BridgeInterfaceName = "fargate-bridge"
ManagedInstanceBridgeName = "mi-bridge"
Comment on lines +56 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This change will impact all EMI tasks, not just the daemon tasks. Should we split this into a separate PR?
  • should we call it emi-bridge ?


IPAMDataFileName = "eni-ipam.db"

Expand Down Expand Up @@ -121,6 +122,40 @@ func createBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
return bridgeConfig
}

// createBridgePluginConfig constructs the configuration object for bridge plugin
func createDaemonBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a managed instance bridge, not a daemon bridge .. correct?

cniConfig := ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: BridgePluginName,
}

_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
route := &types.Route{
Dst: *routeIPNet,
}

ipamConfig := &ecscni.IPAMConfig{
CNIConfig: ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: IPAMPluginName,
},
IPV4Subnet: ECSSubNet,
IPV4Routes: []*types.Route{route},
ID: netNSPath,
}

// Invoke the bridge plugin and ipam plugin
bridgeConfig := &ecscni.BridgeConfig{
CNIConfig: cniConfig,
Name: ManagedInstanceBridgeName,
IPAM: *ipamConfig,
}

return bridgeConfig
}

func createAppMeshPluginConfig(
netNSPath string,
cfg *appmesh.AppMesh,
Expand Down
38 changes: 38 additions & 0 deletions ecs-agent/netlib/platform/cniconf_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,44 @@ func TestCreateBridgeConfig(t *testing.T) {
require.Equal(t, expected, actual)
}

func TestCreateDaemonBridgeConfig(t *testing.T) {
cniConfig := ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: BridgePluginName,
}

_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
route := &types.Route{
Dst: *routeIPNet,
}

ipamConfig := &ecscni.IPAMConfig{
CNIConfig: ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: IPAMPluginName,
},
IPV4Subnet: ECSSubNet,
IPV4Routes: []*types.Route{route},
ID: netNSPath,
}

// Invoke the bridge plugin and ipam plugin
bridgeConfig := &ecscni.BridgeConfig{
CNIConfig: cniConfig,
Name: ManagedInstanceBridgeName,
IPAM: *ipamConfig,
}

expected, err := json.Marshal(bridgeConfig)
require.NoError(t, err)
actual, err := json.Marshal(createDaemonBridgePluginConfig(netNSPath))
require.NoError(t, err)

require.Equal(t, expected, actual)
}

func TestCreateENIConfig(t *testing.T) {
for _, tc := range []struct {
name string
Expand Down
13 changes: 13 additions & 0 deletions ecs-agent/netlib/platform/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/ecscni"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"

"github.com/containernetworking/cni/pkg/types"
)
Expand Down Expand Up @@ -92,3 +93,15 @@ func (c *common) interfacesMACToName() (map[string]string, error) {
func (c *common) HandleHostMode() error {
return errors.New("invalid platform for host mode")
}

// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
func (c *common) ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error {
return errors.New("daemon network namespaces are not supported in this platform")
}

// StopDaemonNetNS stops and cleans up a daemon network namespace.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
func (c *common) StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error {
return errors.New("daemon network namespaces are not supported in this platform")
}
7 changes: 4 additions & 3 deletions ecs-agent/netlib/platform/containerd_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,10 @@ func (c *containerd) buildAWSVPCNetworkConfig(
}

netNS := &tasknetworkconfig.NetworkNamespace{
Name: netNSName,
Path: netNSPath,
Index: 0,
Name: netNSName,
Path: netNSPath,
Index: 0,
NetworkMode: ecstypes.NetworkModeAwsvpc,
NetworkInterfaces: []*networkinterface.NetworkInterface{
iface,
},
Expand Down
Loading
Loading