agent/api/task/task_windows.go (176 lines of code) (raw):
//go:build windows
// +build windows
// 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 task
import (
"time"
"github.com/aws/amazon-ecs-agent/agent/config"
"github.com/aws/amazon-ecs-agent/agent/ecscni"
"github.com/aws/amazon-ecs-agent/agent/taskresource"
"github.com/aws/amazon-ecs-agent/agent/taskresource/fsxwindowsfileserver"
resourcetype "github.com/aws/amazon-ecs-agent/agent/taskresource/types"
taskresourcevolume "github.com/aws/amazon-ecs-agent/agent/taskresource/volume"
"github.com/aws/amazon-ecs-agent/agent/utils"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
"github.com/aws/amazon-ecs-agent/ecs-agent/credentials"
ni "github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
eautils "github.com/aws/amazon-ecs-agent/ecs-agent/utils"
"github.com/cihub/seelog"
"github.com/containernetworking/cni/libcni"
dockercontainer "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
)
const (
// cpuSharesPerCore represents the cpu shares of a cpu core in docker
cpuSharesPerCore = 1024
percentageFactor = 100
minimumCPUPercent = 1
// windowsDefaultNetworkName is the name of the docker network to which the containers in
// default mode are attached to.
windowsDefaultNetworkName = "nat"
)
// PlatformFields consists of fields specific to Windows for a task
type PlatformFields struct {
// CpuUnbounded determines whether a mix of unbounded and bounded CPU tasks
// are allowed to run in the instance
CpuUnbounded config.BooleanDefaultFalse `json:"cpuUnbounded"`
// MemoryUnbounded determines whether a mix of unbounded and bounded Memory tasks
// are allowed to run in the instance
MemoryUnbounded config.BooleanDefaultFalse `json:"memoryUnbounded"`
}
var cpuShareScaleFactor = eautils.GetNumCPU() * cpuSharesPerCore
// adjustForPlatform makes Windows-specific changes to the task after unmarshal
func (task *Task) adjustForPlatform(cfg *config.Config) {
task.downcaseAllVolumePaths()
platformFields := PlatformFields{
CpuUnbounded: cfg.PlatformVariables.CPUUnbounded,
MemoryUnbounded: cfg.PlatformVariables.MemoryUnbounded,
}
task.PlatformFields = platformFields
}
// downcaseAllVolumePaths forces all volume paths (host path and container path)
// to be lower-case. This is to account for Windows case-insensitivity and the
// case-sensitive string comparison that takes place elsewhere in the code.
func (task *Task) downcaseAllVolumePaths() {
for _, volume := range task.Volumes {
if hostVol, ok := volume.Volume.(*taskresourcevolume.FSHostVolume); ok {
hostVol.FSSourcePath = utils.GetCanonicalPath(hostVol.FSSourcePath)
}
}
for _, container := range task.Containers {
for i, mountPoint := range container.MountPoints {
// container.MountPoints is a slice of values, not a slice of pointers so
// we need to mutate the actual value instead of the copied value
container.MountPoints[i].ContainerPath = utils.GetCanonicalPath(mountPoint.ContainerPath)
}
}
}
// platformHostConfigOverride provides an entry point to set up default HostConfig options to be
// passed to Docker API.
func (task *Task) platformHostConfigOverride(hostConfig *dockercontainer.HostConfig) error {
// Convert the CPUShares to CPUPercent
hostConfig.CPUPercent = hostConfig.CPUShares * percentageFactor / int64(cpuShareScaleFactor)
if hostConfig.CPUPercent == 0 && hostConfig.CPUShares != 0 {
// if CPU percent is too low, we set it to the minimum(linux and some windows tasks).
// if the CPU is explicitly set to zero or not set at all, and CPU unbounded
// tasks are allowed for windows, let CPU percent be zero.
// this is a workaround to allow CPU unbounded tasks(https://github.com/aws/amazon-ecs-agent/issues/1127)
hostConfig.CPUPercent = minimumCPUPercent
}
hostConfig.CPUShares = 0
if hostConfig.Memory <= 0 && task.PlatformFields.MemoryUnbounded.Enabled() {
// As of version 17.06.2-ee-6 of docker. MemoryReservation is not supported on windows. This ensures that
// this parameter is not passed, allowing to launch a container without a hard limit.
hostConfig.MemoryReservation = 0
}
// On Windows, bridge mode equivalent for Linux is "default" or "nat" network.
// We need to perform explicit conversion for the same.
networkMode := hostConfig.NetworkMode.NetworkName()
if networkMode == BridgeNetworkMode {
hostConfig.NetworkMode = windowsDefaultNetworkName
}
return nil
}
// dockerCPUShares converts containerCPU shares if needed as per the logic stated below:
// Docker silently converts 0 to 1024 CPU shares, which is probably not what we
// want. Instead, we convert 0 to 2 to be closer to expected behavior. The
// reason for 2 over 1 is that 1 is an invalid value (Linux's choice, not Docker's).
func (task *Task) dockerCPUShares(containerCPU uint) int64 {
if containerCPU <= 1 && !task.PlatformFields.CpuUnbounded.Enabled() {
seelog.Debugf(
"Converting CPU shares to allowed minimum of 2 for task arn: [%s] and cpu shares: %d",
task.Arn, containerCPU)
return 2
}
return int64(containerCPU)
}
func (task *Task) initializeCgroupResourceSpec(cgroupPath string, cGroupCPUPeriod time.Duration, taskPidsLimit int, resourceFields *taskresource.ResourceFields) error {
if !task.MemoryCPULimitsEnabled {
if task.CPU > 0 || task.Memory > 0 {
// Client-side validation/warning if a task with task-level CPU/memory limits specified somehow lands on an instance
// where agent does not support it. These limits will be ignored.
logger.Warn("Ignoring task-level CPU/memory limits since agent does not support the TaskCPUMemLimits capability", logger.Fields{
field.TaskID: task.GetID(),
})
}
return nil
}
return errors.New("unsupported platform")
}
func enableIPv6SysctlSetting(hostConfig *dockercontainer.HostConfig) {
return
}
// requiresFSxWindowsFileServerResource returns true if at least one volume in the task
// is of type 'fsxWindowsFileServer'
func (task *Task) requiresFSxWindowsFileServerResource() bool {
for _, volume := range task.Volumes {
if volume.Type == FSxWindowsFileServerVolumeType {
return true
}
}
return false
}
// initializeFSxWindowsFileServerResource builds the resource dependency map for the fsxwindowsfileserver resource
func (task *Task) initializeFSxWindowsFileServerResource(cfg *config.Config, credentialsManager credentials.Manager,
resourceFields *taskresource.ResourceFields) error {
for i, vol := range task.Volumes {
if vol.Type != FSxWindowsFileServerVolumeType {
continue
}
fsxWindowsFileServerVol, ok := vol.Volume.(*fsxwindowsfileserver.FSxWindowsFileServerVolumeConfig)
if !ok {
return errors.New("task volume: volume configuration does not match the type 'fsxWindowsFileServer'")
}
err := task.addFSxWindowsFileServerResource(cfg, credentialsManager, resourceFields, &task.Volumes[i], fsxWindowsFileServerVol)
if err != nil {
return err
}
}
return nil
}
// addFSxWindowsFileServerResource creates a fsxwindowsfileserver resource for every fsxwindowsfileserver task volume
// and updates container dependency
func (task *Task) addFSxWindowsFileServerResource(
cfg *config.Config,
credentialsManager credentials.Manager,
resourceFields *taskresource.ResourceFields,
vol *TaskVolume,
fsxWindowsFileServerVol *fsxwindowsfileserver.FSxWindowsFileServerVolumeConfig,
) error {
fsxwindowsfileserverResource, err := fsxwindowsfileserver.NewFSxWindowsFileServerResource(
task.Arn,
cfg.AWSRegion,
vol.Name,
FSxWindowsFileServerVolumeType,
fsxWindowsFileServerVol,
task.ExecutionCredentialsID,
credentialsManager,
resourceFields.SSMClientCreator,
resourceFields.ASMClientCreator,
resourceFields.FSxClientCreator)
if err != nil {
return err
}
vol.Volume = &fsxwindowsfileserverResource.VolumeConfig
task.AddResource(resourcetype.FSxWindowsFileServerKey, fsxwindowsfileserverResource)
task.updateContainerVolumeDependency(vol.Name)
return nil
}
// BuildCNIConfigAwsvpc builds a list of CNI network configurations for the task.
func (task *Task) BuildCNIConfigAwsvpc(includeIPAMConfig bool, cniConfig *ecscni.Config) (*ecscni.Config, error) {
if !task.IsNetworkModeAWSVPC() {
return nil, errors.New("task config: task network mode is not awsvpc")
}
var netconf *libcni.NetworkConfig
var err error
// Build a CNI network configuration for each ENI.
for _, eni := range task.ENIs {
switch eni.InterfaceAssociationProtocol {
// If the association protocol is set to "default" or unset (to preserve backwards
// compatibility), consider it a "standard" ENI attachment.
case "", ni.DefaultInterfaceAssociationProtocol:
cniConfig.ID = eni.MacAddress
netconf, err = ecscni.NewVPCENIPluginConfigForTaskNSSetup(eni, cniConfig)
default:
err = errors.Errorf("task config: unknown interface association type: %s",
eni.InterfaceAssociationProtocol)
}
if err != nil {
return nil, err
}
// IfName is expected by the plugin but is not used.
cniConfig.NetworkConfigs = append(cniConfig.NetworkConfigs, &ecscni.NetworkConfig{
IfName: ecscni.DefaultENIName,
CNINetworkConfig: netconf,
})
}
// Create the vpc-eni plugin configuration to setup ecs-bridge endpoint in the task namespace.
netconf, err = ecscni.NewVPCENIPluginConfigForECSBridgeSetup(cniConfig)
if err != nil {
return nil, err
}
cniConfig.NetworkConfigs = append(cniConfig.NetworkConfigs, &ecscni.NetworkConfig{
IfName: ecscni.ECSBridgeNetworkName,
CNINetworkConfig: netconf,
})
return cniConfig, nil
}
// BuildCNIConfigBridgeMode builds a list of CNI network configurations for a task in docker bridge mode.
func (task *Task) BuildCNIConfigBridgeMode(cniConfig *ecscni.Config, containerName string) (*ecscni.Config, error) {
return nil, errors.New("unsupported platform")
}