-
Notifications
You must be signed in to change notification settings - Fork 602
/
task_windows.go
266 lines (233 loc) · 10.1 KB
/
task_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//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/ecscni"
"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/containernetworking/cni/libcni"
"github.com/aws/amazon-ecs-agent/agent/config"
"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/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"
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")
}