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:
parent
54a90a3cf6
commit
6906fd12b6
@ -35,15 +35,13 @@ except ImportError:
|
|||||||
class DockerContainer(resource.Resource):
|
class DockerContainer(resource.Resource):
|
||||||
|
|
||||||
PROPERTIES = (
|
PROPERTIES = (
|
||||||
DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, ATTACH_STDIN,
|
DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, PORT_SPECS,
|
||||||
ATTACH_STDOUT, ATTACH_STDERR, PORT_SPECS, PRIVILEGED, TTY,
|
PRIVILEGED, TTY, OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS,
|
||||||
OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS, IMAGE, VOLUMES,
|
IMAGE, VOLUMES, VOLUMES_FROM, PORT_BINDINGS, LINKS, NAME,
|
||||||
VOLUMES_FROM,
|
|
||||||
) = (
|
) = (
|
||||||
'docker_endpoint', 'hostname', 'user', 'memory', 'attach_stdin',
|
'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs',
|
||||||
'attach_stdout', 'attach_stderr', 'port_specs', 'privileged', 'tty',
|
'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns',
|
||||||
'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', 'image', 'volumes',
|
'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name'
|
||||||
'volumes_from',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ATTRIBUTES = (
|
ATTRIBUTES = (
|
||||||
@ -60,122 +58,117 @@ class DockerContainer(resource.Resource):
|
|||||||
DOCKER_ENDPOINT: properties.Schema(
|
DOCKER_ENDPOINT: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Docker daemon endpoint (by default the local docker daemon '
|
_('Docker daemon endpoint (by default the local docker daemon '
|
||||||
'will be used)'),
|
'will be used).'),
|
||||||
default=None
|
default=None
|
||||||
),
|
),
|
||||||
HOSTNAME: properties.Schema(
|
HOSTNAME: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Hostname of the container'),
|
_('Hostname of the container.'),
|
||||||
default=''
|
default=''
|
||||||
),
|
),
|
||||||
USER: properties.Schema(
|
USER: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Username or UID'),
|
_('Username or UID.'),
|
||||||
default=''
|
default=''
|
||||||
),
|
),
|
||||||
MEMORY: properties.Schema(
|
MEMORY: properties.Schema(
|
||||||
properties.Schema.INTEGER,
|
properties.Schema.INTEGER,
|
||||||
_('Memory limit (Bytes)'),
|
_('Memory limit (Bytes).'),
|
||||||
default=0
|
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(
|
PORT_SPECS: properties.Schema(
|
||||||
properties.Schema.LIST,
|
properties.Schema.LIST,
|
||||||
_('TCP/UDP ports mapping'),
|
_('TCP/UDP ports mapping.'),
|
||||||
default=None
|
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(
|
PRIVILEGED: properties.Schema(
|
||||||
properties.Schema.BOOLEAN,
|
properties.Schema.BOOLEAN,
|
||||||
_('Enable extended privileges'),
|
_('Enable extended privileges.'),
|
||||||
default=False
|
default=False
|
||||||
),
|
),
|
||||||
TTY: properties.Schema(
|
TTY: properties.Schema(
|
||||||
properties.Schema.BOOLEAN,
|
properties.Schema.BOOLEAN,
|
||||||
_('Allocate a pseudo-tty'),
|
_('Allocate a pseudo-tty.'),
|
||||||
default=False
|
default=False
|
||||||
),
|
),
|
||||||
OPEN_STDIN: properties.Schema(
|
OPEN_STDIN: properties.Schema(
|
||||||
properties.Schema.BOOLEAN,
|
properties.Schema.BOOLEAN,
|
||||||
_('Open stdin'),
|
_('Open stdin.'),
|
||||||
default=False
|
default=False
|
||||||
),
|
),
|
||||||
STDIN_ONCE: properties.Schema(
|
STDIN_ONCE: properties.Schema(
|
||||||
properties.Schema.BOOLEAN,
|
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
|
default=False
|
||||||
),
|
),
|
||||||
ENV: properties.Schema(
|
ENV: properties.Schema(
|
||||||
properties.Schema.LIST,
|
properties.Schema.LIST,
|
||||||
_('Set environment variables'),
|
_('Set environment variables.'),
|
||||||
default=None
|
|
||||||
),
|
),
|
||||||
CMD: properties.Schema(
|
CMD: properties.Schema(
|
||||||
properties.Schema.LIST,
|
properties.Schema.LIST,
|
||||||
_('Command to run after spawning the container'),
|
_('Command to run after spawning the container.'),
|
||||||
default=[]
|
default=[]
|
||||||
),
|
),
|
||||||
DNS: properties.Schema(
|
DNS: properties.Schema(
|
||||||
properties.Schema.LIST,
|
properties.Schema.LIST,
|
||||||
_('Set custom dns servers'),
|
_('Set custom dns servers.'),
|
||||||
default=None
|
|
||||||
),
|
),
|
||||||
IMAGE: properties.Schema(
|
IMAGE: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Image name')
|
_('Image name.')
|
||||||
),
|
),
|
||||||
VOLUMES: properties.Schema(
|
VOLUMES: properties.Schema(
|
||||||
properties.Schema.MAP,
|
properties.Schema.MAP,
|
||||||
_('Create a bind mount'),
|
_('Create a bind mount.'),
|
||||||
default={}
|
default={}
|
||||||
),
|
),
|
||||||
VOLUMES_FROM: properties.Schema(
|
VOLUMES_FROM: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Mount all specified volumes'),
|
_('Mount all specified volumes.'),
|
||||||
default=''
|
default=''
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes_schema = {
|
attributes_schema = {
|
||||||
INFO: attributes.Schema(
|
INFO: attributes.Schema(
|
||||||
_('Container info')
|
_('Container info.')
|
||||||
),
|
),
|
||||||
NETWORK_INFO: attributes.Schema(
|
NETWORK_INFO: attributes.Schema(
|
||||||
_('Container network info')
|
_('Container network info.')
|
||||||
),
|
),
|
||||||
NETWORK_IP: attributes.Schema(
|
NETWORK_IP: attributes.Schema(
|
||||||
_('Container ip address')
|
_('Container ip address.')
|
||||||
),
|
),
|
||||||
NETWORK_GATEWAY: attributes.Schema(
|
NETWORK_GATEWAY: attributes.Schema(
|
||||||
_('Container ip gateway')
|
_('Container ip gateway.')
|
||||||
),
|
),
|
||||||
NETWORK_TCP_PORTS: attributes.Schema(
|
NETWORK_TCP_PORTS: attributes.Schema(
|
||||||
_('Container TCP ports')
|
_('Container TCP ports.')
|
||||||
),
|
),
|
||||||
NETWORK_UDP_PORTS: attributes.Schema(
|
NETWORK_UDP_PORTS: attributes.Schema(
|
||||||
_('Container UDP ports')
|
_('Container UDP ports.')
|
||||||
),
|
),
|
||||||
LOGS: attributes.Schema(
|
LOGS: attributes.Schema(
|
||||||
_('Container logs')
|
_('Container logs.')
|
||||||
),
|
),
|
||||||
LOGS_HEAD: attributes.Schema(
|
LOGS_HEAD: attributes.Schema(
|
||||||
_('Container first logs line')
|
_('Container first logs line.')
|
||||||
),
|
),
|
||||||
LOGS_TAIL: attributes.Schema(
|
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],
|
'dns': self.properties[self.DNS],
|
||||||
'volumes': self.properties[self.VOLUMES],
|
'volumes': self.properties[self.VOLUMES],
|
||||||
'volumes_from': self.properties[self.VOLUMES_FROM],
|
'volumes_from': self.properties[self.VOLUMES_FROM],
|
||||||
|
'name': self.properties[self.NAME]
|
||||||
}
|
}
|
||||||
client = self.get_client()
|
client = self.get_client()
|
||||||
|
client.pull(self.properties[self.IMAGE])
|
||||||
result = client.create_container(**args)
|
result = client.create_container(**args)
|
||||||
container_id = result['Id']
|
container_id = result['Id']
|
||||||
self.resource_id_set(container_id)
|
self.resource_id_set(container_id)
|
||||||
@ -275,6 +270,10 @@ class DockerContainer(resource.Resource):
|
|||||||
kwargs[self.PRIVILEGED] = True
|
kwargs[self.PRIVILEGED] = True
|
||||||
if self.properties[self.VOLUMES]:
|
if self.properties[self.VOLUMES]:
|
||||||
kwargs['binds'] = 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)
|
client.start(container_id, **kwargs)
|
||||||
return container_id
|
return container_id
|
||||||
|
@ -27,6 +27,9 @@ class FakeDockerClient(object):
|
|||||||
def __init__(self, endpoint=None):
|
def __init__(self, endpoint=None):
|
||||||
self._endpoint = endpoint
|
self._endpoint = endpoint
|
||||||
self._containers = {}
|
self._containers = {}
|
||||||
|
self.pulled_images = []
|
||||||
|
self.container_create = []
|
||||||
|
self.container_start = []
|
||||||
|
|
||||||
def _generate_string(self, n=32):
|
def _generate_string(self, n=32):
|
||||||
return ''.join(random.choice(string.lowercase) for i in range(n))
|
return ''.join(random.choice(string.lowercase) for i in range(n))
|
||||||
@ -66,13 +69,15 @@ class FakeDockerClient(object):
|
|||||||
logs.append('---logs_end---')
|
logs.append('---logs_end---')
|
||||||
return '\n'.join(logs)
|
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()
|
container_id = self._generate_string()
|
||||||
self._containers[container_id] = None
|
self._containers[container_id] = None
|
||||||
self._set_running(container_id, False)
|
self._set_running(container_id, False)
|
||||||
return self.inspect_container(container_id)
|
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)
|
self._set_running(container_id, True)
|
||||||
|
|
||||||
def stop(self, container_id):
|
def stop(self, container_id):
|
||||||
@ -80,3 +85,6 @@ class FakeDockerClient(object):
|
|||||||
|
|
||||||
def kill(self, container_id):
|
def kill(self, container_id):
|
||||||
self._set_running(container_id, False)
|
self._set_running(container_id, False)
|
||||||
|
|
||||||
|
def pull(self, image):
|
||||||
|
self.pulled_images.append(image)
|
||||||
|
@ -51,6 +51,7 @@ class DockerContainerTest(HeatTestCase):
|
|||||||
super(DockerContainerTest, self).setUp()
|
super(DockerContainerTest, self).setUp()
|
||||||
for res_name, res_class in docker_container.resource_mapping().items():
|
for res_name, res_class in docker_container.resource_mapping().items():
|
||||||
resource._register_class(res_name, res_class)
|
resource._register_class(res_name, res_class)
|
||||||
|
self.addCleanup(self.m.VerifyAll)
|
||||||
|
|
||||||
def create_container(self, resource_name):
|
def create_container(self, resource_name):
|
||||||
t = template_format.parse(template)
|
t = template_format.parse(template)
|
||||||
@ -76,7 +77,50 @@ class DockerContainerTest(HeatTestCase):
|
|||||||
self.assertTrue(container.resource_id)
|
self.assertTrue(container.resource_id)
|
||||||
running = self.get_container_state(container)['Running']
|
running = self.get_container_state(container)['Running']
|
||||||
self.assertIs(True, 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):
|
def test_resource_attributes(self):
|
||||||
container = self.create_container('Blog')
|
container = self.create_container('Blog')
|
||||||
@ -91,7 +135,6 @@ class DockerContainerTest(HeatTestCase):
|
|||||||
# Test a non existing attribute
|
# Test a non existing attribute
|
||||||
self.assertRaises(exception.InvalidTemplateAttribute,
|
self.assertRaises(exception.InvalidTemplateAttribute,
|
||||||
container.FnGetAtt, 'invalid_attribute')
|
container.FnGetAtt, 'invalid_attribute')
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_resource_delete(self):
|
def test_resource_delete(self):
|
||||||
container = self.create_container('Blog')
|
container = self.create_container('Blog')
|
||||||
@ -100,7 +143,6 @@ class DockerContainerTest(HeatTestCase):
|
|||||||
container.state)
|
container.state)
|
||||||
running = self.get_container_state(container)['Running']
|
running = self.get_container_state(container)['Running']
|
||||||
self.assertIs(False, running)
|
self.assertIs(False, running)
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_resource_suspend_resume(self):
|
def test_resource_suspend_resume(self):
|
||||||
container = self.create_container('Blog')
|
container = self.create_container('Blog')
|
||||||
@ -116,4 +158,3 @@ class DockerContainerTest(HeatTestCase):
|
|||||||
container.state)
|
container.state)
|
||||||
running = self.get_container_state(container)['Running']
|
running = self.get_container_state(container)['Running']
|
||||||
self.assertIs(True, running)
|
self.assertIs(True, running)
|
||||||
self.m.VerifyAll()
|
|
||||||
|
Loading…
Reference in New Issue
Block a user