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
|
||||
from zun.network import neutron
|
||||
from zun import objects
|
||||
from zun.volume import cinder_api as cinder
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -242,6 +243,17 @@ class ContainersController(base.Controller):
|
||||
nets = container_dict.get('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
|
||||
if container_dict.get('image_driver'):
|
||||
container_dict['image_driver'] = api_utils.string_or_none(
|
||||
@ -275,6 +287,7 @@ class ContainersController(base.Controller):
|
||||
kwargs = {}
|
||||
kwargs['extra_spec'] = extra_spec
|
||||
kwargs['requested_networks'] = requested_networks
|
||||
kwargs['requested_volumes'] = requested_volumes
|
||||
kwargs['run'] = run
|
||||
compute_api.container_create(context, new_container, **kwargs)
|
||||
# Set the HTTP Location Header
|
||||
@ -331,6 +344,25 @@ class ContainersController(base.Controller):
|
||||
self._check_external_network_attach(context, 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):
|
||||
if security_group.get("uuid"):
|
||||
security_group_id = security_group.get("uuid")
|
||||
|
@ -30,6 +30,7 @@ _container_properties = {
|
||||
'image_driver': parameter_types.image_driver,
|
||||
'security_groups': parameter_types.security_groups,
|
||||
'hints': parameter_types.hints,
|
||||
'mounts': parameter_types.mounts,
|
||||
'nets': parameter_types.nets,
|
||||
'runtime': parameter_types.runtime,
|
||||
'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.9 - Add support set container's hostname
|
||||
* 1.10 - Make delete container async
|
||||
* 1.11 - Add mounts to container create
|
||||
"""
|
||||
|
||||
BASE_VER = '1.1'
|
||||
CURRENT_MAX_VER = '1.10'
|
||||
CURRENT_MAX_VER = '1.11'
|
||||
|
||||
|
||||
class Version(object):
|
||||
|
@ -86,3 +86,14 @@ user documentation.
|
||||
----
|
||||
Make container delete API async. Delete operation for a container
|
||||
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']
|
||||
}
|
||||
|
||||
mounts = {
|
||||
'type': ['array', 'null'],
|
||||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'source': {
|
||||
'type': ['string'],
|
||||
},
|
||||
'destination': {
|
||||
'type': ['string'],
|
||||
}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
'required': ['source', 'destination']
|
||||
}
|
||||
}
|
||||
|
||||
environment = {
|
||||
'type': ['object', 'null'],
|
||||
'patternProperties': {
|
||||
|
@ -29,7 +29,7 @@ class API(object):
|
||||
super(API, self).__init__()
|
||||
|
||||
def container_create(self, context, new_container, extra_spec,
|
||||
requested_networks, run):
|
||||
requested_networks, requested_volumes, run):
|
||||
host_state = None
|
||||
try:
|
||||
host_state = self._schedule_container(context, new_container,
|
||||
@ -42,7 +42,8 @@ class API(object):
|
||||
|
||||
self.rpcapi.container_create(context, host_state['host'],
|
||||
new_container, host_state['limits'],
|
||||
requested_networks, run)
|
||||
requested_networks, requested_volumes,
|
||||
run)
|
||||
|
||||
def _schedule_container(self, context, new_container, extra_spec):
|
||||
dests = self.scheduler_client.select_destinations(context,
|
||||
|
@ -49,6 +49,12 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
self.use_sandbox = 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_reason = error
|
||||
container.task_state = None
|
||||
@ -56,16 +62,19 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
container.host = None
|
||||
container.save(context)
|
||||
|
||||
def container_create(self, context, limits, requested_networks, container,
|
||||
run):
|
||||
def container_create(self, context, limits, requested_networks,
|
||||
requested_volumes, container, run):
|
||||
@utils.synchronized(container.uuid)
|
||||
def do_container_create(run, context, *args):
|
||||
created_container = self._do_container_create(context, *args)
|
||||
def do_container_create():
|
||||
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:
|
||||
self._do_container_start(context, created_container)
|
||||
|
||||
utils.spawn_n(do_container_create, run, context, container,
|
||||
requested_networks, limits)
|
||||
utils.spawn_n(do_container_create)
|
||||
|
||||
def _do_sandbox_cleanup(self, context, container):
|
||||
sandbox_id = container.get_sandbox_id()
|
||||
@ -83,6 +92,7 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
container.save(context)
|
||||
|
||||
def _do_container_create_base(self, context, container, requested_networks,
|
||||
requested_volumes,
|
||||
sandbox=None, limits=None, reraise=False):
|
||||
self._update_task_state(context, container, consts.IMAGE_PULLING)
|
||||
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,
|
||||
limits):
|
||||
container = self.driver.create(context, container, image,
|
||||
requested_networks)
|
||||
requested_networks,
|
||||
requested_volumes)
|
||||
self._update_task_state(context, container, None)
|
||||
return container
|
||||
except exception.DockerError as e:
|
||||
@ -148,23 +159,66 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
return
|
||||
|
||||
def _do_container_create(self, context, container, requested_networks,
|
||||
requested_volumes,
|
||||
limits=None, reraise=False):
|
||||
LOG.debug('Creating container: %s', container.uuid)
|
||||
|
||||
sandbox = None
|
||||
if self.use_sandbox:
|
||||
sandbox = self._create_sandbox(context, container,
|
||||
requested_networks, reraise)
|
||||
requested_networks,
|
||||
requested_volumes,
|
||||
reraise)
|
||||
if sandbox is None:
|
||||
return
|
||||
|
||||
created_container = self._do_container_create_base(context,
|
||||
container,
|
||||
requested_networks,
|
||||
requested_volumes,
|
||||
sandbox, limits,
|
||||
reraise)
|
||||
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):
|
||||
if CONF.use_sandbox and self.driver.capabilities["support_sandbox"]:
|
||||
return True
|
||||
@ -179,7 +233,7 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
'driver': self.driver})
|
||||
|
||||
def _create_sandbox(self, context, container, requested_networks,
|
||||
reraise=False):
|
||||
requested_volumes, reraise=False):
|
||||
self._update_task_state(context, container, consts.SANDBOX_CREATING)
|
||||
sandbox_image = CONF.sandbox_image
|
||||
sandbox_image_driver = CONF.sandbox_image_driver
|
||||
@ -193,7 +247,8 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
self.driver.load_image(image['path'])
|
||||
sandbox_id = self.driver.create_sandbox(
|
||||
context, container, image=sandbox_image,
|
||||
requested_networks=requested_networks)
|
||||
requested_networks=requested_networks,
|
||||
requested_volumes=requested_volumes)
|
||||
return sandbox_id
|
||||
except Exception as e:
|
||||
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))
|
||||
self._fail_container(context, container, six.text_type(e))
|
||||
|
||||
self._detach_volumes(context, container, reraise=reraise)
|
||||
|
||||
self._update_task_state(context, container, None)
|
||||
container.destroy(context)
|
||||
self._get_resource_tracker()
|
||||
|
@ -56,9 +56,11 @@ class API(rpc_service.API):
|
||||
transport, context, topic=zun.conf.CONF.compute.topic)
|
||||
|
||||
def container_create(self, context, host, container, limits,
|
||||
requested_networks, run):
|
||||
requested_networks, requested_volumes, run):
|
||||
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)
|
||||
|
||||
@check_container_host
|
||||
|
@ -31,6 +31,7 @@ from zun.container.docker import utils as docker_utils
|
||||
from zun.container import driver
|
||||
from zun.network import network as zun_network
|
||||
from zun import objects
|
||||
from zun.volume import driver as vol_driver
|
||||
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
@ -112,7 +113,8 @@ class DockerDriver(driver.ContainerDriver):
|
||||
except Exception:
|
||||
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()
|
||||
|
||||
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',
|
||||
{'image': image['image'], 'name': name})
|
||||
self._provision_network(context, network_api, requested_networks)
|
||||
binds = self._get_binds(context, requested_volumes)
|
||||
kwargs = {
|
||||
'name': self.get_container_name(container),
|
||||
'command': container.command,
|
||||
@ -147,6 +150,9 @@ class DockerDriver(driver.ContainerDriver):
|
||||
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
||||
host_config['ipc_mode'] = 'container:%s' % 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:
|
||||
host_config['auto_remove'] = container.auto_remove
|
||||
if container.memory is not None:
|
||||
@ -179,6 +185,15 @@ class DockerDriver(driver.ContainerDriver):
|
||||
self._get_or_create_docker_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,
|
||||
requested_networks, network_api):
|
||||
security_group_ids = utils.get_security_group_ids(context, container.
|
||||
@ -675,14 +690,19 @@ class DockerDriver(driver.ContainerDriver):
|
||||
return value.encode('utf-8')
|
||||
|
||||
def create_sandbox(self, context, container, requested_networks,
|
||||
requested_volumes,
|
||||
image='kubernetes/pause'):
|
||||
with docker_utils.docker_client() as docker:
|
||||
network_api = zun_network.api(context=context, docker_api=docker)
|
||||
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)
|
||||
sandbox = docker.create_container(
|
||||
image, name=name,
|
||||
hostname=container.hostname or name[:63])
|
||||
volumes = [b['bind'] for b in binds.values()]
|
||||
sandbox = docker.create_container(image, name=name,
|
||||
hostname=name[:63],
|
||||
volumes=volumes,
|
||||
host_config=host_config)
|
||||
container.set_sandbox_id(sandbox['Id'])
|
||||
addresses = self._setup_network_for_container(
|
||||
context, container, requested_networks, network_api)
|
||||
@ -696,6 +716,18 @@ class DockerDriver(driver.ContainerDriver):
|
||||
docker.start(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,
|
||||
neutron_net_id):
|
||||
docker_net_name = self._get_docker_network_name(context,
|
||||
|
@ -192,6 +192,12 @@ class ContainerDriver(object):
|
||||
def get_cpu_used(self):
|
||||
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):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -47,6 +47,10 @@ class BaseTestCase(testscenarios.WithScenarios, base.BaseTestCase):
|
||||
self.addCleanup(CONF.reset)
|
||||
|
||||
|
||||
class TestingException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
"""Test case base class for all unit tests."""
|
||||
|
||||
|
@ -27,7 +27,7 @@ from zun.tests.unit.db import base
|
||||
|
||||
|
||||
PATH_PREFIX = '/v1'
|
||||
CURRENT_VERSION = "container 1.9"
|
||||
CURRENT_VERSION = "container 1.11"
|
||||
|
||||
|
||||
class FunctionalTest(base.DbTestCase):
|
||||
|
@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
'default_version':
|
||||
{'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||
'max_version': '1.10',
|
||||
'max_version': '1.11',
|
||||
'min_version': '1.1',
|
||||
'status': 'CURRENT'},
|
||||
'description': 'Zun is an OpenStack project which '
|
||||
@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest):
|
||||
'versions': [{'id': 'v1',
|
||||
'links': [{'href': 'http://localhost/v1/',
|
||||
'rel': 'self'}],
|
||||
'max_version': '1.10',
|
||||
'max_version': '1.11',
|
||||
'min_version': '1.1',
|
||||
'status': 'CURRENT'}]}
|
||||
|
||||
|
@ -721,6 +721,53 @@ class TestContainerController(api_base.FunctionalTest):
|
||||
mock_authorize.return_value = fake_admin_authorize
|
||||
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.compute.api.API.container_show')
|
||||
@patch('zun.compute.api.API.container_create')
|
||||
|
@ -24,6 +24,7 @@ from zun.compute import manager
|
||||
import zun.conf
|
||||
from zun.objects.container import Container
|
||||
from zun.objects.image import Image
|
||||
from zun.objects.volume_mapping import VolumeMapping
|
||||
from zun.tests import base
|
||||
from zun.tests.unit.container.fake_driver import FakeDriver as fake_driver
|
||||
from zun.tests.unit.db import utils
|
||||
@ -35,6 +36,27 @@ class FakeResourceTracker(object):
|
||||
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):
|
||||
|
||||
def setUp(self):
|
||||
@ -62,13 +84,14 @@ class TestManager(base.TestCase):
|
||||
mock_pull.return_value = image, False
|
||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||
networks = []
|
||||
volumes = []
|
||||
self.compute_manager._do_container_create(self.context, container,
|
||||
networks)
|
||||
networks, volumes)
|
||||
mock_save.assert_called_with(self.context)
|
||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||
'always', 'glance')
|
||||
mock_create.assert_called_once_with(self.context, container, image,
|
||||
networks)
|
||||
networks, volumes)
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@ -78,8 +101,9 @@ class TestManager(base.TestCase):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.side_effect = exception.DockerError("Pull Failed")
|
||||
networks = []
|
||||
volumes = []
|
||||
self.compute_manager._do_container_create(self.context, container,
|
||||
networks)
|
||||
networks, volumes)
|
||||
mock_fail.assert_called_once_with(self.context,
|
||||
container, "Pull Failed")
|
||||
|
||||
@ -91,8 +115,9 @@ class TestManager(base.TestCase):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.side_effect = exception.ImageNotFound("Image Not Found")
|
||||
networks = []
|
||||
volumes = []
|
||||
self.compute_manager._do_container_create(self.context, container,
|
||||
networks)
|
||||
networks, volumes)
|
||||
mock_fail.assert_called_once_with(self.context,
|
||||
container, "Image Not Found")
|
||||
|
||||
@ -105,8 +130,9 @@ class TestManager(base.TestCase):
|
||||
mock_pull.side_effect = exception.ZunException(
|
||||
message="Image Not Found")
|
||||
networks = []
|
||||
volumes = []
|
||||
self.compute_manager._do_container_create(self.context, container,
|
||||
networks)
|
||||
networks, volumes)
|
||||
mock_fail.assert_called_once_with(self.context,
|
||||
container, "Image Not Found")
|
||||
|
||||
@ -124,18 +150,25 @@ class TestManager(base.TestCase):
|
||||
mock_create.side_effect = exception.DockerError("Creation Failed")
|
||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||
networks = []
|
||||
volumes = []
|
||||
self.compute_manager._do_container_create(self.context, container,
|
||||
networks)
|
||||
networks, volumes)
|
||||
mock_fail.assert_called_once_with(
|
||||
self.context, container, "Creation Failed", unset_host=True)
|
||||
|
||||
@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('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, 'start')
|
||||
def test_container_run(self, mock_start,
|
||||
mock_create, mock_pull, mock_save, mock_spawn_n):
|
||||
def test_container_run(
|
||||
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())
|
||||
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
|
||||
mock_create.return_value = container
|
||||
@ -144,25 +177,74 @@ class TestManager(base.TestCase):
|
||||
container.status = 'Stopped'
|
||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||
networks = []
|
||||
volumes = [FakeVolumeMapping()]
|
||||
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_any_call(self.context, container.image, 'latest',
|
||||
'always', 'glance')
|
||||
mock_create.assert_called_once_with(self.context, container, image,
|
||||
networks)
|
||||
networks, volumes)
|
||||
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.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.object(manager.Manager, '_fail_container')
|
||||
def test_container_run_image_not_found(self, mock_fail,
|
||||
mock_pull, mock_save,
|
||||
mock_spawn_n):
|
||||
@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, '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(
|
||||
image='test:latest', image_driver='docker',
|
||||
image_pull_policy='ifnotpresent')
|
||||
@ -171,24 +253,32 @@ class TestManager(base.TestCase):
|
||||
message="Image Not Found")
|
||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||
networks = []
|
||||
volumes = [FakeVolumeMapping()]
|
||||
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_fail.assert_called_with(self.context,
|
||||
container, 'Image Not Found')
|
||||
self.assertEqual('Error', container.status)
|
||||
self.assertEqual('Image Not Found', container.status_reason)
|
||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||
'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.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.object(manager.Manager, '_fail_container')
|
||||
def test_container_run_image_pull_exception_raised(self, mock_fail,
|
||||
mock_pull, mock_save,
|
||||
mock_spawn_n):
|
||||
def test_container_run_image_pull_exception_raised(
|
||||
self, mock_pull, mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n):
|
||||
container_dict = utils.get_test_container(
|
||||
image='test:latest', image_driver='docker',
|
||||
image_pull_policy='ifnotpresent')
|
||||
@ -197,24 +287,32 @@ class TestManager(base.TestCase):
|
||||
message="Image Not Found")
|
||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||
networks = []
|
||||
volumes = [FakeVolumeMapping()]
|
||||
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_fail.assert_called_with(self.context,
|
||||
container, 'Image Not Found')
|
||||
self.assertEqual('Error', container.status)
|
||||
self.assertEqual('Image Not Found', container.status_reason)
|
||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||
'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.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.object(manager.Manager, '_fail_container')
|
||||
def test_container_run_image_pull_docker_error(self, mock_fail,
|
||||
mock_pull, mock_save,
|
||||
mock_spawn_n):
|
||||
def test_container_run_image_pull_docker_error(
|
||||
self, mock_pull, mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n):
|
||||
container_dict = utils.get_test_container(
|
||||
image='test:latest', image_driver='docker',
|
||||
image_pull_policy='ifnotpresent')
|
||||
@ -223,26 +321,34 @@ class TestManager(base.TestCase):
|
||||
message="Docker Error occurred")
|
||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||
networks = []
|
||||
volumes = [FakeVolumeMapping()]
|
||||
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_fail.assert_called_with(self.context,
|
||||
container, 'Docker Error occurred')
|
||||
self.assertEqual('Error', container.status)
|
||||
self.assertEqual('Docker Error occurred', container.status_reason)
|
||||
mock_pull.assert_called_once_with(self.context, 'test', 'latest',
|
||||
'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.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.object(manager.Manager, '_fail_container')
|
||||
@mock.patch.object(fake_driver, 'create')
|
||||
def test_container_run_create_raises_docker_error(self, mock_create,
|
||||
mock_fail,
|
||||
mock_pull, mock_save,
|
||||
mock_spawn_n):
|
||||
def test_container_run_create_raises_docker_error(
|
||||
self, mock_create, mock_pull, mock_attach_volume,
|
||||
mock_detach_volume, mock_list_by_container, mock_save,
|
||||
mock_spawn_n):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance',
|
||||
'repo': 'test', 'tag': 'testtag'}
|
||||
@ -252,26 +358,34 @@ class TestManager(base.TestCase):
|
||||
mock_spawn_n.side_effect = lambda f, *x, **y: f(*x, **y)
|
||||
self.compute_manager._resource_tracker = FakeResourceTracker()
|
||||
networks = []
|
||||
volumes = [FakeVolumeMapping()]
|
||||
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_fail.assert_called_with(
|
||||
self.context, container, 'Docker Error occurred', unset_host=True)
|
||||
self.assertEqual('Error', container.status)
|
||||
self.assertEqual('Docker Error occurred', container.status_reason)
|
||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||
'always', 'glance')
|
||||
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,
|
||||
'remove_usage_from_container')
|
||||
@mock.patch.object(Container, 'destroy')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||
@mock.patch.object(fake_driver, 'delete')
|
||||
def test_container_delete(self, mock_delete, mock_save, mock_cnt_destroy,
|
||||
mock_remove_usage):
|
||||
def test_container_delete(
|
||||
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())
|
||||
self.compute_manager._do_container_delete(self. context, container,
|
||||
False)
|
||||
@ -307,10 +421,14 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(Container, 'destroy')
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||
@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_remove_usage):
|
||||
mock_list_by_container.return_value = []
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_delete.side_effect = exception.DockerError(
|
||||
message="Docker Error occurred")
|
||||
@ -354,12 +472,15 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
@mock.patch.object(manager.Manager, '_delete_sandbox')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container')
|
||||
@mock.patch.object(fake_driver, 'delete')
|
||||
def test_container_delete_sandbox_failed_force(self, mock_delete,
|
||||
mock_list_by_container,
|
||||
mock_save,
|
||||
mock_delete_sandbox,
|
||||
mock_fail, mock_destroy,
|
||||
mock_remove_usage):
|
||||
mock_list_by_container.return_value = []
|
||||
self.compute_manager.use_sandbox = True
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
container.set_sandbox_id("sandbox_id")
|
||||
|
@ -91,14 +91,17 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||
image = {'path': '', 'image': '', 'repo': '', 'tag': ''}
|
||||
mock_container = self.mock_default_container
|
||||
networks = []
|
||||
volumes = []
|
||||
result_container = self.driver.create(self.context, mock_container,
|
||||
image, [])
|
||||
image, networks, volumes)
|
||||
host_config = {}
|
||||
host_config['mem_limit'] = '512m'
|
||||
host_config['cpu_quota'] = 100000
|
||||
host_config['cpu_period'] = 100000
|
||||
host_config['restart_policy'] = {'Name': 'no', 'MaximumRetryCount': 0}
|
||||
host_config['runtime'] = 'runc'
|
||||
host_config['binds'] = {}
|
||||
self.mock_docker.create_host_config.assert_called_once_with(
|
||||
**host_config)
|
||||
|
||||
@ -112,6 +115,7 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
'stdin_open': True,
|
||||
'tty': True,
|
||||
'hostname': 'testhost',
|
||||
'volumes': [],
|
||||
}
|
||||
self.mock_docker.create_container.assert_called_once_with(
|
||||
image['repo'] + ":" + image['tag'], **kwargs)
|
||||
@ -364,15 +368,19 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
mock_get_sandbox_name.return_value = sandbox_name
|
||||
self.mock_docker.create_container = mock.Mock(
|
||||
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()
|
||||
hostname = 'my_hostname'
|
||||
mock_container.hostname = hostname
|
||||
requested_networks = []
|
||||
requested_volumes = []
|
||||
result_sandbox_id = self.driver.create_sandbox(
|
||||
self.context, mock_container, requested_networks,
|
||||
'kubernetes/pause')
|
||||
requested_volumes, 'kubernetes/pause')
|
||||
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')
|
||||
|
||||
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||
@ -386,14 +394,18 @@ class TestDockerDriver(base.DriverTestCase):
|
||||
mock_get_sandbox_name.return_value = sandbox_name
|
||||
self.mock_docker.create_container = mock.Mock(
|
||||
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.hostname = None
|
||||
requested_networks = []
|
||||
requested_volumes = []
|
||||
result_sandbox_id = self.driver.create_sandbox(
|
||||
self.context, mock_container, requested_networks,
|
||||
'kubernetes/pause')
|
||||
requested_volumes, 'kubernetes/pause')
|
||||
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')
|
||||
|
||||
def test_delete_sandbox(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user