From 26ff26d6b3702938eb30776d6a4234275b60f7dc Mon Sep 17 00:00:00 2001 From: aws-gibbskt <88005500+aws-gibbskt@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:52:17 -0500 Subject: [PATCH] Add loading of AppNet Agent and capability publishing (#3281) * Add a container loader for AppNet Agent * Add the agent capability for ServiceConnect --- agent/app/agent.go | 3 + agent/app/agent_capability.go | 23 ++ agent/app/agent_capability_test.go | 378 +++++++++++++++------ agent/app/agent_capability_unix_test.go | 163 +++++---- agent/app/agent_capability_windows_test.go | 27 +- agent/app/agent_test.go | 149 +++++--- agent/app/agent_unix_test.go | 18 +- agent/engine/docker_task_engine.go | 9 +- agent/serviceconnect/error.go | 52 +++ agent/serviceconnect/error_test.go | 52 +++ agent/serviceconnect/generate_mocks.go | 16 + agent/serviceconnect/load.go | 82 +++++ agent/serviceconnect/load_linux.go | 70 ++++ agent/serviceconnect/load_linux_test.go | 122 +++++++ agent/serviceconnect/load_test.go | 158 +++++++++ agent/serviceconnect/load_unsupported.go | 43 +++ agent/serviceconnect/mocks/load_mocks.go | 81 +++++ 17 files changed, 1213 insertions(+), 233 deletions(-) create mode 100644 agent/serviceconnect/error.go create mode 100644 agent/serviceconnect/error_test.go create mode 100644 agent/serviceconnect/generate_mocks.go create mode 100644 agent/serviceconnect/load.go create mode 100644 agent/serviceconnect/load_linux.go create mode 100644 agent/serviceconnect/load_linux_test.go create mode 100644 agent/serviceconnect/load_test.go create mode 100644 agent/serviceconnect/load_unsupported.go create mode 100644 agent/serviceconnect/mocks/load_mocks.go diff --git a/agent/app/agent.go b/agent/app/agent.go index 8c7cfd8363..146cf34f0b 100644 --- a/agent/app/agent.go +++ b/agent/app/agent.go @@ -52,6 +52,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/eventhandler" "github.com/aws/amazon-ecs-agent/agent/eventstream" "github.com/aws/amazon-ecs-agent/agent/handlers" + "github.com/aws/amazon-ecs-agent/agent/serviceconnect" "github.com/aws/amazon-ecs-agent/agent/sighandlers" "github.com/aws/amazon-ecs-agent/agent/sighandlers/exitcodes" "github.com/aws/amazon-ecs-agent/agent/statemanager" @@ -134,6 +135,7 @@ type ecsAgent struct { stateManagerFactory factory.StateManager saveableOptionFactory factory.SaveableOption pauseLoader pause.Loader + serviceconnectLoader serviceconnect.Loader eniWatcher *watcher.ENIWatcher cniClient ecscni.CNIClient vpc string @@ -229,6 +231,7 @@ func newAgent(blackholeEC2Metadata bool, acceptInsecureCert *bool) (agent, error stateManagerFactory: factory.NewStateManager(), saveableOptionFactory: factory.NewSaveableOption(), pauseLoader: pause.New(), + serviceconnectLoader: serviceconnect.New(), cniClient: ecscni.NewClient(cfg.CNIPluginsPath), metadataManager: metadataManager, terminationHandler: sighandlers.StartDefaultTerminationHandler, diff --git a/agent/app/agent_capability.go b/agent/app/agent_capability.go index 86b2bbe437..e0c8d5903e 100644 --- a/agent/app/agent_capability.go +++ b/agent/app/agent_capability.go @@ -23,6 +23,8 @@ import ( "github.com/aws/amazon-ecs-agent/agent/config" "github.com/aws/amazon-ecs-agent/agent/dockerclient" "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" + "github.com/aws/amazon-ecs-agent/agent/logger" + "github.com/aws/amazon-ecs-agent/agent/logger/field" "github.com/aws/aws-sdk-go/aws" "github.com/cihub/seelog" "github.com/pkg/errors" @@ -73,6 +75,7 @@ const ( capabilityExecConfigRelativePath = "config" capabilityExecCertsRelativePath = "certs" capabilityExternal = "external" + capabilityServiceConnect = "service-connect-v1" ) var ( @@ -114,6 +117,7 @@ var ( attributePrefix + appMeshAttributeSuffix, attributePrefix + taskEIAAttributeSuffix, attributePrefix + taskEIAWithOptimizedCPU, + attributePrefix + capabilityServiceConnect, } // List of capabilities that are only supported on external capaciity. Currently only one but keep as a list // for future proof and also align with externalUnsupportedCapabilities. @@ -176,6 +180,7 @@ var ( // ecs.capability.fsxWindowsFileServer // ecs.capability.execute-command // ecs.capability.external +// ecs.capability.service-connect-v1 func (agent *ecsAgent) capabilities() ([]*ecs.Attribute, error) { var capabilities []*ecs.Attribute @@ -271,6 +276,8 @@ func (agent *ecsAgent) capabilities() ([]*ecs.Attribute, error) { if err != nil { return nil, err } + // add service-connect capabilities if applicable + capabilities = agent.appendServiceConnectCapabilities(capabilities) if agent.cfg.External.Enabled() { // Add external specific capability; remove external unsupported capabilities. @@ -438,6 +445,22 @@ func (agent *ecsAgent) appendExecCapabilities(capabilities []*ecs.Attribute) ([] return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityExec), nil } +func (agent *ecsAgent) appendServiceConnectCapabilities(capabilities []*ecs.Attribute) []*ecs.Attribute { + if loaded, _ := agent.serviceconnectLoader.IsLoaded(agent.dockerClient); !loaded { + _, err := agent.serviceconnectLoader.LoadImage(agent.ctx, agent.dockerClient) + if err != nil { + logger.Error("ServiceConnect Capability: Failed to load appnet Agent container. This container instance will not be able to support ServiceConnect tasks", + logger.Fields{ + field.Error: err, + }, + ) + return capabilities + } + } + + return appendNameOnlyAttribute(capabilities, attributePrefix+capabilityServiceConnect) +} + func defaultGetSubDirectories(path string) ([]string, error) { var subDirectories []string diff --git a/agent/app/agent_capability_test.go b/agent/app/agent_capability_test.go index 4048496598..002a39699e 100644 --- a/agent/app/agent_capability_test.go +++ b/agent/app/agent_capability_test.go @@ -32,6 +32,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" mock_ecscni "github.com/aws/amazon-ecs-agent/agent/ecscni/mocks" mock_pause "github.com/aws/amazon-ecs-agent/agent/eni/pause/mocks" + mock_serviceconnect "github.com/aws/amazon-ecs-agent/agent/serviceconnect/mocks" mock_mobypkgwrapper "github.com/aws/amazon-ecs-agent/agent/utils/mobypkgwrapper/mocks" "github.com/aws/aws-sdk-go/aws" @@ -90,6 +91,10 @@ func TestCapabilities(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + // Scan() and ListPluginsWithFilters() are tested with // AnyTimes() because they are not called in windows. gomock.InOrder( @@ -132,6 +137,7 @@ func TestCapabilities(t *testing.T) { attributePrefix + capabilityEnvFilesS3, attributePrefix + taskENIBlockInstanceMetadataAttributeSuffix, attributePrefix + capabilityExec, + attributePrefix + capabilityServiceConnect, } var expectedCapabilities []*ecs.Attribute @@ -151,13 +157,14 @@ func TestCapabilities(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -225,6 +232,9 @@ func getCapabilitiesWithConfig(cfg *config.Config, t *testing.T) []*ecs.Attribut mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() // CNI plugins are platform dependent. Therefore return version for any plugin query. mockCNIClient.EXPECT().Version(gomock.Any()).Return("v1", nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -244,13 +254,14 @@ func getCapabilitiesWithConfig(cfg *config.Config, t *testing.T) []*ecs.Attribut // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: cfg, - dockerClient: client, - pauseLoader: mockPauseLoader, - cniClient: mockCNIClient, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: cfg, + dockerClient: client, + pauseLoader: mockPauseLoader, + cniClient: mockCNIClient, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() require.NoError(t, err) @@ -275,15 +286,19 @@ func TestCapabilitiesECR(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - pauseLoader: mockPauseLoader, - dockerClient: client, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + pauseLoader: mockPauseLoader, + dockerClient: client, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -321,15 +336,19 @@ func TestCapabilitiesTaskIAMRoleForSupportedDockerVersion(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -364,15 +383,19 @@ func TestCapabilitiesTaskIAMRoleForUnSupportedDockerVersion(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -408,15 +431,19 @@ func TestCapabilitiesTaskIAMRoleNetworkHostForSupportedDockerVersion(t *testing. mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -452,15 +479,19 @@ func TestCapabilitiesTaskIAMRoleNetworkHostForUnSupportedDockerVersion(t *testin mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -493,6 +524,9 @@ func TestAWSVPCBlockInstanceMetadataWhenTaskENIIsDisabled(t *testing.T) { mockMobyPlugins := mock_mobypkgwrapper.NewMockPlugins(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -525,13 +559,14 @@ func TestAWSVPCBlockInstanceMetadataWhenTaskENIIsDisabled(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -576,16 +611,19 @@ func TestCapabilitiesExecutionRoleAWSLogs(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -610,6 +648,8 @@ func TestCapabilitiesTaskResourceLimit(t *testing.T) { mockMobyPlugins := mock_mobypkgwrapper.NewMockPlugins(ctrl) mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -621,11 +661,12 @@ func TestCapabilitiesTaskResourceLimit(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } expectedCapability := attributePrefix + capabilityTaskCPUMemLimit @@ -653,6 +694,8 @@ func TestCapabilitesTaskResourceLimitDisabledByMissingDockerVersion(t *testing.T mockMobyPlugins := mock_mobypkgwrapper.NewMockPlugins(ctrl) mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -664,11 +707,12 @@ func TestCapabilitesTaskResourceLimitDisabledByMissingDockerVersion(t *testing.T // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } unexpectedCapability := attributePrefix + capabilityTaskCPUMemLimit @@ -695,6 +739,8 @@ func TestCapabilitesTaskResourceLimitErrorCase(t *testing.T) { versionList := []dockerclient.DockerVersion{dockerclient.Version_1_19} mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -703,10 +749,11 @@ func TestCapabilitesTaskResourceLimitErrorCase(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - pauseLoader: mockPauseLoader, - dockerClient: client, + ctx: ctx, + cfg: conf, + pauseLoader: mockPauseLoader, + dockerClient: client, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -754,6 +801,8 @@ func TestCapabilitiesIncreasedTaskCPULimit(t *testing.T) { mockMobyPlugins := mock_mobypkgwrapper.NewMockPlugins(ctrl) mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -765,11 +814,12 @@ func TestCapabilitiesIncreasedTaskCPULimit(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capability := attributePrefix + capabilityIncreasedTaskCPULimit @@ -804,16 +854,19 @@ func TestCapabilitiesContainerHealth(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &config.Config{}, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &config.Config{}, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -845,16 +898,19 @@ func TestCapabilitiesContainerHealthDisabled(t *testing.T) { mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() ctx, cancel := context.WithCancel(context.TODO()) // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &config.Config{DisableDockerHealthCheck: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}}, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &config.Config{DisableDockerHealthCheck: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}}, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -877,6 +933,8 @@ func TestCapabilitesListPluginsErrorCase(t *testing.T) { versionList := []dockerclient.DockerVersion{dockerclient.Version_1_19} mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -888,11 +946,12 @@ func TestCapabilitesListPluginsErrorCase(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &config.Config{}, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &config.Config{}, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -914,6 +973,8 @@ func TestCapabilitesScanPluginsErrorCase(t *testing.T) { versionList := []dockerclient.DockerVersion{dockerclient.Version_1_19} mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -925,11 +986,12 @@ func TestCapabilitesScanPluginsErrorCase(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &config.Config{}, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &config.Config{}, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -1021,6 +1083,8 @@ func TestCapabilitiesExecuteCommand(t *testing.T) { versionList := []dockerclient.DockerVersion{dockerclient.Version_1_19} mockPauseLoader := mock_pause.NewMockLoader(ctrl) mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return(versionList), client.EXPECT().KnownVersions().Return(versionList), @@ -1032,11 +1096,12 @@ func TestCapabilitiesExecuteCommand(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &config.Config{}, - dockerClient: client, - pauseLoader: mockPauseLoader, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &config.Config{}, + dockerClient: client, + pauseLoader: mockPauseLoader, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() @@ -1053,6 +1118,127 @@ func TestCapabilitiesExecuteCommand(t *testing.T) { } } +func TestCapabilitiesNoServiceConnect(t *testing.T) { + mockPathExists(true) + defer mockPathExists(false) + getSubDirectories = func(path string) ([]string, error) { + // appendExecCapabilities() requires at least 1 version to exist + return []string{"3.0.236.0"}, nil + } + defer func() { + getSubDirectories = defaultGetSubDirectories + }() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + client := mock_dockerapi.NewMockDockerClient(ctrl) + cniClient := mock_ecscni.NewMockCNIClient(ctrl) + mockCredentialsProvider := app_mocks.NewMockProvider(ctrl) + mockMobyPlugins := mock_mobypkgwrapper.NewMockPlugins(ctrl) + mockPauseLoader := mock_pause.NewMockLoader(ctrl) + conf := &config.Config{ + AvailableLoggingDrivers: []dockerclient.LoggingDriver{ + dockerclient.JSONFileDriver, + dockerclient.SyslogDriver, + dockerclient.JournaldDriver, + dockerclient.GelfDriver, + dockerclient.FluentdDriver, + }, + PrivilegedDisabled: config.BooleanDefaultFalse{Value: config.ExplicitlyDisabled}, + SELinuxCapable: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}, + AppArmorCapable: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}, + TaskENIEnabled: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}, + AWSVPCBlockInstanceMetdata: config.BooleanDefaultFalse{Value: config.ExplicitlyEnabled}, + TaskCleanupWaitDuration: config.DefaultConfig().TaskCleanupWaitDuration, + } + + mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() + mockServiceConnectLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any()).Return(nil, errors.New("No File")).AnyTimes() + + // Scan() and ListPluginsWithFilters() are tested with + // AnyTimes() because they are not called in windows. + gomock.InOrder( + client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ + dockerclient.Version_1_17, + dockerclient.Version_1_18, + }), + client.EXPECT().KnownVersions().Return([]dockerclient.DockerVersion{ + dockerclient.Version_1_17, + dockerclient.Version_1_18, + dockerclient.Version_1_19, + }), + // CNI plugins are platform dependent. + // Therefore, for any version query for any plugin return an appropriate version + cniClient.EXPECT().Version(gomock.Any()).Return("v1", nil), + mockMobyPlugins.EXPECT().Scan().AnyTimes().Return([]string{}, nil), + client.EXPECT().ListPluginsWithFilters(gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Any()).AnyTimes().Return([]string{}, nil), + ) + + expectedNameOnlyCapabilities := []string{ + capabilityPrefix + "privileged-container", + capabilityPrefix + "docker-remote-api.1.17", + capabilityPrefix + "docker-remote-api.1.18", + capabilityPrefix + "logging-driver.json-file", + capabilityPrefix + "logging-driver.syslog", + capabilityPrefix + "logging-driver.journald", + capabilityPrefix + "selinux", + capabilityPrefix + "apparmor", + attributePrefix + "docker-plugin.local", + attributePrefix + taskENIAttributeSuffix, + attributePrefix + capabilityPrivateRegistryAuthASM, + attributePrefix + capabilitySecretEnvSSM, + attributePrefix + capabilitySecretLogDriverSSM, + attributePrefix + capabilityECREndpoint, + attributePrefix + capabilitySecretEnvASM, + attributePrefix + capabilitySecretLogDriverASM, + attributePrefix + capabilityContainerOrdering, + attributePrefix + capabilityFullTaskSync, + attributePrefix + capabilityEnvFilesS3, + attributePrefix + taskENIBlockInstanceMetadataAttributeSuffix, + attributePrefix + capabilityExec, + } + + var expectedCapabilities []*ecs.Attribute + for _, name := range expectedNameOnlyCapabilities { + expectedCapabilities = append(expectedCapabilities, + &ecs.Attribute{Name: aws.String(name)}) + } + expectedCapabilities = append(expectedCapabilities, + []*ecs.Attribute{ + { + Name: aws.String(attributePrefix + cniPluginVersionSuffix), + Value: aws.String("v1"), + }, + }...) + + ctx, cancel := context.WithCancel(context.TODO()) + // Cancel the context to cancel async routines + defer cancel() + agent := &ecsAgent{ + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, + } + capabilities, err := agent.capabilities() + assert.NoError(t, err) + + for _, expected := range expectedCapabilities { + assert.Contains(t, capabilities, &ecs.Attribute{ + Name: expected.Name, + Value: expected.Value, + }) + } +} + func TestDefaultGetSubDirectories(t *testing.T) { rootDir, err := ioutil.TempDir(os.TempDir(), testTempDirPrefix) if err != nil { diff --git a/agent/app/agent_capability_unix_test.go b/agent/app/agent_capability_unix_test.go index fc476506f6..48844303f8 100644 --- a/agent/app/agent_capability_unix_test.go +++ b/agent/app/agent_capability_unix_test.go @@ -33,6 +33,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecscni" mock_ecscni "github.com/aws/amazon-ecs-agent/agent/ecscni/mocks" "github.com/aws/amazon-ecs-agent/agent/gpu" + mock_serviceconnect "github.com/aws/amazon-ecs-agent/agent/serviceconnect/mocks" "github.com/aws/amazon-ecs-agent/agent/taskresource" "github.com/aws/amazon-ecs-agent/agent/utils" mock_mobypkgwrapper "github.com/aws/amazon-ecs-agent/agent/utils/mobypkgwrapper/mocks" @@ -72,6 +73,8 @@ func TestVolumeDriverCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -123,13 +126,14 @@ func TestVolumeDriverCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -156,6 +160,8 @@ func TestNvidiaDriverCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -199,6 +205,7 @@ func TestNvidiaDriverCapabilitiesUnix(t *testing.T) { DriverVersion: "396.44", }, }, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -225,6 +232,8 @@ func TestEmptyNvidiaDriverCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -266,6 +275,7 @@ func TestEmptyNvidiaDriverCapabilitiesUnix(t *testing.T) { DriverVersion: "", }, }, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -294,6 +304,8 @@ func TestENITrunkingCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -342,13 +354,14 @@ func TestENITrunkingCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -378,6 +391,8 @@ func TestNoENITrunkingCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -418,13 +433,14 @@ func TestNoENITrunkingCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -450,6 +466,8 @@ func TestPIDAndIPCNamespaceSharingCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -486,12 +504,13 @@ func TestPIDAndIPCNamespaceSharingCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -517,6 +536,8 @@ func TestPIDAndIPCNamespaceSharingCapabilitiesNoPauseContainer(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, errors.New("mock error")) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -552,12 +573,13 @@ func TestPIDAndIPCNamespaceSharingCapabilitiesNoPauseContainer(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -583,6 +605,8 @@ func TestAppMeshCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -621,12 +645,13 @@ func TestAppMeshCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -657,6 +682,8 @@ func TestTaskEIACapabilitiesNoOptimizedCPU(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -673,12 +700,13 @@ func TestTaskEIACapabilitiesNoOptimizedCPU(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -705,6 +733,8 @@ func TestTaskEIACapabilitiesWithOptimizedCPU(t *testing.T) { defer resetOpenFile() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -721,12 +751,13 @@ func TestTaskEIACapabilitiesWithOptimizedCPU(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -750,6 +781,8 @@ func TestCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -793,12 +826,13 @@ func TestCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -823,6 +857,8 @@ func TestFirelensConfigCapabilitiesUnix(t *testing.T) { } mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( client.EXPECT().SupportedVersions().Return([]dockerclient.DockerVersion{ dockerclient.Version_1_17, @@ -839,12 +875,13 @@ func TestFirelensConfigCapabilitiesUnix(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } capabilities, err := agent.capabilities() assert.NoError(t, err) diff --git a/agent/app/agent_capability_windows_test.go b/agent/app/agent_capability_windows_test.go index c5568537c9..60212448fc 100644 --- a/agent/app/agent_capability_windows_test.go +++ b/agent/app/agent_capability_windows_test.go @@ -27,6 +27,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/ecs_client/model/ecs" "github.com/aws/amazon-ecs-agent/agent/ecscni" mock_ecscni "github.com/aws/amazon-ecs-agent/agent/ecscni/mocks" + "github.com/aws/amazon-ecs-agent/agent/serviceconnect" mock_mobypkgwrapper "github.com/aws/amazon-ecs-agent/agent/utils/mobypkgwrapper/mocks" "github.com/aws/aws-sdk-go/aws" @@ -107,12 +108,13 @@ func TestVolumeDriverCapabilitiesWindows(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: serviceconnect.New(), } capabilities, err := agent.capabilities() assert.NoError(t, err) @@ -203,12 +205,13 @@ func TestSupportedCapabilitiesWindows(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: conf, - dockerClient: client, - cniClient: cniClient, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: conf, + dockerClient: client, + cniClient: cniClient, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: serviceconnect.New(), } capabilities, err := agent.capabilities() assert.NoError(t, err) diff --git a/agent/app/agent_test.go b/agent/app/agent_test.go index 5a19cca1fb..81a3cf8b7f 100644 --- a/agent/app/agent_test.go +++ b/agent/app/agent_test.go @@ -47,6 +47,7 @@ import ( mock_engine "github.com/aws/amazon-ecs-agent/agent/engine/mocks" mock_pause "github.com/aws/amazon-ecs-agent/agent/eni/pause/mocks" "github.com/aws/amazon-ecs-agent/agent/eventstream" + mock_serviceconnect "github.com/aws/amazon-ecs-agent/agent/serviceconnect/mocks" "github.com/aws/amazon-ecs-agent/agent/sighandlers/exitcodes" "github.com/aws/amazon-ecs-agent/agent/statemanager" mock_statemanager "github.com/aws/amazon-ecs-agent/agent/statemanager/mocks" @@ -217,6 +218,8 @@ func TestDoStartRegisterContainerInstanceErrorTerminal(t *testing.T) { mockEC2Metadata.EXPECT().PrimaryENIMAC().Return("mac", nil) mockEC2Metadata.EXPECT().VPCID(gomock.Eq("mac")).Return("vpc-id", nil) mockEC2Metadata.EXPECT().SubnetID(gomock.Eq("mac")).Return("subnet-id", nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( dockerClient.EXPECT().SupportedVersions().Return(apiVersions), mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -246,6 +249,7 @@ func TestDoStartRegisterContainerInstanceErrorTerminal(t *testing.T) { ec2MetadataClient: mockEC2Metadata, terminationHandler: func(taskEngineState dockerstate.TaskEngineState, dataClient data.Client, taskEngine engine.TaskEngine, cancel context.CancelFunc) { }, + serviceconnectLoader: mockServiceConnectLoader, } exitCode := agent.doStart(eventstream.NewEventStream("events", ctx), @@ -267,6 +271,8 @@ func TestDoStartRegisterContainerInstanceErrorNonTerminal(t *testing.T) { mockEC2Metadata.EXPECT().PrimaryENIMAC().Return("mac", nil) mockEC2Metadata.EXPECT().VPCID(gomock.Eq("mac")).Return("vpc-id", nil) mockEC2Metadata.EXPECT().SubnetID(gomock.Eq("mac")).Return("subnet-id", nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( dockerClient.EXPECT().SupportedVersions().Return(apiVersions), mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -295,6 +301,7 @@ func TestDoStartRegisterContainerInstanceErrorNonTerminal(t *testing.T) { ec2MetadataClient: mockEC2Metadata, terminationHandler: func(taskEngineState dockerstate.TaskEngineState, dataClient data.Client, taskEngine engine.TaskEngine, cancel context.CancelFunc) { }, + serviceconnectLoader: mockServiceConnectLoader, } exitCode := agent.doStart(eventstream.NewEventStream("events", ctx), @@ -403,6 +410,8 @@ func testDoStartHappyPathWithConditions(t *testing.T, blackholed bool, warmPools mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() imageManager.EXPECT().StartImageCleanupProcess(gomock.Any()).MaxTimes(1) dockerClient.EXPECT().ListContainers(gomock.Any(), gomock.Any(), gomock.Any()).Return( dockerapi.ListContainersResponse{}).AnyTimes() @@ -466,6 +475,7 @@ func testDoStartHappyPathWithConditions(t *testing.T, blackholed bool, warmPools stateManagerFactory: stateManagerFactory, ec2MetadataClient: ec2MetadataClient, saveableOptionFactory: saveableOptionFactory, + serviceconnectLoader: mockServiceConnectLoader, } var agentW sync.WaitGroup @@ -897,6 +907,8 @@ func TestReregisterContainerInstanceHappyPath(t *testing.T) { mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), mockDockerClient.EXPECT().SupportedVersions().Return(nil), @@ -916,13 +928,14 @@ func TestReregisterContainerInstanceHappyPath(t *testing.T) { mockEC2Metadata.EXPECT().OutpostARN().Return("", nil) agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - dockerClient: mockDockerClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, - ec2MetadataClient: mockEC2Metadata, + ctx: ctx, + cfg: &cfg, + dockerClient: mockDockerClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + ec2MetadataClient: mockEC2Metadata, + serviceconnectLoader: mockServiceConnectLoader, } agent.containerInstanceARN = containerInstanceARN agent.availabilityZone = availabilityZone @@ -944,6 +957,8 @@ func TestReregisterContainerInstanceInstanceTypeChanged(t *testing.T) { mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), mockDockerClient.EXPECT().SupportedVersions().Return(nil), @@ -964,13 +979,14 @@ func TestReregisterContainerInstanceInstanceTypeChanged(t *testing.T) { mockEC2Metadata.EXPECT().OutpostARN().Return("", nil) agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - dockerClient: mockDockerClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - ec2MetadataClient: mockEC2Metadata, - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + dockerClient: mockDockerClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + ec2MetadataClient: mockEC2Metadata, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } agent.containerInstanceARN = containerInstanceARN agent.availabilityZone = availabilityZone @@ -993,6 +1009,8 @@ func TestReregisterContainerInstanceAttributeError(t *testing.T) { mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -1012,13 +1030,14 @@ func TestReregisterContainerInstanceAttributeError(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - ec2MetadataClient: mockEC2Metadata, - dockerClient: mockDockerClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + ec2MetadataClient: mockEC2Metadata, + dockerClient: mockDockerClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } agent.containerInstanceARN = containerInstanceARN agent.availabilityZone = availabilityZone @@ -1041,6 +1060,8 @@ func TestReregisterContainerInstanceNonTerminalError(t *testing.T) { mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), mockDockerClient.EXPECT().SupportedVersions().Return(nil), @@ -1059,13 +1080,14 @@ func TestReregisterContainerInstanceNonTerminalError(t *testing.T) { // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - dockerClient: mockDockerClient, - ec2MetadataClient: mockEC2Metadata, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + dockerClient: mockDockerClient, + ec2MetadataClient: mockEC2Metadata, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } agent.containerInstanceARN = containerInstanceARN agent.availabilityZone = availabilityZone @@ -1089,6 +1111,8 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetHappyPath(t *t mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), mockDockerClient.EXPECT().SupportedVersions().Return(nil), @@ -1107,13 +1131,14 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetHappyPath(t *t // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - dockerClient: mockDockerClient, - ec2MetadataClient: mockEC2Metadata, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + dockerClient: mockDockerClient, + ec2MetadataClient: mockEC2Metadata, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } err := agent.registerContainerInstance(client, nil) assert.NoError(t, err) @@ -1134,6 +1159,8 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCanRetryError( mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() retriableError := apierrors.NewRetriableError(apierrors.NewRetriable(true), errors.New("error")) gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -1153,13 +1180,14 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCanRetryError( // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - dockerClient: mockDockerClient, - ec2MetadataClient: mockEC2Metadata, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + dockerClient: mockDockerClient, + ec2MetadataClient: mockEC2Metadata, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } err := agent.registerContainerInstance(client, nil) @@ -1180,6 +1208,8 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCannotRetryErr mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() cannotRetryError := apierrors.NewRetriableError(apierrors.NewRetriable(false), errors.New("error")) gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -1199,13 +1229,14 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetCannotRetryErr // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - ec2MetadataClient: mockEC2Metadata, - dockerClient: mockDockerClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + ec2MetadataClient: mockEC2Metadata, + dockerClient: mockDockerClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } err := agent.registerContainerInstance(client, nil) @@ -1226,6 +1257,8 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetAttributeError mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(false, nil).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), mockDockerClient.EXPECT().SupportedVersions().Return(nil), @@ -1244,13 +1277,14 @@ func TestRegisterContainerInstanceWhenContainerInstanceARNIsNotSetAttributeError // Cancel the context to cancel async routines defer cancel() agent := &ecsAgent{ - ctx: ctx, - cfg: &cfg, - ec2MetadataClient: mockEC2Metadata, - dockerClient: mockDockerClient, - pauseLoader: mockPauseLoader, - credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), - mobyPlugins: mockMobyPlugins, + ctx: ctx, + cfg: &cfg, + ec2MetadataClient: mockEC2Metadata, + dockerClient: mockDockerClient, + pauseLoader: mockPauseLoader, + credentialProvider: aws_credentials.NewCredentials(mockCredentialsProvider), + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } err := agent.registerContainerInstance(client, nil) @@ -1273,6 +1307,8 @@ func TestRegisterContainerInstanceInvalidParameterTerminalError(t *testing.T) { mockEC2Metadata.EXPECT().PrimaryENIMAC().Return("mac", nil) mockEC2Metadata.EXPECT().VPCID(gomock.Eq("mac")).Return("vpc-id", nil) mockEC2Metadata.EXPECT().SubnetID(gomock.Eq("mac")).Return("subnet-id", nil) + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( dockerClient.EXPECT().SupportedVersions().Return(apiVersions), mockCredentialsProvider.EXPECT().Retrieve().Return(aws_credentials.Value{}, nil), @@ -1300,6 +1336,7 @@ func TestRegisterContainerInstanceInvalidParameterTerminalError(t *testing.T) { mobyPlugins: mockMobyPlugins, terminationHandler: func(taskEngineState dockerstate.TaskEngineState, dataClient data.Client, taskEngine engine.TaskEngine, cancel context.CancelFunc) { }, + serviceconnectLoader: mockServiceConnectLoader, } exitCode := agent.doStart(eventstream.NewEventStream("events", ctx), diff --git a/agent/app/agent_unix_test.go b/agent/app/agent_unix_test.go index 22a965d66b..202c4767ca 100644 --- a/agent/app/agent_unix_test.go +++ b/agent/app/agent_unix_test.go @@ -40,6 +40,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/eni/watcher" "github.com/aws/amazon-ecs-agent/agent/eventstream" mock_gpu "github.com/aws/amazon-ecs-agent/agent/gpu/mocks" + mock_serviceconnect "github.com/aws/amazon-ecs-agent/agent/serviceconnect/mocks" "github.com/aws/amazon-ecs-agent/agent/sighandlers/exitcodes" "github.com/aws/amazon-ecs-agent/agent/taskresource" "github.com/aws/amazon-ecs-agent/agent/taskresource/cgroup/control/mock_control" @@ -106,6 +107,8 @@ func TestDoStartTaskENIHappyPath(t *testing.T) { mockMetadata.EXPECT().OutpostARN().Return("", nil) mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() mockUdevMonitor.EXPECT().Monitor(gomock.Any()).Return(monitoShutdownEvents).AnyTimes() gomock.InOrder( @@ -165,7 +168,8 @@ func TestDoStartTaskENIHappyPath(t *testing.T) { ec2MetadataClient: mockMetadata, terminationHandler: func(state dockerstate.TaskEngineState, dataClient data.Client, taskEngine engine.TaskEngine, cancel context.CancelFunc) { }, - mobyPlugins: mockMobyPlugins, + mobyPlugins: mockMobyPlugins, + serviceconnectLoader: mockServiceConnectLoader, } getPid = func() int { @@ -442,6 +446,8 @@ func TestDoStartCgroupInitHappyPath(t *testing.T) { ec2MetadataClient.EXPECT().OutpostARN().Return("", nil) mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockControl.EXPECT().Init().Return(nil), @@ -489,6 +495,7 @@ func TestDoStartCgroupInitHappyPath(t *testing.T) { resourceFields: &taskresource.ResourceFields{ Control: mockControl, }, + serviceconnectLoader: mockServiceConnectLoader, } var agentW sync.WaitGroup @@ -525,6 +532,8 @@ func TestDoStartCgroupInitErrorPath(t *testing.T) { mockCredentialsProvider.EXPECT().IsExpired().Return(false).AnyTimes() mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() mockControl.EXPECT().Init().Return(errors.New("test error")) @@ -545,6 +554,7 @@ func TestDoStartCgroupInitErrorPath(t *testing.T) { resourceFields: &taskresource.ResourceFields{ Control: mockControl, }, + serviceconnectLoader: mockServiceConnectLoader, } status := agent.doStart(eventstream.NewEventStream("events", ctx), @@ -591,6 +601,8 @@ func TestDoStartGPUManagerHappyPath(t *testing.T) { ec2MetadataClient.EXPECT().OutpostARN().Return("", nil) mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() gomock.InOrder( mockGPUManager.EXPECT().Initialize().Return(nil), @@ -641,6 +653,7 @@ func TestDoStartGPUManagerHappyPath(t *testing.T) { resourceFields: &taskresource.ResourceFields{ NvidiaGPUManager: mockGPUManager, }, + serviceconnectLoader: mockServiceConnectLoader, } var agentW sync.WaitGroup @@ -678,6 +691,8 @@ func TestDoStartGPUManagerInitError(t *testing.T) { mockGPUManager.EXPECT().Initialize().Return(errors.New("init error")) mockPauseLoader.EXPECT().LoadImage(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockPauseLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() + mockServiceConnectLoader := mock_serviceconnect.NewMockLoader(ctrl) + mockServiceConnectLoader.EXPECT().IsLoaded(gomock.Any()).Return(true, nil).AnyTimes() cfg := getTestConfig() cfg.GPUSupportEnabled = true @@ -695,6 +710,7 @@ func TestDoStartGPUManagerInitError(t *testing.T) { resourceFields: &taskresource.ResourceFields{ NvidiaGPUManager: mockGPUManager, }, + serviceconnectLoader: mockServiceConnectLoader, } status := agent.doStart(eventstream.NewEventStream("events", ctx), diff --git a/agent/engine/docker_task_engine.go b/agent/engine/docker_task_engine.go index db96df67d1..32d49b6d16 100644 --- a/agent/engine/docker_task_engine.go +++ b/agent/engine/docker_task_engine.go @@ -25,11 +25,9 @@ import ( "sync" "time" - "github.com/aws/amazon-ecs-agent/agent/api/appnet" - - serviceconnect "github.com/aws/amazon-ecs-agent/agent/engine/service_connect" "github.com/aws/amazon-ecs-agent/agent/api" + "github.com/aws/amazon-ecs-agent/agent/api/appnet" apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" apicontainerstatus "github.com/aws/amazon-ecs-agent/agent/api/container/status" apierrors "github.com/aws/amazon-ecs-agent/agent/api/errors" @@ -45,6 +43,7 @@ import ( "github.com/aws/amazon-ecs-agent/agent/engine/dependencygraph" "github.com/aws/amazon-ecs-agent/agent/engine/dockerstate" "github.com/aws/amazon-ecs-agent/agent/engine/execcmd" + engineserviceconnect "github.com/aws/amazon-ecs-agent/agent/engine/service_connect" "github.com/aws/amazon-ecs-agent/agent/eventstream" "github.com/aws/amazon-ecs-agent/agent/logger" "github.com/aws/amazon-ecs-agent/agent/logger/field" @@ -163,7 +162,7 @@ type DockerTaskEngine struct { imageManager ImageManager containerStatusToTransitionFunction map[apicontainerstatus.ContainerStatus]transitionApplyFunc metadataManager containermetadata.Manager - serviceconnectManager serviceconnect.Manager + serviceconnectManager engineserviceconnect.Manager // taskSteadyStatePollInterval is the duration that a managed task waits // once the task gets into steady state before polling the state of all of @@ -218,7 +217,7 @@ func NewDockerTaskEngine(cfg *config.Config, appnetClient: appnet.Client(), metadataManager: metadataManager, - serviceconnectManager: serviceconnect.NewManager(), + serviceconnectManager: engineserviceconnect.NewManager(), taskSteadyStatePollInterval: defaultTaskSteadyStatePollInterval, taskSteadyStatePollIntervalJitter: defaultTaskSteadyStatePollIntervalJitter, resourceFields: resourceFields, diff --git a/agent/serviceconnect/error.go b/agent/serviceconnect/error.go new file mode 100644 index 0000000000..bbd9c71a7c --- /dev/null +++ b/agent/serviceconnect/error.go @@ -0,0 +1,52 @@ +// 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 serviceconnect + +// https://golang.org/src/syscall/zerrors_linux_386.go#L1382 +const noSuchFile = "no such file or directory" + +// UnsupportedPlatformError indicates an error when loading appnet container +// image on an unsupported OS platform +type UnsupportedPlatformError struct { + error +} + +// IsUnsupportedPlatform returns true if the error is of UnsupportedPlatformError +// type +func IsUnsupportedPlatform(err error) bool { + _, ok := err.(UnsupportedPlatformError) + return ok +} + +// NewUnsupportedPlatformError creates a new UnsupportedPlatformError object +func NewUnsupportedPlatformError(err error) UnsupportedPlatformError { + return UnsupportedPlatformError{err} +} + +// NoSuchFileError wraps the error from the os package with the message +// "no such file error" +type NoSuchFileError struct { + error +} + +// NewNoSuchFileError creates a new NoSuchFileError object +func NewNoSuchFileError(err error) NoSuchFileError { + return NoSuchFileError{err} +} + +// IsNoSuchFileError returns true if the error is of NoSuchFileError type +func IsNoSuchFileError(err error) bool { + _, ok := err.(NoSuchFileError) + return ok +} diff --git a/agent/serviceconnect/error_test.go b/agent/serviceconnect/error_test.go new file mode 100644 index 0000000000..ee9702eeea --- /dev/null +++ b/agent/serviceconnect/error_test.go @@ -0,0 +1,52 @@ +//go:build unit +// +build unit + +// 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 serviceconnect + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUnsupportedPlatform(t *testing.T) { + testCases := map[error]bool{ + errors.New("error"): false, + NewUnsupportedPlatformError(errors.New("error")): true, + } + + for err, expected := range testCases { + t.Run(fmt.Sprintf("returns %t for type %s", expected, reflect.TypeOf(err)), func(t *testing.T) { + assert.Equal(t, expected, IsUnsupportedPlatform(err)) + }) + } +} + +func TestIsNoSuchFileError(t *testing.T) { + testCases := map[error]bool{ + errors.New("error"): false, + NewNoSuchFileError(errors.New("No such file")): true, + } + + for err, expected := range testCases { + t.Run(fmt.Sprintf("return %t for type %s", expected, reflect.TypeOf(err)), func(t *testing.T) { + assert.Equal(t, expected, IsNoSuchFileError(err)) + }) + } +} diff --git a/agent/serviceconnect/generate_mocks.go b/agent/serviceconnect/generate_mocks.go new file mode 100644 index 0000000000..4bc8760fd7 --- /dev/null +++ b/agent/serviceconnect/generate_mocks.go @@ -0,0 +1,16 @@ +// 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 serviceconnect + +//go:generate mockgen -destination=mocks/load_mocks.go -copyright_file=../../scripts/copyright_file github.com/aws/amazon-ecs-agent/agent/serviceconnect Loader diff --git a/agent/serviceconnect/load.go b/agent/serviceconnect/load.go new file mode 100644 index 0000000000..2e1affd7d2 --- /dev/null +++ b/agent/serviceconnect/load.go @@ -0,0 +1,82 @@ +// 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 serviceconnect + +import ( + "context" + "fmt" + + "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + "github.com/aws/amazon-ecs-agent/agent/logger" + "github.com/aws/amazon-ecs-agent/agent/logger/field" + "github.com/docker/docker/api/types" +) + +var ( + defaultAgentContainerImageName = "appnet_agent" + defaultAgentContainerTag = "service_connect.v1" +) + +// Loader defines an interface for loading the appnetAgent container image. This is mostly +// to facilitate mocking and testing of the LoadImage method +type Loader interface { + LoadImage(ctx context.Context, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) + IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) +} + +type loader struct { + AgentContainerImageName string + AgentContainerTag string + AgentContainerTarballPath string +} + +// New creates a new AppNet Agent image loader +func New() Loader { + return &loader{ + AgentContainerImageName: defaultAgentContainerImageName, + AgentContainerTag: defaultAgentContainerTag, + AgentContainerTarballPath: defaultAgentContainerTarballPath, + } +} + +// This function uses the DockerClient to inspect the image with the given name and tag. +func getAgentContainerImage(name string, tag string, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) { + imageName := fmt.Sprintf("%s:%s", name, tag) + logger.Debug("Inspecting appnet agent container image:", logger.Fields{ + field.Image: imageName, + }) + + image, err := dockerClient.InspectImage(imageName) + if err != nil { + return nil, fmt.Errorf("appnet agent container load: failed to inspect image: %s; %w", imageName, err) + } + + return image, nil +} + +// Common function for linux and windows to check if the container appnet Agent image has been loaded +func (agent *loader) isImageLoaded(dockerClient dockerapi.DockerClient) (bool, error) { + image, err := getAgentContainerImage( + agent.AgentContainerImageName, agent.AgentContainerTag, dockerClient) + + if err != nil { + return false, err + } + + if image == nil || image.ID == "" { + return false, nil + } + + return true, nil +} diff --git a/agent/serviceconnect/load_linux.go b/agent/serviceconnect/load_linux.go new file mode 100644 index 0000000000..063cd1fc69 --- /dev/null +++ b/agent/serviceconnect/load_linux.go @@ -0,0 +1,70 @@ +//go:build linux +// +build linux + +// 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 serviceconnect + +import ( + "context" + "fmt" + "os" + + "github.com/aws/amazon-ecs-agent/agent/dockerclient" + "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + "github.com/aws/amazon-ecs-agent/agent/logger" + "github.com/aws/amazon-ecs-agent/agent/logger/field" + + "github.com/docker/docker/api/types" +) + +var ( + defaultAgentContainerTarballPath = "/managed-agents/serviceconnect/appnet_agent.interface-v1.tar" +) + +// LoadImage helps load the AppNetAgent container image for the agent +func (agent *loader) LoadImage(ctx context.Context, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) { + logger.Debug("Loading appnet agent container tarball:", logger.Fields{ + field.Image: agent.AgentContainerTarballPath, + }) + if err := loadFromFile(ctx, agent.AgentContainerTarballPath, dockerClient); err != nil { + return nil, err + } + + return getAgentContainerImage( + agent.AgentContainerImageName, agent.AgentContainerTag, dockerClient) +} + +func (agent *loader) IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) { + return agent.isImageLoaded(dockerClient) +} + +var open = os.Open + +func loadFromFile(ctx context.Context, path string, dockerClient dockerapi.DockerClient) error { + containerReader, err := open(path) + if err != nil { + if err.Error() == noSuchFile { + return NewNoSuchFileError(fmt.Errorf( + "appnet agent container load: failed to read container image: %s : %w", path, err)) + } + return fmt.Errorf("appnet agent container load: failed to read container image: %s : %w", path, err) + } + if err := dockerClient.LoadImage(ctx, containerReader, dockerclient.LoadImageTimeout); err != nil { + return fmt.Errorf("appnet agent container load: failed to load container image: %s : %w", path, err) + } + + return nil + +} diff --git a/agent/serviceconnect/load_linux_test.go b/agent/serviceconnect/load_linux_test.go new file mode 100644 index 0000000000..9e30a3acca --- /dev/null +++ b/agent/serviceconnect/load_linux_test.go @@ -0,0 +1,122 @@ +//go:build linux && unit +// +build linux,unit + +// 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 serviceconnect + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + mock_sdkclient "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclient/mocks" + mock_sdkclientfactory "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory/mocks" + + "github.com/docker/docker/api/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +const ( + containerTarballPath = "/path/to/container.tar" +) + +func mockOpen() func() { + open = func(name string) (*os.File, error) { + return nil, nil + } + return func() { + open = os.Open + } +} + +// TestLoadFromFileWithReaderError tests loadFromFile with reader error +func TestLoadFromFileWithReaderError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + + open = func(name string) (*os.File, error) { + return nil, errors.New("Dummy Reader Error") + } + defer func() { + open = os.Open + }() + + err = loadFromFile(ctx, containerTarballPath, client) + assert.Error(t, err) +} + +// TestLoadFromFileHappyPath tests loadFromFile against happy path +func TestLoadFromFileHappyPath(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageLoad(gomock.Any(), gomock.Any(), false).Return(types.ImageLoadResponse{}, nil) + defer mockOpen()() + + err = loadFromFile(ctx, containerTarballPath, client) + assert.NoError(t, err) +} + +// TestLoadFromFileDockerLoadImageError tests loadFromFile against error +// from Docker clients LoadImage +func TestLoadFromFileDockerLoadImageError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageLoad(gomock.Any(), gomock.Any(), false).Return(types.ImageLoadResponse{}, + errors.New("Dummy Load Image Error")) + + defer mockOpen()() + + err = loadFromFile(ctx, containerTarballPath, client) + assert.Error(t, err) +} diff --git a/agent/serviceconnect/load_test.go b/agent/serviceconnect/load_test.go new file mode 100644 index 0000000000..c96e7ebd92 --- /dev/null +++ b/agent/serviceconnect/load_test.go @@ -0,0 +1,158 @@ +//go:build unit +// +build unit + +// 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 serviceconnect + +import ( + "context" + "errors" + "testing" + + "github.com/aws/amazon-ecs-agent/agent/config" + "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + mock_sdkclient "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclient/mocks" + mock_sdkclientfactory "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory/mocks" + + "github.com/docker/docker/api/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +const ( + agentName = "appnet-agent" + agentTag = "tag" +) + +var defaultConfig = config.DefaultConfig() + +func TestGetAppnetAgentContainerImageInspectImageError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageInspectWithRaw(gomock.Any(), agentName+":"+agentTag).Return( + types.ImageInspect{}, nil, errors.New("error")) + + _, err = getAgentContainerImage(agentName, agentTag, client) + assert.Error(t, err) +} + +func TestGetAgentContainerHappyPath(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageInspectWithRaw(gomock.Any(), agentName+":"+agentTag).Return(types.ImageInspect{}, nil, nil) + + _, err = getAgentContainerImage(agentName, agentTag, client) + assert.NoError(t, err) +} + +func TestIsImageLoadedHappyPath(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(types.ImageInspect{ID: "test123"}, nil, nil) + + isLoaded, err := (&loader{ + AgentContainerImageName: agentName, + AgentContainerTag: agentTag, + }).isImageLoaded(client) + assert.NoError(t, err) + assert.True(t, isLoaded) +} + +func TestIsImageLoadedNotLoaded(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return(types.ImageInspect{}, nil, nil) + + isLoaded, err := (&loader{ + AgentContainerImageName: agentName, + AgentContainerTag: agentTag, + }).isImageLoaded(client) + assert.NoError(t, err) + assert.False(t, isLoaded) +} + +func TestIsImageLoadedError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Docker SDK tests + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + client, err := dockerapi.NewDockerGoClient(sdkFactory, &defaultConfig, ctx) + assert.NoError(t, err) + mockDockerSDK.EXPECT().ImageInspectWithRaw(gomock.Any(), gomock.Any()).Return( + types.ImageInspect{}, nil, errors.New("error")) + + isLoaded, err := (&loader{ + AgentContainerImageName: agentName, + AgentContainerTag: agentTag, + }).isImageLoaded(client) + assert.Error(t, err) + assert.False(t, isLoaded) +} diff --git a/agent/serviceconnect/load_unsupported.go b/agent/serviceconnect/load_unsupported.go new file mode 100644 index 0000000000..fd7556c3fe --- /dev/null +++ b/agent/serviceconnect/load_unsupported.go @@ -0,0 +1,43 @@ +//go:build !linux +// +build !linux + +// 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 serviceconnect + +import ( + "context" + "fmt" + "runtime" + + "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + "github.com/docker/docker/api/types" +) + +var ( + defaultAgentContainerTarballPath = "" +) + +// LoadImage returns UnsupportedPlatformError on the unsupported platform +func (*loader) LoadImage(ctx context.Context, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) { + return nil, NewUnsupportedPlatformError(fmt.Errorf( + "appnetAgent container load: unsupported platform: %s/%s", + runtime.GOOS, runtime.GOARCH)) +} + +func (*loader) IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) { + return false, NewUnsupportedPlatformError(fmt.Errorf( + "appnetAgent container isloaded: unsupported platform: %s/%s", + runtime.GOOS, runtime.GOARCH)) +} diff --git a/agent/serviceconnect/mocks/load_mocks.go b/agent/serviceconnect/mocks/load_mocks.go new file mode 100644 index 0000000000..33bcfe3b11 --- /dev/null +++ b/agent/serviceconnect/mocks/load_mocks.go @@ -0,0 +1,81 @@ +// 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. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/amazon-ecs-agent/agent/serviceconnect (interfaces: Loader) + +// Package mock_serviceconnect is a generated GoMock package. +package mock_serviceconnect + +import ( + context "context" + reflect "reflect" + + dockerapi "github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi" + types "github.com/docker/docker/api/types" + gomock "github.com/golang/mock/gomock" +) + +// MockLoader is a mock of Loader interface +type MockLoader struct { + ctrl *gomock.Controller + recorder *MockLoaderMockRecorder +} + +// MockLoaderMockRecorder is the mock recorder for MockLoader +type MockLoaderMockRecorder struct { + mock *MockLoader +} + +// NewMockLoader creates a new mock instance +func NewMockLoader(ctrl *gomock.Controller) *MockLoader { + mock := &MockLoader{ctrl: ctrl} + mock.recorder = &MockLoaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockLoader) EXPECT() *MockLoaderMockRecorder { + return m.recorder +} + +// IsLoaded mocks base method +func (m *MockLoader) IsLoaded(arg0 dockerapi.DockerClient) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsLoaded", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsLoaded indicates an expected call of IsLoaded +func (mr *MockLoaderMockRecorder) IsLoaded(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLoaded", reflect.TypeOf((*MockLoader)(nil).IsLoaded), arg0) +} + +// LoadImage mocks base method +func (m *MockLoader) LoadImage(arg0 context.Context, arg1 dockerapi.DockerClient) (*types.ImageInspect, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadImage", arg0, arg1) + ret0, _ := ret[0].(*types.ImageInspect) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadImage indicates an expected call of LoadImage +func (mr *MockLoaderMockRecorder) LoadImage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadImage", reflect.TypeOf((*MockLoader)(nil).LoadImage), arg0, arg1) +}