Expose recent docker features in the resource

This adds the name, port_bindings and links properties to the docker
container resource which are passed to container creation and start.
This also removes properties which were never implemented.

Change-Id: I7320e3a95895b894cc53ebb77ebb220482f990c1
This commit is contained in:
Thomas Herve 2014-07-07 17:49:57 +02:00
parent 54a90a3cf6
commit 6906fd12b6
3 changed files with 103 additions and 55 deletions

View File

@ -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

View File

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

View File

@ -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()