Skip to content

Commit f8f2404

Browse files
huzechmr-cgeorge
authored
docker: enforce cores ResourceRequirement if requested (#1594)
Co-authored-by: Michael R. Crusoe <[email protected]> Co-authored-by: george <[email protected]>
1 parent 7a86924 commit f8f2404

9 files changed

+144
-10
lines changed

cwltool/argparser.py

+10
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,16 @@ def arg_parser() -> argparse.ArgumentParser:
391391
"default of 1 gigabyte to Docker's --memory option.",
392392
)
393393

394+
parser.add_argument(
395+
"--strict-cpu-limit",
396+
action="store_true",
397+
help="When running with "
398+
"software containers and the Docker engine, pass either the "
399+
"calculated cpu allocation from ResourceRequirements or the "
400+
"default of 1 core to Docker's --cpu option. "
401+
"Requires docker version >= v1.13.",
402+
)
403+
394404
parser.add_argument(
395405
"--timestamps",
396406
action="store_true",

cwltool/context.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def __init__(self, kwargs: Optional[Dict[str, Any]] = None) -> None:
151151
) # type: Optional[Callable[[CWLObjectType], Optional[CWLObjectType]]]
152152
self.on_error = "stop" # type: str
153153
self.strict_memory_limit = False # type: bool
154-
154+
self.strict_cpu_limit = False # type: bool
155155
self.cidfile_dir = None # type: Optional[str]
156156
self.cidfile_prefix = None # type: Optional[str]
157157

