Extend API with update_container_from_resources

The update container command allows to update quite some properties.
From Docker's Swagger yaml it seems that all properties from the
Resource object are supported. Introduce a new API
update_container_from_resources similar to create_container_from_config
to support all properties supported by the Resources object.

Signed-off-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
Stefan Agner 2021-04-06 14:45:26 +02:00
parent c2b09f824c
commit 2d74a3bb1a
No known key found for this signature in database
GPG Key ID: AE01353D1E44747D
4 changed files with 194 additions and 37 deletions

View File

@ -8,6 +8,7 @@ from ..types import ContainerConfig
from ..types import EndpointConfig
from ..types import HostConfig
from ..types import NetworkingConfig
from ..types import HostResources
class ContainerApiMixin:
@ -607,6 +608,92 @@ class ContainerApiMixin:
kwargs['version'] = self._version
return HostConfig(*args, **kwargs)
def create_resources(self, *args, **kwargs):
"""
Create a dictionary for the ``resources`` argument to
:py:meth:`update_container`.
Args:
blkio_weight_device: Block IO weight (relative device weight) in
the form of: ``[{"Path": "device_path", "Weight": weight}]``.
blkio_weight: Block IO weight (relative weight), accepts a weight
value between 10 and 1000.
cgroup_parent (string): Path to `cgroups` under which the
container's `cgroup` is created. If the path is not absolute,
the path is considered to be relative to the `cgroups` path of
the init process. Cgroups are created if they do not already
exist.
cpu_period (int): The length of a CPU period in microseconds.
cpu_quota (int): Microseconds of CPU time that the container can
get in a CPU period.
cpu_rt_period (int): The length of a CPU real-time period in
microseconds. Set to 0 to allocate no time allocated to
real-time tasks.
cpu_rt_runtime (int): The length of a CPU real-time runtime in
microseconds. Set to 0 to allocate no time allocated to
real-time tasks.
cpu_shares (int): CPU shares (relative weight).
cpuset_cpus (str): CPUs in which to allow execution (``0-3``,
``0,1``).
cpuset_mems (str): Memory nodes (MEMs) in which to allow execution
(``0-3``, ``0,1``). Only effective on NUMA systems.
device_cgroup_rules (:py:class:`list`): A list of cgroup rules to
apply to the container.
device_read_bps: Limit read rate (bytes per second) from a device
in the form of: `[{"Path": "device_path", "Rate": rate}]`
device_read_iops: Limit read rate (IO per second) from a device.
device_write_bps: Limit write rate (bytes per second) from a
device.
device_write_iops: Limit write rate (IO per second) from a device.
devices (:py:class:`list`): Expose host devices to the container,
as a list of strings in the form
``<path_on_host>:<path_in_container>:<cgroup_permissions>``.
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
kernel_memory (int or str): Kernel memory limit
mem_limit (float or str): Memory limit. Accepts float values
(which represent the memory limit of the created container in
bytes) or a string with a units identification char
(``100000b``, ``1000k``, ``128m``, ``1g``). If a string is
specified without a units character, bytes are assumed as an
mem_reservation (float or str): Memory soft limit.
mem_swappiness (int): Tune a container's memory swappiness
behavior. Accepts number between 0 and 100.
memswap_limit (str or int): Maximum amount of memory + swap a
container is allowed to consume.
nano_cpus (int): CPU quota in units of 10<sup>-9</sup> CPUs.
oom_kill_disable (bool): Whether to disable OOM killer.
pids_limit (int): Tune a container's pids limit. Set ``-1`` for
unlimited.
ulimits (:py:class:`list`): Ulimits to set inside the container,
as a list of :py:class:`docker.types.Ulimit` instances.
Returns:
(dict) A dictionary which can be passed to the ``resources``
argument to :py:meth:`update_container`.
Example:
>>> cli.create_resources(mem_limit='1g', cpuset_cpus='0-3')
{'MemLimit': '1g', 'CpusetCpus': '0-3'}
"""
if not kwargs:
kwargs = {}
if 'version' in kwargs:
raise TypeError(
"create_resources() got an unexpected "
"keyword argument 'version'"
)
kwargs['version'] = self._version
hres = HostResources(*args, **kwargs)
return hres
def create_networking_config(self, *args, **kwargs):
"""
Create a networking config dictionary to be used as the
@ -1246,28 +1333,25 @@ class ContainerApiMixin:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
url = self._url('/containers/{0}/update', container)
data = {}
if blkio_weight:
data['BlkioWeight'] = blkio_weight
if cpu_period:
data['CpuPeriod'] = cpu_period
if cpu_shares:
data['CpuShares'] = cpu_shares
if cpu_quota:
data['CpuQuota'] = cpu_quota
if cpuset_cpus:
data['CpusetCpus'] = cpuset_cpus
if cpuset_mems:
data['CpusetMems'] = cpuset_mems
if mem_limit:
data['Memory'] = utils.parse_bytes(mem_limit)
if mem_reservation:
data['MemoryReservation'] = utils.parse_bytes(mem_reservation)
if memswap_limit:
data['MemorySwap'] = utils.parse_bytes(memswap_limit)
if kernel_memory:
data['KernelMemory'] = utils.parse_bytes(kernel_memory)
resources = self.create_resources(
blkio_weight=blkio_weight, cpu_period=cpu_period,
cpu_shares=cpu_shares, cpu_quota=cpu_quota,
cpuset_cpus=cpuset_cpus, cpuset_mems=cpuset_mems,
mem_limit=mem_limit, mem_reservation=mem_reservation,
memswap_limit=memswap_limit, kernel_memory=kernel_memory
)
return self.update_container_from_resources(
container, resources, restart_policy
)
@utils.minimum_version('1.22')
@utils.check_resource('container')
def update_container_from_resources(self, container, resources,
restart_policy):
u = self._url('/containers/{0}/update', container)
data = resources.copy()
if restart_policy:
if utils.version_lt(self._version, '1.23'):
raise errors.InvalidVersion(
@ -1275,8 +1359,8 @@ class ContainerApiMixin:
'for API version < 1.23'
)
data['RestartPolicy'] = restart_policy
res = self._post_json(url, data=data)
print(data)
res = self._post_json(u, data=data)
return self._result(res, True)
@utils.check_resource('container')

View File

@ -9,6 +9,7 @@ from ..errors import (
NotFound, create_unexpected_kwargs_error
)
from ..types import HostConfig
from ..types import HostResources
from ..utils import version_gte
from .images import Image
from .resource import Collection, Model
@ -466,18 +467,66 @@ class Container(Model):
Update resource configuration of the containers.
Args:
blkio_weight (int): Block IO (relative weight), between 10 and 1000
cpu_period (int): Limit CPU CFS (Completely Fair Scheduler) period
cpu_quota (int): Limit CPU CFS (Completely Fair Scheduler) quota
cpu_shares (int): CPU shares (relative weight)
cpuset_cpus (str): CPUs in which to allow execution
cpuset_mems (str): MEMs in which to allow execution
mem_limit (int or str): Memory limit
mem_reservation (int or str): Memory soft limit
memswap_limit (int or str): Total memory (memory + swap), -1 to
disable swap
blkio_weight_device: Block IO weight (relative device weight) in
the form of: ``[{"Path": "device_path", "Weight": weight}]``.
blkio_weight: Block IO weight (relative weight), accepts a weight
value between 10 and 1000.
cgroup_parent (string): Path to `cgroups` under which the
container's `cgroup` is created. If the path is not absolute,
the path is considered to be relative to the `cgroups` path of
the init process. Cgroups are created if they do not already
exist.
cpu_period (int): The length of a CPU period in microseconds.
cpu_quota (int): Microseconds of CPU time that the container can
get in a CPU period.
cpu_rt_period (int): The length of a CPU real-time period in
microseconds. Set to 0 to allocate no time allocated to
real-time tasks.
cpu_rt_runtime (int): The length of a CPU real-time runtime in
microseconds. Set to 0 to allocate no time allocated to
real-time tasks.
cpu_shares (int): CPU shares (relative weight).
cpuset_cpus (str): CPUs in which to allow execution (``0-3``,
``0,1``).
cpuset_mems (str): Memory nodes (MEMs) in which to allow execution
(``0-3``, ``0,1``). Only effective on NUMA systems.
device_cgroup_rules (:py:class:`list`): A list of cgroup rules to
apply to the container.
device_read_bps: Limit read rate (bytes per second) from a device
in the form of: `[{"Path": "device_path", "Rate": rate}]`
device_read_iops: Limit read rate (IO per second) from a device.
device_write_bps: Limit write rate (bytes per second) from a
device.
device_write_iops: Limit write rate (IO per second) from a device.
devices (:py:class:`list`): Expose host devices to the container,
as a list of strings in the form
``<path_on_host>:<path_in_container>:<cgroup_permissions>``.
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
kernel_memory (int or str): Kernel memory limit
mem_limit (float or str): Memory limit. Accepts float values
(which represent the memory limit of the created container in
bytes) or a string with a units identification char
(``100000b``, ``1000k``, ``128m``, ``1g``). If a string is
specified without a units character, bytes are assumed as an
mem_reservation (float or str): Memory soft limit.
mem_swappiness (int): Tune a container's memory swappiness
behavior. Accepts number between 0 and 100.
memswap_limit (str or int): Maximum amount of memory + swap a
container is allowed to consume.
nano_cpus (int): CPU quota in units of 10<sup>-9</sup> CPUs.
oom_kill_disable (bool): Whether to disable OOM killer.
pids_limit (int): Tune a container's pids limit. Set ``-1`` for
unlimited.
restart_policy (dict): Restart policy dictionary
ulimits (:py:class:`list`): Ulimits to set inside the container,
as a list of :py:class:`docker.types.Ulimit` instances.
Returns:
(dict): Dictionary containing a ``Warnings`` key.
@ -486,7 +535,14 @@ class Container(Model):
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
return self.client.api.update_container(self.id, **kwargs)
resources_kwargs = copy.copy(kwargs)
resources_kwargs.pop("restart_policy", None)
resources_kwargs['version'] = self.client.api._version
return self.client.api.update_container_from_resources(
self.id, resources=HostResources(**resources_kwargs),
restart_policy=kwargs.get("restart_policy", None)
)
def wait(self, **kwargs):
"""

