Allow volume to be shared across capsule

This allows a cinder volume to be shared by multiple containers
that are in the same capsule. This is for supporting the use
cases that multiple containers inside the capsule read/write
to a shared storage.

Change-Id: Ia7a14e52d03b50cfe19d8959e715ebc7f4c3f1d2
Closes-Bug: #1787855
This commit is contained in:
Hongbin Lu 2018-08-20 04:16:24 +00:00
parent 1bb78daacd
commit 04a186edde
8 changed files with 95 additions and 35 deletions

View File

@ -372,23 +372,28 @@ class CapsuleController(base.Controller):
mount_destination = None
container_name = None
volume_object = objects.Volume(
context,
cinder_volume_id=volume.id,
volume_provider=volume_driver,
user_id=context.user_id,
project_id=context.project_id,
auto_remove=auto_remove)
volume_object.create(context)
for item in volume_mounts:
if item['name'] == mount['name']:
mount_destination = item['mountPath']
container_name = item['container_name']
break
volmapp = objects.VolumeMapping(
context,
container_path=mount_destination,
user_id=context.user_id,
project_id=context.project_id,
volume_id=volume_object.id)
requested_volumes.append({container_name: volmapp})
if mount_destination and container_name:
volmapp = objects.VolumeMapping(
context,
cinder_volume_id=volume.id,
volume_provider=volume_driver,
container_path=mount_destination,
user_id=context.user_id,
project_id=context.project_id,
auto_remove=auto_remove)
requested_volumes.append({container_name: volmapp})
else:
if not mount_destination or not container_name:
msg = _("volume mount parameters is invalid.")
raise exception.Invalid(msg)
except Exception as e:

View File

