Skip to content

Commit

Permalink
Add daemon manager and agent manageddaemon integration
Browse files Browse the repository at this point in the history
  • Loading branch information
fierlion committed Jul 7, 2023
1 parent e9ce7c7 commit 198d9d1
Show file tree
Hide file tree
Showing 12 changed files with 1,453 additions and 497 deletions.
4 changes: 4 additions & 0 deletions agent/api/container/containertype.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const (
// ContainerServiceConnectRelay represents the internal container type
// for the relay to share connections to management infrastructure.
ContainerServiceConnectRelay

// ContainerManagedDaemon represents the internal container type
// for Managed Daemons
ContainerManagedDaemon
)

// ContainerType represents the type of the internal container created
Expand Down
31 changes: 31 additions & 0 deletions agent/engine/daemonmanager/daemon_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 daemonmanager

import (
"context"

apitask "github.com/aws/amazon-ecs-agent/agent/api/task"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
"github.com/aws/amazon-ecs-agent/agent/utils/loader"
"github.com/docker/docker/api/types"
)

type DaemonManager interface {
loader.Loader
CreateDaemonTask() (*apitask.Task, error)
LoadImage(ctx context.Context, _ *config.Config, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error)
IsLoaded(dockerClient dockerapi.DockerClient) (bool, error)
}
166 changes: 166 additions & 0 deletions agent/engine/daemonmanager/daemon_manager_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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 daemonmanager

import (
"context"
"encoding/json"
"fmt"
"io/fs"
"os"

"github.com/pborman/uuid"

apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
apicontainerstatus "github.com/aws/amazon-ecs-agent/agent/api/container/status"
apitask "github.com/aws/amazon-ecs-agent/agent/api/task"
apitaskstatus "github.com/aws/amazon-ecs-agent/agent/api/task/status"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/dockerclient/dockerapi"
"github.com/aws/amazon-ecs-agent/agent/taskresource"
"github.com/aws/amazon-ecs-agent/agent/utils/loader"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
md "github.com/aws/amazon-ecs-agent/ecs-agent/manageddaemon"
"github.com/aws/aws-sdk-go/aws"
"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
)

const (
// all daemons will share the same user id
// note: AppNetUID is 20000
daemonUID = 20001
ecsAgentLogFileENV = "ECS_LOGFILE"
defaultECSAgentLogPathContainer = "/log"
)

// each daemon manager manages one single daemon container
type daemonManager struct {
managedDaemon *md.ManagedDaemon
}

func NewDaemonManager(manageddaemon *md.ManagedDaemon) DaemonManager {
return &daemonManager{managedDaemon: manageddaemon}
}

