NEC Driver: Support multi-attach
Adding support for multi-attach in NEC driver. Change-Id: I345165d84ff7c75c5a55e1277111f9f93cc0ca55
This commit is contained in:
parent
806d52c45c
commit
e4ee4e953f
@ -17,9 +17,12 @@
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.objects import volume_attachment
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_constants as constants
|
||||
from cinder.tests.unit.fake_volume import fake_volume_obj
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.nec import cli
|
||||
from cinder.volume.drivers.nec import volume_common
|
||||
@ -360,6 +363,7 @@ class DummyVolume(object):
|
||||
self.volume_id = None
|
||||
self.volume_type_id = None
|
||||
self.attach_status = None
|
||||
self.volume_attachment = None
|
||||
self.provider_location = None
|
||||
|
||||
|
||||
@ -781,24 +785,85 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
|
||||
self.assertEqual(88, info['data']['target_luns'][1])
|
||||
|
||||
def test_iscsi_terminate_connection(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
|
||||
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255",
|
||||
'multipath': True}
|
||||
ret = self._iscsi_terminate_connection(self.vol, connector)
|
||||
self.assertIsNone(ret)
|
||||
'multipath': True, 'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
with mock.patch.object(self._cli, 'delldsetld',
|
||||
return_value=(True, '')
|
||||
) as delldsetld_mock:
|
||||
ret = self._iscsi_terminate_connection(vol, connector)
|
||||
delldsetld_mock.assert_called_once_with(
|
||||
'LX:OpenStack0', 'LX:287RbQoP7VdwR1WsPC2fZT')
|
||||
self.assertIsNone(ret)
|
||||
|
||||
attachment1 = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attachment2 = {
|
||||
'id': constants.ATTACHMENT2_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
|
||||
attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
|
||||
attachments = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object1, attach_object2])
|
||||
vol.volume_attachment = attachments
|
||||
with mock.patch.object(self._cli, 'delldsetld',
|
||||
return_value=(True, '')
|
||||
) as delldsetld_mock:
|
||||
ret = self._iscsi_terminate_connection(vol, connector)
|
||||
delldsetld_mock.assert_not_called()
|
||||
self.assertIsNone(ret)
|
||||
|
||||
def test_iscsi_terminate_connection_negative(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
|
||||
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255",
|
||||
'multipath': True}
|
||||
'multipath': True, 'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
with self.assertRaisesRegex(exception.VolumeBackendAPIException,
|
||||
r'Failed to unregister Logical Disk from'
|
||||
r' Logical Disk Set \(iSM31064\)'):
|
||||
self.mock_object(self._cli, 'delldsetld',
|
||||
return_value=(False, 'iSM31064'))
|
||||
self._iscsi_terminate_connection(self.vol, connector)
|
||||
self._iscsi_terminate_connection(vol, connector)
|
||||
|
||||
def test_fc_initialize_connection(self):
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
|
||||
info = self._fc_initialize_connection(self.vol, connector)
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
|
||||
'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
info = self._fc_initialize_connection(vol, connector)
|
||||
self.assertEqual('fibre_channel', info['driver_volume_type'])
|
||||
self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
|
||||
self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
|
||||
@ -833,11 +898,61 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
|
||||
r' Logical Disk Set \(iSM31064\)'):
|
||||
self.mock_object(self._cli, 'delldsetld',
|
||||
return_value=(False, 'iSM31064'))
|
||||
self._fc_terminate_connection(self.vol, connector)
|
||||
self._fc_terminate_connection(vol, connector)
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
with mock.patch.object(self._cli, 'delldsetld',
|
||||
return_value=(True, '')
|
||||
) as delldsetld_mock:
|
||||
self._fc_terminate_connection(vol, connector)
|
||||
delldsetld_mock.assert_called_once_with(
|
||||
'LX:OpenStack1', 'LX:287RbQoP7VdwR1WsPC2fZT')
|
||||
|
||||
attachment1 = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attachment2 = {
|
||||
'id': constants.ATTACHMENT2_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
|
||||
attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
|
||||
attachments = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object1, attach_object2])
|
||||
vol.volume_attachment = attachments
|
||||
with mock.patch.object(self._cli, 'delldsetld',
|
||||
return_value=(True, '')
|
||||
) as delldsetld_mock:
|
||||
self._fc_terminate_connection(vol, connector)
|
||||
delldsetld_mock.assert_not_called()
|
||||
|
||||
def test_fc_terminate_connection(self):
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
|
||||
info = self._fc_terminate_connection(self.vol, connector)
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
|
||||
'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
info = self._fc_terminate_connection(vol, connector)
|
||||
self.assertEqual('fibre_channel', info['driver_volume_type'])
|
||||
self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
|
||||
self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
|
||||
@ -867,10 +982,43 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
|
||||
self.assertEqual(
|
||||
'2A00000991020012',
|
||||
info['data']['initiator_target_map']['10000090FAA0786B'][3])
|
||||
info = self._fc_terminate_connection(self.vol, None)
|
||||
info = self._fc_terminate_connection(vol, None)
|
||||
self.assertEqual('fibre_channel', info['driver_volume_type'])
|
||||
self.assertEqual({}, info['data'])
|
||||
|
||||
def test_is_multi_attachment(self):
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id=constants.VOLUME_ID)
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
|
||||
'host': 'DummyHost'}
|
||||
attachment1 = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attachment2 = {
|
||||
'id': constants.ATTACHMENT2_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
|
||||
attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
|
||||
attachments = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object1, attach_object2])
|
||||
vol.volume_attachment = attachments
|
||||
ret = self._is_multi_attachment(vol, connector)
|
||||
self.assertTrue(ret)
|
||||
|
||||
attachments = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object1])
|
||||
vol.volume_attachment = attachments
|
||||
ret = self._is_multi_attachment(vol, connector)
|
||||
self.assertFalse(ret)
|
||||
|
||||
vol.volume_attachment = None
|
||||
ret = self._is_multi_attachment(vol, connector)
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_iscsi_portal_with_controller_node_name(self):
|
||||
self.vol.status = 'downloading'
|
||||
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
|
||||
@ -1010,11 +1158,33 @@ class NonDisruptiveBackup_test(volume_helper.MStorageDSVDriver,
|
||||
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
||||
|
||||
def test_terminate_connection_snapshot(self):
|
||||
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
|
||||
self.iscsi_terminate_connection_snapshot(self.vol, connector)
|
||||
ctx = context.RequestContext('admin', 'fake', True)
|
||||
vol = fake_volume_obj(ctx, id="46045673-41e7-44a7-9333-02f07feab04b")
|
||||
connector = {'initiator': 'iqn.1994-05.com.redhat:d1d8e8f23255',
|
||||
'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
self.iscsi_terminate_connection_snapshot(vol, connector)
|
||||
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
|
||||
ret = self.fc_terminate_connection_snapshot(self.vol, connector)
|
||||
connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
|
||||
'host': 'DummyHost'}
|
||||
attachment = {
|
||||
'id': constants.ATTACHMENT_ID,
|
||||
'volume_id': vol.id,
|
||||
'connector': connector
|
||||
}
|
||||
attach_object = volume_attachment.VolumeAttachment(**attachment)
|
||||
attachment = volume_attachment.VolumeAttachmentList(
|
||||
objects=[attach_object])
|
||||
vol.volume_attachment = attachment
|
||||
ret = self.fc_terminate_connection_snapshot(vol, connector)
|
||||
self.assertEqual('fibre_channel', ret['driver_volume_type'])
|
||||
|
||||
def test_remove_export_snapshot(self):
|
||||
|
@ -26,10 +26,31 @@ from cinder.zonemanager import utils as fczm_utils
|
||||
@interface.volumedriver
|
||||
class MStorageISCSIDriver(volume_helper.MStorageDSVDriver,
|
||||
driver.ISCSIDriver):
|
||||
"""M-Series Storage Snapshot iSCSI Driver."""
|
||||
"""M-Series Storage Snapshot iSCSI Driver.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Version history:
|
||||
|
||||
1.8.1 - First open source driver version.
|
||||
1.8.2 - Code refactoring.
|
||||
1.9.1 - Support optimal path for non-disruptive backup.
|
||||
1.9.2 - Support manage/unmanage and manage/unmanage snapshot.
|
||||
Delete an unused configuration
|
||||
parameter (ldset_controller_node_name).
|
||||
Fixed bug #1705001: driver fails to start.
|
||||
1.10.1 - Support automatic configuration of SAN access control.
|
||||
Fixed bug #1753375: SAN access remains permitted on the
|
||||
source node.
|
||||
1.10.2 - Delete max volumes per pool limit.
|
||||
1.10.3 - Add faster clone status check.
|
||||
Fixed bug #1777385: driver removed access permission from
|
||||
the destination node after live-migraion.
|
||||
Fixed bug #1778669: LUNs of detached volumes are never reused.
|
||||
"""
|
||||
|
||||
VERSION = '1.10.3'
|
||||
WIKI_NAME = 'NEC_Cinder_CI'
|
||||
CI_WIKI_NAME = 'NEC_Cinder_CI'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MStorageISCSIDriver, self).__init__(*args, **kwargs)
|
||||
@ -72,10 +93,31 @@ class MStorageISCSIDriver(volume_helper.MStorageDSVDriver,
|
||||
@interface.volumedriver
|
||||
class MStorageFCDriver(volume_helper.MStorageDSVDriver,
|
||||
driver.FibreChannelDriver):
|
||||
"""M-Series Storage Snapshot FC Driver."""
|
||||
"""M-Series Storage Snapshot FC Driver.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
Version history:
|
||||
|
||||
1.8.1 - First open source driver version.
|
||||
1.8.2 - Code refactoring.
|
||||
1.9.1 - Support optimal path for non-disruptive backup.
|
||||
1.9.2 - Support manage/unmanage and manage/unmanage snapshot.
|
||||
Delete an unused configuration
|
||||
parameter (ldset_controller_node_name).
|
||||
Fixed bug #1705001: driver fails to start.
|
||||
1.10.1 - Support automatic configuration of SAN access control.
|
||||
Fixed bug #1753375: SAN access remains permitted on the
|
||||
source node.
|
||||
1.10.2 - Delete max volumes per pool limit.
|
||||
1.10.3 - Add faster clone status check.
|
||||
Fixed bug #1777385: driver removed access permission from
|
||||
the destination node after live-migraion.
|
||||
Fixed bug #1778669: LUNs of detached volumes are never reused.
|
||||
"""
|
||||
|
||||
VERSION = '1.10.3'
|
||||
WIKI_NAME = 'NEC_Cinder_CI'
|
||||
CI_WIKI_NAME = 'NEC_Cinder_CI'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MStorageFCDriver, self).__init__(*args, **kwargs)
|
||||
|
@ -1143,6 +1143,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
{'msgparm': msgparm, 'exception': e})
|
||||
return ret
|
||||
|
||||
@coordination.synchronized('mstorage_iscsi_terminate_{volume.id}')
|
||||
def iscsi_terminate_connection(self, volume, connector):
|
||||
msgparm = ('Volume ID = %(id)s, Connector = %(connector)s'
|
||||
% {'id': volume.id, 'connector': connector})
|
||||
@ -1166,6 +1167,9 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
LOG.debug('Connector is not specified. Nothing to do.')
|
||||
return
|
||||
|
||||
if self._is_multi_attachment(volume, connector):
|
||||
return
|
||||
|
||||
# delete unused access control setting.
|
||||
xml = self._cli.view_all(self._properties['ismview_path'])
|
||||
pools, lds, ldsets, used_ldns, hostports, max_ld_count = (
|
||||
@ -1312,6 +1316,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
'(%(msgparm)s) (%(exception)s)',
|
||||
{'msgparm': msgparm, 'exception': e})
|
||||
|
||||
@coordination.synchronized('mstorage_fc_terminate_{volume.id}')
|
||||
def fc_terminate_connection(self, volume, connector):
|
||||
msgparm = ('Volume ID = %(id)s, Connector = %(connector)s'
|
||||
% {'id': volume.id, 'connector': connector})
|
||||
@ -1332,6 +1337,10 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
'(Volume ID = %(id)s, connector = %(connector)s) Start.',
|
||||
{'id': volume.id, 'connector': connector})
|
||||
|
||||
if connector is not None and (
|
||||
self._is_multi_attachment(volume, connector)):
|
||||
return
|
||||
|
||||
xml = self._cli.view_all(self._properties['ismview_path'])
|
||||
pools, lds, ldsets, used_ldns, hostports, max_ld_count = (
|
||||
self.configs(xml))
|
||||
@ -1393,6 +1402,26 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
'(%(msgparm)s) (%(exception)s)',
|
||||
{'msgparm': msgparm, 'exception': e})
|
||||
|
||||
def _is_multi_attachment(self, volume, connector):
|
||||
"""Check the number of attached instances.
|
||||
|
||||
Returns true if the volume is attached to multiple instances.
|
||||
Returns false if the volume is attached to a single instance.
|
||||
"""
|
||||
host = connector['host']
|
||||
attach_list = volume.volume_attachment
|
||||
|
||||
if attach_list is None:
|
||||
return False
|
||||
|
||||
host_list = [att.connector['host'] for att in attach_list if
|
||||
att is not None and att.connector is not None]
|
||||
if host_list.count(host) > 1:
|
||||
LOG.info("Volume is attached to multiple instances on "
|
||||
"this host.")
|
||||
return True
|
||||
return False
|
||||
|
||||
def _build_initiator_target_map(self, connector, fc_ports):
|
||||
target_wwns = []
|
||||
for port in fc_ports:
|
||||
@ -1419,6 +1448,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
|
||||
data['driver_version'] = self.VERSION
|
||||
data['reserved_percentage'] = self._properties['reserved_percentage']
|
||||
data['QoS_support'] = True
|
||||
data['multiattach'] = True
|
||||
data['location_info'] = (self._properties['cli_fip'] + ":"
|
||||
+ (','.join(map(str,
|
||||
self._properties['pool_pools']))))
|
||||
|
@ -52,7 +52,7 @@ Supported operations
|
||||
- Efficient non-disruptive volume backup.
|
||||
- Manage and unmanage a volume.
|
||||
- Manage and unmanage a snapshot.
|
||||
|
||||
- Attach a volume to multiple instances at once (multi-attach).
|
||||
|
||||
Preparation
|
||||
~~~~~~~~~~~
|
||||
|
@ -786,7 +786,7 @@ driver.lenovo=complete
|
||||
driver.linbit_linstor=missing
|
||||
driver.lvm=complete
|
||||
driver.macrosan=missing
|
||||
driver.nec=missing
|
||||
driver.nec=complete
|
||||
driver.netapp_ontap=complete
|
||||
driver.netapp_solidfire=complete
|
||||
driver.nexenta=missing
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
NEC Driver: Added multiattach support.
|
||||
|
Loading…
Reference in New Issue
Block a user