Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load Managed Daemon images in background #3984

Merged
merged 6 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions agent/app/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,19 +433,15 @@ func (agent *ecsAgent) doStart(containerChangeEventStream *eventstream.EventStre
return exitcodes.ExitTerminal
}

// Load Managed Daemon images asynchronously
agent.loadManagedDaemonImagesAsync(imageManager)

scManager := agent.serviceconnectManager
scManager.SetECSClient(client, agent.containerInstanceARN)
if loaded, _ := scManager.IsLoaded(agent.dockerClient); loaded {
imageManager.AddImageToCleanUpExclusionList(agent.serviceconnectManager.GetLoadedImageName())
}

// exclude all daemon images from cleanup
for _, csiDM := range agent.daemonManagers {
if loaded, _ := csiDM.IsLoaded(agent.dockerClient); loaded {
imageManager.AddImageToCleanUpExclusionList(csiDM.GetManagedDaemon().GetLoadedDaemonImageRef())
}
}

// Add container instance ARN to metadata manager
if agent.cfg.ContainerMetadataEnabled.Enabled() {
agent.metadataManager.SetContainerInstanceARN(agent.containerInstanceARN)
Expand Down Expand Up @@ -485,7 +481,7 @@ func (agent *ecsAgent) doStart(containerChangeEventStream *eventstream.EventStre
agent.startAsyncRoutines(containerChangeEventStream, credentialsManager, imageManager,
taskEngine, deregisterInstanceEventStream, client, taskHandler, attachmentEventHandler, state, doctor)
// TODO add EBS watcher to async routines
agent.startEBSWatcher(state, taskEngine)
agent.startEBSWatcher(state, taskEngine, agent.dockerClient)
// Start the acs session, which should block doStart
return agent.startACSSession(credentialsManager, taskEngine,
deregisterInstanceEventStream, client, state, taskHandler, doctor)
Expand Down Expand Up @@ -751,6 +747,38 @@ func (agent *ecsAgent) constructVPCSubnetAttributes() []*ecs.Attribute {
}
}

// Loads Managed Daemon images for all Managed Daemons registered on the Agent.
// The images are loaded in the background. Successfully loaded images are added to
// imageManager's cleanup exclusion list.
func (agent *ecsAgent) loadManagedDaemonImagesAsync(imageManager engine.ImageManager) {
daemonManagers := agent.getDaemonManagers()
logger.Debug(fmt.Sprintf("Will load images for %d Managed Daemons", len(daemonManagers)))
for _, daemonManager := range daemonManagers {
go agent.loadManagedDaemonImage(daemonManager, imageManager)
}
}

// Loads Managed Daemon image and adds it to image cleanup exclusion list upon success.
func (agent *ecsAgent) loadManagedDaemonImage(dm dm.DaemonManager, imageManager engine.ImageManager) {
imageRef := dm.GetManagedDaemon().GetImageRef()
logger.Info("Starting to load Managed Daemon image", logger.Fields{
field.ImageRef: imageRef,
})
image, err := dm.LoadImage(agent.ctx, agent.dockerClient)
if err != nil {
logger.Error("Failed to load Managed Daemon image", logger.Fields{
field.ImageRef: imageRef,
field.Error: err,
})
return
}
logger.Info("Successfully loaded Managed Daemon image", logger.Fields{
field.ImageRef: imageRef,
field.ImageID: image.ID,
})
imageManager.AddImageToCleanUpExclusionList(imageRef)
}

// registerContainerInstance registers the container instance ID for the ECS Agent
func (agent *ecsAgent) registerContainerInstance(
client api.ECSClient,
Expand Down Expand Up @@ -1140,6 +1168,11 @@ func (agent *ecsAgent) setDaemonManager(key string, val dm.DaemonManager) {
agent.daemonManagers[key] = val
}

// Returns daemon managers map. Not designed to be thread-safe.
func (agent *ecsAgent) getDaemonManagers() map[string]dm.DaemonManager {
return agent.daemonManagers
}

// setVPCSubnet sets the vpc and subnet ids for the agent by querying the
// instance metadata service
func (agent *ecsAgent) setVPCSubnet() (error, bool) {
Expand Down
14 changes: 9 additions & 5 deletions agent/app/agent_capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,12 +522,16 @@ func (agent *ecsAgent) appendEBSTaskAttachCapabilities(capabilities []*ecs.Attri
if daemonDef.GetImageName() == md.EbsCsiDriver {
csiDaemonManager := dm.NewDaemonManager(daemonDef)
agent.setDaemonManager(md.EbsCsiDriver, csiDaemonManager)
if _, err := csiDaemonManager.LoadImage(agent.ctx, agent.dockerClient); err != nil {
logger.Error("Failed to load the EBS CSI Driver. This container instance will not be able to support EBS Task Attach",
imageExists, err := csiDaemonManager.ImageExists()
if !imageExists {
logger.Error(
"Either EBS Daemon image does not exist or failed to check its existence."+
" This container instance will not advertise EBS Task Attach capability.",
logger.Fields{
field.Error: err,
},
)
field.ImageName: csiDaemonManager.GetManagedDaemon().GetImageName(),
field.ImageTARPath: csiDaemonManager.GetManagedDaemon().GetImageTarPath(),
field.Error: err,
})
return capabilities
}
}
Expand Down
47 changes: 47 additions & 0 deletions agent/app/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
aws_credentials "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/docker/docker/api/types"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -1859,3 +1860,49 @@ func TestWaitUntilInstanceInServicePolling(t *testing.T) {
})
}
}

func TestLoadManagedDaemonImage(t *testing.T) {
tcs := []struct {
name string
setDaemonManagerExpectations func(*mock_daemonmanager.MockDaemonManager)
setImageManagerExpectations func(*mock_engine.MockImageManager)
}{
{
name: "no exclusion list update if image load fails",
setDaemonManagerExpectations: func(mdm *mock_daemonmanager.MockDaemonManager) {
mdm.EXPECT().GetManagedDaemon().Return(md.NewManagedDaemon("name", "tag")).Times(2)
mdm.EXPECT().LoadImage(gomock.Any(), gomock.Any()).Return(nil, errors.New("error"))
},
},
{
name: "exclusion list is updated if image load succeeds",
setDaemonManagerExpectations: func(mdm *mock_daemonmanager.MockDaemonManager) {
mdm.EXPECT().GetManagedDaemon().Return(md.NewManagedDaemon("name", "tag")).Times(3)
mdm.EXPECT().
LoadImage(gomock.Any(), gomock.Any()).
Return(&types.ImageInspect{ID: "image-id"}, nil)
},
setImageManagerExpectations: func(mim *mock_engine.MockImageManager) {
mim.EXPECT().AddImageToCleanUpExclusionList("name:tag")
},
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
ctrl.Finish()

dockerClient := mock_dockerapi.NewMockDockerClient(ctrl)
daemonManager := mock_daemonmanager.NewMockDaemonManager(ctrl)
imageManager := mock_engine.NewMockImageManager(ctrl)

tc.setDaemonManagerExpectations(daemonManager)
if tc.setImageManagerExpectations != nil {
tc.setImageManagerExpectations(imageManager)
}

agent := &ecsAgent{ctx: context.Background(), dockerClient: dockerClient}
agent.loadManagedDaemonImage(daemonManager, imageManager)
})
}
}
9 changes: 7 additions & 2 deletions agent/app/agent_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

