diff --git a/contrib/heat_docker/heat_docker/resources/docker_container.py b/contrib/heat_docker/heat_docker/resources/docker_container.py index 393c5de47a..ac9f09f9d0 100644 --- a/contrib/heat_docker/heat_docker/resources/docker_container.py +++ b/contrib/heat_docker/heat_docker/resources/docker_container.py @@ -20,6 +20,7 @@ import six from heat.common.i18n import _ from heat.common.i18n import _LW from heat.engine import attributes +from heat.engine import constraints from heat.engine import properties from heat.engine import resource @@ -41,10 +42,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, ) = ( 'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs', 'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', - 'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name' + 'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name', + 'restart_policy', 'cap_add', 'cap_drop' ) ATTRIBUTES = ( @@ -57,6 +60,23 @@ class DockerContainer(resource.Resource): 'logs_tail', ) + _RESTART_POLICY_KEYS = ( + POLICY_NAME, POLICY_MAXIMUM_RETRY_COUNT, + ) = ( + 'Name', 'MaximumRetryCount', + ) + + _CAPABILITIES = ['SETPCAP', 'SYS_MODULE', 'SYS_RAWIO', 'SYS_PACCT', + 'SYS_ADMIN', 'SYS_NICE', 'SYS_RESOURCE', 'SYS_TIME', + 'SYS_TTY_CONFIG', 'MKNOD', 'AUDIT_WRITE', + 'AUDIT_CONTROL', 'MAC_OVERRIDE', 'MAC_ADMIN', + 'NET_ADMIN', 'SYSLOG', 'CHOWN', 'NET_RAW', + 'DAC_OVERRIDE', 'FOWNER', 'DAC_READ_SEARCH', 'FSETID', + 'KILL', 'SETGID', 'SETUID', 'LINUX_IMMUTABLE', + 'NET_BIND_SERVICE', 'NET_BROADCAST', 'IPC_LOCK', + 'IPC_OWNER', 'SYS_CHROOT', 'SYS_PTRACE', 'SYS_BOOT', + 'LEASE', 'SETFCAP', 'WAKE_ALARM', 'BLOCK_SUSPEND', 'ALL'] + properties_schema = { DOCKER_ENDPOINT: properties.Schema( properties.Schema.STRING, @@ -143,6 +163,54 @@ class DockerContainer(resource.Resource): _('Mount all specified volumes.'), default='' ), + RESTART_POLICY: properties.Schema( + properties.Schema.MAP, + _('Restart policies (only supported for API version >= 1.2.0).'), + schema={ + POLICY_NAME: properties.Schema( + properties.Schema.STRING, + _('The behavior to apply when the container exits.'), + default='no', + constraints=[ + constraints.AllowedValues(['no', 'on-failure', + 'always']), + ] + ), + POLICY_MAXIMUM_RETRY_COUNT: properties.Schema( + properties.Schema.INTEGER, + _('A maximum restart count for the ' + 'on-failure policy.'), + default=0 + ) + }, + default={} + ), + CAP_ADD: properties.Schema( + properties.Schema.LIST, + _('Be used to add kernel capabilities (only supported for ' + 'API version >= 1.2.0).'), + schema=properties.Schema( + properties.Schema.STRING, + _('The security features provided by Linux kernels.'), + constraints=[ + constraints.AllowedValues(_CAPABILITIES), + ] + ), + default=[] + ), + CAP_DROP: properties.Schema( + properties.Schema.LIST, + _('Be used to drop kernel capabilities (only supported for ' + 'API version >= 1.2.0).'), + schema=properties.Schema( + properties.Schema.STRING, + _('The security features provided by Linux kernels.'), + constraints=[ + constraints.AllowedValues(_CAPABILITIES), + ] + ), + default=[] + ) } attributes_schema = { @@ -279,6 +347,12 @@ class DockerContainer(resource.Resource): start_args['port_bindings'] = self.properties[self.PORT_BINDINGS] if self.properties[self.LINKS]: start_args['links'] = self.properties[self.LINKS] + if self.properties[self.RESTART_POLICY]: + start_args['restart_policy'] = self.properties[self.RESTART_POLICY] + if self.properties[self.CAP_ADD]: + start_args['cap_add'] = self.properties[self.CAP_ADD] + if self.properties[self.CAP_DROP]: + start_args['cap_drop'] = self.properties[self.CAP_DROP] client.start(container_id, **start_args) return container_id 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 a103445318..d232f51bbf 100644 --- a/contrib/heat_docker/heat_docker/tests/test_docker_container.py +++ b/contrib/heat_docker/heat_docker/tests/test_docker_container.py @@ -219,3 +219,79 @@ class DockerContainerTest(common.HeatTestCase): container.state) running = self.get_container_state(container)['Running'] self.assertIs(True, running) + + def test_start_with_restart_policy_no(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['restart_policy'] = { + 'Name': 'no', 'MaximumRetryCount': 0} + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + get_client_mock = self.patchobject(resource, 'get_client') + get_client_mock.return_value = fakeclient.FakeDockerClient() + self.assertIsNone(resource.validate()) + scheduler.TaskRunner(resource.create)() + self.assertEqual((resource.CREATE, resource.COMPLETE), + resource.state) + client = resource.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertEqual({'Name': 'no', 'MaximumRetryCount': 0}, + client.container_start[0]['restart_policy']) + + def test_start_with_restart_policy_on_failure(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['restart_policy'] = { + 'Name': 'on-failure', 'MaximumRetryCount': 10} + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + get_client_mock = self.patchobject(resource, 'get_client') + get_client_mock.return_value = fakeclient.FakeDockerClient() + self.assertIsNone(resource.validate()) + scheduler.TaskRunner(resource.create)() + self.assertEqual((resource.CREATE, resource.COMPLETE), + resource.state) + client = resource.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertEqual({'Name': 'on-failure', 'MaximumRetryCount': 10}, + client.container_start[0]['restart_policy']) + + def test_start_with_restart_policy_always(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['restart_policy'] = { + 'Name': 'always', 'MaximumRetryCount': 0} + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + get_client_mock = self.patchobject(resource, 'get_client') + get_client_mock.return_value = fakeclient.FakeDockerClient() + self.assertIsNone(resource.validate()) + scheduler.TaskRunner(resource.create)() + self.assertEqual((resource.CREATE, resource.COMPLETE), + resource.state) + client = resource.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertEqual({'Name': 'always', 'MaximumRetryCount': 0}, + client.container_start[0]['restart_policy']) + + def test_start_with_caps(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['cap_add'] = ['NET_ADMIN'] + definition['Properties']['cap_drop'] = ['MKNOD'] + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + get_client_mock = self.patchobject(resource, 'get_client') + get_client_mock.return_value = fakeclient.FakeDockerClient() + self.assertIsNone(resource.validate()) + scheduler.TaskRunner(resource.create)() + self.assertEqual((resource.CREATE, resource.COMPLETE), + resource.state) + client = resource.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertEqual(['NET_ADMIN'], client.container_start[0]['cap_add']) + self.assertEqual(['MKNOD'], client.container_start[0]['cap_drop'])