Implement Zun compute
* Implement compute manager * Introduce zun/container folder for containers (equivalent to nova/virt in Nova) * Have a docker driver in zun/container/docker * Remove conductor since it is not needed anymore Change-Id: I627f7ba1c40584178e1526947088c847554a82af
This commit is contained in:
parent
70984e38d4
commit
d740149945
@ -1,48 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Starter script for the Zun conductor service."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_service import service
|
|
||||||
|
|
||||||
from zun.common.i18n import _LI
|
|
||||||
from zun.common import rpc_service
|
|
||||||
from zun.common import service as zun_service
|
|
||||||
from zun.common import short_id
|
|
||||||
from zun.conductor.handlers import default as default_handler
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
zun_service.prepare_service(sys.argv)
|
|
||||||
|
|
||||||
LOG.info(_LI('Starting server in PID %s'), os.getpid())
|
|
||||||
cfg.CONF.log_opt_values(LOG, logging.DEBUG)
|
|
||||||
|
|
||||||
cfg.CONF.import_opt('topic', 'zun.conductor.config', group='conductor')
|
|
||||||
|
|
||||||
conductor_id = short_id.generate_id()
|
|
||||||
endpoints = [
|
|
||||||
default_handler.Handler(),
|
|
||||||
]
|
|
||||||
|
|
||||||
server = rpc_service.Service.create(cfg.CONF.conductor.topic,
|
|
||||||
conductor_id, endpoints,
|
|
||||||
binary='zun-conductor')
|
|
||||||
launcher = service.launch(cfg.CONF, server)
|
|
||||||
launcher.wait()
|
|
@ -337,5 +337,9 @@ class ConfigInvalid(ZunException):
|
|||||||
message = _("Invalid configuration file. %(error_msg)s")
|
message = _("Invalid configuration file. %(error_msg)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerNotFound(HTTPNotFound):
|
||||||
|
message = _("Container %(container)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
class ContainerAlreadyExists(ResourceExists):
|
class ContainerAlreadyExists(ResourceExists):
|
||||||
message = _("A container with UUID %(uuid)s already exists.")
|
message = _("A container with UUID %(uuid)s already exists.")
|
||||||
|
@ -36,29 +36,29 @@ class API(rpc_service.API):
|
|||||||
def container_create(self, context, container):
|
def container_create(self, context, container):
|
||||||
return self._call('container_create', container=container)
|
return self._call('container_create', container=container)
|
||||||
|
|
||||||
def container_delete(self, context, container_uuid):
|
def container_delete(self, context, container):
|
||||||
return self._call('container_delete', container_uuid=container_uuid)
|
return self._call('container_delete', container=container)
|
||||||
|
|
||||||
def container_show(self, context, container_uuid):
|
def container_show(self, context, container):
|
||||||
return self._call('container_show', container_uuid=container_uuid)
|
return self._call('container_show', container=container)
|
||||||
|
|
||||||
def container_reboot(self, context, container_uuid):
|
def container_reboot(self, context, container):
|
||||||
return self._call('container_reboot', container_uuid=container_uuid)
|
return self._call('container_reboot', container=container)
|
||||||
|
|
||||||
def container_stop(self, context, container_uuid):
|
def container_stop(self, context, container):
|
||||||
return self._call('container_stop', container_uuid=container_uuid)
|
return self._call('container_stop', container=container)
|
||||||
|
|
||||||
def container_start(self, context, container_uuid):
|
def container_start(self, context, container):
|
||||||
return self._call('container_start', container_uuid=container_uuid)
|
return self._call('container_start', container=container)
|
||||||
|
|
||||||
def container_pause(self, context, container_uuid):
|
def container_pause(self, context, container):
|
||||||
return self._call('container_pause', container_uuid=container_uuid)
|
return self._call('container_pause', container=container)
|
||||||
|
|
||||||
def container_unpause(self, context, container_uuid):
|
def container_unpause(self, context, container):
|
||||||
return self._call('container_unpause', container_uuid=container_uuid)
|
return self._call('container_unpause', container=container)
|
||||||
|
|
||||||
def container_logs(self, context, container_uuid):
|
def container_logs(self, context, container):
|
||||||
return self._call('container_logs', container_uuid=container_uuid)
|
return self._call('container_logs', container=container)
|
||||||
|
|
||||||
def container_exec(self, context, container_uuid, command):
|
def container_exec(self, context, container, command):
|
||||||
return self._call('container_exec', container_uuid=container_uuid)
|
return self._call('container_exec', container=container)
|
||||||
|
@ -12,39 +12,132 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.common.i18n import _LE
|
||||||
|
from zun.container import driver
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Manager(object):
|
class Manager(object):
|
||||||
'''Manages the running containers.'''
|
'''Manages the running containers.'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, container_driver=None):
|
||||||
super(Manager, self).__init__()
|
super(Manager, self).__init__()
|
||||||
|
self.driver = driver.load_container_driver(container_driver)
|
||||||
|
|
||||||
def container_create(self, context, container):
|
def container_create(self, context, container):
|
||||||
pass
|
LOG.debug('Creating container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
container = self.driver.create(container)
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_delete(self, context, container_uuid):
|
def container_delete(self, context, container):
|
||||||
pass
|
LOG.debug('Deleting container...', context=context,
|
||||||
|
container=container.uuid)
|
||||||
|
try:
|
||||||
|
self.driver.delete(container)
|
||||||
|
container.destroy()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_show(self, context, container_uuid):
|
def container_list(self, context):
|
||||||
pass
|
LOG.debug('Showing container...', context=context)
|
||||||
|
try:
|
||||||
|
return self.driver.list()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_reboot(self, context, container_uuid):
|
def container_show(self, context, container):
|
||||||
pass
|
LOG.debug('Showing container...', context=context,
|
||||||
|
container=container.uuid)
|
||||||
|
try:
|
||||||
|
container = self.driver.show(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_stop(self, context, container_uuid):
|
def container_reboot(self, context, container):
|
||||||
pass
|
LOG.debug('Rebooting container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
container = self.driver.reboot(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_start(self, context, container_uuid):
|
def container_stop(self, context, container):
|
||||||
pass
|
LOG.debug('Stopping container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
container = self.driver.stop(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_pause(self, context, container_uuid):
|
def container_start(self, context, container):
|
||||||
pass
|
LOG.debug('Starting container...', context=context,
|
||||||
|
container=container.uuid)
|
||||||
|
try:
|
||||||
|
container = self.driver.start(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_unpause(self, context, container_uuid):
|
def container_pause(self, context, container):
|
||||||
pass
|
LOG.debug('Pausing container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
container = self.driver.pause(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_logs(self, context, container_uuid):
|
def container_unpause(self, context, container):
|
||||||
pass
|
LOG.debug('Unpausing container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
container = self.driver.unpause(container)
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
def container_exec(self, context, container_uuid, command):
|
def container_logs(self, context, container):
|
||||||
pass
|
LOG.debug('Showing container logs...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
return self.driver.show_logs(container)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
|
||||||
|
def container_exec(self, context, container, command):
|
||||||
|
# TODO(hongbin): support exec command interactively
|
||||||
|
LOG.debug('Executing command in container...', context=context,
|
||||||
|
container=container)
|
||||||
|
try:
|
||||||
|
return self.driver.execute(container)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(_LE("Unexpected exception: %s,"), str(e))
|
||||||
|
raise
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""API for interfacing with Zun Backend."""
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from zun.common import rpc_service
|
|
||||||
|
|
||||||
|
|
||||||
# The Backend API class serves as a AMQP client for communicating
|
|
||||||
# on a topic exchange specific to the conductors. This allows the ReST
|
|
||||||
# API to trigger operations on the conductors
|
|
||||||
|
|
||||||
class API(rpc_service.API):
|
|
||||||
def __init__(self, transport=None, context=None, topic=None):
|
|
||||||
if topic is None:
|
|
||||||
cfg.CONF.import_opt('topic', 'zun.conductor.config',
|
|
||||||
group='conductor')
|
|
||||||
super(API, self).__init__(transport, context,
|
|
||||||
topic=cfg.CONF.conductor.topic)
|
|
||||||
|
|
||||||
# NOTE(vivek): Add all APIs here
|
|
||||||
def container_get(self, uuid):
|
|
||||||
return self._call('container_get', uuid=uuid)
|
|
@ -1,28 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Config options for Zun Backend service."""
|
|
||||||
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
SERVICE_OPTS = [
|
|
||||||
cfg.StrOpt('topic',
|
|
||||||
default='zun-conductor',
|
|
||||||
help='The queue to add conductor tasks to.'),
|
|
||||||
]
|
|
||||||
|
|
||||||
opt_group = cfg.OptGroup(
|
|
||||||
name='conductor',
|
|
||||||
title='Options for the zun-conductor service')
|
|
||||||
cfg.CONF.register_group(opt_group)
|
|
||||||
cfg.CONF.register_opts(SERVICE_OPTS, opt_group)
|
|
@ -1,24 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
"""Zun Conductor default handler."""
|
|
||||||
|
|
||||||
|
|
||||||
# These are the database operations - They are executed by the conductor
|
|
||||||
# service. API calls via AMQP trigger the handlers to be called.
|
|
||||||
|
|
||||||
class Handler(object):
|
|
||||||
def __init__(self):
|
|
||||||
super(Handler, self).__init__()
|
|
||||||
|
|
||||||
def container_get(uuid):
|
|
||||||
pass
|
|
142
zun/container/docker/driver.py
Normal file
142
zun/container/docker/driver.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from docker import errors
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.container.docker import utils as docker_utils
|
||||||
|
from zun.container import driver
|
||||||
|
from zun.objects import fields
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class DockerDriver(driver.ContainerDriver):
|
||||||
|
'''Implementation of container drivers for Docker.'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DockerDriver, self).__init__()
|
||||||
|
|
||||||
|
def create(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
name = container.name
|
||||||
|
image = container.image
|
||||||
|
LOG.debug('Creating container with image %s name %s'
|
||||||
|
% (image, name))
|
||||||
|
try:
|
||||||
|
image_repo, image_tag = docker_utils.parse_docker_image(image)
|
||||||
|
docker.pull(image_repo, tag=image_tag)
|
||||||
|
kwargs = {'name': name,
|
||||||
|
'hostname': container.uuid,
|
||||||
|
'command': container.command,
|
||||||
|
'environment': container.environment}
|
||||||
|
if docker_utils.is_docker_api_version_atleast(docker, '1.19'):
|
||||||
|
if container.memory is not None:
|
||||||
|
kwargs['host_config'] = {'mem_limit':
|
||||||
|
container.memory}
|
||||||
|
else:
|
||||||
|
kwargs['mem_limit'] = container.memory
|
||||||
|
|
||||||
|
response = docker.create_container(image, **kwargs)
|
||||||
|
container.container_id = response['Id']
|
||||||
|
container.status = fields.ContainerStatus.STOPPED
|
||||||
|
except errors.APIError as e:
|
||||||
|
container.status = fields.ContainerStatus.ERROR
|
||||||
|
container.status_reason = six.text_type(e)
|
||||||
|
raise
|
||||||
|
container.save()
|
||||||
|
return container
|
||||||
|
|
||||||
|
def delete(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
return docker.remove_container(container.container_id)
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
return docker.list_instances()
|
||||||
|
|
||||||
|
def show(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
try:
|
||||||
|
result = docker.inspect_container(container.uuid)
|
||||||
|
status = result.get('State')
|
||||||
|
if status:
|
||||||
|
if status.get('Error') is True:
|
||||||
|
container.status = fields.ContainerStatus.ERROR
|
||||||
|
elif status.get('Paused'):
|
||||||
|
container.status = fields.ContainerStatus.PAUSED
|
||||||
|
elif status.get('Running'):
|
||||||
|
container.status = fields.ContainerStatus.RUNNING
|
||||||
|
else:
|
||||||
|
container.status = fields.ContainerStatus.STOPPED
|
||||||
|
return container
|
||||||
|
except errors.APIError as api_error:
|
||||||
|
if '404' in str(api_error):
|
||||||
|
container.status = fields.ContainerStatus.ERROR
|
||||||
|
return container
|
||||||
|
raise
|
||||||
|
|
||||||
|
def reboot(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
docker.restart(container.container_id)
|
||||||
|
container.status = fields.ContainerStatus.RUNNING
|
||||||
|
return container
|
||||||
|
|
||||||
|
def stop(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
docker.stop(container.container_id)
|
||||||
|
container.status = fields.ContainerStatus.STOPPED
|
||||||
|
return container
|
||||||
|
|
||||||
|
def start(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
docker.start(container.container_id)
|
||||||
|
container.status = fields.ContainerStatus.RUNNING
|
||||||
|
return container
|
||||||
|
|
||||||
|
def pause(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
docker.pause(container.container_id)
|
||||||
|
container.status = fields.ContainerStatus.PAUSED
|
||||||
|
return container
|
||||||
|
|
||||||
|
def unpause(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
docker.unpause(container.container_id)
|
||||||
|
container.status = fields.ContainerStatus.RUNNING
|
||||||
|
return container
|
||||||
|
|
||||||
|
def show_logs(self, container):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
return docker.get_container_logs(container.container_id)
|
||||||
|
|
||||||
|
def execute(self, container, command):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
if docker_utils.is_docker_library_version_atleast('1.2.0'):
|
||||||
|
create_res = docker.exec_create(
|
||||||
|
container.container_id, command, True, True, False)
|
||||||
|
exec_output = docker.exec_start(create_res, False, False,
|
||||||
|
False)
|
||||||
|
else:
|
||||||
|
exec_output = docker.execute(container.container_id, command)
|
||||||
|
return exec_output
|
||||||
|
|
||||||
|
def _encode_utf8(self, value):
|
||||||
|
if six.PY2 and not isinstance(value, unicode):
|
||||||
|
value = unicode(value)
|
||||||
|
return value.encode('utf-8')
|
146
zun/container/docker/utils.py
Normal file
146
zun/container/docker/utils.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import docker
|
||||||
|
from docker import client
|
||||||
|
from docker import tls
|
||||||
|
from docker.utils import utils
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
docker_opts = [
|
||||||
|
cfg.StrOpt('docker_remote_api_version',
|
||||||
|
default='1.20',
|
||||||
|
help='Docker remote api version. Override it according to '
|
||||||
|
'specific docker api version in your environment.'),
|
||||||
|
cfg.IntOpt('default_timeout',
|
||||||
|
default=60,
|
||||||
|
help='Default timeout in seconds for docker client '
|
||||||
|
'operations.'),
|
||||||
|
cfg.StrOpt('api_url',
|
||||||
|
default='unix:///var/run/docker.sock',
|
||||||
|
help='API endpoint of docker daemon'),
|
||||||
|
cfg.BoolOpt('api_insecure',
|
||||||
|
default=False,
|
||||||
|
help='If set, ignore any SSL validation issues'),
|
||||||
|
cfg.StrOpt('ca_file',
|
||||||
|
help='Location of CA certificates file for '
|
||||||
|
'securing docker api requests (tlscacert).'),
|
||||||
|
cfg.StrOpt('cert_file',
|
||||||
|
help='Location of TLS certificate file for '
|
||||||
|
'securing docker api requests (tlscert).'),
|
||||||
|
cfg.StrOpt('key_file',
|
||||||
|
help='Location of TLS private key file for '
|
||||||
|
'securing docker api requests (tlskey).'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(docker_opts, 'docker')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_docker_image(image):
|
||||||
|
image_parts = image.split(':', 1)
|
||||||
|
|
||||||
|
image_repo = image_parts[0]
|
||||||
|
image_tag = None
|
||||||
|
|
||||||
|
if len(image_parts) > 1:
|
||||||
|
image_tag = image_parts[1]
|
||||||
|
|
||||||
|
return image_repo, image_tag
|
||||||
|
|
||||||
|
|
||||||
|
def is_docker_library_version_atleast(version):
|
||||||
|
if utils.compare_version(docker.version, version) <= 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_docker_api_version_atleast(docker, version):
|
||||||
|
if utils.compare_version(docker.version()['ApiVersion'], version) <= 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def docker_client():
|
||||||
|
client_kwargs = dict()
|
||||||
|
if not CONF.docker.api_insecure:
|
||||||
|
client_kwargs['ca_cert'] = CONF.docker.ca_file
|
||||||
|
client_kwargs['client_key'] = CONF.docker.key_file
|
||||||
|
client_kwargs['client_cert'] = CONF.docker.key_file
|
||||||
|
|
||||||
|
yield DockerHTTPClient(
|
||||||
|
CONF.docker.api_url,
|
||||||
|
CONF.docker.docker_remote_api_version,
|
||||||
|
CONF.docker.default_timeout,
|
||||||
|
**client_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DockerHTTPClient(client.Client):
|
||||||
|
def __init__(self, url=CONF.docker.api_url,
|
||||||
|
ver=CONF.docker.docker_remote_api_version,
|
||||||
|
timeout=CONF.docker.default_timeout,
|
||||||
|
ca_cert=None,
|
||||||
|
client_key=None,
|
||||||
|
client_cert=None):
|
||||||
|
|
||||||
|
if ca_cert and client_key and client_cert:
|
||||||
|
ssl_config = tls.TLSConfig(
|
||||||
|
client_cert=(client_cert, client_key),
|
||||||
|
verify=ca_cert,
|
||||||
|
assert_hostname=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ssl_config = False
|
||||||
|
|
||||||
|
super(DockerHTTPClient, self).__init__(
|
||||||
|
base_url=url,
|
||||||
|
version=ver,
|
||||||
|
timeout=timeout,
|
||||||
|
tls=ssl_config
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_instances(self, inspect=False):
|
||||||
|
"""List all containers."""
|
||||||
|
res = []
|
||||||
|
for container in self.containers(all=True):
|
||||||
|
info = self.inspect_container(container['Id'])
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
if inspect:
|
||||||
|
res.append(info)
|
||||||
|
else:
|
||||||
|
res.append(info['Config'].get('Hostname'))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def pause(self, container):
|
||||||
|
"""Pause a running container."""
|
||||||
|
if isinstance(container, dict):
|
||||||
|
container = container.get('Id')
|
||||||
|
url = self._url('/containers/{0}/pause'.format(container))
|
||||||
|
res = self._post(url)
|
||||||
|
self._raise_for_status(res)
|
||||||
|
|
||||||
|
def unpause(self, container):
|
||||||
|
"""Unpause a paused container."""
|
||||||
|
if isinstance(container, dict):
|
||||||
|
container = container.get('Id')
|
||||||
|
url = self._url('/containers/{0}/unpause'.format(container))
|
||||||
|
res = self._post(url)
|
||||||
|
self._raise_for_status(res)
|
||||||
|
|
||||||
|
def get_container_logs(self, docker_id):
|
||||||
|
"""Fetch the logs of a container."""
|
||||||
|
return self.logs(docker_id)
|
123
zun/container/driver.py
Normal file
123
zun/container/driver.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
from zun.common.i18n import _LE
|
||||||
|
from zun.common.i18n import _LI
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
driver_opts = [
|
||||||
|
cfg.StrOpt('container_driver',
|
||||||
|
default='docker.driver.DockerDriver',
|
||||||
|
help="""Defines which driver to use for controlling container.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* ``docker.driver.DockerDriver``
|
||||||
|
|
||||||
|
Services which consume this:
|
||||||
|
|
||||||
|
* ``zun-compute``
|
||||||
|
|
||||||
|
Interdependencies to other options:
|
||||||
|
|
||||||
|
* None
|
||||||
|
""")
|
||||||
|
]
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(driver_opts)
|
||||||
|
|
||||||
|
|
||||||
|
def load_container_driver(container_driver=None):
|
||||||
|
"""Load a container driver module.
|
||||||
|
|
||||||
|
Load the container driver module specified by the container_driver
|
||||||
|
configuration option or, if supplied, the driver name supplied as an
|
||||||
|
argument.
|
||||||
|
:param container_driver: a container driver name to override the config opt
|
||||||
|
:returns: a ContainerDriver instance
|
||||||
|
"""
|
||||||
|
if not container_driver:
|
||||||
|
container_driver = CONF.container_driver
|
||||||
|
|
||||||
|
if not container_driver:
|
||||||
|
LOG.error(_LE("Container driver option required, but not specified"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
LOG.info(_LI("Loading container driver '%s'"), container_driver)
|
||||||
|
try:
|
||||||
|
driver = importutils.import_object(
|
||||||
|
'zun.container.%s' % container_driver)
|
||||||
|
if not isinstance(driver, ContainerDriver):
|
||||||
|
raise Exception(_('Expected driver of type: %s') %
|
||||||
|
str(ContainerDriver))
|
||||||
|
|
||||||
|
return driver
|
||||||
|
except ImportError:
|
||||||
|
LOG.exception(_LE("Unable to load the container driver"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerDriver(object):
|
||||||
|
'''Base class for container drivers.'''
|
||||||
|
|
||||||
|
def create(self, container):
|
||||||
|
"""Create a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def delete(self, container):
|
||||||
|
"""Delete a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
"""List all containers."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def show(self, container):
|
||||||
|
"""Show the details of a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def reboot(self, container):
|
||||||
|
"""Reboot a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def stop(self, container):
|
||||||
|
"""Stop a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def start(self, container):
|
||||||
|
"""Start a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def pause(self, container):
|
||||||
|
"""Pause a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def unpause(self, container):
|
||||||
|
"""Pause a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def show_logs(self, container):
|
||||||
|
"""Show logs of a container."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def execute(self, container, command):
|
||||||
|
"""Execute a command in a running container."""
|
||||||
|
raise NotImplementedError()
|
@ -0,0 +1,34 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""add container_id column to container
|
||||||
|
|
||||||
|
Revision ID: 5971a6844738
|
||||||
|
Revises: 9fe371393a24
|
||||||
|
Create Date: 2016-08-05 17:38:05.231740
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '5971a6844738'
|
||||||
|
down_revision = '9fe371393a24'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('container',
|
||||||
|
sa.Column('container_id', sa.String(length=255),
|
||||||
|
nullable=True))
|
@ -160,7 +160,7 @@ class Connection(api.Connection):
|
|||||||
try:
|
try:
|
||||||
return query.one()
|
return query.one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.InstanceNotFound(container=container_id)
|
raise exception.ContainerNotFound(container=container_id)
|
||||||
|
|
||||||
def get_container_by_uuid(self, context, container_uuid):
|
def get_container_by_uuid(self, context, container_uuid):
|
||||||
query = model_query(models.Container)
|
query = model_query(models.Container)
|
||||||
@ -169,7 +169,7 @@ class Connection(api.Connection):
|
|||||||
try:
|
try:
|
||||||
return query.one()
|
return query.one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.InstanceNotFound(container=container_uuid)
|
raise exception.ContainerNotFound(container=container_uuid)
|
||||||
|
|
||||||
def get_container_by_name(self, context, container_name):
|
def get_container_by_name(self, context, container_name):
|
||||||
query = model_query(models.Container)
|
query = model_query(models.Container)
|
||||||
@ -178,7 +178,7 @@ class Connection(api.Connection):
|
|||||||
try:
|
try:
|
||||||
return query.one()
|
return query.one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.InstanceNotFound(container=container_name)
|
raise exception.ContainerNotFound(container=container_name)
|
||||||
except MultipleResultsFound:
|
except MultipleResultsFound:
|
||||||
raise exception.Conflict('Multiple containers exist with same '
|
raise exception.Conflict('Multiple containers exist with same '
|
||||||
'name. Please use the container uuid '
|
'name. Please use the container uuid '
|
||||||
@ -191,7 +191,7 @@ class Connection(api.Connection):
|
|||||||
query = add_identity_filter(query, container_id)
|
query = add_identity_filter(query, container_id)
|
||||||
count = query.delete()
|
count = query.delete()
|
||||||
if count != 1:
|
if count != 1:
|
||||||
raise exception.InstanceNotFound(container_id)
|
raise exception.ContainerNotFound(container_id)
|
||||||
|
|
||||||
def update_container(self, container_id, values):
|
def update_container(self, container_id, values):
|
||||||
# NOTE(dtantsur): this can lead to very strange errors
|
# NOTE(dtantsur): this can lead to very strange errors
|
||||||
@ -209,7 +209,7 @@ class Connection(api.Connection):
|
|||||||
try:
|
try:
|
||||||
ref = query.with_lockmode('update').one()
|
ref = query.with_lockmode('update').one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.InstanceNotFound(container=container_id)
|
raise exception.ContainerNotFound(container=container_id)
|
||||||
|
|
||||||
if 'provision_state' in values:
|
if 'provision_state' in values:
|
||||||
values['provision_updated_at'] = timeutils.utcnow()
|
values['provision_updated_at'] = timeutils.utcnow()
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
@ -127,6 +125,7 @@ class Container(Base):
|
|||||||
project_id = Column(String(255))
|
project_id = Column(String(255))
|
||||||
user_id = Column(String(255))
|
user_id = Column(String(255))
|
||||||
uuid = Column(String(36))
|
uuid = Column(String(36))
|
||||||
|
container_id = Column(String(36))
|
||||||
name = Column(String(255))
|
name = Column(String(255))
|
||||||
image = Column(String(255))
|
image = Column(String(255))
|
||||||
command = Column(String(255))
|
command = Column(String(255))
|
||||||
|
@ -21,12 +21,14 @@ from zun.objects import fields as z_fields
|
|||||||
class Container(base.ZunPersistentObject, base.ZunObject,
|
class Container(base.ZunPersistentObject, base.ZunObject,
|
||||||
base.ZunObjectDictCompat):
|
base.ZunObjectDictCompat):
|
||||||
# Version 1.0: Initial version
|
# Version 1.0: Initial version
|
||||||
VERSION = '1.0'
|
# Version 1.1: Add container_id column
|
||||||
|
VERSION = '1.1'
|
||||||
|
|
||||||
dbapi = dbapi.get_instance()
|
dbapi = dbapi.get_instance()
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
|
'container_id': fields.StringField(nullable=True),
|
||||||
'uuid': fields.StringField(nullable=True),
|
'uuid': fields.StringField(nullable=True),
|
||||||
'name': fields.StringField(nullable=True),
|
'name': fields.StringField(nullable=True),
|
||||||
'project_id': fields.StringField(nullable=True),
|
'project_id': fields.StringField(nullable=True),
|
||||||
|
@ -16,7 +16,7 @@ import zun.api.app
|
|||||||
import zun.common.keystone
|
import zun.common.keystone
|
||||||
import zun.common.rpc_service
|
import zun.common.rpc_service
|
||||||
import zun.common.service
|
import zun.common.service
|
||||||
import zun.conductor.config
|
import zun.compute.config
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
@ -27,6 +27,6 @@ def list_opts():
|
|||||||
zun.common.service.service_opts,
|
zun.common.service.service_opts,
|
||||||
)),
|
)),
|
||||||
('api', zun.api.app.API_SERVICE_OPTS),
|
('api', zun.api.app.API_SERVICE_OPTS),
|
||||||
('conductor', zun.conductor.config.SERVICE_OPTS),
|
('compute', zun.compute.config.SERVICE_OPTS),
|
||||||
('keystone_auth', zun.common.keystone.keystone_auth_opts),
|
('keystone_auth', zun.common.keystone.keystone_auth_opts),
|
||||||
]
|
]
|
||||||
|
@ -50,9 +50,9 @@ class DbContainerTestCase(base.DbTestCase):
|
|||||||
self.assertEqual(container.uuid, res.uuid)
|
self.assertEqual(container.uuid, res.uuid)
|
||||||
|
|
||||||
def test_get_container_that_does_not_exist(self):
|
def test_get_container_that_does_not_exist(self):
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.get_container_by_id, self.context, 99)
|
self.dbapi.get_container_by_id, self.context, 99)
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.get_container_by_uuid,
|
self.dbapi.get_container_by_uuid,
|
||||||
self.context,
|
self.context,
|
||||||
uuidutils.generate_uuid())
|
uuidutils.generate_uuid())
|
||||||
@ -110,19 +110,19 @@ class DbContainerTestCase(base.DbTestCase):
|
|||||||
def test_destroy_container(self):
|
def test_destroy_container(self):
|
||||||
container = utils.create_test_container()
|
container = utils.create_test_container()
|
||||||
self.dbapi.destroy_container(container.id)
|
self.dbapi.destroy_container(container.id)
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.get_container_by_id,
|
self.dbapi.get_container_by_id,
|
||||||
self.context, container.id)
|
self.context, container.id)
|
||||||
|
|
||||||
def test_destroy_container_by_uuid(self):
|
def test_destroy_container_by_uuid(self):
|
||||||
container = utils.create_test_container()
|
container = utils.create_test_container()
|
||||||
self.dbapi.destroy_container(container.uuid)
|
self.dbapi.destroy_container(container.uuid)
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.get_container_by_uuid,
|
self.dbapi.get_container_by_uuid,
|
||||||
self.context, container.uuid)
|
self.context, container.uuid)
|
||||||
|
|
||||||
def test_destroy_container_that_does_not_exist(self):
|
def test_destroy_container_that_does_not_exist(self):
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.destroy_container,
|
self.dbapi.destroy_container,
|
||||||
uuidutils.generate_uuid())
|
uuidutils.generate_uuid())
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ class DbContainerTestCase(base.DbTestCase):
|
|||||||
def test_update_container_not_found(self):
|
def test_update_container_not_found(self):
|
||||||
container_uuid = uuidutils.generate_uuid()
|
container_uuid = uuidutils.generate_uuid()
|
||||||
new_image = 'new-image'
|
new_image = 'new-image'
|
||||||
self.assertRaises(exception.InstanceNotFound,
|
self.assertRaises(exception.ContainerNotFound,
|
||||||
self.dbapi.update_container,
|
self.dbapi.update_container,
|
||||||
container_uuid, {'image': new_image})
|
container_uuid, {'image': new_image})
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ def get_test_container(**kw):
|
|||||||
return {
|
return {
|
||||||
'id': kw.get('id', 42),
|
'id': kw.get('id', 42),
|
||||||
'uuid': kw.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
'uuid': kw.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
||||||
|
'container_id': kw.get('container_id', 'ddcb39a3fcec'),
|
||||||
'name': kw.get('name', 'container1'),
|
'name': kw.get('name', 'container1'),
|
||||||
'project_id': kw.get('project_id', 'fake_project'),
|
'project_id': kw.get('project_id', 'fake_project'),
|
||||||
'user_id': kw.get('user_id', 'fake_user'),
|
'user_id': kw.get('user_id', 'fake_user'),
|
||||||
|
@ -71,14 +71,15 @@ class TestContainerObject(base.DbTestCase):
|
|||||||
with mock.patch.object(self.dbapi, 'list_container',
|
with mock.patch.object(self.dbapi, 'list_container',
|
||||||
autospec=True) as mock_get_list:
|
autospec=True) as mock_get_list:
|
||||||
mock_get_list.return_value = [self.fake_container]
|
mock_get_list.return_value = [self.fake_container]
|
||||||
|
filt = {'status': 'Running'}
|
||||||
containers = objects.Container.list(self.context,
|
containers = objects.Container.list(self.context,
|
||||||
filters={'bay_uuid': 'uuid'})
|
filters=filt)
|
||||||
self.assertEqual(1, mock_get_list.call_count)
|
self.assertEqual(1, mock_get_list.call_count)
|
||||||
self.assertThat(containers, HasLength(1))
|
self.assertThat(containers, HasLength(1))
|
||||||
self.assertIsInstance(containers[0], objects.Container)
|
self.assertIsInstance(containers[0], objects.Container)
|
||||||
self.assertEqual(self.context, containers[0]._context)
|
self.assertEqual(self.context, containers[0]._context)
|
||||||
mock_get_list.assert_called_once_with(self.context,
|
mock_get_list.assert_called_once_with(self.context,
|
||||||
filters={'bay_uuid': 'uuid'},
|
filters=filt,
|
||||||
limit=None, marker=None,
|
limit=None, marker=None,
|
||||||
sort_key=None, sort_dir=None)
|
sort_key=None, sort_dir=None)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user