View File

@ -434,6 +434,20 @@ class ContainerTest(BaseIntegrationTest):
container.reload()
assert container.attrs['HostConfig']['CpuShares'] == 3
def test_update_device_cgroup_rules(self):
client = docker.from_env(version=TEST_API_VERSION)
container = client.containers.run("alpine", "sleep 60", detach=True,
device_cgroup_rules=["c 13:* rwm"])
self.tmp_containers.append(container.id)
device_cgrp_rules = container.attrs['HostConfig']['DeviceCgroupRules']
assert len(device_cgrp_rules) == 1
assert device_cgrp_rules[0] == "c 13:* rwm"
container.update(device_cgroup_rules=["c 13:* rw"])
container.reload()
device_cgrp_rules_new = container.attrs['HostConfig']['DeviceCgroupRules']
assert len(device_cgrp_rules_new) == 1
assert device_cgrp_rules_new[0] == "c 13:* rw"
def test_wait(self):
client = docker.from_env(version=TEST_API_VERSION)
container = client.containers.run("alpine", "sh -c 'exit 0'",

View File

@ -582,8 +582,11 @@ class ContainerTest(unittest.TestCase):
client = make_fake_client()
container = client.containers.get(FAKE_CONTAINER_ID)
container.update(cpu_shares=2)
client.api.update_container.assert_called_with(FAKE_CONTAINER_ID,
cpu_shares=2)
client.api.update_container_from_resources.assert_called_with(
FAKE_CONTAINER_ID,
resources={'CpuShares': 2},
restart_policy=None
)
def test_wait(self):
client = make_fake_client()