asmfactory "github.com/aws/amazon-ecs-agent/agent/asm/factory"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
ebs "github.com/aws/amazon-ecs-agent/agent/ebs"
"github.com/aws/amazon-ecs-agent/agent/ecscni"
"github.com/aws/amazon-ecs-agent/agent/engine"
Expand Down Expand Up @@ -151,10 +152,14 @@ func (agent *ecsAgent) startENIWatcher(state dockerstate.TaskEngineState, stateC
return nil
}

func (agent *ecsAgent) startEBSWatcher(state dockerstate.TaskEngineState, taskEngine engine.TaskEngine) {
func (agent *ecsAgent) startEBSWatcher(
state dockerstate.TaskEngineState,
taskEngine engine.TaskEngine,
dockerClient dockerapi.DockerClient,
) {
if agent.ebsWatcher == nil {
seelog.Debug("Creating new EBS watcher...")
agent.ebsWatcher = ebs.NewWatcher(agent.ctx, state, taskEngine)
agent.ebsWatcher = ebs.NewWatcher(agent.ctx, state, taskEngine, dockerClient)
go agent.ebsWatcher.Start()
}
}
Expand Down
8 changes: 6 additions & 2 deletions agent/app/agent_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

asmfactory "github.com/aws/amazon-ecs-agent/agent/asm/factory"
"github.com/aws/amazon-ecs-agent/agent/data"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
"github.com/aws/amazon-ecs-agent/agent/ecscni"
"github.com/aws/amazon-ecs-agent/agent/engine"
"github.com/aws/amazon-ecs-agent/agent/engine/dockerstate"
Expand Down Expand Up @@ -99,9 +100,12 @@ func (agent *ecsAgent) startWindowsService() int {
return 0
}

func (agent *ecsAgent) startEBSWatcher(state dockerstate.TaskEngineState, taskEngine engine.TaskEngine) error {
func (agent *ecsAgent) startEBSWatcher(
state dockerstate.TaskEngineState,
taskEngine engine.TaskEngine,
dockerClient dockerapi.DockerClient,
) {
seelog.Debug("Windows EBS Watcher not implemented: No Op")
return nil
}

// handler implements https://godoc.org/golang.org/x/sys/windows/svc#Handler
Expand Down
Loading