cwltool/docker.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import csv
44
import datetime
5+
import math
56
import os
67
import re
78
import shutil
@@ -434,11 +435,12 @@ def create_runtime(
434435
for key, value in self.environment.items():
435436
runtime.append(f"--env={key}={value}")
436437

438+
res_req, _ = self.builder.get_requirement("ResourceRequirement")
439+
437440
if runtimeContext.strict_memory_limit and not user_space_docker_cmd:
438441
ram = self.builder.resources["ram"]
439442
runtime.append("--memory=%dm" % ram)
440443
elif not user_space_docker_cmd:
441-
res_req, _ = self.builder.get_requirement("ResourceRequirement")
442444
if res_req and ("ramMin" in res_req or "ramMax" in res_req):
443445
_logger.warning(
444446
"[job %s] Skipping Docker software container '--memory' limit "
@@ -448,5 +450,18 @@ def create_runtime(
448450
"assurance.",
449451
self.name,
450452
)
453+
if runtimeContext.strict_cpu_limit and not user_space_docker_cmd:
454+
cpus = math.ceil(self.builder.resources["cores"])
455+
runtime.append(f"--cpus={cpus}")
456+
elif not user_space_docker_cmd:
457+
if res_req and ("coresMin" in res_req or "coresMax" in res_req):
458+
_logger.warning(
459+
"[job %s] Skipping Docker software container '--cpus' limit "
460+
"despite presence of ResourceRequirement with coresMin "
461+
"and/or coresMax setting. Consider running with "
462+
"--strict-cpu-limit for increased portability "
463+
"assurance.",
464+
self.name,
465+
)
451466

452467
return runtime, cidfile_path

tests/test_docker.py

+61-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Tests for docker engine."""
2+
import re
23
from pathlib import Path
34
from shutil import which
45

@@ -13,7 +14,7 @@ def test_docker_workflow(tmp_path: Path) -> None:
1314
result_code, _, stderr = get_main_output(
1415
[
1516
"--default-container",
16-
"debian",
17+
"docker.io/debian:stable-slim",
1718
"--outdir",
1819
str(tmp_path),
1920
get_data("tests/wf/hello-workflow.cwl"),
@@ -30,7 +31,7 @@ def test_docker_iwdr() -> None:
3031
result_code = main(
3132
[
3233
"--default-container",
33-
"debian",
34+
"docker.io/debian:stable-slim",
3435
get_data("tests/wf/iwdr-entry.cwl"),
3536
"--message",
3637
"hello",
@@ -69,3 +70,61 @@ def test_docker_file_mount() -> None:
6970
[get_data("tests/wf/literalfile.cwl"), get_data("tests/wf/literalfile-job.yml")]
7071
)
7172
assert result_code == 0
73+
74+
75+
@needs_docker
76+
def test_docker_strict_cpu_limit() -> None:
77+
result_code, stdout, stderr = get_main_output(
78+
[
79+
"--strict-cpu-limit",
80+
"--default-container",
81+
"docker.io/debian:stable-slim",
82+
get_data("tests/wf/cores_float.cwl"),
83+
]
84+
)
85+
stderr = re.sub(r"\s\s+", " ", stderr)
86+
assert result_code == 0
87+
assert "--cpus=2" in stderr
88+
89+
90+
@needs_docker
91+
def test_docker_strict_memory_limit() -> None:
92+
result_code, stdout, stderr = get_main_output(
93+
[
94+
"--strict-memory-limit",
95+
"--default-container",
96+
"docker.io/debian:stable-slim",
97+
get_data("tests/wf/storage_float.cwl"),
98+
]
99+
)
100+
stderr = re.sub(r"\s\s+", " ", stderr)
101+
assert result_code == 0
102+
assert "--memory=255m" in stderr
103+
104+
105+
@needs_docker
106+
def test_docker_strict_cpu_limit_warning() -> None:
107+
result_code, stdout, stderr = get_main_output(
108+
[
109+
"--default-container",
110+
"docker.io/debian:stable-slim",
111+
get_data("tests/wf/cores_float.cwl"),
112+
]
113+
)
114+
stderr = re.sub(r"\s\s+", " ", stderr)
115+
assert result_code == 0
116+
assert "Skipping Docker software container '--cpus' limit" in stderr
117+
118+
119+
@needs_docker
120+
def test_docker_strict_memory_limit_warning() -> None:
121+
result_code, stdout, stderr = get_main_output(
122+
[
123+
"--default-container",
124+
"docker.io/debian:stable-slim",
125+
get_data("tests/wf/storage_float.cwl"),
126+
]
127+
)
128+
stderr = re.sub(r"\s\s+", " ", stderr)
129+
assert result_code == 0
130+
assert "Skipping Docker software container '--memory' limit" in stderr

tests/test_environment.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def HOME(v: str, env: Env) -> bool:
106106
"HOSTNAME": None,
107107
}
108108

109-
flags = ["--default-container=debian"]
109+
flags = ["--default-container=docker.io/debian:stable-slim"]
110110
env_accepts_null = True
111111

112112

@@ -174,7 +174,7 @@ def BIND(v: str, env: Env) -> bool:
174174

175175
return ans
176176

177-
flags = ["--default-container=debian", "--singularity"]
177+
flags = ["--default-container=docker.io/debian:stable-slim", "--singularity"]
178178
env_accepts_null = True
179179

180180

tests/test_examples.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ def test_bad_userspace_runtime(factor: str) -> None:
13451345
commands.extend(
13461346
[
13471347
"--user-space-docker-cmd=quaquioN",
1348-
"--default-container=debian",
1348+
"--default-container=docker.io/debian:stable-slim",
13491349
get_data(test_file),
13501350
get_data(job_file),
13511351
]
@@ -1372,7 +1372,14 @@ def test_bad_basecommand(factor: str) -> None:
13721372
def test_bad_basecommand_docker(factor: str) -> None:
13731373
test_file = "tests/wf/missing-tool.cwl"
13741374
commands = factor.split()
1375-
commands.extend(["--debug", "--default-container", "debian", get_data(test_file)])
1375+
commands.extend(
1376+
[
1377+
"--debug",
1378+
"--default-container",
1379+
"docker.io/debian:stable-slim",
1380+
get_data(test_file),
1381+
]
1382+
)
13761383
error_code, stdout, stderr = get_main_output(commands)
13771384
assert "permanentFail" in stderr, stderr
13781385
assert error_code == 1

tests/test_singularity.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_singularity_workflow(tmp_path: Path) -> None:
4646
[
4747
"--singularity",
4848
"--default-container",
49-
"debian",
49+
"docker.io/debian:stable-slim",
5050
"--debug",
5151
get_data("tests/wf/hello-workflow.cwl"),
5252
"--usermessage",
@@ -62,7 +62,7 @@ def test_singularity_iwdr() -> None:
6262
[
6363
"--singularity",
6464
"--default-container",
65-
"debian",
65+
"docker.io/debian:stable-slim",
6666
get_data("tests/wf/iwdr-entry.cwl"),
6767
"--message",
6868
"hello",

tests/wf/cores_float.cwl

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env cwl-runner
2+
class: CommandLineTool
3+
cwlVersion: v1.2
4+
5+
requirements:
6+
ResourceRequirement:
7+
coresMin: 1.25
8+
coresMax: 1.75
9+
10+
inputs: []
11+
12+
outputs:
13+
output:
14+
type: stdout
15+
16+
baseCommand: echo
17+
18+
stdout: cores.txt
19+
20+
arguments: [ $(runtime.cores) ]

tests/wf/storage_float.cwl

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env cwl-runner
2+
class: CommandLineTool
3+
cwlVersion: v1.2
4+
5+
requirements:
6+
ResourceRequirement:
7+
ramMin: 254.1
8+
ramMax: 254.9
9+
tmpdirMin: 255.1
10+
tmpdirMax: 255.9
11+
outdirMin: 256.1
12+
outdirMax: 256.9
13+
14+
inputs: []
15+
16+
outputs:
17+
output: stdout
18+
19+
baseCommand: echo
20+
21+
stdout: values.txt
22+
23+
arguments: [ $(runtime.ram), $(runtime.tmpdirSize), $(runtime.outdirSize) ]

0 commit comments

Comments
 (0)