func (dm *daemonManager) CreateDaemonTask() (*apitask.Task, error) {
imageName := dm.managedDaemon.GetLoadedDaemonImageRef()
containerRunning := apicontainerstatus.ContainerRunning
dockerHostConfig := dockercontainer.HostConfig{
NetworkMode: apitask.HostNetworkMode,
// the default value of 0 for MaximumRetryCount means retry indefinitely
RestartPolicy: dockercontainer.RestartPolicy{
Name: "on-failure",
MaximumRetryCount: 0,
},
}
if !dm.managedDaemon.IsValidManagedDaemon() {
return nil, fmt.Errorf("%s is an invalid managed daemon", dm.managedDaemon.GetImageName())
}
for _, mount := range dm.managedDaemon.GetMountPoints() {
err := mkdirAllAndChown(mount.SourceVolumeHostPath, 0700, daemonUID, os.Getegid())
if err != nil {
return nil, err
}
dockerHostConfig.Binds = append(dockerHostConfig.Binds,
fmt.Sprintf("%s:%s", mount.SourceVolumeHostPath, mount.ContainerPath))
}
rawHostConfig, err := json.Marshal(&dockerHostConfig)
if err != nil {
return nil, err
}
healthConfig := dm.managedDaemon.GetDockerHealthConfig()
rawHealthConfig, err := json.Marshal(&healthConfig)
if err != nil {
return nil, err
}
// The raw host config needs to be created this way - if we marshal the entire config object
// directly, and the object only contains healthcheck, all other fields will be written as empty/nil
// in the result string. This will override the configurations that comes with the container image
// (CMD for example)
rawConfig := fmt.Sprintf("{\"Healthcheck\":%s}", string(rawHealthConfig))

daemonTask := &apitask.Task{
Arn: fmt.Sprintf("arn:::::/%s-%s", dm.managedDaemon.GetImageName(), uuid.NewUUID()),
DesiredStatusUnsafe: apitaskstatus.TaskRunning,
Containers: []*apicontainer.Container{{
Name: dm.managedDaemon.GetImageName(),
Image: imageName,
ContainerArn: fmt.Sprintf("arn:::::/instance-%s", dm.managedDaemon.GetImageName()),
Type: apicontainer.ContainerManagedDaemon,
TransitionDependenciesMap: make(map[apicontainerstatus.ContainerStatus]apicontainer.TransitionDependencySet),
Essential: true,
SteadyStateStatusUnsafe: &containerRunning,
DockerConfig: apicontainer.DockerConfig{
Config: aws.String(rawConfig),
HostConfig: aws.String(string(rawHostConfig)),
},
HealthCheckType: "DOCKER",
}},
LaunchType: "EC2",
NetworkMode: apitask.HostNetworkMode,
ResourcesMapUnsafe: make(map[string][]taskresource.TaskResource),
IsInternal: true,
}
// add managed daemon environment to daemon task container
daemonTask.Containers[0].MergeEnvironmentVariables(dm.managedDaemon.GetEnvironment())
return daemonTask, nil
}

// LoadImage loads the daemon's latest image
func (dm *daemonManager) LoadImage(ctx context.Context, _ *config.Config, dockerClient dockerapi.DockerClient) (*types.ImageInspect, error) {
var loadErr error
daemonImageToLoad := dm.managedDaemon.GetImageName()
daemonImageTarPath := dm.managedDaemon.GetImageTarPath()
if _, err := os.Stat(daemonImageTarPath); err != nil {
logger.Warn(fmt.Sprintf("%s container tarball unavailable at path: %s", daemonImageToLoad, daemonImageTarPath), logger.Fields{
field.Error: err,
})
}
logger.Debug(fmt.Sprintf("Loading %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath))
if loadErr = loader.LoadFromFile(ctx, daemonImageTarPath, dockerClient); loadErr != nil {
logger.Warn(fmt.Sprintf("Unable to load %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath), logger.Fields{
field.Error: loadErr,
})
}
dm.managedDaemon.SetLoadedDaemonImageRef(dm.managedDaemon.GetImageCanonicalRef())
loadedImageRef := dm.managedDaemon.GetLoadedDaemonImageRef()
logger.Info(fmt.Sprintf("Successfully loaded %s container image from tarball: %s", daemonImageToLoad, daemonImageTarPath),
logger.Fields{
field.Image: loadedImageRef,
})
return loader.GetContainerImage(loadedImageRef, dockerClient)
}

// isImageLoaded uses the image ref with its tag
func (dm *daemonManager) IsLoaded(dockerClient dockerapi.DockerClient) (bool, error) {
return loader.IsImageLoaded(dm.managedDaemon.GetImageCanonicalRef(), dockerClient)
}

var mkdirAllAndChown = defaultMkdirAllAndChown

func defaultMkdirAllAndChown(path string, perm fs.FileMode, uid, gid int) error {
_, err := os.Stat(path)
if os.IsNotExist(err) {
err = os.MkdirAll(path, perm)
}
if err != nil {
return fmt.Errorf("failed to mkdir %s: %+v", path, err)
}
if err = os.Chown(path, uid, gid); err != nil {
return fmt.Errorf("failed to chown %s: %+v", path, err)
}
return nil
}
Loading

0 comments on commit 198d9d1

Please sign in to comment.