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.
* 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:
parent
176c9a90ca
commit
19a7aa462b
3 changed files with 147 additions and 0 deletions
|
@ -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)."
|
|
@ -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'),
|
||||||
|
|
|
@ -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 ########################################################
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
Loading…
Reference in a new issue