diff --git a/changelogs/fragments/64683-docker_container-cpus.yml b/changelogs/fragments/64683-docker_container-cpus.yml new file mode 100644 index 00000000000..cb82b29b156 --- /dev/null +++ b/changelogs/fragments/64683-docker_container-cpus.yml @@ -0,0 +1,2 @@ +minor_changes: +- "docker_container - add ``cpus`` option (https://github.com/ansible/ansible/issues/34320)." diff --git a/lib/ansible/modules/cloud/docker/docker_container.py b/lib/ansible/modules/cloud/docker/docker_container.py index 6b23cca516d..d7ef9f00278 100644 --- a/lib/ansible/modules/cloud/docker/docker_container.py +++ b/lib/ansible/modules/cloud/docker/docker_container.py @@ -112,11 +112,19 @@ options: cpu_period: description: - Limit CPU CFS (Completely Fair Scheduler) period. + - See I(cpus) for an easier to use alternative. type: int cpu_quota: description: - Limit CPU CFS (Completely Fair Scheduler) quota. + - See I(cpus) for an easier to use alternative. type: int + cpus: + description: + - Specify how much of the available CPU resources a container can use. + - A value of C(1.5) means that at most one and a half CPU (core) will be used. + type: float + version_added: '2.10' cpuset_cpus: description: - CPUs in which to allow execution C(1,3) or C(1-3). @@ -1207,6 +1215,7 @@ class TaskParameters(DockerBaseClass): self.command = None self.cpu_period = None self.cpu_quota = None + self.cpus = None self.cpuset_cpus = None self.cpuset_mems = None self.cpu_shares = None @@ -1293,6 +1302,9 @@ class TaskParameters(DockerBaseClass): if self.state == 'absent': return + if self.cpus is not None: + self.cpus = int(round(self.cpus * 1E9)) + if self.groups: # In case integers are passed as groups, we need to convert them to # strings as docker internally treats them as strings. @@ -1544,6 +1556,7 @@ class TaskParameters(DockerBaseClass): device_write_iops='device_write_iops', pids_limit='pids_limit', mounts='mounts', + nano_cpus='cpus', ) if self.client.docker_py_version >= LooseVersion('1.9') and self.client.docker_api_version >= LooseVersion('1.22'): @@ -2125,6 +2138,7 @@ class Container(DockerBaseClass): # The previous tag, v1.9.1, has API version 1.21 and does not have # HostConfig.Mounts. I have no idea what about API 1.25... expected_mounts=self._decode_mounts(host_config.get('Mounts')), + cpus=host_config.get('NanoCpus'), ) # Options which don't make sense without their accompanying option if self.parameters.restart_policy: @@ -3156,6 +3170,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient): uts=dict(docker_py_version='3.5.0', docker_api_version='1.25'), pids_limit=dict(docker_py_version='1.10.0', docker_api_version='1.23'), mounts=dict(docker_py_version='2.6.0', docker_api_version='1.25'), + cpus=dict(docker_py_version='2.3.0', docker_api_version='1.25'), # specials ipvX_address_supported=dict(docker_py_version='1.9.0', docker_api_version='1.22', detect_usage=detect_ipvX_address_usage, @@ -3212,6 +3227,7 @@ def main(): container_default_behavior=dict(type='str', choices=['compatibility', 'no_defaults']), cpu_period=dict(type='int'), cpu_quota=dict(type='int'), + cpus=dict(type='float'), cpuset_cpus=dict(type='str'), cpuset_mems=dict(type='str'), cpu_shares=dict(type='int'), diff --git a/test/integration/targets/docker_container/tasks/tests/options.yml b/test/integration/targets/docker_container/tasks/tests/options.yml index 3ec843655e2..5cced99584b 100644 --- a/test/integration/targets/docker_container/tasks/tests/options.yml +++ b/test/integration/targets/docker_container/tasks/tests/options.yml @@ -430,6 +430,63 @@ - cpuset_mems_2 is not changed - cpuset_mems_3 is failed or cpuset_mems_3 is changed +#################################################################### +## cpus ############################################################ +#################################################################### + +- name: cpus + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1 + state: started + ignore_errors: yes + register: cpus_1 + +- name: cpus (idempotency) + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1 + state: started + ignore_errors: yes + register: cpus_2 + +- name: cpus (change) + docker_container: + image: alpine:3.8 + command: '/bin/sh -c "sleep 10m"' + name: "{{ cname }}" + cpus: 1.5 + state: started + force_kill: yes + # This will fail if the system the test is run on doesn't have + # multiple MEMs available. + ignore_errors: yes + register: cpus_3 + +- name: cleanup + docker_container: + name: "{{ cname }}" + state: absent + force_kill: yes + diff: no + +- assert: + that: + - cpus_1 is changed + - cpus_2 is not changed and cpus_2 is not failed + - cpus_3 is failed or cpus_3 is changed + when: docker_py_version is version('2.3.0', '>=') +- assert: + that: + - cpus_1 is failed + - "('version is ' ~ docker_py_version ~ ' ') in cpus_1.msg" + - "'Minimum version required is 2.3.0 ' in cpus_1.msg" + when: docker_py_version is version('2.3.0', '<') + #################################################################### ## debug ########################################################### ####################################################################