ecs-agent/netlib/network_builder.go (212 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package netlib
import (
"context"
"fmt"
"github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/metrics"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/data"
netlibdata "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/data"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/status"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/platform"
"github.com/aws/amazon-ecs-agent/ecs-agent/utils/netwrapper"
"github.com/aws/amazon-ecs-agent/ecs-agent/volume"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
multierror "github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)
type NetworkBuilder interface {
BuildTaskNetworkConfiguration(taskID string, taskPayload *ecsacs.Task) (*tasknetworkconfig.TaskNetworkConfig, error)
Start(ctx context.Context, mode types.NetworkMode, taskID string, netNS *tasknetworkconfig.NetworkNamespace) error
Stop(ctx context.Context, mode types.NetworkMode, taskID string, netNS *tasknetworkconfig.NetworkNamespace) error
}
type networkBuilder struct {
platformAPI platform.API
metricsFactory metrics.EntryFactory
networkDAO netlibdata.NetworkDataClient
}
func NewNetworkBuilder(
platformConfig platform.Config,
metricsFactory metrics.EntryFactory,
volumeAccessor volume.TaskVolumeAccessor,
networkDao data.NetworkDataClient,
stateDBDir string) (NetworkBuilder, error) {
pAPI, err := platform.NewPlatform(
platformConfig,
volumeAccessor,
stateDBDir,
netwrapper.NewNet(),
)
if err != nil {
return nil, errors.Wrap(err, "failed to instantiate network builder")
}
return &networkBuilder{
platformAPI: pAPI,
metricsFactory: metricsFactory,
networkDAO: networkDao,
}, nil
}
// BuildTaskNetworkConfiguration builds the task's network configuration
func (nb *networkBuilder) BuildTaskNetworkConfiguration(
taskID string, taskPayload *ecsacs.Task) (*tasknetworkconfig.TaskNetworkConfig, error) {
return nb.platformAPI.BuildTaskNetworkConfiguration(taskID, taskPayload)
}
// Start builds up a particular network namespace for the task as per desired configuration.
func (nb *networkBuilder) Start(
ctx context.Context,
mode types.NetworkMode, taskID string,
netNS *tasknetworkconfig.NetworkNamespace,
) error {
logFields := map[string]interface{}{
"NetworkMode": mode,
"NetNSName": netNS.Name,
"NetNSPath": netNS.Path,
"AppMeshEnabled": netNS.AppMeshConfig != nil,
"ServiceConnectEnabled": netNS.ServiceConnectConfig != nil,
}
metricEntry := nb.metricsFactory.New(metrics.BuildNetworkNamespaceMetricName).WithFields(logFields)
netNS.Mutex.Lock()
defer netNS.Mutex.Unlock()
logger.Info("Starting network namespace setup", logFields)
var err error
switch mode {
case types.NetworkModeAwsvpc:
err = nb.startAWSVPC(ctx, taskID, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
default:
err = errors.New("invalid network mode: " + string(mode))
}
metricEntry.Done(err)
return err
}
func (nb *networkBuilder) Stop(ctx context.Context, mode types.NetworkMode, taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
logFields := map[string]interface{}{
"NetworkMode": mode,
"NetNSName": netNS.Name,
"NetNSPath": netNS.Path,
"AppMeshEnabled": netNS.AppMeshConfig != nil,
"ServiceConnectEnabled": netNS.ServiceConnectConfig != nil,
}
metricEntry := nb.metricsFactory.New(metrics.DeleteNetworkNamespaceMetricName).WithFields(logFields)
netNS.Mutex.Lock()
defer netNS.Mutex.Unlock()
logger.Info("Deleting network namespace setup", logFields)
var err error
switch mode {
case types.NetworkModeAwsvpc:
err = nb.stopAWSVPC(ctx, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
default:
err = errors.New("invalid network mode: " + string(mode))
}
metricEntry.Done(err)
return err
}
// startAWSVPC executes the required platform API methods in order to configure
// the task's network namespace running in AWSVPC mode.
func (nb *networkBuilder) startAWSVPC(ctx context.Context, taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
var err error
if netNS.DesiredState == status.NetworkDeleted {
return errors.New("invalid transition state encountered: " + netNS.DesiredState.String())
}
// Create the network namespace and setup DNS configuration within the netns.
// This has to happen before any CNI plugin is executed.
if netNS.KnownState == status.NetworkNone &&
netNS.DesiredState == status.NetworkReadyPull {
logger.Debug("Creating netns: " + netNS.Path)
// Create network namespace on the host.
err = nb.platformAPI.CreateNetNS(netNS.Path)
if err != nil {
return err
}
logger.Debug("Creating DNS config files")
// Create necessary DNS config files for the netns.
err = nb.platformAPI.CreateDNSConfig(taskID, netNS)
if err != nil {
return err
}
}
// Configure each interface inside the network namespace.
err = nb.configureNetNSInterfaces(ctx, netNS)
if err != nil {
return err
}
// Configure AppMesh and service connect rules in the netns.
if netNS.KnownState == status.NetworkReadyPull &&
netNS.DesiredState == status.NetworkReady {
if netNS.AppMeshConfig != nil {
logger.Debug("Configuring AppMesh", logger.Fields{
"AppMeshConfig": netNS.AppMeshConfig,
})
err = nb.platformAPI.ConfigureAppMesh(ctx, netNS.Path, netNS.AppMeshConfig)
if err != nil {
return errors.Wrapf(err, "failed to configure AppMesh in netns %s", netNS.Name)
}
}
if netNS.ServiceConnectConfig != nil {
logger.Debug("Configuring ServiceConnect", logger.Fields{
"ServiceConnectConfig": netNS.ServiceConnectConfig,
})
err = nb.platformAPI.ConfigureServiceConnect(
ctx, netNS.Path, netNS.GetPrimaryInterface(), netNS.ServiceConnectConfig)
if err != nil {
return errors.Wrapf(err, "failed to configure ServiceConnect in netns %s", netNS.Name)
}
}
}
return err
}
// configureNetNSInterfaces executes the platform API to configure every interface inside a network namespace.
func (nb *networkBuilder) configureNetNSInterfaces(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error {
var errs error
for _, iface := range netNS.NetworkInterfaces {
logFields := logger.Fields{
"Interface": iface,
"NetNSName": netNS.Name,
"KnownStatus": iface.KnownStatus,
"DesiredStatus": iface.DesiredStatus,
}
if iface.KnownStatus == netNS.DesiredState {
logger.Debug("Interface already in desired state", logFields)
continue
}
// The interface desired status is driven by the network namespace's desired state.
logger.Debug("Configuring interface", logFields)
iface.DesiredStatus = netNS.DesiredState
err := nb.platformAPI.ConfigureInterface(ctx, netNS.Path, iface, nb.networkDAO)
if err != nil {
if netNS.DesiredState == status.NetworkDeleted {
logger.Error(fmt.Sprintf("Failed to configure interface: %v", err), logFields)
errs = multierror.Append(err, errs)
} else {
return err
}
}
iface.KnownStatus = netNS.DesiredState
// Save new state of the network interface in the database.
if err = nb.networkDAO.SaveNetworkNamespace(netNS); err != nil {
if netNS.DesiredState == status.NetworkDeleted {
logger.Error(fmt.Sprintf("Failed to persist interface state: %v", err), logFields)
errs = multierror.Append(err, errs)
} else {
return err
}
}
}
return errs
}
func (nb *networkBuilder) stopAWSVPC(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error {
var errs error
if netNS.DesiredState != status.NetworkDeleted {
return errors.New("invalid transition state encountered: " + netNS.DesiredState.String())
}
logFields := logger.Fields{
"NetNSName": netNS.Name,
}
err := nb.configureNetNSInterfaces(ctx, netNS)
if err != nil {
logger.Error(fmt.Sprintf("Failed to cleanup interfaces in netns: %v", err), logFields)
errs = multierror.Append(err, errs)
}
err = nb.platformAPI.DeleteDNSConfig(netNS.Name)
if err != nil {
logger.Error(fmt.Sprintf("Failed to cleanup DNS config files: %v", err))
errs = multierror.Append(err, errs)
}
err = nb.platformAPI.DeleteNetNS(netNS.Path)
if err != nil {
logger.Error(fmt.Sprintf("Failed to delete network namespace: %v", err), logFields)
errs = multierror.Append(err, errs)
}
return errs
}