Merge "Implement APIs for mounting Cinder volumes"
This commit is contained in:
commit
7d9403a85c
@ -35,6 +35,7 @@ from zun.common import validation
|
|||||||
import zun.conf
|
import zun.conf
|
||||||
from zun.network import neutron
|
from zun.network import neutron
|
||||||
from zun import objects
|
from zun import objects
|
||||||
|
from zun.volume import cinder_api as cinder
|
||||||
|
|
||||||
CONF = zun.conf.CONF
|
CONF = zun.conf.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -242,6 +243,17 @@ class ContainersController(base.Controller):
|
|||||||
nets = container_dict.get('nets', [])
|
nets = container_dict.get('nets', [])
|
||||||
requested_networks = self._build_requested_networks(context, nets)
|
requested_networks = self._build_requested_networks(context, nets)
|
||||||
|
|
||||||
|
mounts = container_dict.pop('mounts', [])
|
||||||
|
if mounts:
|
||||||
|
req_version = pecan.request.version
|
||||||
|
min_version = versions.Version('', '', '', '1.11')
|
||||||
|
if req_version < min_version:
|
||||||
|
raise exception.InvalidParamInVersion(param='mounts',
|
||||||
|
req_version=req_version,
|
||||||
|
min_version=min_version)
|
||||||
|
|
||||||
|
requested_volumes = self._build_requested_volumes(context, mounts)
|
||||||
|
|
||||||
# Valiadtion accepts 'None' so need to convert it to None
|
# Valiadtion accepts 'None' so need to convert it to None
|
||||||
if container_dict.get('image_driver'):
|
if container_dict.get('image_driver'):
|
||||||
container_dict['image_driver'] = api_utils.string_or_none(
|
container_dict['image_driver'] = api_utils.string_or_none(
|
||||||
@ -275,6 +287,7 @@ class ContainersController(base.Controller):
|
|||||||
kwargs = {}
|
kwargs = {}
|
||||||
kwargs['extra_spec'] = extra_spec
|
kwargs['extra_spec'] = extra_spec
|
||||||
kwargs['requested_networks'] = requested_networks
|
kwargs['requested_networks'] = requested_networks
|
||||||
|
kwargs['requested_volumes'] = requested_volumes
|
||||||
kwargs['run'] = run
|
kwargs['run'] = run
|
||||||
compute_api.container_create(context, new_container, **kwargs)
|
compute_api.container_create(context, new_container, **kwargs)
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
@ -331,6 +344,25 @@ class ContainersController(base.Controller):
|
|||||||
self._check_external_network_attach(context, requested_networks)
|
self._check_external_network_attach(context, requested_networks)
|
||||||
return requested_networks
|
return requested_networks
|
||||||
|
|
||||||
|
def _build_requested_volumes(self, context, mounts):
|
||||||
|
# NOTE(hongbin): We assume cinder is the only volume provider here.
|
||||||
|
# The logic needs to be re-visited if a second volume provider
|
||||||
|
# (i.e. Manila) is introduced.
|
||||||
|
cinder_api = cinder.CinderAPI(context)
|
||||||
|
requested_volumes = []
|
||||||
|
for mount in mounts:
|
||||||
|
volume = cinder_api.search_volume(mount['source'])
|
||||||
|
cinder_api.ensure_volume_usable(volume)
|
||||||
|
volmapp = objects.VolumeMapping(
|
||||||
|
context,
|
||||||
|
volume_id=volume.id, volume_provider='cinder',
|
||||||
|
container_path=mount['destination'],
|
||||||
|
user_id=context.user_id,
|
||||||
|
project_id=context.project_id)
|
||||||
|
requested_volumes.append(volmapp)
|
||||||
|
|
||||||
|
return requested_volumes
|
||||||
|
|
||||||
def _check_security_group(self, context, security_group, container):
|
def _check_security_group(self, context, security_group, container):
|
||||||
if security_group.get("uuid"):
|
if security_group.get("uuid"):
|
||||||
security_group_id = security_group.get("uuid")
|
security_group_id = security_group.get("uuid")
|
||||||
|
@ -30,6 +30,7 @@ _container_properties = {
|
|||||||
'image_driver': parameter_types.image_driver,
|
'image_driver': parameter_types.image_driver,
|
||||||
'security_groups': parameter_types.security_groups,
|
'security_groups': parameter_types.security_groups,
|
||||||
'hints': parameter_types.hints,
|
'hints': parameter_types.hints,
|
||||||
|
'mounts': parameter_types.mounts,
|
||||||
'nets': parameter_types.nets,
|
'nets': parameter_types.nets,
|
||||||
'runtime': parameter_types.runtime,
|
'runtime': parameter_types.runtime,
|
||||||
'hostname': parameter_types.hostname,
|
'hostname': parameter_types.hostname,
|
||||||
|
@ -43,10 +43,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 1.8 - Support attach a network to a container
|
* 1.8 - Support attach a network to a container
|
||||||
* 1.9 - Add support set container's hostname
|
* 1.9 - Add support set container's hostname
|
||||||
* 1.10 - Make delete container async
|
* 1.10 - Make delete container async
|
||||||
|
* 1.11 - Add mounts to container create
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_VER = '1.1'
|
BASE_VER = '1.1'
|
||||||
CURRENT_MAX_VER = '1.10'
|
CURRENT_MAX_VER = '1.11'
|
||||||
|
|
||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
|
@ -86,3 +86,14 @@ user documentation.
|
|||||||
----
|
----
|
||||||
Make container delete API async. Delete operation for a container
|
Make container delete API async. Delete operation for a container
|
||||||
can take long time, so making it async to improve user experience.
|
can take long time, so making it async to improve user experience.
|
||||||
|
|
||||||
|
1.11
|
||||||
|
----
|
||||||
|
Add a new attribute 'mounts' to the request to create a container.
|
||||||
|
Users can use this attribute to specify one or multiple mounts for
|
||||||
|
the container. Each mount could specify the source and destination.
|
||||||
|
The source is the Cinder volume id or name, and the destination is
|
||||||
|
the path where the file or directory will be mounted in the container.
|
||||||
|
For examples:
|
||||||
|
|
||||||
|
[{'source': 'my-vol', 'destination': '/data'}]
|
||||||
|
@ -113,6 +113,23 @@ nets = {
|
|||||||
'type': ['array', 'null']
|
'type': ['array', 'null']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mounts = {
|
||||||
|
'type': ['array', 'null'],
|
||||||
|
'items': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'source': {
|
||||||
|
'type': ['string'],
|
||||||
|
},
|
||||||
|
'destination': {
|
||||||
|
'type': ['string'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'additionalProperties': False,
|
||||||
|
'required': ['source', 'destination']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
environment = {
|
environment = {
|
||||||
'type': ['object', 'null'],
|
'type': ['object', 'null'],
|
||||||
'patternProperties': {
|
'patternProperties': {
|
||||||
|
@ -29,7 +29,7 @@ class API(object):
|
|||||||
super(API, self).__init__()
|
super(API, self).__init__()
|
||||||
|
|
||||||
def container_create(self, context, new_container, extra_spec,
|
def container_create(self, context, new_container, extra_spec,
|
||||||
requested_networks, run):
|
requested_networks, requested_volumes, run):
|
||||||
host_state = None
|
host_state = None
|
||||||
try:
|
try:
|
||||||
host_state = self._schedule_container(context, new_container,
|
host_state = self._schedule_container(context, new_container,
|
||||||
@ -42,7 +42,8 @@ class API(object):
|
|||||||
|
|
||||||
self.rpcapi.container_create(context, host_state['host'],
|
self.rpcapi.container_create(context, host_state['host'],
|
||||||
new_container, host_state['limits'],
|
new_container, host_state['limits'],
|
||||||
requested_networks, run)
|
requested_networks, requested_volumes,
|
||||||
|
run)
|
||||||
|
|
||||||
def _schedule_container(self, context, new_container, extra_spec):
|
def _schedule_container(self, context, new_container, extra_spec):
|
||||||
dests = self.scheduler_client.select_destinations(context,
|
dests = self.scheduler_client.select_destinations(context,
|
||||||
|
@ -49,6 +49,12 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
self.use_sandbox = False
|
self.use_sandbox = False
|
||||||
|
|
||||||
def _fail_container(self, context, container, error, unset_host=False):
|
def _fail_container(self, context, container, error, unset_host=False):
|
||||||
|
try:
|
||||||
|
self._detach_volumes(context, container)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception("Failed to detach volumes: %s",
|
||||||
|
six.text_type(e))
|
||||||
|
|
||||||
container.status = consts.ERROR
|
container.status = consts.ERROR
|
||||||
container.status_reason = error
|
container.status_reason = error
|
||||||
container.task_state = None
|
container.task_state = None
|
||||||
@ -56,16 +62,19 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
container.host = None
|
container.host = None
|
||||||
container.save(context)
|
container.save(context)
|
||||||
|
|
||||||
def container_create(self, context, limits, requested_networks, container,
|
def container_create(self, context, limits, requested_networks,
|
||||||
run):
|
requested_volumes, container, run):
|
||||||
@utils.synchronized(container.uuid)
|
@utils.synchronized(container.uuid)
|
||||||
def do_container_create(run, context, *args):
|
def do_container_create():
|
||||||
created_container = self._do_container_create(context, *args)
|
if not self._attach_volumes(context, container, requested_volumes):
|
||||||
|
return
|
||||||
|
created_container = self._do_container_create(
|
||||||
|
context, container, requested_networks, requested_volumes,
|
||||||
|
limits)
|
||||||
if run and created_container:
|
if run and created_container:
|
||||||
self._do_container_start(context, created_container)
|
self._do_container_start(context, created_container)
|
||||||
|
|
||||||
utils.spawn_n(do_container_create, run, context, container,
|
utils.spawn_n(do_container_create)
|
||||||
requested_networks, limits)
|
|
||||||
|
|
||||||
def _do_sandbox_cleanup(self, context, container):
|
def _do_sandbox_cleanup(self, context, container):
|
||||||
sandbox_id = container.get_sandbox_id()
|
sandbox_id = container.get_sandbox_id()
|
||||||
@ -83,6 +92,7 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
container.save(context)
|
container.save(context)
|
||||||
|
|
||||||
def _do_container_create_base(self, context, container, requested_networks,
|
def _do_container_create_base(self, context, container, requested_networks,
|
||||||
|
requested_volumes,
|
||||||
sandbox=None, limits=None, reraise=False):
|
sandbox=None, limits=None, reraise=False):
|
||||||
self._update_task_state(context, container, consts.IMAGE_PULLING)
|
self._update_task_state(context, container, consts.IMAGE_PULLING)
|
||||||
repo, tag = utils.parse_image_name(container.image)
|
repo, tag = utils.parse_image_name(container.image)
|
||||||
@ -127,7 +137,8 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
with rt.container_claim(context, container, container.host,
|
with rt.container_claim(context, container, container.host,
|
||||||
limits):
|
limits):
|
||||||
container = self.driver.create(context, container, image,
|
container = self.driver.create(context, container, image,
|
||||||
requested_networks)
|
requested_networks,
|
||||||
|
requested_volumes)
|
||||||
self._update_task_state(context, container, None)
|
self._update_task_state(context, container, None)
|
||||||
return container
|
return container
|
||||||
except exception.DockerError as e:
|
except exception.DockerError as e:
|
||||||
@ -148,23 +159,66 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _do_container_create(self, context, container, requested_networks,
|
def _do_container_create(self, context, container, requested_networks,
|
||||||
|
requested_volumes,
|
||||||
limits=None, reraise=False):
|
limits=None, reraise=False):
|
||||||
LOG.debug('Creating container: %s', container.uuid)
|
LOG.debug('Creating container: %s', container.uuid)
|
||||||
|
|
||||||
sandbox = None
|
sandbox = None
|
||||||
if self.use_sandbox:
|
if self.use_sandbox:
|
||||||
sandbox = self._create_sandbox(context, container,
|
sandbox = self._create_sandbox(context, container,
|
||||||
requested_networks, reraise)
|
requested_networks,
|
||||||
|
requested_volumes,
|
||||||
|
reraise)
|
||||||
if sandbox is None:
|
if sandbox is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
created_container = self._do_container_create_base(context,
|
created_container = self._do_container_create_base(context,
|
||||||
container,
|
container,
|
||||||
requested_networks,
|
requested_networks,
|
||||||
|
requested_volumes,
|
||||||
sandbox, limits,
|
sandbox, limits,
|
||||||
reraise)
|
reraise)
|
||||||
return created_container
|
return created_container
|
||||||
|
|
||||||
|
def _attach_volumes(self, context, container, volumes):
|
||||||
|
try:
|
||||||
|
for volume in volumes:
|
||||||
|
volume.container_uuid = container.uuid
|
||||||
|
self._attach_volume(context, volume)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
with excutils.save_and_reraise_exception(reraise=False):
|
||||||
|
self._fail_container(context, container, six.text_type(e),
|
||||||
|
unset_host=True)
|
||||||
|
|
||||||
|
def _attach_volume(self, context, volume):
|
||||||
|
volume.create(context)
|
||||||
|
context = context.elevated()
|
||||||
|
LOG.info('Attaching volume %(volume_id)s to %(host)s',
|
||||||
|
{'volume_id': volume.volume_id,
|
||||||
|
'host': CONF.host})
|
||||||
|
try:
|
||||||
|
self.driver.attach_volume(context, volume)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
volume.destroy()
|
||||||
|
|
||||||
|
def _detach_volumes(self, context, container, reraise=True):
|
||||||
|
volumes = objects.VolumeMapping.list_by_container(context,
|
||||||
|
container.uuid)
|
||||||
|
for volume in volumes:
|
||||||
|
self._detach_volume(context, volume, reraise=reraise)
|
||||||
|
|
||||||
|
def _detach_volume(self, context, volume, reraise=True):
|
||||||
|
context = context.elevated()
|
||||||
|
try:
|
||||||
|
self.driver.detach_volume(context, volume)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception(reraise=reraise):
|
||||||
|
LOG.error("Failed to detach %(volume_id)s",
|
||||||
|
{'volume_id': volume.volume_id})
|
||||||
|
volume.destroy()
|
||||||
|
|
||||||
def _use_sandbox(self):
|
def _use_sandbox(self):
|
||||||
if CONF.use_sandbox and self.driver.capabilities["support_sandbox"]:
|
if CONF.use_sandbox and self.driver.capabilities["support_sandbox"]:
|
||||||
return True
|
return True
|
||||||
@ -179,7 +233,7 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
'driver': self.driver})
|
'driver': self.driver})
|
||||||
|
|
||||||
def _create_sandbox(self, context, container, requested_networks,
|
def _create_sandbox(self, context, container, requested_networks,
|
||||||
reraise=False):
|
requested_volumes, reraise=False):
|
||||||
self._update_task_state(context, container, consts.SANDBOX_CREATING)
|
self._update_task_state(context, container, consts.SANDBOX_CREATING)
|
||||||
sandbox_image = CONF.sandbox_image
|
sandbox_image = CONF.sandbox_image
|
||||||
sandbox_image_driver = CONF.sandbox_image_driver
|
sandbox_image_driver = CONF.sandbox_image_driver
|
||||||
@ -193,7 +247,8 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
self.driver.load_image(image['path'])
|
self.driver.load_image(image['path'])
|
||||||
sandbox_id = self.driver.create_sandbox(
|
sandbox_id = self.driver.create_sandbox(
|
||||||
context, container, image=sandbox_image,
|
context, container, image=sandbox_image,
|
||||||
requested_networks=requested_networks)
|
requested_networks=requested_networks,
|
||||||
|
requested_volumes=requested_volumes)
|
||||||
return sandbox_id
|
return sandbox_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
with excutils.save_and_reraise_exception(reraise=reraise):
|
with excutils.save_and_reraise_exception(reraise=reraise):
|
||||||
@ -245,6 +300,8 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||||
self._fail_container(context, container, six.text_type(e))
|
self._fail_container(context, container, six.text_type(e))
|
||||||
|
|
||||||
|
self._detach_volumes(context, container, reraise=reraise)
|
||||||
|
|
||||||
self._update_task_state(context, container, None)
|
self._update_task_state(context, container, None)
|
||||||
container.destroy(context)
|
container.destroy(context)
|
||||||
self._get_resource_tracker()
|
self._get_resource_tracker()
|
||||||
|
@ -56,9 +56,11 @@ class API(rpc_service.API):
|
|||||||
transport, context, topic=zun.conf.CONF.compute.topic)
|
transport, context, topic=zun.conf.CONF.compute.topic)
|
||||||
|
|
||||||
def container_create(self, context, host, container, limits,
|
def container_create(self, context, host, container, limits,
|
||||||
requested_networks, run):
|
requested_networks, requested_volumes, run):
|
||||||
self._cast(host, 'container_create', limits=limits,
|
self._cast(host, 'container_create', limits=limits,
|
||||||
requested_networks=requested_networks, container=container,
|
requested_networks=requested_networks,
|
||||||
|
requested_volumes=requested_volumes,
|
||||||
|
container=container,
|
||||||
run=run)
|
run=run)
|
||||||
|
|
||||||
@check_container_host
|
@check_container_host
|
||||||
|
@ -31,6 +31,7 @@ from zun.container.docker import utils as docker_utils
|
|||||||
from zun.container import driver
|
from zun.container import driver
|
||||||
from zun.network import network as zun_network
|
from zun.network import network as zun_network
|
||||||
from zun import objects
|
from zun import objects
|
||||||
|
from zun.volume import driver as vol_driver
|
||||||
|
|
||||||
|
|
||||||
CONF = zun.conf.CONF
|
CONF = zun.conf.CONF
|
||||||
@ -112,7 +113,8 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
except Exception:
|
except Exception:
|
||||||
LOG.warning("Unable to read image data from tarfile")
|
LOG.warning("Unable to read image data from tarfile")
|
||||||
|
|
||||||
def create(self, context, container, image, requested_networks):
|
def create(self, context, container, image, requested_networks,
|
||||||
|
requested_volumes):
|
||||||
sandbox_id = container.get_sandbox_id()
|
sandbox_id = container.get_sandbox_id()
|
||||||
|
|
||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
@ -121,6 +123,7 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
LOG.debug('Creating container with image %(image)s name %(name)s',
|
LOG.debug('Creating container with image %(image)s name %(name)s',
|
||||||
{'image': image['image'], 'name': name})
|
{'image': image['image'], 'name': name})
|
||||||
self._provision_network(context, network_api, requested_networks)
|
self._provision_network(context, network_api, requested_networks)
|
||||||
|
binds = self._get_binds(context, requested_volumes)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'name': self.get_container_name(container),
|
'name': self.get_container_name(container),
|
||||||
'command': container.command,
|
'command': container.command,
|
||||||
@ -147,6 +150,9 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
||||||
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
||||||
host_config['volumes_from'] = sandbox_id
|
host_config['volumes_from'] = sandbox_id
|
||||||
|
else:
|
||||||
|
host_config['binds'] = binds
|
||||||
|
kwargs['volumes'] = [b['bind'] for b in binds.values()]
|
||||||
if container.auto_remove:
|
if container.auto_remove:
|
||||||
host_config['auto_remove'] = container.auto_remove
|
host_config['auto_remove'] = container.auto_remove
|
||||||
if container.memory is not None:
|
if container.memory is not None:
|
||||||
@ -179,6 +185,15 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
self._get_or_create_docker_network(
|
self._get_or_create_docker_network(
|
||||||
context, network_api, rq_network['network'])
|
context, network_api, rq_network['network'])
|
||||||
|
|
||||||
|
def _get_binds(self, context, requested_volumes):
|
||||||
|
binds = {}
|
||||||
|
for volume in requested_volumes:
|
||||||
|
volume_driver = vol_driver.driver(provider=volume.volume_provider,
|
||||||
|
context=context)
|
||||||
|
source, destination = volume_driver.bind_mount(volume)
|
||||||
|
binds[source] = {'bind': destination}
|
||||||
|
return binds
|
||||||
|
|
||||||
def _setup_network_for_container(self, context, container,
|
def _setup_network_for_container(self, context, container,
|
||||||
requested_networks, network_api):
|
requested_networks, network_api):
|
||||||
security_group_ids = utils.get_security_group_ids(context, container.
|
security_group_ids = utils.get_security_group_ids(context, container.
|
||||||
@ -675,14 +690,19 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
return value.encode('utf-8')
|
return value.encode('utf-8')
|
||||||
|
|
||||||
def create_sandbox(self, context, container, requested_networks,
|
def create_sandbox(self, context, container, requested_networks,
|
||||||
|
requested_volumes,
|
||||||
image='kubernetes/pause'):
|
image='kubernetes/pause'):
|
||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
network_api = zun_network.api(context=context, docker_api=docker)
|
network_api = zun_network.api(context=context, docker_api=docker)
|
||||||
self._provision_network(context, network_api, requested_networks)
|
self._provision_network(context, network_api, requested_networks)
|
||||||
|
binds = self._get_binds(context, requested_volumes)
|
||||||
|
host_config = docker.create_host_config(binds=binds)
|
||||||
name = self.get_sandbox_name(container)
|
name = self.get_sandbox_name(container)
|
||||||
sandbox = docker.create_container(
|
volumes = [b['bind'] for b in binds.values()]
|
||||||
image, name=name,
|
sandbox = docker.create_container(image, name=name,
|
||||||
hostname=container.hostname or name[:63])
|
hostname=name[:63],
|
||||||
|
volumes=volumes,
|
||||||
|
host_config=host_config)
|
||||||
container.set_sandbox_id(sandbox['Id'])
|
container.set_sandbox_id(sandbox['Id'])
|
||||||
addresses = self._setup_network_for_container(
|
addresses = self._setup_network_for_container(
|
||||||
context, container, requested_networks, network_api)
|
context, container, requested_networks, network_api)
|
||||||
@ -696,6 +716,18 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
docker.start(sandbox['Id'])
|
docker.start(sandbox['Id'])
|
||||||
return sandbox['Id']
|
return sandbox['Id']
|
||||||
|
|
||||||
|
def attach_volume(self, context, volume_mapping):
|
||||||
|
volume_driver = vol_driver.driver(
|
||||||
|
provider=volume_mapping.volume_provider,
|
||||||
|
context=context)
|
||||||
|
volume_driver.attach(volume_mapping)
|
||||||
|
|
||||||
|
def detach_volume(self, context, volume_mapping):
|
||||||
|
volume_driver = vol_driver.driver(
|
||||||
|
provider=volume_mapping.volume_provider,
|
||||||
|
context=context)
|
||||||
|
volume_driver.detach(volume_mapping)
|
||||||
|
|
||||||
def _get_or_create_docker_network(self, context, network_api,
|
def _get_or_create_docker_network(self, context, network_api,
|
||||||
neutron_net_id):
|
neutron_net_id):
|
||||||
docker_net_name = self._get_docker_network_name(context,
|
docker_net_name = self._get_docker_network_name(context,
|
||||||
|
@ -192,6 +192,12 @@ class ContainerDriver(object):
|
|||||||
def get_cpu_used(self):
|
def get_cpu_used(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def attach_volume(self, context, volume_mapping):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def detach_volume(self, context, volume_mapping):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def add_security_group(self, context, container, security_group, **kwargs):
|
def add_security_group(self, context, container, security_group, **kwargs):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -47,6 +47,10 @@ class BaseTestCase(testscenarios.WithScenarios, base.BaseTestCase):
|
|||||||
self.addCleanup(CONF.reset)
|
self.addCleanup(CONF.reset)
|
||||||
|
|
||||||
|
|
||||||
|
class TestingException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestCase(base.BaseTestCase):
|
class TestCase(base.BaseTestCase):
|
||||||
"""Test case base class for all unit tests."""
|
"""Test case base class for all unit tests."""
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ from zun.tests.unit.db import base
|
|||||||
|
|
||||||
|
|
||||||
PATH_PREFIX = '/v1'
|
PATH_PREFIX = '/v1'
|
||||||
CURRENT_VERSION = "container 1.9"
|
CURRENT_VERSION = "container 1.11"
|
||||||
|
|
||||||
|
|
||||||
class FunctionalTest(base.DbTestCase):
|
class FunctionalTest(base.DbTestCase):
|
||||||
|
@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'default_version':
|
'default_version':
|
||||||
{'id': 'v1',
|
{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||||
'max_version': '1.10',
|
'max_version': '1.11',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'},
|
'status': 'CURRENT'},
|
||||||
'description': 'Zun is an OpenStack project which '
|
'description': 'Zun is an OpenStack project which '
|
||||||
@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'versions': [{'id': 'v1',
|
'versions': [{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/',
|
'links': [{'href': 'http://localhost/v1/',
|
||||||
'rel': 'self'}],
|
'rel': 'self'}],
|
||||||
'max_version': '1.10',
|
'max_version': '1.11',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'}]}
|
'status': 'CURRENT'}]}
|
||||||
|
|
||||||
|
@ -721,6 +721,53 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_authorize.return_value = fake_admin_authorize
|
mock_authorize.return_value = fake_admin_authorize
|
||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
|
|
||||||
|
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
||||||
|
@patch('zun.compute.api.API.container_show')
|
||||||
|
@patch('zun.compute.api.API.container_create')
|
||||||
|
@patch('zun.common.context.RequestContext.can')
|
||||||
|
@patch('zun.volume.cinder_api.CinderAPI.search_volume')
|
||||||
|
@patch('zun.volume.cinder_api.CinderAPI.ensure_volume_usable')
|
||||||
|
@patch('zun.compute.api.API.image_search')
|
||||||
|
def test_create_container_with_volume(
|
||||||
|
self, mock_search, mock_ensure_volume_usable, mock_search_volume,
|
||||||
|
mock_authorize, mock_container_create, mock_container_show,
|
||||||
|
mock_neutron_get_network):
|
||||||
|
fake_network = {'id': 'foo'}
|
||||||
|
mock_neutron_get_network.return_value = fake_network
|
||||||
|
fake_volume_id = 'fakevolid'
|
||||||
|
fake_volume = mock.Mock(id=fake_volume_id)
|
||||||
|
mock_search_volume.return_value = fake_volume
|
||||||
|
# Create a container with a command
|
||||||
|
params = ('{"name": "MyDocker", "image": "ubuntu",'
|
||||||
|
'"command": "env", "memory": "512",'
|
||||||
|
'"mounts": [{"source": "s", "destination": "d"}]}')
|
||||||
|
response = self.post('/v1/containers/',
|
||||||
|
params=params,
|
||||||
|
content_type='application/json')
|
||||||
|
self.assertEqual(202, response.status_int)
|
||||||
|
# get all containers
|
||||||
|
container = objects.Container.list(self.context)[0]
|
||||||
|
container.status = 'Stopped'
|
||||||
|
mock_container_show.return_value = container
|
||||||
|
response = self.app.get('/v1/containers/')
|
||||||
|
self.assertEqual(200, response.status_int)
|
||||||
|
self.assertEqual(2, len(response.json))
|
||||||
|
c = response.json['containers'][0]
|
||||||
|
self.assertIsNotNone(c.get('uuid'))
|
||||||
|
self.assertEqual('MyDocker', c.get('name'))
|
||||||
|
self.assertEqual('env', c.get('command'))
|
||||||
|
self.assertEqual('Stopped', c.get('status'))
|
||||||
|
self.assertEqual('512M', c.get('memory'))
|
||||||
|
requested_networks = \
|
||||||
|
mock_container_create.call_args[1]['requested_networks']
|
||||||
|
self.assertEqual(1, len(requested_networks))
|
||||||
|
self.assertEqual(fake_network['id'], requested_networks[0]['network'])
|
||||||
|
mock_search_volume.assert_called_once()
|
||||||
|
requested_volumes = \
|
||||||
|
mock_container_create.call_args[1]['requested_volumes']
|
||||||
|
self.assertEqual(1, len(requested_volumes))
|
||||||
|
self.assertEqual(fake_volume_id, requested_volumes[0].volume_id)
|
||||||
|
|
||||||
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
@patch('zun.network.neutron.NeutronAPI.get_available_network')
|
||||||
@patch('zun.compute.api.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.compute.api.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
|
@ -24,6 +24,7 @@ from zun.compute import manager
|
|||||||
import zun.conf
|
import zun.conf
|
||||||
from zun.objects.container import Container
|
from zun.objects.container import Container
|
||||||
from zun.objects.image import Image
|
from zun.objects.image import Image
|
||||||
|
from zun.objects.volume_mapping import VolumeMapping
|
||||||
from zun.tests import base
|
from zun.tests import base
|
||||||
from zun.tests.unit.container.fake_driver import FakeDriver as fake_driver
|
from zun.tests.unit.container.fake_driver import FakeDriver as fake_driver
|
||||||
from zun.tests.unit.db import utils
|
from zun.tests.unit.db import utils
|
||||||
@ -35,6 +36,27 @@ class FakeResourceTracker(object):
|
|||||||
return claims.NopClaim()
|
return claims.NopClaim()
|
||||||
|
|
||||||
|
|
||||||
|
class FakeVolumeMapping(object):
|
||||||
|
|
||||||
|
volume_provider = 'fake_provider'
|
||||||
|
container_path = 'fake_path'
|
||||||
|
container_uuid = 'fake-cid'
|
||||||
|
volume_id = 'fake-vid'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__class__.volumes = []
|
||||||
|
|
||||||
|
def create(self, context):
|
||||||
|
self.__class__.volumes.append(self)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self.__class__.volumes.remove(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_by_container(cls, context, container_id):
|
||||||
|
return cls.volumes
|
||||||
|
|
||||||
|
|
||||||
class TestManager(base.TestCase):
|
class TestManager(base.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -62,13 +84,14 @@ class TestManager(base.TestCase):
|
|||||||
mock_pull.return_value = image, False
|
mock_pull.return_value = image, False
|
||||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = []
|
||||||
self.compute_manager._do_container_create(self.context, container,
|
self.compute_manager._do_container_create(self.context, container,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||||
'always', 'glance')
|
'always', 'glance')
|
||||||
mock_create.assert_called_once_with(self.context, container, image,
|
mock_create.assert_called_once_with(self.context, container, image,
|
||||||
networks)
|
networks, volumes)
|
||||||
|
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
@ -78,8 +101,9 @@ class TestManager(base.TestCase):
|
|||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
mock_pull.side_effect = exception.DockerError("Pull Failed")
|
mock_pull.side_effect = exception.DockerError("Pull Failed")
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = []
|
||||||
self.compute_manager._do_container_create(self.context, container,
|
self.compute_manager._do_container_create(self.context, container,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_fail.assert_called_once_with(self.context,
|
mock_fail.assert_called_once_with(self.context,
|
||||||
container, "Pull Failed")
|
container, "Pull Failed")
|
||||||
|
|
||||||
@ -91,8 +115,9 @@ class TestManager(base.TestCase):
|
|||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
mock_pull.side_effect = exception.ImageNotFound("Image Not Found")
|
mock_pull.side_effect = exception.ImageNotFound("Image Not Found")
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = []
|
||||||
self.compute_manager._do_container_create(self.context, container,
|
self.compute_manager._do_container_create(self.context, container,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_fail.assert_called_once_with(self.context,
|
mock_fail.assert_called_once_with(self.context,
|
||||||
container, "Image Not Found")
|
container, "Image Not Found")
|
||||||
|
|
||||||
@ -105,8 +130,9 @@ class TestManager(base.TestCase):
|
|||||||
mock_pull.side_effect = exception.ZunException(
|
mock_pull.side_effect = exception.ZunException(
|
||||||
message="Image Not Found")
|
message="Image Not Found")
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = []
|
||||||
self.compute_manager._do_container_create(self.context, container,
|
self.compute_manager._do_container_create(self.context, container,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_fail.assert_called_once_with(self.context,
|
mock_fail.assert_called_once_with(self.context,
|
||||||
container, "Image Not Found")
|
container, "Image Not Found")
|
||||||
|
|
||||||
@ -124,18 +150,25 @@ class TestManager(base.TestCase):
|
|||||||
mock_create.side_effect = exception.DockerError("Creation Failed")
|
mock_create.side_effect = exception.DockerError("Creation Failed")
|
||||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = []
|
||||||
self.compute_manager._do_container_create(self.context, container,
|
self.compute_manager._do_container_create(self.context, container,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_fail.assert_called_once_with(
|
mock_fail.assert_called_once_with(
|
||||||
self.context, container, "Creation Failed", unset_host=True)
|
self.context, container, "Creation Failed", unset_host=True)
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.spawn_n')
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
@mock.patch.object(fake_driver, 'create')
|
@mock.patch.object(fake_driver, 'create')
|
||||||
@mock.patch.object(fake_driver, 'start')
|
@mock.patch.object(fake_driver, 'start')
|
||||||
def test_container_run(self, mock_start,
|
def test_container_run(
|
||||||
mock_create, mock_pull, mock_save, mock_spawn_n):
|
self, mock_start, mock_create, mock_attach_volume,
|
||||||
|
mock_detach_volume, mock_pull, mock_list_by_container, mock_save,
|
||||||
|
mock_spawn_n):
|
||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
|
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
|
||||||
mock_create.return_value = container
|
mock_create.return_value = container
|
||||||
@ -144,25 +177,74 @@ class TestManager(base.TestCase):
|
|||||||
container.status = 'Stopped'
|
container.status = 'Stopped'
|
||||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = [FakeVolumeMapping()]
|
||||||
self.compute_manager.container_create(
|
self.compute_manager.container_create(
|
||||||
self.context,
|
self.context,
|
||||||
requested_networks=networks,
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
container=container,
|
container=container,
|
||||||
limits=None, run=True)
|
limits=None, run=True)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||||
'always', 'glance')
|
'always', 'glance')
|
||||||
mock_create.assert_called_once_with(self.context, container, image,
|
mock_create.assert_called_once_with(self.context, container, image,
|
||||||
networks)
|
networks, volumes)
|
||||||
mock_start.assert_called_once_with(self.context, container)
|
mock_start.assert_called_once_with(self.context, container)
|
||||||
|
mock_attach_volume.assert_called_once()
|
||||||
|
mock_detach_volume.assert_not_called()
|
||||||
|
self.assertEqual(1, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.spawn_n')
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
def test_container_run_image_not_found(self, mock_fail,
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
mock_pull, mock_save,
|
@mock.patch.object(fake_driver, 'create')
|
||||||
mock_spawn_n):
|
@mock.patch.object(fake_driver, 'start')
|
||||||
|
def test_container_run_driver_attach_failed(
|
||||||
|
self, mock_start, mock_create, mock_attach_volume,
|
||||||
|
mock_detach_volume, mock_pull, mock_list_by_container, mock_save,
|
||||||
|
mock_spawn_n):
|
||||||
|
mock_attach_volume.side_effect = [None, base.TestingException("fake")]
|
||||||
|
container = Container(self.context, **utils.get_test_container())
|
||||||
|
vol = FakeVolumeMapping()
|
||||||
|
vol2 = FakeVolumeMapping()
|
||||||
|
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
|
||||||
|
mock_create.return_value = container
|
||||||
|
mock_pull.return_value = image, False
|
||||||
|
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||||
|
container.status = 'Stopped'
|
||||||
|
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||||
|
networks = []
|
||||||
|
volumes = [vol, vol2]
|
||||||
|
self.compute_manager.container_create(
|
||||||
|
self.context,
|
||||||
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
|
container=container,
|
||||||
|
limits=None, run=True)
|
||||||
|
mock_save.assert_called_with(self.context)
|
||||||
|
mock_pull.assert_not_called()
|
||||||
|
mock_create.assert_not_called()
|
||||||
|
mock_start.assert_not_called()
|
||||||
|
mock_attach_volume.assert_has_calls([
|
||||||
|
mock.call(mock.ANY, vol), mock.call(mock.ANY, vol2)])
|
||||||
|
mock_detach_volume.assert_has_calls([
|
||||||
|
mock.call(mock.ANY, vol)])
|
||||||
|
self.assertEqual(0, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
|
def test_container_run_image_not_found(
|
||||||
|
self, mock_pull, mock_attach_volume, mock_detach_volume,
|
||||||
|
mock_list_by_container, mock_save, mock_spawn_n):
|
||||||
container_dict = utils.get_test_container(
|
container_dict = utils.get_test_container(
|
||||||
image='test:latest', image_driver='docker',
|
image='test:latest', image_driver='docker',
|
||||||
image_pull_policy='ifnotpresent')
|
image_pull_policy='ifnotpresent')
|
||||||
@ -171,24 +253,32 @@ class TestManager(base.TestCase):
|
|||||||
message="Image Not Found")
|
message="Image Not Found")
|
||||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = [FakeVolumeMapping()]
|
||||||
self.compute_manager.container_create(
|
self.compute_manager.container_create(
|
||||||
self.context,
|
self.context,
|
||||||
requested_networks=networks,
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
container=container,
|
container=container,
|
||||||
limits=None, run=True)
|
limits=None, run=True)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_fail.assert_called_with(self.context,
|
self.assertEqual('Error', container.status)
|
||||||
container, 'Image Not Found')
|
self.assertEqual('Image Not Found', container.status_reason)
|
||||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||||
'ifnotpresent', 'docker')
|
'ifnotpresent', 'docker')
|
||||||
|
mock_attach_volume.assert_called_once()
|
||||||
|
mock_detach_volume.assert_called_once()
|
||||||
|
self.assertEqual(0, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.spawn_n')
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
def test_container_run_image_pull_exception_raised(
|
||||||
def test_container_run_image_pull_exception_raised(self, mock_fail,
|
self, mock_pull, mock_attach_volume, mock_detach_volume,
|
||||||
mock_pull, mock_save,
|
mock_list_by_container, mock_save, mock_spawn_n):
|
||||||
mock_spawn_n):
|
|
||||||
container_dict = utils.get_test_container(
|
container_dict = utils.get_test_container(
|
||||||
image='test:latest', image_driver='docker',
|
image='test:latest', image_driver='docker',
|
||||||
image_pull_policy='ifnotpresent')
|
image_pull_policy='ifnotpresent')
|
||||||
@ -197,24 +287,32 @@ class TestManager(base.TestCase):
|
|||||||
message="Image Not Found")
|
message="Image Not Found")
|
||||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = [FakeVolumeMapping()]
|
||||||
self.compute_manager.container_create(
|
self.compute_manager.container_create(
|
||||||
self.context,
|
self.context,
|
||||||
requested_networks=networks,
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
container=container,
|
container=container,
|
||||||
limits=None, run=True)
|
limits=None, run=True)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_fail.assert_called_with(self.context,
|
self.assertEqual('Error', container.status)
|
||||||
container, 'Image Not Found')
|
self.assertEqual('Image Not Found', container.status_reason)
|
||||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||||
'ifnotpresent', 'docker')
|
'ifnotpresent', 'docker')
|
||||||
|
mock_attach_volume.assert_called_once()
|
||||||
|
mock_detach_volume.assert_called_once()
|
||||||
|
self.assertEqual(0, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.spawn_n')
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
def test_container_run_image_pull_docker_error(
|
||||||
def test_container_run_image_pull_docker_error(self, mock_fail,
|
self, mock_pull, mock_attach_volume, mock_detach_volume,
|
||||||
mock_pull, mock_save,
|
mock_list_by_container, mock_save, mock_spawn_n):
|
||||||
mock_spawn_n):
|
|
||||||
container_dict = utils.get_test_container(
|
container_dict = utils.get_test_container(
|
||||||
image='test:latest', image_driver='docker',
|
image='test:latest', image_driver='docker',
|
||||||
image_pull_policy='ifnotpresent')
|
image_pull_policy='ifnotpresent')
|
||||||
@ -223,26 +321,34 @@ class TestManager(base.TestCase):
|
|||||||
message="Docker Error occurred")
|
message="Docker Error occurred")
|
||||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = [FakeVolumeMapping()]
|
||||||
self.compute_manager.container_create(
|
self.compute_manager.container_create(
|
||||||
self.context,
|
self.context,
|
||||||
requested_networks=networks,
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
container=container,
|
container=container,
|
||||||
limits=None, run=True)
|
limits=None, run=True)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_fail.assert_called_with(self.context,
|
self.assertEqual('Error', container.status)
|
||||||
container, 'Docker Error occurred')
|
self.assertEqual('Docker Error occurred', container.status_reason)
|
||||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||||
'ifnotpresent', 'docker')
|
'ifnotpresent', 'docker')
|
||||||
|
mock_attach_volume.assert_called_once()
|
||||||
|
mock_detach_volume.assert_called_once()
|
||||||
|
self.assertEqual(0, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.spawn_n')
|
@mock.patch('zun.common.utils.spawn_n')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||||
|
side_effect=FakeVolumeMapping.list_by_container)
|
||||||
|
@mock.patch.object(fake_driver, 'detach_volume')
|
||||||
|
@mock.patch.object(fake_driver, 'attach_volume')
|
||||||
@mock.patch('zun.image.driver.pull_image')
|
@mock.patch('zun.image.driver.pull_image')
|
||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
|
||||||
@mock.patch.object(fake_driver, 'create')
|
@mock.patch.object(fake_driver, 'create')
|
||||||
def test_container_run_create_raises_docker_error(self, mock_create,
|
def test_container_run_create_raises_docker_error(
|
||||||
mock_fail,
|
self, mock_create, mock_pull, mock_attach_volume,
|
||||||
mock_pull, mock_save,
|
mock_detach_volume, mock_list_by_container, mock_save,
|
||||||
mock_spawn_n):
|
mock_spawn_n):
|
||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance',
|
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance',
|
||||||
'repo': 'test', 'tag': 'testtag'}
|
'repo': 'test', 'tag': 'testtag'}
|
||||||
@ -252,26 +358,34 @@ class TestManager(base.TestCase):
|
|||||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||||
networks = []
|
networks = []
|
||||||
|
volumes = [FakeVolumeMapping()]
|
||||||
self.compute_manager.container_create(
|
self.compute_manager.container_create(
|
||||||
self.context,
|
self.context,
|
||||||
requested_networks=networks,
|
requested_networks=networks,
|
||||||
|
requested_volumes=volumes,
|
||||||
container=container,
|
container=container,
|
||||||
limits=None, run=True)
|
limits=None, run=True)
|
||||||
mock_save.assert_called_with(self.context)
|
mock_save.assert_called_with(self.context)
|
||||||
mock_fail.assert_called_with(
|
self.assertEqual('Error', container.status)
|
||||||
self.context, container, 'Docker Error occurred', unset_host=True)
|
self.assertEqual('Docker Error occurred', container.status_reason)
|
||||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||||
'always', 'glance')
|
'always', 'glance')
|
||||||
mock_create.assert_called_once_with(
|
mock_create.assert_called_once_with(
|
||||||
self.context, container, image, networks)
|
self.context, container, image, networks, volumes)
|
||||||
|
mock_attach_volume.assert_called_once()
|
||||||
|
mock_detach_volume.assert_called_once()
|
||||||
|
self.assertEqual(0, len(FakeVolumeMapping.volumes))
|
||||||
|
|
||||||
@mock.patch.object(compute_node_tracker.ComputeNodeTracker,
|
@mock.patch.object(compute_node_tracker.ComputeNodeTracker,
|
||||||
'remove_usage_from_container')
|
'remove_usage_from_container')
|
||||||
@mock.patch.object(Container, 'destroy')
|
@mock.patch.object(Container, 'destroy')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||||
@mock.patch.object(fake_driver, 'delete')
|
@mock.patch.object(fake_driver, 'delete')
|
||||||
def test_container_delete(self, mock_delete, mock_save, mock_cnt_destroy,
|
def test_container_delete(
|
||||||
mock_remove_usage):
|
self, mock_delete, mock_list_by_container, mock_save,
|
||||||
|
mock_cnt_destroy, mock_remove_usage):
|
||||||
|
mock_list_by_container.return_value = []
|
||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
self.compute_manager._do_container_delete(self. context, container,
|
self.compute_manager._do_container_delete(self. context, container,
|
||||||
False)
|
False)
|
||||||
@ -307,10 +421,14 @@ class TestManager(base.TestCase):
|
|||||||
@mock.patch.object(Container, 'destroy')
|
@mock.patch.object(Container, 'destroy')
|
||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
@mock.patch.object(manager.Manager, '_fail_container')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||||
@mock.patch.object(fake_driver, 'delete')
|
@mock.patch.object(fake_driver, 'delete')
|
||||||
def test_container_delete_failed_force(self, mock_delete, mock_save,
|
def test_container_delete_failed_force(self, mock_delete,
|
||||||
|
mock_list_by_container,
|
||||||
|
mock_save,
|
||||||
mock_fail, mock_destroy,
|
mock_fail, mock_destroy,
|
||||||
mock_remove_usage):
|
mock_remove_usage):
|
||||||
|
mock_list_by_container.return_value = []
|
||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
mock_delete.side_effect = exception.DockerError(
|
mock_delete.side_effect = exception.DockerError(
|
||||||
message="Docker Error occurred")
|
message="Docker Error occurred")
|
||||||
@ -354,12 +472,15 @@ class TestManager(base.TestCase):
|
|||||||
@mock.patch.object(manager.Manager, '_fail_container')
|
@mock.patch.object(manager.Manager, '_fail_container')
|
||||||
@mock.patch.object(manager.Manager, '_delete_sandbox')
|
@mock.patch.object(manager.Manager, '_delete_sandbox')
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||||
@mock.patch.object(fake_driver, 'delete')
|
@mock.patch.object(fake_driver, 'delete')
|
||||||
def test_container_delete_sandbox_failed_force(self, mock_delete,
|
def test_container_delete_sandbox_failed_force(self, mock_delete,
|
||||||
|
mock_list_by_container,
|
||||||
mock_save,
|
mock_save,
|
||||||
mock_delete_sandbox,
|
mock_delete_sandbox,
|
||||||
mock_fail, mock_destroy,
|
mock_fail, mock_destroy,
|
||||||
mock_remove_usage):
|
mock_remove_usage):
|
||||||
|
mock_list_by_container.return_value = []
|
||||||
self.compute_manager.use_sandbox = True
|
self.compute_manager.use_sandbox = True
|
||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
container.set_sandbox_id("sandbox_id")
|
container.set_sandbox_id("sandbox_id")
|
||||||
|
@ -91,14 +91,17 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
return_value={'Id': 'val1', 'key1': 'val2'})
|
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||||
image = {'path': '', 'image': '', 'repo': '', 'tag': ''}
|
image = {'path': '', 'image': '', 'repo': '', 'tag': ''}
|
||||||
mock_container = self.mock_default_container
|
mock_container = self.mock_default_container
|
||||||
|
networks = []
|
||||||
|
volumes = []
|
||||||
result_container = self.driver.create(self.context, mock_container,
|
result_container = self.driver.create(self.context, mock_container,
|
||||||
image, [])
|
image, networks, volumes)
|
||||||
host_config = {}
|
host_config = {}
|
||||||
host_config['mem_limit'] = '512m'
|
host_config['mem_limit'] = '512m'
|
||||||
host_config['cpu_quota'] = 100000
|
host_config['cpu_quota'] = 100000
|
||||||
host_config['cpu_period'] = 100000
|
host_config['cpu_period'] = 100000
|
||||||
host_config['restart_policy'] = {'Name': 'no', 'MaximumRetryCount': 0}
|
host_config['restart_policy'] = {'Name': 'no', 'MaximumRetryCount': 0}
|
||||||
host_config['runtime'] = 'runc'
|
host_config['runtime'] = 'runc'
|
||||||
|
host_config['binds'] = {}
|
||||||
self.mock_docker.create_host_config.assert_called_once_with(
|
self.mock_docker.create_host_config.assert_called_once_with(
|
||||||
**host_config)
|
**host_config)
|
||||||
|
|
||||||
@ -112,6 +115,7 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
'stdin_open': True,
|
'stdin_open': True,
|
||||||
'tty': True,
|
'tty': True,
|
||||||
'hostname': 'testhost',
|
'hostname': 'testhost',
|
||||||
|
'volumes': [],
|
||||||
}
|
}
|
||||||
self.mock_docker.create_container.assert_called_once_with(
|
self.mock_docker.create_container.assert_called_once_with(
|
||||||
image['repo'] + ":" + image['tag'], **kwargs)
|
image['repo'] + ":" + image['tag'], **kwargs)
|
||||||
@ -364,15 +368,19 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
mock_get_sandbox_name.return_value = sandbox_name
|
mock_get_sandbox_name.return_value = sandbox_name
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
return_value={'Id': 'val1', 'key1': 'val2'})
|
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||||
|
fake_host_config = mock.Mock()
|
||||||
|
self.mock_docker.create_host_config.return_value = fake_host_config
|
||||||
mock_container = mock.MagicMock()
|
mock_container = mock.MagicMock()
|
||||||
hostname = 'my_hostname'
|
hostname = 'my_hostname'
|
||||||
mock_container.hostname = hostname
|
mock_container.hostname = hostname
|
||||||
requested_networks = []
|
requested_networks = []
|
||||||
|
requested_volumes = []
|
||||||
result_sandbox_id = self.driver.create_sandbox(
|
result_sandbox_id = self.driver.create_sandbox(
|
||||||
self.context, mock_container, requested_networks,
|
self.context, mock_container, requested_networks,
|
||||||
'kubernetes/pause')
|
requested_volumes, 'kubernetes/pause')
|
||||||
self.mock_docker.create_container.assert_called_once_with(
|
self.mock_docker.create_container.assert_called_once_with(
|
||||||
'kubernetes/pause', name=sandbox_name, hostname=hostname)
|
'kubernetes/pause', name=sandbox_name, hostname=sandbox_name,
|
||||||
|
host_config=fake_host_config, volumes=[])
|
||||||
self.assertEqual(result_sandbox_id, 'val1')
|
self.assertEqual(result_sandbox_id, 'val1')
|
||||||
|
|
||||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
@ -386,14 +394,18 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
mock_get_sandbox_name.return_value = sandbox_name
|
mock_get_sandbox_name.return_value = sandbox_name
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
return_value={'Id': 'val1', 'key1': 'val2'})
|
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||||
|
fake_host_config = mock.Mock()
|
||||||
|
self.mock_docker.create_host_config.return_value = fake_host_config
|
||||||
mock_container = mock.MagicMock()
|
mock_container = mock.MagicMock()
|
||||||
mock_container.hostname = None
|
mock_container.hostname = None
|
||||||
requested_networks = []
|
requested_networks = []
|
||||||
|
requested_volumes = []
|
||||||
result_sandbox_id = self.driver.create_sandbox(
|
result_sandbox_id = self.driver.create_sandbox(
|
||||||
self.context, mock_container, requested_networks,
|
self.context, mock_container, requested_networks,
|
||||||
'kubernetes/pause')
|
requested_volumes, 'kubernetes/pause')
|
||||||
self.mock_docker.create_container.assert_called_once_with(
|
self.mock_docker.create_container.assert_called_once_with(
|
||||||
'kubernetes/pause', name=sandbox_name, hostname=sandbox_name[:63])
|
'kubernetes/pause', name=sandbox_name, hostname=sandbox_name[:63],
|
||||||
|
host_config=fake_host_config, volumes=[])
|
||||||
self.assertEqual(result_sandbox_id, 'val1')
|
self.assertEqual(result_sandbox_id, 'val1')
|
||||||
|
|
||||||
def test_delete_sandbox(self):
|
def test_delete_sandbox(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user