From 41437ff8a318dbdf22a5fabcddcee5fe8aa64102 Mon Sep 17 00:00:00 2001 From: LiangChen Date: Thu, 2 Apr 2015 10:34:29 +0800 Subject: [PATCH] Docker plugin add cpu share property Docker API 1.8 and docker-py 0.3.0 support the cpu-shares parameter when creating containers(docker run --cpu-shares=0). By default, all containers run at the same priority and get the same proportion of CPU cycles, but you can tell the kernel to give more shares of CPU time to one or more containers when you start them via Docker. Change-Id: Ic20cc56070c120ab46379aa42d5da6ac9425cae7 Closes-bug: #1439033 --- .../heat_docker/resources/docker_container.py | 43 +++++++++++++------ .../tests/test_docker_container.py | 41 ++++++++++++++---- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/contrib/heat_docker/heat_docker/resources/docker_container.py b/contrib/heat_docker/heat_docker/resources/docker_container.py index bc257c600..f694c4386 100644 --- a/contrib/heat_docker/heat_docker/resources/docker_container.py +++ b/contrib/heat_docker/heat_docker/resources/docker_container.py @@ -31,7 +31,7 @@ from heat.engine import support LOG = logging.getLogger(__name__) DOCKER_INSTALLED = False -READ_ONLY_MIN_API_VERSION = '1.17' +MIN_API_VERSION_MAP = {'read_only': '1.17', 'cpu_shares': '1.8'} # conditionally import so tests can work without having the dependency # satisfied try: @@ -47,12 +47,12 @@ class DockerContainer(resource.Resource): DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, PORT_SPECS, PRIVILEGED, TTY, OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS, IMAGE, VOLUMES, VOLUMES_FROM, PORT_BINDINGS, LINKS, NAME, - RESTART_POLICY, CAP_ADD, CAP_DROP, READ_ONLY, + RESTART_POLICY, CAP_ADD, CAP_DROP, READ_ONLY, CPU_SHARES, ) = ( 'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs', 'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', 'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name', - 'restart_policy', 'cap_add', 'cap_drop', 'read_only' + 'restart_policy', 'cap_add', 'cap_drop', 'read_only', 'cpu_shares', ) ATTRIBUTES = ( @@ -220,9 +220,17 @@ class DockerContainer(resource.Resource): properties.Schema.BOOLEAN, _('If true, mount the container\'s root filesystem ' 'as read only (only supported for API version >= %s).') % - READ_ONLY_MIN_API_VERSION, + MIN_API_VERSION_MAP['read_only'], default=False, support_status=support.SupportStatus(version='2015.1'), + ), + CPU_SHARES: properties.Schema( + properties.Schema.INTEGER, + _('Relative weight which determines the allocation of the CPU ' + 'processing power(only supported for API version >= %s).') % + MIN_API_VERSION_MAP['cpu_shares'], + default=0, + support_status=support.SupportStatus(version='2015.2'), ) } @@ -340,10 +348,10 @@ class DockerContainer(resource.Resource): 'environment': self.properties[self.ENV], 'dns': self.properties[self.DNS], 'volumes': self.properties[self.VOLUMES], - 'name': self.properties[self.NAME] + 'name': self.properties[self.NAME], + 'cpu_shares': self.properties[self.CPU_SHARES] } client = self.get_client() - version = client.version()['ApiVersion'] client.pull(self.properties[self.IMAGE]) result = client.create_container(**create_args) container_id = result['Id'] @@ -368,12 +376,7 @@ class DockerContainer(resource.Resource): if self.properties[self.CAP_DROP]: start_args['cap_drop'] = self.properties[self.CAP_DROP] if self.properties[self.READ_ONLY]: - if compare_version(READ_ONLY_MIN_API_VERSION, version) >= 0: - start_args[self.READ_ONLY] = True - else: - raise InvalidArgForVersion(arg=self.READ_ONLY, - min_version=( - READ_ONLY_MIN_API_VERSION)) + start_args[self.READ_ONLY] = True client.start(container_id, **start_args) return container_id @@ -436,6 +439,22 @@ class DockerContainer(resource.Resource): status = self._get_container_status(container_id) return status['Running'] + def validate(self): + super(DockerContainer, self).validate() + self._validate_arg_for_api_version() + + def _validate_arg_for_api_version(self): + version = None + for key in MIN_API_VERSION_MAP: + if self.properties[key]: + if not version: + client = self.get_client() + version = client.version()['ApiVersion'] + min_version = MIN_API_VERSION_MAP[key] + if compare_version(min_version, version) < 0: + raise InvalidArgForVersion(arg=key, + min_version=min_version) + def resource_mapping(): return { diff --git a/contrib/heat_docker/heat_docker/tests/test_docker_container.py b/contrib/heat_docker/heat_docker/tests/test_docker_container.py index 72989f929..cf207dc57 100644 --- a/contrib/heat_docker/heat_docker/tests/test_docker_container.py +++ b/contrib/heat_docker/heat_docker/tests/test_docker_container.py @@ -19,6 +19,7 @@ from oslo_utils import importutils import six from heat.common import exception +from heat.common.i18n import _ from heat.common import template_format from heat.engine import resource from heat.engine import rsrc_defn @@ -314,24 +315,48 @@ class DockerContainerTest(common.HeatTestCase): self.assertEqual(['samalba/wordpress'], client.pulled_images) self.assertIs(True, client.container_start[0]['read_only']) - def test_start_with_read_only_for_low_api_version(self): + def arg_for_low_api_version(self, arg, value, low_version): t = template_format.parse(template) stack = utils.parse_stack(t) definition = stack.t.resource_definitions(stack)['Blog'] - definition['Properties']['read_only'] = True + definition['Properties'][arg] = value my_resource = docker_container.DockerContainer( 'Blog', definition, stack) get_client_mock = self.patchobject(my_resource, 'get_client') get_client_mock.return_value = fakeclient.FakeDockerClient() - get_client_mock.return_value.set_api_version('1.16') - self.assertIsNone(my_resource.validate()) - msg = self.assertRaises(exception.ResourceFailure, - scheduler.TaskRunner(my_resource.create)) - expected = ('InvalidArgForVersion: "read_only" is not supported ' - 'for API version < "1.17"') + get_client_mock.return_value.set_api_version(low_version) + msg = self.assertRaises(docker_container.InvalidArgForVersion, + my_resource.validate) + min_version = docker_container.MIN_API_VERSION_MAP[arg] + args = dict(arg=arg, min_version=min_version) + expected = _('"%(arg)s" is not supported for API version ' + '< "%(min_version)s"') % args self.assertEqual(expected, six.text_type(msg)) + def test_start_with_read_only_for_low_api_version(self): + self.arg_for_low_api_version('read_only', True, '1.16') + def test_compare_version(self): self.assertEqual(docker_container.compare_version('1.17', '1.17'), 0) self.assertEqual(docker_container.compare_version('1.17', '1.16'), -1) self.assertEqual(docker_container.compare_version('1.17', '1.18'), 1) + + def test_create_with_cpu_shares(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['cpu_shares'] = 512 + my_resource = docker_container.DockerContainer( + 'Blog', definition, stack) + get_client_mock = self.patchobject(my_resource, 'get_client') + get_client_mock.return_value = fakeclient.FakeDockerClient() + self.assertIsNone(my_resource.validate()) + scheduler.TaskRunner(my_resource.create)() + self.assertEqual((my_resource.CREATE, my_resource.COMPLETE), + my_resource.state) + client = my_resource.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertEqual(512, client.container_create[0]['cpu_shares']) + + def test_create_with_cpu_shares_for_low_api_version(self): + self.arg_for_low_api_version('cpu_shares', 512, '1.7')