diff --git a/contrib/docker/docker/resources/docker_container.py b/contrib/docker/docker/resources/docker_container.py index 91a7edd185..0199929c74 100644 --- a/contrib/docker/docker/resources/docker_container.py +++ b/contrib/docker/docker/resources/docker_container.py @@ -35,15 +35,13 @@ except ImportError: class DockerContainer(resource.Resource): PROPERTIES = ( - DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, ATTACH_STDIN, - ATTACH_STDOUT, ATTACH_STDERR, PORT_SPECS, PRIVILEGED, TTY, - OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS, IMAGE, VOLUMES, - VOLUMES_FROM, + DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, PORT_SPECS, + PRIVILEGED, TTY, OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS, + IMAGE, VOLUMES, VOLUMES_FROM, PORT_BINDINGS, LINKS, NAME, ) = ( - 'docker_endpoint', 'hostname', 'user', 'memory', 'attach_stdin', - 'attach_stdout', 'attach_stderr', 'port_specs', 'privileged', 'tty', - 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', 'image', 'volumes', - 'volumes_from', + 'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs', + 'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', + 'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name' ) ATTRIBUTES = ( @@ -60,122 +58,117 @@ class DockerContainer(resource.Resource): DOCKER_ENDPOINT: properties.Schema( properties.Schema.STRING, _('Docker daemon endpoint (by default the local docker daemon ' - 'will be used)'), + 'will be used).'), default=None ), HOSTNAME: properties.Schema( properties.Schema.STRING, - _('Hostname of the container'), + _('Hostname of the container.'), default='' ), USER: properties.Schema( properties.Schema.STRING, - _('Username or UID'), + _('Username or UID.'), default='' ), MEMORY: properties.Schema( properties.Schema.INTEGER, - _('Memory limit (Bytes)'), + _('Memory limit (Bytes).'), default=0 ), - ATTACH_STDIN: properties.Schema( - properties.Schema.BOOLEAN, - _('Attach to the process\' standard input'), - default=False - ), - ATTACH_STDOUT: properties.Schema( - properties.Schema.BOOLEAN, - _('Attach to the process\' standard output'), - default=True - ), - ATTACH_STDERR: properties.Schema( - properties.Schema.BOOLEAN, - _('Attach to the process\' standard error'), - default=True - ), PORT_SPECS: properties.Schema( properties.Schema.LIST, - _('TCP/UDP ports mapping'), + _('TCP/UDP ports mapping.'), default=None ), + PORT_BINDINGS: properties.Schema( + properties.Schema.MAP, + _('TCP/UDP ports bindings.'), + ), + LINKS: properties.Schema( + properties.Schema.MAP, + _('Links to other containers.'), + ), + NAME: properties.Schema( + properties.Schema.STRING, + _('Name of the container.'), + ), PRIVILEGED: properties.Schema( properties.Schema.BOOLEAN, - _('Enable extended privileges'), + _('Enable extended privileges.'), default=False ), TTY: properties.Schema( properties.Schema.BOOLEAN, - _('Allocate a pseudo-tty'), + _('Allocate a pseudo-tty.'), default=False ), OPEN_STDIN: properties.Schema( properties.Schema.BOOLEAN, - _('Open stdin'), + _('Open stdin.'), default=False ), STDIN_ONCE: properties.Schema( properties.Schema.BOOLEAN, - _('If true, close stdin after the 1 attached client disconnects'), + _('If true, close stdin after the 1 attached client disconnects.'), default=False ), ENV: properties.Schema( properties.Schema.LIST, - _('Set environment variables'), - default=None + _('Set environment variables.'), ), CMD: properties.Schema( properties.Schema.LIST, - _('Command to run after spawning the container'), + _('Command to run after spawning the container.'), default=[] ), DNS: properties.Schema( properties.Schema.LIST, - _('Set custom dns servers'), - default=None + _('Set custom dns servers.'), ), IMAGE: properties.Schema( properties.Schema.STRING, - _('Image name') + _('Image name.') ), VOLUMES: properties.Schema( properties.Schema.MAP, - _('Create a bind mount'), + _('Create a bind mount.'), default={} ), VOLUMES_FROM: properties.Schema( properties.Schema.STRING, - _('Mount all specified volumes'), + _('Mount all specified volumes.'), default='' ), } attributes_schema = { INFO: attributes.Schema( - _('Container info') + _('Container info.') ), NETWORK_INFO: attributes.Schema( - _('Container network info') + _('Container network info.') ), NETWORK_IP: attributes.Schema( - _('Container ip address') + _('Container ip address.') ), NETWORK_GATEWAY: attributes.Schema( - _('Container ip gateway') + _('Container ip gateway.') ), NETWORK_TCP_PORTS: attributes.Schema( - _('Container TCP ports') + _('Container TCP ports.') ), NETWORK_UDP_PORTS: attributes.Schema( - _('Container UDP ports') + _('Container UDP ports.') ), LOGS: attributes.Schema( - _('Container logs') + _('Container logs.') ), LOGS_HEAD: attributes.Schema( - _('Container first logs line') + _('Container first logs line.') ), LOGS_TAIL: attributes.Schema( - _('Container last logs line') + _('Container last logs line.') ), } @@ -264,8 +257,10 @@ class DockerContainer(resource.Resource): 'dns': self.properties[self.DNS], 'volumes': self.properties[self.VOLUMES], 'volumes_from': self.properties[self.VOLUMES_FROM], + 'name': self.properties[self.NAME] } client = self.get_client() + client.pull(self.properties[self.IMAGE]) result = client.create_container(**args) container_id = result['Id'] self.resource_id_set(container_id) @@ -275,6 +270,10 @@ class DockerContainer(resource.Resource): kwargs[self.PRIVILEGED] = True if self.properties[self.VOLUMES]: kwargs['binds'] = self.properties[self.VOLUMES] + if self.properties[self.PORT_BINDINGS]: + kwargs['port_bindings'] = self.properties[self.PORT_BINDINGS] + if self.properties[self.LINKS]: + kwargs['links'] = self.properties[self.LINKS] client.start(container_id, **kwargs) return container_id diff --git a/contrib/docker/docker/tests/fake_docker_client.py b/contrib/docker/docker/tests/fake_docker_client.py index 4fde226a4c..5a04c0ad76 100644 --- a/contrib/docker/docker/tests/fake_docker_client.py +++ b/contrib/docker/docker/tests/fake_docker_client.py @@ -27,6 +27,9 @@ class FakeDockerClient(object): def __init__(self, endpoint=None): self._endpoint = endpoint self._containers = {} + self.pulled_images = [] + self.container_create = [] + self.container_start = [] def _generate_string(self, n=32): return ''.join(random.choice(string.lowercase) for i in range(n)) @@ -66,13 +69,15 @@ class FakeDockerClient(object): logs.append('---logs_end---') return '\n'.join(logs) - def create_container(self, *args, **kwargs): + def create_container(self, **kwargs): + self.container_create.append(kwargs) container_id = self._generate_string() self._containers[container_id] = None self._set_running(container_id, False) return self.inspect_container(container_id) - def start(self, container_id, privileged=None): + def start(self, container_id, **kwargs): + self.container_start.append(kwargs) self._set_running(container_id, True) def stop(self, container_id): @@ -80,3 +85,6 @@ class FakeDockerClient(object): def kill(self, container_id): self._set_running(container_id, False) + + def pull(self, image): + self.pulled_images.append(image) diff --git a/contrib/docker/docker/tests/test_docker_container.py b/contrib/docker/docker/tests/test_docker_container.py index fc56c9b8d9..9c638c70de 100644 --- a/contrib/docker/docker/tests/test_docker_container.py +++ b/contrib/docker/docker/tests/test_docker_container.py @@ -51,6 +51,7 @@ class DockerContainerTest(HeatTestCase): super(DockerContainerTest, self).setUp() for res_name, res_class in docker_container.resource_mapping().items(): resource._register_class(res_name, res_class) + self.addCleanup(self.m.VerifyAll) def create_container(self, resource_name): t = template_format.parse(template) @@ -76,7 +77,50 @@ class DockerContainerTest(HeatTestCase): self.assertTrue(container.resource_id) running = self.get_container_state(container)['Running'] self.assertIs(True, running) - self.m.VerifyAll() + client = container.get_client() + self.assertEqual(['samalba/wordpress'], client.pulled_images) + self.assertIsNone(client.container_create[0]['name']) + + def test_create_with_name(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['name'] = 'super-blog' + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + self.m.StubOutWithMock(resource, 'get_client') + resource.get_client().MultipleTimes().AndReturn(FakeDockerClient()) + self.assertIsNone(resource.validate()) + self.m.ReplayAll() + 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('super-blog', client.container_create[0]['name']) + + def test_start_with_bindings_and_links(self): + t = template_format.parse(template) + stack = utils.parse_stack(t) + definition = stack.t.resource_definitions(stack)['Blog'] + definition['Properties']['port_bindings'] = { + '80/tcp': [{'HostPort': '80'}]} + definition['Properties']['links'] = {'db': 'mysql'} + resource = docker_container.DockerContainer( + 'Blog', definition, stack) + self.m.StubOutWithMock(resource, 'get_client') + resource.get_client().MultipleTimes().AndReturn(FakeDockerClient()) + self.assertIsNone(resource.validate()) + self.m.ReplayAll() + 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({'db': 'mysql'}, client.container_start[0]['links']) + self.assertEqual( + {'80/tcp': [{'HostPort': '80'}]}, + client.container_start[0]['port_bindings']) def test_resource_attributes(self): container = self.create_container('Blog') @@ -91,7 +135,6 @@ class DockerContainerTest(HeatTestCase): # Test a non existing attribute self.assertRaises(exception.InvalidTemplateAttribute, container.FnGetAtt, 'invalid_attribute') - self.m.VerifyAll() def test_resource_delete(self): container = self.create_container('Blog') @@ -100,7 +143,6 @@ class DockerContainerTest(HeatTestCase): container.state) running = self.get_container_state(container)['Running'] self.assertIs(False, running) - self.m.VerifyAll() def test_resource_suspend_resume(self): container = self.create_container('Blog') @@ -116,4 +158,3 @@ class DockerContainerTest(HeatTestCase): container.state) running = self.get_container_state(container)['Running'] self.assertIs(True, running) - self.m.VerifyAll()