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 }