1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

docker_container: add device_requests option (#1119) (#1186)

* docker_container: add device_requests option.

* Fix copy'n'paste mistake.

* Fix failure test.

* Added example.

* Adjust tense.

(cherry picked from commit 8670eff750)

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
patchback[bot] 2020-10-27 22:43:25 +01:00 committed by GitHub
parent 176c9a90ca
commit 19a7aa462b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 0 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "docker_container - now supports the ``device_requests`` option, which allows to request additional resources such as GPUs (https://github.com/ansible/ansible/issues/65748, https://github.com/ansible-collections/community.general/pull/1119)."

View file

@ -212,6 +212,40 @@ options:
- "Must be a positive integer." - "Must be a positive integer."
type: int type: int
required: yes required: yes
device_requests:
description:
- Allows to request additional resources, such as GPUs.
type: list
elements: dict
suboptions:
capabilities:
description:
- List of lists of strings to request capabilities.
- The top-level list entries are combined by OR, and for every list entry,
the entries in the list it contains are combined by AND.
- The driver tries to satisfy one of the sub-lists.
- Available capabilities for the C(nvidia) driver can be found at
U(https://github.com/NVIDIA/nvidia-container-runtime).
type: list
elements: list
count:
description:
- Number or devices to request.
- Set to C(-1) to request all available devices.
type: int
device_ids:
description:
- List of device IDs.
type: list
elements: str
driver:
description:
- Which driver to use for this device.
type: str
options:
description:
- Driver-specific options.
type: dict
dns_opts: dns_opts:
description: description:
- List of DNS options. - List of DNS options.
@ -1047,6 +1081,26 @@ EXAMPLES = '''
# Limit read rate for /dev/sdb to 300 IO per second # Limit read rate for /dev/sdb to 300 IO per second
- path: /dev/sdb - path: /dev/sdb
rate: 300 rate: 300
- name: Start container with GPUs
community.general.docker_container:
name: test
image: ubuntu:18.04
state: started
device_requests:
- # Add some specific devices to this container
device_ids:
- '0'
- 'GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a'
- # Add nVidia GPUs to this container
driver: nvidia
count: -1 # this means we want all
capabilities:
# We have one OR condition: 'gpu' AND 'utility'
- - gpu
- utility
# See https://github.com/NVIDIA/nvidia-container-runtime#supported-driver-capabilities
# for a list of capabilities supported by the nvidia driver
''' '''
RETURN = ''' RETURN = '''
@ -1230,6 +1284,7 @@ class TaskParameters(DockerBaseClass):
self.device_write_bps = None self.device_write_bps = None
self.device_read_iops = None self.device_read_iops = None
self.device_write_iops = None self.device_write_iops = None
self.device_requests = None
self.dns_servers = None self.dns_servers = None
self.dns_opts = None self.dns_opts = None
self.dns_search_domains = None self.dns_search_domains = None
@ -1391,6 +1446,22 @@ class TaskParameters(DockerBaseClass):
if client.module.params.get(param_name): if client.module.params.get(param_name):
self._process_rate_iops(option=param_name) self._process_rate_iops(option=param_name)
if self.device_requests:
for dr_index, dr in enumerate(self.device_requests):
# Make sure that capabilities are lists of lists of strings
if dr['capabilities']:
for or_index, or_list in enumerate(dr['capabilities']):
for and_index, and_list in enumerate(or_list):
for term_index, term in enumerate(and_list):
if not isinstance(term, string_types):
self.fail(
"device_requests[{0}].capabilities[{1}][{2}][{3}] is not a string".format(
dr_index, or_index, and_index, term_index))
and_list[term_index] = to_native(term)
# Make sure that options is a dictionary mapping strings to strings
if dr['options']:
dr['options'] = clean_dict_booleans_for_docker_api(dr['options'])
def fail(self, msg): def fail(self, msg):
self.client.fail(msg) self.client.fail(msg)
@ -1594,6 +1665,9 @@ class TaskParameters(DockerBaseClass):
if 'mounts' in params: if 'mounts' in params:
params['mounts'] = self.mounts_opt params['mounts'] = self.mounts_opt
if self.device_requests is not None:
params['device_requests'] = [dict((k, v) for k, v in dr.items() if v is not None) for dr in self.device_requests]
return self.client.create_host_config(**params) return self.client.create_host_config(**params)
@property @property
@ -1990,6 +2064,7 @@ class Container(DockerBaseClass):
self.parameters.expected_sysctls = None self.parameters.expected_sysctls = None
self.parameters.expected_etc_hosts = None self.parameters.expected_etc_hosts = None
self.parameters.expected_env = None self.parameters.expected_env = None
self.parameters.expected_device_requests = None
self.parameters_map = dict() self.parameters_map = dict()
self.parameters_map['expected_links'] = 'links' self.parameters_map['expected_links'] = 'links'
self.parameters_map['expected_ports'] = 'expected_ports' self.parameters_map['expected_ports'] = 'expected_ports'
@ -2005,6 +2080,7 @@ class Container(DockerBaseClass):
self.parameters_map['expected_devices'] = 'devices' self.parameters_map['expected_devices'] = 'devices'
self.parameters_map['expected_healthcheck'] = 'healthcheck' self.parameters_map['expected_healthcheck'] = 'healthcheck'
self.parameters_map['expected_mounts'] = 'mounts' self.parameters_map['expected_mounts'] = 'mounts'
self.parameters_map['expected_device_requests'] = 'device_requests'
def fail(self, msg): def fail(self, msg):
self.parameters.client.fail(msg) self.parameters.client.fail(msg)
@ -2078,6 +2154,7 @@ class Container(DockerBaseClass):
self.parameters.expected_cmd = self._get_expected_cmd() self.parameters.expected_cmd = self._get_expected_cmd()
self.parameters.expected_devices = self._get_expected_devices() self.parameters.expected_devices = self._get_expected_devices()
self.parameters.expected_healthcheck = self._get_expected_healthcheck() self.parameters.expected_healthcheck = self._get_expected_healthcheck()
self.parameters.expected_device_requests = self._get_expected_device_requests()
if not self.container.get('HostConfig'): if not self.container.get('HostConfig'):
self.fail("has_config_diff: Error parsing container properties. HostConfig missing.") self.fail("has_config_diff: Error parsing container properties. HostConfig missing.")
@ -2155,6 +2232,7 @@ class Container(DockerBaseClass):
device_write_bps=host_config.get('BlkioDeviceWriteBps'), device_write_bps=host_config.get('BlkioDeviceWriteBps'),
device_read_iops=host_config.get('BlkioDeviceReadIOps'), device_read_iops=host_config.get('BlkioDeviceReadIOps'),
device_write_iops=host_config.get('BlkioDeviceWriteIOps'), device_write_iops=host_config.get('BlkioDeviceWriteIOps'),
expected_device_requests=host_config.get('DeviceRequests'),
pids_limit=host_config.get('PidsLimit'), pids_limit=host_config.get('PidsLimit'),
# According to https://github.com/moby/moby/, support for HostConfig.Mounts # According to https://github.com/moby/moby/, support for HostConfig.Mounts
# has been included at least since v17.03.0-ce, which has API version 1.26. # has been included at least since v17.03.0-ce, which has API version 1.26.
@ -2454,6 +2532,20 @@ class Container(DockerBaseClass):
self.log(result, pretty_print=True) self.log(result, pretty_print=True)
return result return result
def _get_expected_device_requests(self):
if self.parameters.device_requests is None:
return None
device_requests = []
for dr in self.parameters.device_requests:
device_requests.append({
'Driver': dr['driver'],
'Count': dr['count'],
'DeviceIDs': dr['device_ids'],
'Capabilities': dr['capabilities'],
'Options': dr['options'],
})
return device_requests
def _get_image_binds(self, volumes): def _get_image_binds(self, volumes):
''' '''
Convert array of binds to array of strings with format host_path:container_path:mode Convert array of binds to array of strings with format host_path:container_path:mode
@ -3089,6 +3181,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
explicit_types = dict( explicit_types = dict(
command='list', command='list',
devices='set(dict)', devices='set(dict)',
device_requests='set(dict)',
dns_search_domains='list', dns_search_domains='list',
dns_servers='list', dns_servers='list',
env='set', env='set',
@ -3222,6 +3315,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
device_read_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'), device_read_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_write_bps=dict(docker_py_version='1.9.0', docker_api_version='1.22'), device_write_bps=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_write_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'), device_write_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_requests=dict(docker_py_version='4.3.0', docker_api_version='1.40'),
dns_opts=dict(docker_api_version='1.21', docker_py_version='1.10.0'), dns_opts=dict(docker_api_version='1.21', docker_py_version='1.10.0'),
ipc_mode=dict(docker_api_version='1.25'), ipc_mode=dict(docker_api_version='1.25'),
mac_address=dict(docker_api_version='1.25'), mac_address=dict(docker_api_version='1.25'),
@ -3320,6 +3414,13 @@ def main():
path=dict(required=True, type='str'), path=dict(required=True, type='str'),
rate=dict(required=True, type='int'), rate=dict(required=True, type='int'),
)), )),
device_requests=dict(type='list', elements='dict', options=dict(
capabilities=dict(type='list', elements='list'),
count=dict(type='int'),
device_ids=dict(type='list', elements='str'),
driver=dict(type='str'),
options=dict(type='dict'),
)),
dns_servers=dict(type='list', elements='str'), dns_servers=dict(type='list', elements='str'),
dns_opts=dict(type='list', elements='str'), dns_opts=dict(type='list', elements='str'),
dns_search_domains=dict(type='list', elements='str'), dns_search_domains=dict(type='list', elements='str'),

View file

@ -891,6 +891,50 @@
- "'Minimum version required is 1.9.0 ' in device_write_limit_1.msg" - "'Minimum version required is 1.9.0 ' in device_write_limit_1.msg"
when: docker_py_version is version('1.9.0', '<') when: docker_py_version is version('1.9.0', '<')
####################################################################
## device_requests #################################################
####################################################################
- name: device_requests
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
device_requests: []
register: device_requests_1
ignore_errors: yes
- name: device_requests (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
device_requests: []
register: device_requests_2
ignore_errors: yes
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- device_requests_1 is changed
- device_requests_2 is not changed
when: docker_py_version is version('4.3.0', '>=') and docker_api_version is version('1.40', '>=')
- assert:
that:
- device_requests_1 is failed
- |
(('version is ' ~ docker_py_version ~ ' ') in device_requests_1.msg and 'Minimum version required is 4.3.0 ' in device_requests_1.msg) or
(('API version is ' ~ docker_api_version ~ '.') in device_requests_1.msg and 'Minimum version required is 1.40 ' in device_requests_1.msg)
when: docker_py_version is version('4.3.0', '<') or docker_api_version is version('1.40', '<')
#################################################################### ####################################################################
## dns_opts ######################################################## ## dns_opts ########################################################
#################################################################### ####################################################################