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")
|
||||
|
||||
|
||||
class ContainerNotFound(HTTPNotFound):
|
||||
message = _("Container %(container)s could not be found.")
|
||||
|
||||
|
||||
class ContainerAlreadyExists(ResourceExists):
|
||||
message = _("A container with UUID %(uuid)s already exists.")
|
||||
|
@ -36,29 +36,29 @@ class API(rpc_service.API):
|
||||
def container_create(self, context, container):
|
||||
return self._call('container_create', container=container)
|
||||
|
||||
def container_delete(self, context, container_uuid):
|
||||
return self._call('container_delete', container_uuid=container_uuid)
|
||||
def container_delete(self, context, container):
|
||||
return self._call('container_delete', container=container)
|
||||
|
||||
def container_show(self, context, container_uuid):
|
||||
return self._call('container_show', container_uuid=container_uuid)
|
||||
def container_show(self, context, container):
|
||||
return self._call('container_show', container=container)
|
||||
|
||||
def container_reboot(self, context, container_uuid):
|
||||
return self._call('container_reboot', container_uuid=container_uuid)
|
||||
def container_reboot(self, context, container):
|
||||
return self._call('container_reboot', container=container)
|
||||
|
||||
def container_stop(self, context, container_uuid):
|
||||
return self._call('container_stop', container_uuid=container_uuid)
|
||||
def container_stop(self, context, container):
|
||||
return self._call('container_stop', container=container)
|
||||
|
||||
def container_start(self, context, container_uuid):
|
||||
return self._call('container_start', container_uuid=container_uuid)
|
||||
def container_start(self, context, container):
|
||||
return self._call('container_start', container=container)
|
||||
|
||||
def container_pause(self, context, container_uuid):
|
||||
return self._call('container_pause', container_uuid=container_uuid)
|
||||
def container_pause(self, context, container):
|
||||
return self._call('container_pause', container=container)
|
||||
|
||||
def container_unpause(self, context, container_uuid):
|
||||
return self._call('container_unpause', container_uuid=container_uuid)
|
||||
def container_unpause(self, context, container):
|
||||
return self._call('container_unpause', container=container)
|
||||
|
||||
def container_logs(self, context, container_uuid):
|
||||
return self._call('container_logs', container_uuid=container_uuid)
|
||||
def container_logs(self, context, container):
|
||||
return self._call('container_logs', container=container)
|
||||
|
||||
def container_exec(self, context, container_uuid, command):
|
||||
return self._call('container_exec', container_uuid=container_uuid)
|
||||
def container_exec(self, context, container, command):
|
||||
return self._call('container_exec', container=container)
|
||||
|
@ -12,39 +12,132 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# 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):
|
||||
'''Manages the running containers.'''
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, container_driver=None):
|
||||
super(Manager, self).__init__()
|
||||
self.driver = driver.load_container_driver(container_driver)
|
||||
|
||||
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):
|
||||
pass
|
||||
def container_delete(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_list(self, context):
|
||||
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):
|
||||
pass
|
||||
def container_show(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_reboot(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_stop(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_start(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_pause(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_unpause(self, context, container):
|
||||
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):
|
||||
pass
|
||||
def container_logs(self, context, container):
|
||||
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:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.InstanceNotFound(container=container_id)
|
||||
raise exception.ContainerNotFound(container=container_id)
|
||||
|
||||
def get_container_by_uuid(self, context, container_uuid):
|
||||
query = model_query(models.Container)
|
||||
@ -169,7 +169,7 @@ class Connection(api.Connection):
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.InstanceNotFound(container=container_uuid)
|
||||
raise exception.ContainerNotFound(container=container_uuid)
|
||||
|
||||
def get_container_by_name(self, context, container_name):
|
||||
query = model_query(models.Container)
|
||||
@ -178,7 +178,7 @@ class Connection(api.Connection):
|
||||
try:
|
||||
return query.one()
|
||||
except NoResultFound:
|
||||
raise exception.InstanceNotFound(container=container_name)
|
||||
raise exception.ContainerNotFound(container=container_name)
|
||||
except MultipleResultsFound:
|
||||
raise exception.Conflict('Multiple containers exist with same '
|
||||
'name. Please use the container uuid '
|
||||
@ -191,7 +191,7 @@ class Connection(api.Connection):
|
||||
query = add_identity_filter(query, container_id)
|
||||
count = query.delete()
|
||||
if count != 1:
|
||||
raise exception.InstanceNotFound(container_id)
|
||||
raise exception.ContainerNotFound(container_id)
|
||||
|
||||
def update_container(self, container_id, values):
|
||||
# NOTE(dtantsur): this can lead to very strange errors
|
||||
@ -209,7 +209,7 @@ class Connection(api.Connection):
|
||||
try:
|
||||
ref = query.with_lockmode('update').one()
|
||||
except NoResultFound:
|
||||
raise exception.InstanceNotFound(container=container_id)
|
||||
raise exception.ContainerNotFound(container=container_id)
|
||||
|
||||
if 'provision_state' in values:
|
||||
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
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@ -127,6 +125,7 @@ class Container(Base):
|
||||
project_id = Column(String(255))
|
||||
user_id = Column(String(255))
|
||||
uuid = Column(String(36))
|
||||
container_id = Column(String(36))
|
||||
name = Column(String(255))
|
||||
image = 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,
|
||||
base.ZunObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
# Version 1.1: Add container_id column
|
||||
VERSION = '1.1'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
'container_id': fields.StringField(nullable=True),
|
||||
'uuid': fields.StringField(nullable=True),
|
||||
'name': 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.rpc_service
|
||||
import zun.common.service
|
||||
import zun.conductor.config
|
||||
import zun.compute.config
|
||||
|
||||
|
||||
def list_opts():
|
||||
@ -27,6 +27,6 @@ def list_opts():
|
||||
zun.common.service.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),
|
||||
]
|
||||
|
@ -50,9 +50,9 @@ class DbContainerTestCase(base.DbTestCase):
|
||||
self.assertEqual(container.uuid, res.uuid)
|
||||
|
||||
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.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.ContainerNotFound,
|
||||
self.dbapi.get_container_by_uuid,
|
||||
self.context,
|
||||
uuidutils.generate_uuid())
|
||||
@ -110,19 +110,19 @@ class DbContainerTestCase(base.DbTestCase):
|
||||
def test_destroy_container(self):
|
||||
container = utils.create_test_container()
|
||||
self.dbapi.destroy_container(container.id)
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.ContainerNotFound,
|
||||
self.dbapi.get_container_by_id,
|
||||
self.context, container.id)
|
||||
|
||||
def test_destroy_container_by_uuid(self):
|
||||
container = utils.create_test_container()
|
||||
self.dbapi.destroy_container(container.uuid)
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.ContainerNotFound,
|
||||
self.dbapi.get_container_by_uuid,
|
||||
self.context, container.uuid)
|
||||
|
||||
def test_destroy_container_that_does_not_exist(self):
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.ContainerNotFound,
|
||||
self.dbapi.destroy_container,
|
||||
uuidutils.generate_uuid())
|
||||
|
||||
@ -139,7 +139,7 @@ class DbContainerTestCase(base.DbTestCase):
|
||||
def test_update_container_not_found(self):
|
||||
container_uuid = uuidutils.generate_uuid()
|
||||
new_image = 'new-image'
|
||||
self.assertRaises(exception.InstanceNotFound,
|
||||
self.assertRaises(exception.ContainerNotFound,
|
||||
self.dbapi.update_container,
|
||||
container_uuid, {'image': new_image})
|
||||
|
||||
|
@ -19,6 +19,7 @@ def get_test_container(**kw):
|
||||
return {
|
||||
'id': kw.get('id', 42),
|
||||
'uuid': kw.get('uuid', 'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
||||
'container_id': kw.get('container_id', 'ddcb39a3fcec'),
|
||||
'name': kw.get('name', 'container1'),
|
||||
'project_id': kw.get('project_id', 'fake_project'),
|
||||
'user_id': kw.get('user_id', 'fake_user'),
|
||||
|
@ -71,14 +71,15 @@ class TestContainerObject(base.DbTestCase):
|
||||
with mock.patch.object(self.dbapi, 'list_container',
|
||||
autospec=True) as mock_get_list:
|
||||
mock_get_list.return_value = [self.fake_container]
|
||||
filt = {'status': 'Running'}
|
||||
containers = objects.Container.list(self.context,
|
||||
filters={'bay_uuid': 'uuid'})
|
||||
filters=filt)
|
||||
self.assertEqual(1, mock_get_list.call_count)
|
||||
self.assertThat(containers, HasLength(1))
|
||||
self.assertIsInstance(containers[0], objects.Container)
|
||||
self.assertEqual(self.context, containers[0]._context)
|
||||
mock_get_list.assert_called_once_with(self.context,
|
||||
filters={'bay_uuid': 'uuid'},
|
||||
filters=filt,
|
||||
limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user