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:
parent
b52d26a361
commit
dffff08a20
@ -482,7 +482,8 @@ class CommonAdapterTest(test.TestCase):
|
|||||||
|
|
||||||
def test_terminate_connection_volume(self):
|
def test_terminate_connection_volume(self):
|
||||||
def f():
|
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'}
|
connector = {'host': 'host1'}
|
||||||
self.adapter.terminate_connection(volume, connector)
|
self.adapter.terminate_connection(volume, connector)
|
||||||
|
|
||||||
@ -490,7 +491,8 @@ class CommonAdapterTest(test.TestCase):
|
|||||||
|
|
||||||
def test_terminate_connection_force_detach(self):
|
def test_terminate_connection_force_detach(self):
|
||||||
def f():
|
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.adapter.terminate_connection(volume, None)
|
||||||
|
|
||||||
self.assertRaises(ex.DetachAllIsCalled, f)
|
self.assertRaises(ex.DetachAllIsCalled, f)
|
||||||
@ -498,7 +500,8 @@ class CommonAdapterTest(test.TestCase):
|
|||||||
def test_terminate_connection_snapshot(self):
|
def test_terminate_connection_snapshot(self):
|
||||||
def f():
|
def f():
|
||||||
connector = {'host': 'host1'}
|
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.adapter.terminate_connection_snapshot(snap, connector)
|
||||||
|
|
||||||
self.assertRaises(ex.DetachIsCalled, f)
|
self.assertRaises(ex.DetachIsCalled, f)
|
||||||
@ -508,11 +511,27 @@ class CommonAdapterTest(test.TestCase):
|
|||||||
|
|
||||||
def f():
|
def f():
|
||||||
connector = {'host': 'empty-host'}
|
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.adapter.terminate_connection(vol, connector)
|
||||||
|
|
||||||
self.assertRaises(ex.HostDeleteIsCalled, f)
|
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):
|
def test_manage_existing_by_name(self):
|
||||||
ref = {'source-id': 12}
|
ref = {'source-id': 12}
|
||||||
volume = MockOSResource(name='lun1')
|
volume = MockOSResource(name='lun1')
|
||||||
@ -844,7 +863,8 @@ class FCAdapterTest(test.TestCase):
|
|||||||
|
|
||||||
def test_terminate_connection_auto_zone_enabled(self):
|
def test_terminate_connection_auto_zone_enabled(self):
|
||||||
connector = {'host': 'host1', 'wwpns': 'abcdefg'}
|
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)
|
ret = self.adapter.terminate_connection(volume, connector)
|
||||||
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
||||||
data = ret['data']
|
data = ret['data']
|
||||||
@ -857,7 +877,8 @@ class FCAdapterTest(test.TestCase):
|
|||||||
|
|
||||||
def test_terminate_connection_auto_zone_enabled_none_host_luns(self):
|
def test_terminate_connection_auto_zone_enabled_none_host_luns(self):
|
||||||
connector = {'host': 'host-no-host_luns', 'wwpns': 'abcdefg'}
|
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)
|
ret = self.adapter.terminate_connection(volume, connector)
|
||||||
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
||||||
data = ret['data']
|
data = ret['data']
|
||||||
@ -871,7 +892,8 @@ class FCAdapterTest(test.TestCase):
|
|||||||
def test_terminate_connection_remove_empty_host_return_data(self):
|
def test_terminate_connection_remove_empty_host_return_data(self):
|
||||||
self.adapter.remove_empty_host = True
|
self.adapter.remove_empty_host = True
|
||||||
connector = {'host': 'empty-host-return-data', 'wwpns': 'abcdefg'}
|
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)
|
ret = self.adapter.terminate_connection(volume, connector)
|
||||||
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
||||||
data = ret['data']
|
data = ret['data']
|
||||||
|
@ -348,11 +348,13 @@ class CommonAdapter(object):
|
|||||||
# No target info for iSCSI driver
|
# No target info for iSCSI driver
|
||||||
return []
|
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}')
|
@utils.lock_if(self.to_lock_host, '{lock_name}')
|
||||||
def _lock_helper(lock_name):
|
def _lock_helper(lock_name):
|
||||||
# Only get the host from cache here
|
# Only get the host from cache here
|
||||||
host = self.client.create_host_wo_lock(host_name)
|
host = self.client.create_host_wo_lock(host_name)
|
||||||
|
if not is_multiattach_to_host:
|
||||||
self.client.detach(host, lun_or_snap)
|
self.client.detach(host, lun_or_snap)
|
||||||
host.update() # need update to get the latest `host_luns`
|
host.update() # need update to get the latest `host_luns`
|
||||||
targets = self.filter_targets_by_host(host)
|
targets = self.filter_targets_by_host(host)
|
||||||
@ -368,14 +370,16 @@ class CommonAdapter(object):
|
|||||||
# No return data from terminate_connection for iSCSI driver
|
# No return data from terminate_connection for iSCSI driver
|
||||||
return {}
|
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
|
is_force_detach = connector is None
|
||||||
data = {}
|
data = {}
|
||||||
if is_force_detach:
|
if is_force_detach:
|
||||||
self.client.detach_all(lun_or_snap)
|
self.client.detach_all(lun_or_snap)
|
||||||
else:
|
else:
|
||||||
targets = self._detach_and_delete_host(connector['host'],
|
targets = self._detach_and_delete_host(
|
||||||
lun_or_snap)
|
connector['host'], lun_or_snap,
|
||||||
|
is_multiattach_to_host=is_multiattach_to_host)
|
||||||
data = self.get_terminate_connection_info(connector, targets)
|
data = self.get_terminate_connection_info(connector, targets)
|
||||||
return {
|
return {
|
||||||
'driver_volume_type': self.driver_volume_type,
|
'driver_volume_type': self.driver_volume_type,
|
||||||
@ -385,7 +389,14 @@ class CommonAdapter(object):
|
|||||||
@cinder_utils.trace
|
@cinder_utils.trace
|
||||||
def terminate_connection(self, volume, connector):
|
def terminate_connection(self, volume, connector):
|
||||||
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
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):
|
def get_connector_uids(self, connector):
|
||||||
return None
|
return None
|
||||||
@ -443,7 +454,9 @@ class CommonAdapter(object):
|
|||||||
'thin_provisioning_support': True,
|
'thin_provisioning_support': True,
|
||||||
'thick_provisioning_support': True,
|
'thick_provisioning_support': True,
|
||||||
'max_over_subscription_ratio': (
|
'max_over_subscription_ratio': (
|
||||||
self.max_over_subscription_ratio)}
|
self.max_over_subscription_ratio),
|
||||||
|
'multiattach': True
|
||||||
|
}
|
||||||
|
|
||||||
def get_lun_id(self, volume):
|
def get_lun_id(self, volume):
|
||||||
"""Retrieves id of the volume's backing LUN.
|
"""Retrieves id of the volume's backing LUN.
|
||||||
|
@ -26,6 +26,7 @@ import six
|
|||||||
from cinder import coordination
|
from cinder import coordination
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
|
from cinder.objects import fields
|
||||||
from cinder.volume import utils as vol_utils
|
from cinder.volume import utils as vol_utils
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
from cinder.zonemanager import utils as zm_utils
|
from cinder.zonemanager import utils as zm_utils
|
||||||
@ -304,3 +305,18 @@ def lock_if(condition, lock_name):
|
|||||||
return coordination.synchronized(lock_name)
|
return coordination.synchronized(lock_name)
|
||||||
else:
|
else:
|
||||||
return functools.partial
|
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
|
||||||
|
@ -34,6 +34,7 @@ Supported operations
|
|||||||
- Efficient non-disruptive volume backup.
|
- Efficient non-disruptive volume backup.
|
||||||
- Revert a volume to a snapshot.
|
- Revert a volume to a snapshot.
|
||||||
- Create thick volumes.
|
- Create thick volumes.
|
||||||
|
- Attach a volume to multiple servers simultaneously (multiattach).
|
||||||
|
|
||||||
Driver configuration
|
Driver configuration
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -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.
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user