Skip to content

Commit f7495b8

Browse files
committed
Fix memory limit validation for compose 3
Previously, specifying memory reservation without memory limit would cause a validation error. This fix allows either value to be specified without the other, in either docker compose or ecs-params. Fixes aws#546
1 parent 0d0ced4 commit f7495b8

File tree

4 files changed

+149
-31
lines changed

4 files changed

+149
-31
lines changed

ecs-cli/modules/cli/compose/project/project_parseV1V2.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ func convertV1V2ToContainerConfig(context *project.Context, serviceName string,
7575
memory := adapter.ConvertToMemoryInMB(int64(service.MemLimit))
7676
memoryReservation := adapter.ConvertToMemoryInMB(int64(service.MemReservation))
7777

78+
// Validate memory and memory reservation
79+
if memory == 0 && memoryReservation != 0 {
80+
memory = memoryReservation
81+
}
82+
7883
mountPoints, err := adapter.ConvertToMountPoints(service.Volumes, volumes)
7984
if err != nil {
8085
return nil, err

ecs-cli/modules/cli/compose/project/project_parseV1V2_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func TestParseV1V2_Version1_HappyPath(t *testing.T) {
115115
devices:
116116
- "/dev/sda:/dev/sdd:r"
117117
- "/dev/sdd:/dev/xdr"
118-
- "/dev/sda"
118+
- "/dev/sda"
119119
dns:
120120
- 1.2.3.4
121121
dns_search: search.example.com
@@ -505,6 +505,41 @@ services:
505505
assert.Equal(t, expectedEnv, web.Environment, "Expected Environment to match")
506506
}
507507

508+
func TestParseV1V2_MemoryValidation(t *testing.T) {
509+
// Setup docker-compose file
510+
memory := int64(128)
511+
composeFileString := `version: '2'
512+
services:
513+
web:
514+
image: webapp
515+
mem_reservation: 128m`
516+
517+
tmpfile, err := ioutil.TempFile("", "test")
518+
assert.NoError(t, err, "Unexpected error in creating test file")
519+
520+
defer os.Remove(tmpfile.Name())
521+
522+
_, err = tmpfile.Write([]byte(composeFileString))
523+
assert.NoError(t, err, "Unexpected error in writing to test file")
524+
525+
err = tmpfile.Close()
526+
assert.NoError(t, err, "Unexpected error closing file")
527+
528+
// Set up project
529+
project := setupTestProject(t)
530+
project.ecsContext.ComposeFiles = append(project.ecsContext.ComposeFiles, tmpfile.Name())
531+
532+
actualConfigs, err := project.parseV1V2()
533+
assert.NoError(t, err, "Unexpected error parsing file")
534+
535+
// verify wordpress ServiceConfig
536+
web, err := getContainerConfigByName("web", actualConfigs)
537+
if assert.NoError(t, err) {
538+
assert.Equal(t, memory, web.Memory, "Expected Memory to match")
539+
assert.Equal(t, memory, web.MemoryReservation, "Expected MemoryReservation to match")
540+
}
541+
}
542+
508543
func getContainerConfigByName(name string, configs *[]adapter.ContainerConfig) (*adapter.ContainerConfig, error) {
509544
for _, config := range *configs {
510545
if config.Name == name {

ecs-cli/modules/utils/compose/convert_task_definition.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ func convertToContainerDef(inputCfg *adapter.ContainerConfig, ecsContainerDef *C
166166
if ecsContainerDef != nil {
167167
outputContDef.Essential = aws.Bool(ecsContainerDef.Essential)
168168

169+
// CPU and Memory are expected to be set here if compose v3 was used
169170
cpu = getResourceValue(inputCfg.Name, cpu, ecsContainerDef.Cpu, "CPU")
170171

171172
ecsMemInMB := adapter.ConvertToMemoryInMB(int64(ecsContainerDef.Memory))
@@ -175,17 +176,23 @@ func convertToContainerDef(inputCfg *adapter.ContainerConfig, ecsContainerDef *C
175176
memRes = getResourceValue(inputCfg.Name, memRes, ecsMemResInMB, "MemoryReservation")
176177
}
177178

178-
if mem < memRes {
179-
return nil, errors.New("mem_limit must be greater than mem_reservation")
180-
}
181-
182179
// One or both of memory and memoryReservation is required to register a task definition with ECS
183180
// If neither is provided by 1) compose file or 2) ecs-params, set default
184181
// NOTE: Docker does not set a memory limit for containers
185182
if mem == 0 && memRes == 0 {
186183
mem = defaultMemLimit
187184
}
188185

186+
// Docker compose allows specifying memory reservation with memory, so
187+
// we should default to minimum allowable value for memory hard limit
188+
if mem == 0 && memRes != 0 {
189+
mem = memRes
190+
}
191+
192+
if mem < memRes {
193+
return nil, errors.New("mem_limit must be greater than mem_reservation")
194+
}
195+
189196
outputContDef.SetCpu(cpu)
190197
if mem != 0 {
191198
outputContDef.SetMemory(mem)
@@ -211,7 +218,7 @@ func showResourceOverrideMsg(serviceName string, oldValue int64, newValue int64,
211218
overrideMsg := "Using ecs-params value as override (was %v but is now %v)"
212219

213220
log.WithFields(log.Fields{
214-
"option name": option,
221+
"option name": option,
215222
"service name": serviceName,
216223
}).Infof(overrideMsg, oldValue, newValue)
217224
}

ecs-cli/modules/utils/compose/convert_task_definition_test.go

Lines changed: 96 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,13 @@ func TestConvertToTaskDefinitionLaunchTypeFargate(t *testing.T) {
332332
}
333333

334334
// Tests for ConvertToContainerDefinition with ECS Params
335-
func TestConvertToTaskDefinitionWithECSParams_DefaultMemoryLessThanMemoryRes(t *testing.T) {
335+
func TestConvertToTaskDefinitionWithECSParams_ComposeMemoryLessThanMemoryRes(t *testing.T) {
336336
// set up containerConfig w/o value for Memory
337337
containerConfig := &adapter.ContainerConfig{
338-
Name: "web",
339-
Image: "httpd",
340-
CPU: int64(5),
338+
Name: "web",
339+
Image: "httpd",
340+
CPU: int64(5),
341+
Memory: int64(512),
341342
}
342343

343344
// define ecs-params value we expect to be present in final containerDefinition
@@ -942,7 +943,7 @@ task_definition:
942943
}
943944
}
944945

945-
func TestConvertToTaskDefinitionWithECSParams_SomeContainerResourcesProvided(t *testing.T) {
946+
func TestConvertToTaskDefinitionWithECSParams_MemLimitOnlyProvided(t *testing.T) {
946947
// set up containerConfig w/o value for Memory
947948
containerConfig := &adapter.ContainerConfig{
948949
Name: "web",
@@ -993,6 +994,48 @@ task_definition:
993994
}
994995
}
995996

997+
func TestConvertToTaskDefinitionWithECSParams_MemReservationOnlyProvided(t *testing.T) {
998+
containerConfigs := testContainerConfigs([]string{"web"})
999+
1000+
// define ecs-params value we expect to be present in final containerDefinition
1001+
webMem := int64(20)
1002+
1003+
ecsParamsString := `version: 1
1004+
task_definition:
1005+
services:
1006+
web:
1007+
mem_reservation: 20m`
1008+
1009+
content := []byte(ecsParamsString)
1010+
1011+
tmpfile, err := ioutil.TempFile("", "ecs-params")
1012+
assert.NoError(t, err, "Could not create ecs params tempfile")
1013+
1014+
defer os.Remove(tmpfile.Name())
1015+
1016+
_, err = tmpfile.Write(content)
1017+
assert.NoError(t, err, "Could not write data to ecs params tempfile")
1018+
1019+
err = tmpfile.Close()
1020+
assert.NoError(t, err, "Could not close tempfile")
1021+
1022+
ecsParamsFileName := tmpfile.Name()
1023+
ecsParams, err := ReadECSParams(ecsParamsFileName)
1024+
assert.NoError(t, err, "Could not read ECS Params file")
1025+
1026+
taskDefinition, err := convertToTaskDefWithEcsParamsInTest(t, containerConfigs, "", ecsParams)
1027+
1028+
containerDefs := taskDefinition.ContainerDefinitions
1029+
web := findContainerByName("web", containerDefs)
1030+
1031+
if assert.NoError(t, err) {
1032+
assert.Equal(t, webMem, aws.Int64Value(web.Memory), "Expected Memory to match")
1033+
assert.Equal(t, webMem, aws.Int64Value(web.MemoryReservation), "Expected MemoryReservation to match")
1034+
// check config values not present in ecs-params are present
1035+
assert.Empty(t, web.Cpu, "Expected CPU to be empty")
1036+
}
1037+
}
1038+
9961039
func TestConvertToTaskDefinitionWithECSParams_MemResGreaterThanMemLimit(t *testing.T) {
9971040
containerConfig := &adapter.ContainerConfig{Name: "web"}
9981041
ecsParamsString := `version: 1
@@ -1058,28 +1101,56 @@ task_definition:
10581101
}
10591102
}
10601103

1061-
func TestMemReservationHigherThanMemLimit(t *testing.T) {
1062-
cpu := int64(131072) // 128 * 1024
1063-
command := "cmd"
1064-
hostname := "local360"
1065-
image := "testimage"
1066-
memory := int64(65536) // 64mb
1067-
privileged := true
1068-
readOnly := true
1069-
user := "user"
1070-
workingDir := "/var"
1104+
func TestConvertToTaskDefinition_MemLimitOnlyProvided(t *testing.T) {
1105+
webMem := int64(1048576)
1106+
containerConfig := &adapter.ContainerConfig{
1107+
Name: "web",
1108+
Memory: webMem,
1109+
}
1110+
1111+
taskDefinition := convertToTaskDefinitionInTest(t, containerConfig, "", "")
1112+
containerDefs := taskDefinition.ContainerDefinitions
1113+
web := findContainerByName("web", containerDefs)
10711114

1115+
assert.Equal(t, webMem, aws.Int64Value(web.Memory), "Expected Memory to match")
1116+
assert.Empty(t, web.MemoryReservation, "Expected MemoryReservation to be empty")
1117+
assert.Empty(t, web.Cpu, "Expected CPU to be empty")
1118+
}
1119+
1120+
func TestConvertToTaskDefinition_MemReservationOnlyProvided(t *testing.T) {
1121+
webMem := int64(1048576)
1122+
containerConfig := &adapter.ContainerConfig{
1123+
Name: "web",
1124+
MemoryReservation: webMem,
1125+
}
1126+
1127+
taskDefinition := convertToTaskDefinitionInTest(t, containerConfig, "", "")
1128+
containerDefs := taskDefinition.ContainerDefinitions
1129+
web := findContainerByName("web", containerDefs)
1130+
1131+
assert.Equal(t, webMem, aws.Int64Value(web.Memory), "Expected Memory to match")
1132+
assert.Equal(t, webMem, aws.Int64Value(web.MemoryReservation), "Expected MemoryReservation to match")
1133+
assert.Empty(t, web.Cpu, "Expected CPU to be empty")
1134+
}
1135+
1136+
func TestConvertToTaskDefinition_NoMemoryProvided(t *testing.T) {
1137+
containerConfig := &adapter.ContainerConfig{
1138+
Name: "web",
1139+
}
1140+
1141+
taskDefinition := convertToTaskDefinitionInTest(t, containerConfig, "", "")
1142+
containerDefs := taskDefinition.ContainerDefinitions
1143+
web := findContainerByName("web", containerDefs)
1144+
1145+
assert.Equal(t, aws.Int64(defaultMemLimit), web.Memory, "Expected Memory to match default")
1146+
assert.Empty(t, web.MemoryReservation, "Expected MemoryReservation to be empty")
1147+
assert.Empty(t, web.Cpu, "Expected CPU to be empty")
1148+
}
1149+
1150+
func TestMemReservationHigherThanMemLimit(t *testing.T) {
10721151
containerConfig := adapter.ContainerConfig{
1073-
CPU: cpu,
1074-
Command: []string{command},
1075-
Hostname: hostname,
1076-
Image: image,
1077-
Memory: int64(524288) * memory,
1078-
MemoryReservation: int64(1048576) * memory,
1079-
Privileged: privileged,
1080-
ReadOnly: readOnly,
1081-
User: user,
1082-
WorkingDirectory: workingDir,
1152+
Memory: int64(524288),
1153+
MemoryReservation: int64(1048576),
10831154
}
10841155

10851156
volumeConfigs := adapter.NewVolumes()

0 commit comments

Comments
 (0)