360 lines
14 KiB
Python
360 lines
14 KiB
Python
# 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 six
|
|
|
|
from docker import errors
|
|
from oslo_log import log as logging
|
|
|
|
from zun.common import exception
|
|
from zun.common.i18n import _LE
|
|
from zun.common.i18n import _LI
|
|
from zun.common.i18n import _LW
|
|
from zun.common import nova
|
|
from zun.common import utils
|
|
from zun.common.utils import check_container_id
|
|
import zun.conf
|
|
from zun.container.docker import utils as docker_utils
|
|
from zun.container import driver
|
|
from zun.objects import fields
|
|
|
|
|
|
CONF = zun.conf.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class DockerDriver(driver.ContainerDriver):
|
|
'''Implementation of container drivers for Docker.'''
|
|
|
|
def __init__(self):
|
|
super(DockerDriver, self).__init__()
|
|
|
|
def inspect_image(self, image, image_path=None):
|
|
with docker_utils.docker_client() as docker:
|
|
if image_path:
|
|
LOG.debug('Loading local image in docker %s' % image)
|
|
with open(image_path, 'r') as fd:
|
|
docker.load_image(fd.read())
|
|
LOG.debug('Inspecting image %s' % image)
|
|
image_dict = docker.inspect_image(image)
|
|
return image_dict
|
|
|
|
def images(self, repo, quiet=False):
|
|
with docker_utils.docker_client() as docker:
|
|
response = docker.images(repo, quiet)
|
|
return response
|
|
|
|
def create(self, container, sandbox_id, image):
|
|
with docker_utils.docker_client() as docker:
|
|
name = container.name
|
|
if image['path']:
|
|
LOG.debug('Loading local image %s in docker' % container.image)
|
|
with open(image['path'], 'r') as fd:
|
|
docker.load_image(fd.read())
|
|
image = container.image
|
|
LOG.debug('Creating container with image %s name %s'
|
|
% (image, name))
|
|
|
|
kwargs = {
|
|
'name': self.get_container_name(container),
|
|
'hostname': container.hostname,
|
|
'command': container.command,
|
|
'environment': container.environment,
|
|
'working_dir': container.workdir,
|
|
'ports': container.ports,
|
|
'labels': container.labels,
|
|
}
|
|
|
|
host_config = {}
|
|
host_config['network_mode'] = 'container:%s' % sandbox_id
|
|
# TODO(hongbin): Uncomment this after docker-py add support for
|
|
# container mode for pid namespace.
|
|
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
|
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
|
host_config['volumes_from'] = sandbox_id
|
|
if container.memory is not None:
|
|
host_config['mem_limit'] = container.memory
|
|
if container.cpu is not None:
|
|
host_config['cpu_quota'] = int(100000 * container.cpu)
|
|
host_config['cpu_period'] = 100000
|
|
kwargs['host_config'] = docker.create_host_config(**host_config)
|
|
|
|
response = docker.create_container(image, **kwargs)
|
|
container.container_id = response['Id']
|
|
container.status = fields.ContainerStatus.STOPPED
|
|
container.save()
|
|
return container
|
|
|
|
def delete(self, container, force):
|
|
with docker_utils.docker_client() as docker:
|
|
if container.container_id:
|
|
try:
|
|
docker.remove_container(container.container_id,
|
|
force=force)
|
|
except errors.APIError as api_error:
|
|
if '404' in str(api_error):
|
|
return
|
|
raise
|
|
|
|
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:
|
|
if container.container_id is None:
|
|
return container
|
|
|
|
response = None
|
|
try:
|
|
response = docker.inspect_container(container.container_id)
|
|
except errors.APIError as api_error:
|
|
if '404' in str(api_error):
|
|
container.status = fields.ContainerStatus.ERROR
|
|
return container
|
|
raise
|
|
|
|
self._populate_container(container, response)
|
|
return container
|
|
|
|
def _populate_container(self, container, response):
|
|
status = response.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
|
|
|
|
@check_container_id
|
|
def reboot(self, container, timeout):
|
|
with docker_utils.docker_client() as docker:
|
|
if timeout:
|
|
docker.restart(container.container_id,
|
|
timeout=int(timeout))
|
|
else:
|
|
docker.restart(container.container_id)
|
|
container.status = fields.ContainerStatus.RUNNING
|
|
return container
|
|
|
|
@check_container_id
|
|
def stop(self, container, timeout):
|
|
with docker_utils.docker_client() as docker:
|
|
if timeout:
|
|
docker.stop(container.container_id,
|
|
timeout=int(timeout))
|
|
else:
|
|
docker.stop(container.container_id)
|
|
container.status = fields.ContainerStatus.STOPPED
|
|
return container
|
|
|
|
@check_container_id
|
|
def start(self, container):
|
|
with docker_utils.docker_client() as docker:
|
|
docker.start(container.container_id)
|
|
container.status = fields.ContainerStatus.RUNNING
|
|
return container
|
|
|
|
@check_container_id
|
|
def pause(self, container):
|
|
with docker_utils.docker_client() as docker:
|
|
docker.pause(container.container_id)
|
|
container.status = fields.ContainerStatus.PAUSED
|
|
return container
|
|
|
|
@check_container_id
|
|
def unpause(self, container):
|
|
with docker_utils.docker_client() as docker:
|
|
docker.unpause(container.container_id)
|
|
container.status = fields.ContainerStatus.RUNNING
|
|
return container
|
|
|
|
@check_container_id
|
|
def show_logs(self, container):
|
|
with docker_utils.docker_client() as docker:
|
|
return docker.get_container_logs(container.container_id)
|
|
|
|
@check_container_id
|
|
def execute(self, container, command):
|
|
with docker_utils.docker_client() as docker:
|
|
create_res = docker.exec_create(
|
|
container.container_id, command, True, True, False)
|
|
exec_output = docker.exec_start(create_res, False, False, False)
|
|
return exec_output
|
|
|
|
@check_container_id
|
|
def kill(self, container, signal=None):
|
|
with docker_utils.docker_client() as docker:
|
|
if signal is None or signal == 'None':
|
|
docker.kill(container.container_id)
|
|
else:
|
|
docker.kill(container.container_id, signal)
|
|
try:
|
|
response = docker.inspect_container(container.container_id)
|
|
except errors.APIError as api_error:
|
|
if '404' in str(api_error):
|
|
container.status = fields.ContainerStatus.ERROR
|
|
return container
|
|
raise
|
|
|
|
self._populate_container(container, response)
|
|
return container
|
|
|
|
def _encode_utf8(self, value):
|
|
if six.PY2 and not isinstance(value, unicode):
|
|
value = unicode(value)
|
|
return value.encode('utf-8')
|
|
|
|
def create_sandbox(self, context, container, image='kubernetes/pause'):
|
|
with docker_utils.docker_client() as docker:
|
|
name = self.get_sandbox_name(container)
|
|
response = docker.create_container(image, name=name)
|
|
sandbox_id = response['Id']
|
|
docker.start(sandbox_id)
|
|
return sandbox_id
|
|
|
|
def delete_sandbox(self, context, sandbox_id):
|
|
with docker_utils.docker_client() as docker:
|
|
docker.remove_container(sandbox_id, force=True)
|
|
|
|
def stop_sandbox(self, context, sandbox_id):
|
|
with docker_utils.docker_client() as docker:
|
|
docker.stop(sandbox_id)
|
|
|
|
def get_sandbox_id(self, container):
|
|
if container.meta:
|
|
return container.meta.get('sandbox_id', None)
|
|
else:
|
|
LOG.warning(_LW("Unexpected missing of sandbox_id"))
|
|
return None
|
|
|
|
def set_sandbox_id(self, container, sandbox_id):
|
|
if container.meta is None:
|
|
container.meta = {'sandbox_id': sandbox_id}
|
|
else:
|
|
container.meta['sandbox_id'] = sandbox_id
|
|
|
|
def get_sandbox_name(self, container):
|
|
return 'zun-sandbox-' + container.uuid
|
|
|
|
def get_container_name(self, container):
|
|
return 'zun-' + container.uuid
|
|
|
|
def get_addresses(self, context, container):
|
|
sandbox_id = self.get_sandbox_id(container)
|
|
with docker_utils.docker_client() as docker:
|
|
response = docker.inspect_container(sandbox_id)
|
|
addr = response["NetworkSettings"]["IPAddress"]
|
|
addresses = {
|
|
'default': [
|
|
{
|
|
'addr': addr,
|
|
},
|
|
],
|
|
}
|
|
return addresses
|
|
|
|
|
|
class NovaDockerDriver(DockerDriver):
|
|
def create_sandbox(self, context, container, key_name=None,
|
|
flavor='m1.small', image='kubernetes/pause',
|
|
nics='auto'):
|
|
name = self.get_sandbox_name(container)
|
|
novaclient = nova.NovaClient(context)
|
|
sandbox = novaclient.create_server(name=name, image=image,
|
|
flavor=flavor, key_name=key_name,
|
|
nics=nics)
|
|
self._ensure_active(novaclient, sandbox)
|
|
sandbox_id = self._find_container_by_server_name(name)
|
|
return sandbox_id
|
|
|
|
def _ensure_active(self, novaclient, server, timeout=300):
|
|
'''Wait until the Nova instance to become active.'''
|
|
def _check_active():
|
|
return novaclient.check_active(server)
|
|
|
|
success_msg = _LI("Created server %s successfully.") % server.id
|
|
timeout_msg = _LE("Failed to create server %s. Timeout waiting for "
|
|
"server to become active.") % server.id
|
|
utils.poll_until(_check_active,
|
|
sleep_time=CONF.default_sleep_time,
|
|
time_out=timeout or CONF.default_timeout,
|
|
success_msg=success_msg, timeout_msg=timeout_msg)
|
|
|
|
def delete_sandbox(self, context, sandbox_id):
|
|
novaclient = nova.NovaClient(context)
|
|
server_name = self._find_server_by_container_id(sandbox_id)
|
|
if not server_name:
|
|
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
|
sandbox_id)
|
|
return
|
|
|
|
server_id = novaclient.delete_server(server_name)
|
|
self._ensure_deleted(novaclient, server_id)
|
|
|
|
def stop_sandbox(self, context, sandbox_id):
|
|
novaclient = nova.NovaClient(context)
|
|
server_name = self._find_server_by_container_id(sandbox_id)
|
|
if not server_name:
|
|
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
|
sandbox_id)
|
|
return
|
|
novaclient.stop_server(server_name)
|
|
|
|
def _ensure_deleted(self, novaclient, server_id, timeout=300):
|
|
'''Wait until the Nova instance to be deleted.'''
|
|
def _check_delete_complete():
|
|
return novaclient.check_delete_server_complete(server_id)
|
|
|
|
success_msg = _LI("Delete server %s successfully.") % server_id
|
|
timeout_msg = _LE("Failed to create server %s. Timeout waiting for "
|
|
"server to be deleted.") % server_id
|
|
utils.poll_until(_check_delete_complete,
|
|
sleep_time=CONF.default_sleep_time,
|
|
time_out=timeout or CONF.default_timeout,
|
|
success_msg=success_msg, timeout_msg=timeout_msg)
|
|
|
|
def get_addresses(self, context, container):
|
|
novaclient = nova.NovaClient(context)
|
|
sandbox_id = self.get_sandbox_id(container)
|
|
if sandbox_id:
|
|
server_name = self._find_server_by_container_id(sandbox_id)
|
|
if server_name:
|
|
# TODO(hongbin): Standardize the format of addresses
|
|
return novaclient.get_addresses(server_name)
|
|
else:
|
|
return None
|
|
else:
|
|
return None
|
|
|
|
def _find_container_by_server_name(self, name):
|
|
with docker_utils.docker_client() as docker:
|
|
for info in docker.list_instances(inspect=True):
|
|
if info['Config'].get('Hostname') == name:
|
|
return info['Id']
|
|
raise exception.ZunException(_(
|
|
"Cannot find container with name %s") % name)
|
|
|
|
def _find_server_by_container_id(self, container_id):
|
|
with docker_utils.docker_client() as docker:
|
|
try:
|
|
info = docker.inspect_container(container_id)
|
|
return info['Config'].get('Hostname')
|
|
except errors.APIError as e:
|
|
if e.response.status_code != 404:
|
|
raise
|
|
return None
|