diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py index 6a7b0eeb7bd..1141a85c647 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py @@ -482,7 +482,8 @@ class CommonAdapterTest(test.TestCase): def test_terminate_connection_volume(self): def f(): - volume = MockOSResource(provider_location='id^lun_43', id='id_43') + volume = MockOSResource(provider_location='id^lun_43', id='id_43', + volume_attachment=None) connector = {'host': 'host1'} self.adapter.terminate_connection(volume, connector) @@ -490,7 +491,8 @@ class CommonAdapterTest(test.TestCase): def test_terminate_connection_force_detach(self): def f(): - volume = MockOSResource(provider_location='id^lun_44', id='id_44') + volume = MockOSResource(provider_location='id^lun_44', id='id_44', + volume_attachment=None) self.adapter.terminate_connection(volume, None) self.assertRaises(ex.DetachAllIsCalled, f) @@ -498,7 +500,8 @@ class CommonAdapterTest(test.TestCase): def test_terminate_connection_snapshot(self): def f(): connector = {'host': 'host1'} - snap = MockOSResource(name='snap_0', id='snap_0') + snap = MockOSResource(name='snap_0', id='snap_0', + volume_attachment=None) self.adapter.terminate_connection_snapshot(snap, connector) self.assertRaises(ex.DetachIsCalled, f) @@ -508,11 +511,27 @@ class CommonAdapterTest(test.TestCase): def f(): connector = {'host': 'empty-host'} - vol = MockOSResource(provider_location='id^lun_45', id='id_45') + vol = MockOSResource(provider_location='id^lun_45', id='id_45', + volume_attachment=None) self.adapter.terminate_connection(vol, connector) self.assertRaises(ex.HostDeleteIsCalled, f) + def test_terminate_connection_multiattached_volume(self): + def f(): + connector = {'host': 'host1'} + attachments = [MockOSResource(id='id-1', + attach_status='attached', + attached_host='host1'), + MockOSResource(id='id-2', + attach_status='attached', + attached_host='host1')] + vol = MockOSResource(provider_location='id^lun_45', id='id_45', + volume_attachment=attachments) + self.adapter.terminate_connection(vol, connector) + + self.assertIsNone(f()) + def test_manage_existing_by_name(self): ref = {'source-id': 12} volume = MockOSResource(name='lun1') @@ -844,7 +863,8 @@ class FCAdapterTest(test.TestCase): def test_terminate_connection_auto_zone_enabled(self): connector = {'host': 'host1', 'wwpns': 'abcdefg'} - volume = MockOSResource(provider_location='id^lun_41', id='id_41') + volume = MockOSResource(provider_location='id^lun_41', id='id_41', + volume_attachment=None) ret = self.adapter.terminate_connection(volume, connector) self.assertEqual('fibre_channel', ret['driver_volume_type']) data = ret['data'] @@ -857,7 +877,8 @@ class FCAdapterTest(test.TestCase): def test_terminate_connection_auto_zone_enabled_none_host_luns(self): connector = {'host': 'host-no-host_luns', 'wwpns': 'abcdefg'} - volume = MockOSResource(provider_location='id^lun_41', id='id_41') + volume = MockOSResource(provider_location='id^lun_41', id='id_41', + volume_attachment=None) ret = self.adapter.terminate_connection(volume, connector) self.assertEqual('fibre_channel', ret['driver_volume_type']) data = ret['data'] @@ -871,7 +892,8 @@ class FCAdapterTest(test.TestCase): def test_terminate_connection_remove_empty_host_return_data(self): self.adapter.remove_empty_host = True connector = {'host': 'empty-host-return-data', 'wwpns': 'abcdefg'} - volume = MockOSResource(provider_location='id^lun_41', id='id_41') + volume = MockOSResource(provider_location='id^lun_41', id='id_41', + volume_attachment=None) ret = self.adapter.terminate_connection(volume, connector) self.assertEqual('fibre_channel', ret['driver_volume_type']) data = ret['data'] diff --git a/cinder/volume/drivers/dell_emc/unity/adapter.py b/cinder/volume/drivers/dell_emc/unity/adapter.py index c64ed0542d9..9b3f2013a3f 100644 --- a/cinder/volume/drivers/dell_emc/unity/adapter.py +++ b/cinder/volume/drivers/dell_emc/unity/adapter.py @@ -348,12 +348,14 @@ class CommonAdapter(object): # No target info for iSCSI driver return [] - def _detach_and_delete_host(self, host_name, lun_or_snap): + def _detach_and_delete_host(self, host_name, lun_or_snap, + is_multiattach_to_host=False): @utils.lock_if(self.to_lock_host, '{lock_name}') def _lock_helper(lock_name): # Only get the host from cache here host = self.client.create_host_wo_lock(host_name) - self.client.detach(host, lun_or_snap) + if not is_multiattach_to_host: + self.client.detach(host, lun_or_snap) host.update() # need update to get the latest `host_luns` targets = self.filter_targets_by_host(host) if self.remove_empty_host and not host.host_luns: @@ -368,14 +370,16 @@ class CommonAdapter(object): # No return data from terminate_connection for iSCSI driver return {} - def _terminate_connection(self, lun_or_snap, connector): + def _terminate_connection(self, lun_or_snap, connector, + is_multiattach_to_host=False): is_force_detach = connector is None data = {} if is_force_detach: self.client.detach_all(lun_or_snap) else: - targets = self._detach_and_delete_host(connector['host'], - lun_or_snap) + targets = self._detach_and_delete_host( + connector['host'], lun_or_snap, + is_multiattach_to_host=is_multiattach_to_host) data = self.get_terminate_connection_info(connector, targets) return { 'driver_volume_type': self.driver_volume_type, @@ -385,7 +389,14 @@ class CommonAdapter(object): @cinder_utils.trace def terminate_connection(self, volume, connector): lun = self.client.get_lun(lun_id=self.get_lun_id(volume)) - return self._terminate_connection(lun, connector) + # None `connector` indicates force detach, then detach all even the + # volume is multi-attached. + multiattach_flag = (connector is not None and + utils.is_multiattach_to_host( + volume.volume_attachment, + connector['host'])) + return self._terminate_connection( + lun, connector, is_multiattach_to_host=multiattach_flag) def get_connector_uids(self, connector): return None @@ -443,7 +454,9 @@ class CommonAdapter(object): 'thin_provisioning_support': True, 'thick_provisioning_support': True, 'max_over_subscription_ratio': ( - self.max_over_subscription_ratio)} + self.max_over_subscription_ratio), + 'multiattach': True + } def get_lun_id(self, volume): """Retrieves id of the volume's backing LUN. diff --git a/cinder/volume/drivers/dell_emc/unity/utils.py b/cinder/volume/drivers/dell_emc/unity/utils.py index 6d3c60fc950..c2d77fa5267 100644 --- a/cinder/volume/drivers/dell_emc/unity/utils.py +++ b/cinder/volume/drivers/dell_emc/unity/utils.py @@ -26,6 +26,7 @@ import six from cinder import coordination from cinder import exception from cinder.i18n import _ +from cinder.objects import fields from cinder.volume import utils as vol_utils from cinder.volume import volume_types from cinder.zonemanager import utils as zm_utils @@ -304,3 +305,18 @@ def lock_if(condition, lock_name): return coordination.synchronized(lock_name) else: return functools.partial + + +def is_multiattach_to_host(volume_attachment, host_name): + # When multiattach is enabled, a volume could be attached to two or more + # instances which are hosted on one nova host. + # Because unity cannot recognize the volume is attached to two or more + # instances, we should keep the volume attached to the nova host until + # the volume is detached from the last instance. + if not volume_attachment: + return False + + attachment = [a for a in volume_attachment + if a.attach_status == fields.VolumeAttachStatus.ATTACHED and + a.attached_host == host_name] + return len(attachment) > 1 diff --git a/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst b/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst index a0d65e1322a..50683060d98 100644 --- a/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst +++ b/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst @@ -34,6 +34,7 @@ Supported operations - Efficient non-disruptive volume backup. - Revert a volume to a snapshot. - Create thick volumes. +- Attach a volume to multiple servers simultaneously (multiattach). Driver configuration ~~~~~~~~~~~~~~~~~~~~ diff --git a/releasenotes/notes/unity-multiattach-support-993b997e522d9e84.yaml b/releasenotes/notes/unity-multiattach-support-993b997e522d9e84.yaml new file mode 100644 index 00000000000..4c0a50d348c --- /dev/null +++ b/releasenotes/notes/unity-multiattach-support-993b997e522d9e84.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Dell EMC Unity: Implements `bp unity-multiattach-support + `__ + to support attaching a volume to multiple servers simultaneously. +