NEC Driver: Support multi-attach

Adding support for multi-attach in NEC driver.

Change-Id: I345165d84ff7c75c5a55e1277111f9f93cc0ca55
This commit is contained in:
Naoki Saito 2019-08-08 17:54:18 +09:00
parent 806d52c45c
commit e4ee4e953f
6 changed files with 268 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
---
features:
- |
NEC Driver: Added multiattach support.