Unity: add multiattach support

Add the ability to attach a volume to multiple servers simultaneously.

Implements: blueprint unity-multiattach-support

Change-Id: Ib7222bb3cd13505f9a8789f8cc85685e4ae9cce4
This commit is contained in:
Ryan Liang 2018-07-09 15:28:31 +08:00
parent b52d26a361
commit dffff08a20
5 changed files with 73 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
---
features:
- |
Dell EMC Unity: Implements `bp unity-multiattach-support
<https://blueprints.launchpad.net/cinder/+spec/unity-multiattach-support>`__
to support attaching a volume to multiple servers simultaneously.