diff --git a/docker/models/containers.py b/docker/models/containers.py index fb10ba90b3..c2c0c404bb 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -456,6 +456,9 @@ def run(self, image, command=None, stdout=True, stderr=False, cap_add (list of str): Add kernel capabilities. For example, ``["SYS_ADMIN", "MKNOD"]``. cap_drop (list of str): Drop kernel capabilities. + cpu_count (int): Number of usable CPUs (Windows only). + cpu_percent (int): Usable percentage of the available CPUs + (Windows only). 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. @@ -523,6 +526,7 @@ def run(self, image, command=None, stdout=True, stderr=False, networks (:py:class:`list`): A list of network names to connect this container to. name (str): The name for this container. + nano_cpus (int): CPU quota in units of 10-9 CPUs. network_disabled (bool): Disable networking. network_mode (str): One of: @@ -801,6 +805,8 @@ def prune(self, filters=None): 'cap_add', 'cap_drop', 'cgroup_parent', + 'cpu_count', + 'cpu_percent', 'cpu_period', 'cpu_quota', 'cpu_shares', @@ -827,6 +833,7 @@ def prune(self, filters=None): 'mem_reservation', 'mem_swappiness', 'memswap_limit', + 'nano_cpus', 'network_mode', 'oom_kill_disable', 'oom_score_adj', diff --git a/docker/types/containers.py b/docker/types/containers.py index 0af24cb845..9f1a04d104 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -118,7 +118,8 @@ def __init__(self, version, binds=None, port_bindings=None, tmpfs=None, oom_score_adj=None, dns_opt=None, cpu_shares=None, cpuset_cpus=None, userns_mode=None, pids_limit=None, isolation=None, auto_remove=False, storage_opt=None, - init=None, init_path=None, volume_driver=None): + init=None, init_path=None, volume_driver=None, + cpu_count=None, cpu_percent=None, nano_cpus=None): if mem_limit is not None: self['Memory'] = parse_bytes(mem_limit) @@ -433,6 +434,30 @@ def __init__(self, version, binds=None, port_bindings=None, raise host_config_version_error('volume_driver', '1.21') self['VolumeDriver'] = volume_driver + if cpu_count: + if not isinstance(cpu_count, int): + raise host_config_type_error('cpu_count', cpu_count, 'int') + if version_lt(version, '1.25'): + raise host_config_version_error('cpu_count', '1.25') + + self['CpuCount'] = cpu_count + + if cpu_percent: + if not isinstance(cpu_percent, int): + raise host_config_type_error('cpu_percent', cpu_percent, 'int') + if version_lt(version, '1.25'): + raise host_config_version_error('cpu_percent', '1.25') + + self['CpuPercent'] = cpu_percent + + if nano_cpus: + if not isinstance(nano_cpus, int): + raise host_config_type_error('nano_cpus', nano_cpus, 'int') + if version_lt(version, '1.25'): + raise host_config_version_error('nano_cpus', '1.25') + + self['NanoCpus'] = nano_cpus + def host_config_type_error(param, param_value, expected): error_msg = 'Invalid type for {0} param: expected {1} but found {2}' diff --git a/tests/unit/api_container_test.py b/tests/unit/api_container_test.py index ad79c5c6fe..901934ee7a 100644 --- a/tests/unit/api_container_test.py +++ b/tests/unit/api_container_test.py @@ -1152,6 +1152,38 @@ def test_create_container_with_unicode_envvars(self): self.assertEqual(args[0][1], url_prefix + 'containers/create') self.assertEqual(json.loads(args[1]['data'])['Env'], expected) + @requires_api_version('1.25') + def test_create_container_with_host_config_cpus(self): + self.client.create_container( + 'busybox', 'ls', host_config=self.client.create_host_config( + cpu_count=1, + cpu_percent=20, + nano_cpus=1000 + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "HostConfig": { + "CpuCount": 1, + "CpuPercent": 20, + "NanoCpus": 1000, + "NetworkMode": "default" + }}''')) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + class ContainerTest(BaseAPIClientTest): def test_list_containers(self): diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index cb1d90ca2d..63383677d7 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -173,6 +173,39 @@ def test_create_host_config_with_volume_driver(self): config = create_host_config(version='1.21', volume_driver='local') assert config.get('VolumeDriver') == 'local' + def test_create_host_config_invalid_cpu_count_types(self): + with pytest.raises(TypeError): + create_host_config(version='1.25', cpu_count='1') + + def test_create_host_config_with_cpu_count(self): + config = create_host_config(version='1.25', cpu_count=2) + self.assertEqual(config.get('CpuCount'), 2) + self.assertRaises( + InvalidVersion, lambda: create_host_config( + version='1.24', cpu_count=1)) + + def test_create_host_config_invalid_cpu_percent_types(self): + with pytest.raises(TypeError): + create_host_config(version='1.25', cpu_percent='1') + + def test_create_host_config_with_cpu_percent(self): + config = create_host_config(version='1.25', cpu_percent=15) + self.assertEqual(config.get('CpuPercent'), 15) + self.assertRaises( + InvalidVersion, lambda: create_host_config( + version='1.24', cpu_percent=10)) + + def test_create_host_config_invalid_nano_cpus_types(self): + with pytest.raises(TypeError): + create_host_config(version='1.25', nano_cpus='0') + + def test_create_host_config_with_nano_cpus(self): + config = create_host_config(version='1.25', nano_cpus=1000) + self.assertEqual(config.get('NanoCpus'), 1000) + self.assertRaises( + InvalidVersion, lambda: create_host_config( + version='1.24', nano_cpus=1)) + class ContainerConfigTest(unittest.TestCase): def test_create_container_config_volume_driver_warning(self):