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):
|
||||
|
||||
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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user