Merge "Implement APIs for mounting Cinder volumes"

This commit is contained in:
Zuul 2017-10-25 03:04:44 +00:00 committed by Gerrit Code Review
commit 7d9403a85c
16 changed files with 409 additions and 65 deletions

View File

@ -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")

View File

@ -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,

View File

@ -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):

View File

@ -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'}]

View File

@ -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': {

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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."""

View File

@ -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):

View File

@ -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'}]}

View File

@ -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')

View File

@ -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")

View File

@ -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):