@ -380,6 +380,13 @@ class Manager(periodic_task.PeriodicTasks):
for volmap in volmaps:
volmap.container_uuid = container.uuid
volmap.host = self.host
volmap.create(context)
if container.capsule_id and volmap.connection_info:
# NOTE(hongbin): In this case, the volume is already
# attached to this host so we don't need to do it again.
# This will happen only if there are multiple containers
# inside a capsule sharing the same volume.
continue
self._attach_volume(context, volmap)
except Exception as e:
with excutils.save_and_reraise_exception():
@ -387,7 +394,6 @@ class Manager(periodic_task.PeriodicTasks):
unset_host=True)
def _attach_volume(self, context, volmap):
volmap.create(context)
context = context.elevated()
LOG.info('Attaching volume %(volume_id)s to %(host)s',
{'volume_id': volmap.cinder_volume_id,
@ -420,15 +426,17 @@ class Manager(periodic_task.PeriodicTasks):
self._wait_for_volumes_deleted(context, volmaps, container)
def _detach_volume(self, context, volmap, reraise=True):
context = context.elevated()
try:
self.driver.detach_volume(context, volmap)
except Exception:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error("Failed to detach volume %(volume_id)s from "
"container %(container_id)s",
{'volume_id': volmap.cinder_volume_id,
'container_id': volmap.container_uuid})
if objects.VolumeMapping.count(
context, volume_id=volmap.volume_id) == 1:
context = context.elevated()
try:
self.driver.detach_volume(context, volmap)
except Exception:
with excutils.save_and_reraise_exception(reraise=reraise):
LOG.error("Failed to detach volume %(volume_id)s from "
"container %(container_id)s",
{'volume_id': volmap.cinder_volume_id,
'container_id': volmap.container_uuid})
volmap.destroy()
def _use_sandbox(self):

View File

@ -150,6 +150,20 @@ def list_volume_mappings(context, filters=None, limit=None, marker=None,
context, filters, limit, marker, sort_key, sort_dir)
@profiler.trace("db")
def count_volume_mappings(context, **filters):
"""Count matching volume mappings.
Return the count of all volume mappings that match
the specified filters.
:param context: The security context
:param filters: Filters to apply.
:returns: The count of volume mapping.
"""
return _get_dbdriver_instance().count_volume_mappings(context, **filters)
@profiler.trace("db")
def create_volume_mapping(context, values):
"""Create a volume mapping.

View File

@ -271,6 +271,12 @@ class Connection(object):
return _paginate_query(models.VolumeMapping, limit, marker,
sort_key, sort_dir, query)
def count_volume_mappings(self, context, **filters):
query = model_query(models.VolumeMapping)
query = self._add_project_filters(context, query)
query = self._add_volume_mappings_filters(query, filters)
return query.count()
def create_volume_mapping(self, context, values):
# ensure defaults are present for new volume_mappings
if not values.get('uuid'):

View File

@ -51,7 +51,8 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
# Version 1.2: Add field "host"
# Version 1.3: Add field "contents"
# Version 1.4: Rename field "volume_id" to "cinder_volume_id"
VERSION = '1.4'
# Version 1.5: Add method "count"
VERSION = '1.5'
fields = {
'id': fields.IntegerField(),
@ -131,6 +132,10 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
db_volumes = dbapi.list_volume_mappings(context, filters=filters)
return VolumeMapping._from_db_object_list(db_volumes, cls, context)
@base.remotable_classmethod
def count(cls, context, **filters):
return dbapi.count_volume_mappings(context, **filters)
@base.remotable
def create(self, context):
"""Create a VolumeMapping record in the DB.
@ -165,9 +170,10 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
volume_values[attrname] = values.pop(attrname)
volume_values['user_id'] = values['user_id']
volume_values['project_id'] = values['project_id']
volume = volume_obj.Volume(context, **volume_values)
volume.create(context)
values['volume_id'] = volume.id
if 'volume_id' not in values:
volume = volume_obj.Volume(context, **volume_values)
volume.create(context)
values['volume_id'] = volume.id
@base.remotable
def destroy(self, context=None):
@ -190,7 +196,9 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
self.obj_reset_changes()
def _destroy_volume(self, context):
dbapi.destroy_volume(context, self.volume_id)
if VolumeMapping.count(context,
volume_id=self.volume_id) == 0:
dbapi.destroy_volume(context, self.volume_id)
@base.remotable
def save(self, context=None):

View File

@ -54,6 +54,8 @@ class FakeVolumeMapping(object):
container_path = 'fake_path'
container_uuid = 'fake-cid'
cinder_volume_id = 'fake-vid'
volume_id = 123
connection_info = None
auto_remove = False
def __init__(self):
@ -73,6 +75,10 @@ class FakeVolumeMapping(object):
def list_by_cinder_volume(cls, context, volume_id):
return cls.volumes
@classmethod
def count(cls, context, **filters):
return len(cls.volumes)
class TestManager(base.TestCase):
@ -316,6 +322,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_container',
side_effect=FakeVolumeMapping.list_by_container)
@mock.patch.object(fake_driver, 'pull_image')
@ -327,8 +335,8 @@ class TestManager(base.TestCase):
def test_container_run(
self, mock_start, mock_create,
mock_is_volume_available, mock_attach_volume,
mock_detach_volume, mock_pull, mock_list_by_container, mock_save,
mock_spawn_n, mock_event_finish, mock_event_start):
mock_detach_volume, mock_pull, mock_list_by_container, mock_count,
mock_save, mock_spawn_n, mock_event_finish, mock_event_start):
container = Container(self.context, **utils.get_test_container())
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
mock_create.return_value = container
@ -361,6 +369,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_cinder_volume',
side_effect=FakeVolumeMapping.list_by_cinder_volume)
@mock.patch.object(VolumeMapping, 'list_by_container',
@ -375,7 +385,7 @@ class TestManager(base.TestCase):
self, mock_start, mock_create,
mock_is_volume_available, mock_attach_volume,
mock_detach_volume, mock_pull, mock_list_by_container,
mock_list_by_volume, mock_save,
mock_list_by_volume, mock_count, mock_save,
mock_spawn_n, mock_event_finish, mock_event_start,
mock_delete_volume):
mock_is_volume_available.return_value = True, False
@ -415,6 +425,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_cinder_volume',
side_effect=FakeVolumeMapping.list_by_cinder_volume)
@mock.patch.object(VolumeMapping, 'list_by_container',
@ -426,7 +438,7 @@ class TestManager(base.TestCase):
def test_container_run_image_not_found(
self, mock_pull, mock_is_volume_available,
mock_attach_volume, mock_detach_volume,
mock_list_by_container, mock_list_by_volume,
mock_list_by_container, mock_list_by_volume, mock_count,
mock_save, mock_spawn_n, mock_event_finish,
mock_event_start):
container_dict = utils.get_test_container(
@ -461,6 +473,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_cinder_volume',
side_effect=FakeVolumeMapping.list_by_cinder_volume)
@mock.patch.object(VolumeMapping, 'list_by_container',
@ -472,7 +486,7 @@ class TestManager(base.TestCase):
def test_container_run_image_pull_exception_raised(
self, mock_pull, mock_is_volume_available,
mock_attach_volume, mock_detach_volume,
mock_list_by_container, mock_list_by_volume,
mock_list_by_container, mock_list_by_volume, mock_count,
mock_save, mock_spawn_n, mock_event_finish,
mock_event_start):
container_dict = utils.get_test_container(
@ -507,6 +521,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_cinder_volume',
side_effect=FakeVolumeMapping.list_by_cinder_volume)
@mock.patch.object(VolumeMapping, 'list_by_container',
@ -518,7 +534,7 @@ class TestManager(base.TestCase):
def test_container_run_image_pull_docker_error(
self, mock_pull, mock_is_volume_available,
mock_attach_volume, mock_detach_volume,
mock_list_by_container, mock_list_by_volume,
mock_list_by_container, mock_list_by_volume, mock_count,
mock_save, mock_spawn_n, mock_event_finish,
mock_event_start):
container_dict = utils.get_test_container(
@ -553,6 +569,8 @@ class TestManager(base.TestCase):
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch('zun.common.utils.spawn_n')
@mock.patch.object(Container, 'save')
@mock.patch.object(VolumeMapping, 'count',
side_effect=FakeVolumeMapping.count)
@mock.patch.object(VolumeMapping, 'list_by_cinder_volume',
side_effect=FakeVolumeMapping.list_by_cinder_volume)
@mock.patch.object(VolumeMapping, 'list_by_container',
@ -565,7 +583,7 @@ class TestManager(base.TestCase):
def test_container_run_create_raises_docker_error(
self, mock_create, mock_pull, mock_is_volume_available,
mock_attach_volume, mock_detach_volume,
mock_list_by_container, mock_list_by_volume,
mock_list_by_container, mock_list_by_volume, mock_count,
mock_save, mock_spawn_n,
mock_event_finish, mock_event_start):
container = Container(self.context, **utils.get_test_container())

View File

@ -346,7 +346,7 @@ class TestObject(test_base.TestCase, _TestObject):
object_data = {
'Container': '1.37-cdc1537de5adf3570b598da1a3728a68',
'Volume': '1.0-4ec18c39ea49f898cc354f9ca178dfb7',
'VolumeMapping': '1.4-deaafca8ce3d256d0339e08449908f37',
'VolumeMapping': '1.5-57febc66526185a75a744637e7a387c7',
'Image': '1.2-80504fdd797e9dd86128a91680e876ad',
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',
'NUMANode': '1.0-cba878b70b2f8b52f1e031b41ac13b4e',

View File

@ -96,6 +96,7 @@ class TestVolumeMappingObject(base.DbTestCase):
volume_dict.pop(attr)
volume_mapping = dict(volume_dict)
volume_mapping.update(volume_mapping_dict)
volume_mapping.pop('volume_id')
volume_mapping = objects.VolumeMapping(self.context,
**volume_mapping)
volume_mapping.create(self.context)