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:
parent
1bb78daacd
commit
04a186edde
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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'):
|
||||
|
@ -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):
|
||||
|
@ -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())
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user