Skip to content

Commit 3c3afda

Browse files
committed
define maximumCPUShares
1 parent 404c217 commit 3c3afda

File tree

2 files changed

+168
-10
lines changed

2 files changed

+168
-10
lines changed

agent/api/task/task_linux.go

+23-7
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ import (
4343

4444
const (
4545
// Reference: http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
46-
minimumCPUShare = 2
46+
// These min, max values are defined in the kernel:
47+
// https://github.com/torvalds/linux/blob/0bddd227f3dc55975e2b8dfa7fc6f959b062a2c7/kernel/sched/sched.h#L427-L428
48+
// We need to make sure task and container cgroups define cpu shares within this range.
49+
minimumCPUShares = 2
50+
maximumCPUShares = 262144
4751

4852
minimumCPUPercent = 0
4953
bytesPerMegabyte = 1024 * 1024
@@ -178,8 +182,14 @@ func (task *Task) buildImplicitLinuxCPUSpec() specs.LinuxCPU {
178182
// aggregate container CPU shares when present
179183
var taskCPUShares uint64
180184
for _, container := range task.Containers {
181-
if container.CPU < minimumCPUShare {
182-
taskCPUShares += minimumCPUShare
185+
if container.CPU < minimumCPUShares {
186+
taskCPUShares += minimumCPUShares
187+
} else if container.CPU > maximumCPUShares {
188+
// If one of the containers is requesting more than the max permitted CPU shares,
189+
// then we set taskCPUShares to be the maximumCPUShares and break out of this loop.
190+
// This is to prevent the taskCPUShares to be more than the max CPU shares permitted by the Linux kernel.
191+
taskCPUShares = maximumCPUShares
192+
break
183193
} else {
184194
taskCPUShares += uint64(container.CPU)
185195
}
@@ -236,12 +246,18 @@ func (task *Task) overrideCgroupParent(hostConfig *dockercontainer.HostConfig) e
236246
// Docker silently converts 0 to 1024 CPU shares, which is probably not what we
237247
// want. Instead, we convert 0 to 2 to be closer to expected behavior. The
238248
// reason for 2 over 1 is that 1 is an invalid value (Linux's choice, not Docker's).
249+
// Similarly, if the container CPU shares is more than the max permitted value (Linux's choice again),
250+
// we set it to the maximum.
239251
func (task *Task) dockerCPUShares(containerCPU uint) int64 {
240-
if containerCPU <= 1 {
252+
if containerCPU < minimumCPUShares {
241253
seelog.Debugf(
242-
"Converting CPU shares to allowed minimum of 2 for task arn: [%s] and cpu shares: %d",
243-
task.Arn, containerCPU)
244-
return 2
254+
"Converting CPU shares to allowed minimum of %d for task arn: [%s] and cpu shares: %d",
255+
minimumCPUShares, task.Arn, containerCPU)
256+
return minimumCPUShares
257+
} else if containerCPU > maximumCPUShares {
258+
seelog.Debugf("Converting CPU shares to allowed maximum of %d for task arn [%s] and cpu shares: %d",
259+
maximumCPUShares, task.Arn, containerCPU)
260+
return maximumCPUShares
245261
}
246262
return int64(containerCPU)
247263
}

agent/api/task/task_linux_test.go

+145-3
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ func TestBuildLinuxResourceSpecWithoutTaskCPULimits(t *testing.T) {
425425
},
426426
},
427427
}
428-
expectedCPUShares := uint64(minimumCPUShare)
428+
expectedCPUShares := uint64(minimumCPUShares)
429429
expectedLinuxResourceSpec := specs.LinuxResources{
430430
CPU: &specs.LinuxCPU{
431431
Shares: &expectedCPUShares,
@@ -449,7 +449,7 @@ func TestBuildLinuxResourceSpecWithoutTaskCPULimits_WithPidLimits(t *testing.T)
449449
},
450450
},
451451
}
452-
expectedCPUShares := uint64(minimumCPUShare)
452+
expectedCPUShares := uint64(minimumCPUShares)
453453
expectedLinuxResourceSpec := specs.LinuxResources{
454454
CPU: &specs.LinuxCPU{
455455
Shares: &expectedCPUShares,
@@ -490,7 +490,7 @@ func TestBuildLinuxResourceSpecWithoutTaskCPUWithContainerCPULimits(t *testing.T
490490
}
491491

492492
// TestBuildLinuxResourceSpecWithoutTaskCPUWithLessThanMinimumContainerCPULimits validates behavior of CPU Shares
493-
// when container CPU share is 1 (less than the current minimumCPUShare which is 2)
493+
// when container CPU share is 1 (less than the current minimumCPUShares which is 2)
494494
func TestBuildLinuxResourceSpecWithoutTaskCPUWithLessThanMinimumContainerCPULimits(t *testing.T) {
495495
task := &Task{
496496
Arn: validTaskArn,
@@ -1578,3 +1578,145 @@ func TestBuildCNIBridgeModeWithServiceConnect(t *testing.T) {
15781578
})
15791579
}
15801580
}
1581+
1582+
// TestBuildImplicitLinuxCPUSpec tests the task's CPU spec generation when "task.CPU" is not explicitly set.
1583+
func TestBuildImplicitLinuxCPUSpec(t *testing.T) {
1584+
testCases := []struct {
1585+
name string
1586+
task *Task
1587+
expectedTaskCPUSpec specs.LinuxCPU
1588+
}{
1589+
{
1590+
name: "2 containers: each with CPU within permitted range",
1591+
task: &Task{
1592+
Arn: validTaskArn,
1593+
Containers: []*apicontainer.Container{
1594+
{
1595+
CPU: uint(10),
1596+
},
1597+
{
1598+
CPU: uint(20),
1599+
},
1600+
},
1601+
},
1602+
expectedTaskCPUSpec: specs.LinuxCPU{
1603+
Shares: aws.Uint64(30),
1604+
},
1605+
},
1606+
{
1607+
name: "2 containers: 1 of them with CPU below min",
1608+
task: &Task{
1609+
Arn: validTaskArn,
1610+
Containers: []*apicontainer.Container{
1611+
{
1612+
CPU: uint(1),
1613+
},
1614+
{
1615+
CPU: uint(10),
1616+
},
1617+
},
1618+
},
1619+
expectedTaskCPUSpec: specs.LinuxCPU{
1620+
Shares: aws.Uint64(uint64(12)),
1621+
},
1622+
},
1623+
{
1624+
name: "2 containers: 1 of them with CPU above max",
1625+
task: &Task{
1626+
Arn: validTaskArn,
1627+
Containers: []*apicontainer.Container{
1628+
{
1629+
CPU: uint(1),
1630+
},
1631+
{
1632+
CPU: uint(364544),
1633+
},
1634+
},
1635+
},
1636+
expectedTaskCPUSpec: specs.LinuxCPU{
1637+
Shares: aws.Uint64(uint64(maximumCPUShares)),
1638+
},
1639+
},
1640+
{
1641+
name: "3 containers: 1 with CPU within permitted range, 1 above max, and 1 below min",
1642+
task: &Task{
1643+
Arn: validTaskArn,
1644+
Containers: []*apicontainer.Container{
1645+
{
1646+
CPU: uint(5),
1647+
},
1648+
{
1649+
CPU: uint(1),
1650+
},
1651+
{
1652+
CPU: uint(364544),
1653+
},
1654+
},
1655+
},
1656+
expectedTaskCPUSpec: specs.LinuxCPU{
1657+
Shares: aws.Uint64(uint64(maximumCPUShares)),
1658+
},
1659+
},
1660+
}
1661+
1662+
for _, tc := range testCases {
1663+
t.Run(tc.name, func(t *testing.T) {
1664+
result := tc.task.buildImplicitLinuxCPUSpec()
1665+
assert.Equal(t, tc.expectedTaskCPUSpec, result)
1666+
})
1667+
}
1668+
}
1669+
1670+
// TestDockerCPUShares tests the dockerCPUShares() conversion method,
1671+
// which is used to send the container shares info is sent to Docker
1672+
func TestDockerCPUShares(t *testing.T) {
1673+
testCases := []struct {
1674+
name string
1675+
task *Task
1676+
expectedContainerShares int64
1677+
}{
1678+
{
1679+
name: "container CPU within permitted range",
1680+
task: &Task{
1681+
Arn: validTaskArn,
1682+
Containers: []*apicontainer.Container{
1683+
{
1684+
CPU: uint(10),
1685+
},
1686+
},
1687+
},
1688+
expectedContainerShares: int64(10),
1689+
},
1690+
{
1691+
name: "container CPU below min",
1692+
task: &Task{
1693+
Arn: validTaskArn,
1694+
Containers: []*apicontainer.Container{
1695+
{
1696+
CPU: uint(1),
1697+
},
1698+
},
1699+
},
1700+
expectedContainerShares: int64(minimumCPUShares),
1701+
},
1702+
{
1703+
name: "container CPU above max",
1704+
task: &Task{
1705+
Arn: validTaskArn,
1706+
Containers: []*apicontainer.Container{
1707+
{
1708+
CPU: uint(364544),
1709+
},
1710+
},
1711+
},
1712+
expectedContainerShares: int64(maximumCPUShares),
1713+
},
1714+
}
1715+
1716+
for _, tc := range testCases {
1717+
t.Run(tc.name, func(t *testing.T) {
1718+
result := tc.task.dockerCPUShares(tc.task.Containers[0].CPU)
1719+
assert.Equal(t, tc.expectedContainerShares, result)
1720+
})
1721+
}
1722+
}

0 commit comments

Comments
 (0)