Support for device read write limit parameters (#47814)
* Support for device read write limit parameters * Add following options in docker_container module - device_read_bps - device_write_bps - device_read_iops - device_write_iops Fixes #36831 * test for device_read_iops * combined test for device_write_bps and device_write_iops
This commit is contained in:
parent
042a0cff23
commit
80ca779aa7
3 changed files with 321 additions and 0 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "docker_container - Add support for device I/O rate limit parameters. This includes ``device_read_bps``, ``device_write_bps``, ``device_read_iops`` and ``device_write_iops``"
|
|
@ -100,6 +100,38 @@ options:
|
|||
description:
|
||||
- "List of host device bindings to add to the container. Each binding is a mapping expressed
|
||||
in the format: <path_on_host>:<path_in_container>:<cgroup_permissions>"
|
||||
device_read_bps:
|
||||
description:
|
||||
- "List of device path and read rate (bytes per second) from device."
|
||||
- "I(path) - device path in the container."
|
||||
- "I(rate) - device read limit (format: <number>[<unit>])"
|
||||
- "Number is a positive integer. Unit can be one of C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
|
||||
C(T) (tebibyte), or C(P) (pebibyte)"
|
||||
- "Omitting the unit defaults to bytes."
|
||||
version_added: "2.8"
|
||||
device_write_bps:
|
||||
description:
|
||||
- "List of device and write rate (bytes per second) to device."
|
||||
- "I(path) - device path in the container."
|
||||
- "I(rate) - device write limit (format: <number>[<unit>])"
|
||||
- "Number is a positive integer. Unit can be one of C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
|
||||
C(T) (tebibyte), or C(P) (pebibyte)"
|
||||
- "Omitting the unit defaults to bytes."
|
||||
version_added: "2.8"
|
||||
device_read_iops:
|
||||
description:
|
||||
- "List of device and read rate (IO per second) from device."
|
||||
- "I(path) - device path in the container."
|
||||
- "I(rate) - device read limit (format: <number>)"
|
||||
- "Number is a positive integer."
|
||||
version_added: "2.8"
|
||||
device_write_iops:
|
||||
description:
|
||||
- "List of device and write rate (IO per second) to device."
|
||||
- "I(path) - device path in the container."
|
||||
- "I(rate) - device write limit (format: <number>)"
|
||||
- "Number is a positive integer."
|
||||
version_added: "2.8"
|
||||
dns_opts:
|
||||
description:
|
||||
- list of DNS options
|
||||
|
@ -697,6 +729,20 @@ EXAMPLES = '''
|
|||
healthcheck:
|
||||
# The "NONE" check needs to be specified
|
||||
test: ["NONE"]
|
||||
|
||||
- name: start container with block device read limit
|
||||
docker_container:
|
||||
name: test
|
||||
image: ubuntu:18.04
|
||||
state: started
|
||||
device_read_bps:
|
||||
# Limit read rate for /dev/sda to 20 mebibytes per second
|
||||
- path: /dev/sda
|
||||
rate: 20M
|
||||
device_read_iops:
|
||||
# Limit read rate for /dev/sdb to 300 IO per second
|
||||
- path: /dev/sdb
|
||||
rate: 300
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -853,6 +899,10 @@ class TaskParameters(DockerBaseClass):
|
|||
self.detach = None
|
||||
self.debug = None
|
||||
self.devices = None
|
||||
self.device_read_bps = None
|
||||
self.device_write_bps = None
|
||||
self.device_read_iops = None
|
||||
self.device_write_iops = None
|
||||
self.dns_servers = None
|
||||
self.dns_opts = None
|
||||
self.dns_search_domains = None
|
||||
|
@ -990,6 +1040,14 @@ class TaskParameters(DockerBaseClass):
|
|||
if isinstance(self.command, list):
|
||||
self.command = ' '.join([str(x) for x in self.command])
|
||||
|
||||
for param_name in ["device_read_bps", "device_write_bps"]:
|
||||
if client.module.params.get(param_name):
|
||||
self._process_rate_bps(option=param_name)
|
||||
|
||||
for param_name in ["device_read_iops", "device_write_iops"]:
|
||||
if client.module.params.get(param_name):
|
||||
self._process_rate_iops(option=param_name)
|
||||
|
||||
def fail(self, msg):
|
||||
self.client.module.fail_json(msg=msg)
|
||||
|
||||
|
@ -1171,6 +1229,13 @@ class TaskParameters(DockerBaseClass):
|
|||
if self.client.HAS_RUNTIME_OPT:
|
||||
host_config_params['runtime'] = 'runtime'
|
||||
|
||||
if self.client.HAS_DEVICE_RW_LIMIT_OPT:
|
||||
# device_read/write_bps/iops are only supported in docker>=1.9 and docker-api>=1.22
|
||||
host_config_params['device_read_bps'] = 'device_read_bps'
|
||||
host_config_params['device_write_bps'] = 'device_write_bps'
|
||||
host_config_params['device_read_iops'] = 'device_read_iops'
|
||||
host_config_params['device_write_iops'] = 'device_write_iops'
|
||||
|
||||
params = dict()
|
||||
for key, value in host_config_params.items():
|
||||
if getattr(self, value, None) is not None:
|
||||
|
@ -1488,6 +1553,33 @@ class TaskParameters(DockerBaseClass):
|
|||
self.fail("Error getting network id for %s - %s" % (network_name, str(exc)))
|
||||
return network_id
|
||||
|
||||
def _process_rate_bps(self, option):
|
||||
"""
|
||||
Format device_read_bps and device_write_bps option
|
||||
"""
|
||||
devices_list = []
|
||||
for v in getattr(self, option):
|
||||
device_dict = dict((x.title(), y) for x, y in v.items())
|
||||
device_dict['Rate'] = human_to_bytes(device_dict.get('Rate', 0))
|
||||
devices_list.append(device_dict)
|
||||
|
||||
setattr(self, option, devices_list)
|
||||
|
||||
def _process_rate_iops(self, option):
|
||||
"""
|
||||
Format device_read_iops and device_write_iops option
|
||||
"""
|
||||
devices_list = []
|
||||
for v in getattr(self, option):
|
||||
try:
|
||||
device_dict = dict((x.title(), y) for x, y in v.items())
|
||||
device_dict['Rate'] = int(device_dict.get('Rate', 0))
|
||||
devices_list.append(device_dict)
|
||||
except ValueError:
|
||||
self.fail("Invalid device iops value: '{0}'. Must be a positive integer.".format(device_dict.get('Rate')))
|
||||
|
||||
setattr(self, option, devices_list)
|
||||
|
||||
|
||||
class Container(DockerBaseClass):
|
||||
|
||||
|
@ -1523,6 +1615,10 @@ class Container(DockerBaseClass):
|
|||
self.parameters_map['expected_cmd'] = 'command'
|
||||
self.parameters_map['expected_devices'] = 'devices'
|
||||
self.parameters_map['expected_healthcheck'] = 'healthcheck'
|
||||
self.parameters_map['device_read_bps'] = 'device_read_bps'
|
||||
self.parameters_map['device_write_bps'] = 'device_write_bps'
|
||||
self.parameters_map['device_read_iops'] = 'device_read_iops'
|
||||
self.parameters_map['device_write_iops'] = 'device_write_iops'
|
||||
|
||||
def fail(self, msg):
|
||||
self.parameters.client.module.fail_json(msg=msg)
|
||||
|
@ -1642,6 +1738,10 @@ class Container(DockerBaseClass):
|
|||
publish_all_ports=host_config.get('PublishAllPorts'),
|
||||
expected_healthcheck=config.get('Healthcheck'),
|
||||
disable_healthcheck=(not config.get('Healthcheck') or config.get('Healthcheck').get('Test') == ['NONE']),
|
||||
device_read_bps=host_config.get('BlkioDeviceReadBps'),
|
||||
device_write_bps=host_config.get('BlkioDeviceWriteBps'),
|
||||
device_read_iops=host_config.get('BlkioDeviceReadIOps'),
|
||||
device_write_iops=host_config.get('BlkioDeviceWriteIOps'),
|
||||
)
|
||||
if self.parameters.restart_policy:
|
||||
config_mapping['restart_retries'] = restart_policy.get('MaximumRetryCount')
|
||||
|
@ -2394,6 +2494,10 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
|
|||
entrypoint='list',
|
||||
etc_hosts='set',
|
||||
ulimits='set(dict)',
|
||||
device_read_bps='set(dict)',
|
||||
device_write_bps='set(dict)',
|
||||
device_read_iops='set(dict)',
|
||||
device_write_iops='set(dict)',
|
||||
)
|
||||
all_options = set() # this is for improving user feedback when a wrong option was specified for comparison
|
||||
default_values = dict(
|
||||
|
@ -2539,12 +2643,28 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
|
|||
if self.module.params.get("healthcheck") and not healthcheck_supported:
|
||||
self.fail("docker or docker-py version is %s. Minimum version required is 2.0 to set healthcheck option." % (docker_version,))
|
||||
|
||||
found_device_limit_param = False
|
||||
for x in ["device_read_bps", "device_write_bps", "device_read_iops", "device_write_iops"]:
|
||||
if self.module.params.get(x):
|
||||
found_device_limit_param = True
|
||||
break
|
||||
|
||||
device_rw_limit_supported = LooseVersion(docker_api_version) >= LooseVersion('1.22')
|
||||
if found_device_limit_param and not device_rw_limit_supported:
|
||||
self.fail('docker API version is %s. Minimum version required is 1.22 to set device IO limit options.' % (docker_api_version,))
|
||||
|
||||
device_rw_limit_supported = device_rw_limit_supported and LooseVersion(docker_version) >= LooseVersion('1.9.0')
|
||||
if found_device_limit_param and not device_rw_limit_supported:
|
||||
self.fail("docker or docker-py version is %s. Minimum version required is 1.9 to set device IO limit optons. "
|
||||
"If you use the 'docker-py' module, you have to switch to the docker 'Python' package." % (docker_version,))
|
||||
|
||||
self.HAS_INIT_OPT = init_supported
|
||||
self.HAS_UTS_MODE_OPT = uts_mode_supported
|
||||
self.HAS_BLKIO_WEIGHT_OPT = blkio_weight_supported
|
||||
self.HAS_CPUSET_MEMS_OPT = cpuset_mems_supported
|
||||
self.HAS_STOP_TIMEOUT_OPT = stop_timeout_supported
|
||||
self.HAS_HEALTHCHECK_OPT = healthcheck_supported
|
||||
self.HAS_DEVICE_RW_LIMIT_OPT = device_rw_limit_supported
|
||||
|
||||
self.HAS_AUTO_REMOVE_OPT = HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3
|
||||
self.HAS_RUNTIME_OPT = runtime_supported
|
||||
|
@ -2571,6 +2691,10 @@ def main():
|
|||
cpu_shares=dict(type='int'),
|
||||
detach=dict(type='bool', default=True),
|
||||
devices=dict(type='list'),
|
||||
device_read_bps=dict(type='list'),
|
||||
device_write_bps=dict(type='list'),
|
||||
device_read_iops=dict(type='list'),
|
||||
device_write_iops=dict(type='list'),
|
||||
dns_servers=dict(type='list'),
|
||||
dns_opts=dict(type='list'),
|
||||
dns_search_domains=dict(type='list'),
|
||||
|
|
|
@ -586,6 +586,201 @@
|
|||
- devices_3 is not changed
|
||||
- devices_4 is changed
|
||||
|
||||
####################################################################
|
||||
## device_read_bps #################################################
|
||||
####################################################################
|
||||
|
||||
- name: device_read_bps
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_bps:
|
||||
- path: /dev/random
|
||||
rate: 20M
|
||||
- path: /dev/urandom
|
||||
rate: 10K
|
||||
register: device_read_bps_1
|
||||
|
||||
- name: device_read_bps (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_bps:
|
||||
- path: /dev/urandom
|
||||
rate: 10K
|
||||
- path: /dev/random
|
||||
rate: 20M
|
||||
register: device_read_bps_2
|
||||
|
||||
- name: device_read_bps (lesser entries)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_bps:
|
||||
- path: /dev/random
|
||||
rate: 20M
|
||||
register: device_read_bps_3
|
||||
|
||||
- name: device_read_bps (changed)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_bps:
|
||||
- path: /dev/random
|
||||
rate: 10M
|
||||
- path: /dev/urandom
|
||||
rate: 5K
|
||||
stop_timeout: 1
|
||||
register: device_read_bps_4
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
stop_timeout: 1
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- device_read_bps_1 is changed
|
||||
- device_read_bps_2 is not changed
|
||||
- device_read_bps_3 is not changed
|
||||
- device_read_bps_4 is changed
|
||||
|
||||
####################################################################
|
||||
## device_read_iops ################################################
|
||||
####################################################################
|
||||
|
||||
- name: device_read_iops
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_iops:
|
||||
- path: /dev/random
|
||||
rate: 10
|
||||
- path: /dev/urandom
|
||||
rate: 20
|
||||
register: device_read_iops_1
|
||||
|
||||
- name: device_read_iops (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_iops:
|
||||
- path: /dev/urandom
|
||||
rate: 20
|
||||
- path: /dev/random
|
||||
rate: 10
|
||||
register: device_read_iops_2
|
||||
|
||||
- name: device_read_iops (less)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_iops:
|
||||
- path: /dev/random
|
||||
rate: 10
|
||||
register: device_read_iops_3
|
||||
|
||||
- name: device_read_iops (changed)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_read_iops:
|
||||
- path: /dev/random
|
||||
rate: 30
|
||||
- path: /dev/urandom
|
||||
rate: 50
|
||||
stop_timeout: 1
|
||||
register: device_read_iops_4
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
stop_timeout: 1
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- device_read_iops_1 is changed
|
||||
- device_read_iops_2 is not changed
|
||||
- device_read_iops_3 is not changed
|
||||
- device_read_iops_4 is changed
|
||||
|
||||
####################################################################
|
||||
## device_write_bps and device_write_iops ##########################
|
||||
####################################################################
|
||||
|
||||
- name: device_write_bps and device_write_iops
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_write_bps:
|
||||
- path: /dev/random
|
||||
rate: 10M
|
||||
device_write_iops:
|
||||
- path: /dev/urandom
|
||||
rate: 30
|
||||
register: device_write_limit_1
|
||||
|
||||
- name: device_write_bps and device_write_iops (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_write_bps:
|
||||
- path: /dev/random
|
||||
rate: 10M
|
||||
device_write_iops:
|
||||
- path: /dev/urandom
|
||||
rate: 30
|
||||
register: device_write_limit_2
|
||||
|
||||
- name: device_write_bps device_write_iops (changed)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
device_write_bps:
|
||||
- path: /dev/random
|
||||
rate: 20K
|
||||
device_write_iops:
|
||||
- path: /dev/urandom
|
||||
rate: 100
|
||||
stop_timeout: 1
|
||||
register: device_write_limit_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
stop_timeout: 1
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- device_write_limit_1 is changed
|
||||
- device_write_limit_2 is not changed
|
||||
- device_write_limit_3 is changed
|
||||
|
||||
####################################################################
|
||||
## dns_opts ########################################################
|
||||
####################################################################
|
||||
|
|
Loading…
Reference in a new issue