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')