Browse Source

Merge "NEC Driver: Support multi-attach"

changes/88/680588/2
Zuul 1 week ago
parent
commit
de097719b5

+ 186
- 16
cinder/tests/unit/volume/drivers/nec/test_volume.py View File

@@ -17,9 +17,12 @@
17 17
 import ddt
18 18
 import mock
19 19
 
20
+from cinder import context
20 21
 from cinder import exception
22
+from cinder.objects import volume_attachment
21 23
 from cinder import test
22 24
 from cinder.tests.unit import fake_constants as constants
25
+from cinder.tests.unit.fake_volume import fake_volume_obj
23 26
 from cinder.volume import configuration as conf
24 27
 from cinder.volume.drivers.nec import cli
25 28
 from cinder.volume.drivers.nec import volume_common
@@ -360,6 +363,7 @@ class DummyVolume(object):
360 363
         self.volume_id = None
361 364
         self.volume_type_id = None
362 365
         self.attach_status = None
366
+        self.volume_attachment = None
363 367
         self.provider_location = None
364 368
 
365 369
 
@@ -781,24 +785,85 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
781 785
         self.assertEqual(88, info['data']['target_luns'][1])
782 786
 
783 787
     def test_iscsi_terminate_connection(self):
788
+        ctx = context.RequestContext('admin', 'fake', True)
789
+        vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
784 790
         connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255",
785
-                     'multipath': True}
786
-        ret = self._iscsi_terminate_connection(self.vol, connector)
787
-        self.assertIsNone(ret)
791
+                     'multipath': True, 'host': 'DummyHost'}
792
+        attachment = {
793
+            'id': constants.ATTACHMENT_ID,
794
+            'volume_id': vol.id,
795
+            'connector': connector
796
+        }
797
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
798
+        attachment = volume_attachment.VolumeAttachmentList(
799
+            objects=[attach_object])
800
+        vol.volume_attachment = attachment
801
+        with mock.patch.object(self._cli, 'delldsetld',
802
+                               return_value=(True, '')
803
+                               ) as delldsetld_mock:
804
+            ret = self._iscsi_terminate_connection(vol, connector)
805
+            delldsetld_mock.assert_called_once_with(
806
+                'LX:OpenStack0', 'LX:287RbQoP7VdwR1WsPC2fZT')
807
+            self.assertIsNone(ret)
808
+
809
+        attachment1 = {
810
+            'id': constants.ATTACHMENT_ID,
811
+            'volume_id': vol.id,
812
+            'connector': connector
813
+        }
814
+        attachment2 = {
815
+            'id': constants.ATTACHMENT2_ID,
816
+            'volume_id': vol.id,
817
+            'connector': connector
818
+        }
819
+        attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
820
+        attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
821
+        attachments = volume_attachment.VolumeAttachmentList(
822
+            objects=[attach_object1, attach_object2])
823
+        vol.volume_attachment = attachments
824
+        with mock.patch.object(self._cli, 'delldsetld',
825
+                               return_value=(True, '')
826
+                               ) as delldsetld_mock:
827
+            ret = self._iscsi_terminate_connection(vol, connector)
828
+            delldsetld_mock.assert_not_called()
829
+            self.assertIsNone(ret)
788 830
 
789 831
     def test_iscsi_terminate_connection_negative(self):
832
+        ctx = context.RequestContext('admin', 'fake', True)
833
+        vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
790 834
         connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255",
791
-                     'multipath': True}
835
+                     'multipath': True, 'host': 'DummyHost'}
836
+        attachment = {
837
+            'id': constants.ATTACHMENT_ID,
838
+            'volume_id': vol.id,
839
+            'connector': connector
840
+        }
841
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
842
+        attachment = volume_attachment.VolumeAttachmentList(
843
+            objects=[attach_object])
844
+        vol.volume_attachment = attachment
792 845
         with self.assertRaisesRegex(exception.VolumeBackendAPIException,
793 846
                                     r'Failed to unregister Logical Disk from'
794 847
                                     r' Logical Disk Set \(iSM31064\)'):
795 848
             self.mock_object(self._cli, 'delldsetld',
796 849
                              return_value=(False, 'iSM31064'))
797
-            self._iscsi_terminate_connection(self.vol, connector)
850
+            self._iscsi_terminate_connection(vol, connector)
798 851
 
799 852
     def test_fc_initialize_connection(self):
800
-        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
801
-        info = self._fc_initialize_connection(self.vol, connector)
853
+        ctx = context.RequestContext('admin', 'fake', True)
854
+        vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
855
+        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
856
+                     'host': 'DummyHost'}
857
+        attachment = {
858
+            'id': constants.ATTACHMENT_ID,
859
+            'volume_id': vol.id,
860
+            'connector': connector
861
+        }
862
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
863
+        attachment = volume_attachment.VolumeAttachmentList(
864
+            objects=[attach_object])
865
+        vol.volume_attachment = attachment
866
+        info = self._fc_initialize_connection(vol, connector)
802 867
         self.assertEqual('fibre_channel', info['driver_volume_type'])
803 868
         self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
804 869
         self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
@@ -833,11 +898,61 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
833 898
                                     r' Logical Disk Set \(iSM31064\)'):
834 899
             self.mock_object(self._cli, 'delldsetld',
835 900
                              return_value=(False, 'iSM31064'))
836
-            self._fc_terminate_connection(self.vol, connector)
901
+            self._fc_terminate_connection(vol, connector)
902
+        ctx = context.RequestContext('admin', 'fake', True)
903
+        vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
904
+        attachment = {
905
+            'id': constants.ATTACHMENT_ID,
906
+            'volume_id': vol.id,
907
+            'connector': connector
908
+        }
909
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
910
+        attachment = volume_attachment.VolumeAttachmentList(
911
+            objects=[attach_object])
912
+        vol.volume_attachment = attachment
913
+        with mock.patch.object(self._cli, 'delldsetld',
914
+                               return_value=(True, '')
915
+                               ) as delldsetld_mock:
916
+            self._fc_terminate_connection(vol, connector)
917
+            delldsetld_mock.assert_called_once_with(
918
+                'LX:OpenStack1', 'LX:287RbQoP7VdwR1WsPC2fZT')
919
+
920
+        attachment1 = {
921
+            'id': constants.ATTACHMENT_ID,
922
+            'volume_id': vol.id,
923
+            'connector': connector
924
+        }
925
+        attachment2 = {
926
+            'id': constants.ATTACHMENT2_ID,
927
+            'volume_id': vol.id,
928
+            'connector': connector
929
+        }
930
+        attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
931
+        attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
932
+        attachments = volume_attachment.VolumeAttachmentList(
933
+            objects=[attach_object1, attach_object2])
934
+        vol.volume_attachment = attachments
935
+        with mock.patch.object(self._cli, 'delldsetld',
936
+                               return_value=(True, '')
937
+                               ) as delldsetld_mock:
938
+            self._fc_terminate_connection(vol, connector)
939
+            delldsetld_mock.assert_not_called()
837 940
 
838 941
     def test_fc_terminate_connection(self):
839
-        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
840
-        info = self._fc_terminate_connection(self.vol, connector)
942
+        ctx = context.RequestContext('admin', 'fake', True)
943
+        vol = fake_volume_obj(ctx, id='46045673-41e7-44a7-9333-02f07feab04b')
944
+        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
945
+                     'host': 'DummyHost'}
946
+        attachment = {
947
+            'id': constants.ATTACHMENT_ID,
948
+            'volume_id': vol.id,
949
+            'connector': connector
950
+        }
951
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
952
+        attachment = volume_attachment.VolumeAttachmentList(
953
+            objects=[attach_object])
954
+        vol.volume_attachment = attachment
955
+        info = self._fc_terminate_connection(vol, connector)
841 956
         self.assertEqual('fibre_channel', info['driver_volume_type'])
842 957
         self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
843 958
         self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
@@ -867,10 +982,43 @@ class ExportTest(volume_helper.MStorageDSVDriver, test.TestCase):
867 982
         self.assertEqual(
868 983
             '2A00000991020012',
869 984
             info['data']['initiator_target_map']['10000090FAA0786B'][3])
870
-        info = self._fc_terminate_connection(self.vol, None)
985
+        info = self._fc_terminate_connection(vol, None)
871 986
         self.assertEqual('fibre_channel', info['driver_volume_type'])
872 987
         self.assertEqual({}, info['data'])
873 988
 
989
+    def test_is_multi_attachment(self):
990
+        ctx = context.RequestContext('admin', 'fake', True)
991
+        vol = fake_volume_obj(ctx, id=constants.VOLUME_ID)
992
+        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
993
+                     'host': 'DummyHost'}
994
+        attachment1 = {
995
+            'id': constants.ATTACHMENT_ID,
996
+            'volume_id': vol.id,
997
+            'connector': connector
998
+        }
999
+        attachment2 = {
1000
+            'id': constants.ATTACHMENT2_ID,
1001
+            'volume_id': vol.id,
1002
+            'connector': connector
1003
+        }
1004
+        attach_object1 = volume_attachment.VolumeAttachment(**attachment1)
1005
+        attach_object2 = volume_attachment.VolumeAttachment(**attachment2)
1006
+        attachments = volume_attachment.VolumeAttachmentList(
1007
+            objects=[attach_object1, attach_object2])
1008
+        vol.volume_attachment = attachments
1009
+        ret = self._is_multi_attachment(vol, connector)
1010
+        self.assertTrue(ret)
1011
+
1012
+        attachments = volume_attachment.VolumeAttachmentList(
1013
+            objects=[attach_object1])
1014
+        vol.volume_attachment = attachments
1015
+        ret = self._is_multi_attachment(vol, connector)
1016
+        self.assertFalse(ret)
1017
+
1018
+        vol.volume_attachment = None
1019
+        ret = self._is_multi_attachment(vol, connector)
1020
+        self.assertFalse(ret)
1021
+
874 1022
     def test_iscsi_portal_with_controller_node_name(self):
875 1023
         self.vol.status = 'downloading'
876 1024
         connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
@@ -1010,11 +1158,33 @@ class NonDisruptiveBackup_test(volume_helper.MStorageDSVDriver,
1010 1158
         self.assertEqual('fibre_channel', ret['driver_volume_type'])
1011 1159
 
1012 1160
     def test_terminate_connection_snapshot(self):
1013
-        connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
1014
-        self.iscsi_terminate_connection_snapshot(self.vol, connector)
1015
-
1016
-        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"]}
1017
-        ret = self.fc_terminate_connection_snapshot(self.vol, connector)
1161
+        ctx = context.RequestContext('admin', 'fake', True)
1162
+        vol = fake_volume_obj(ctx, id="46045673-41e7-44a7-9333-02f07feab04b")
1163
+        connector = {'initiator': 'iqn.1994-05.com.redhat:d1d8e8f23255',
1164
+                     'host': 'DummyHost'}
1165
+        attachment = {
1166
+            'id': constants.ATTACHMENT_ID,
1167
+            'volume_id': vol.id,
1168
+            'connector': connector
1169
+        }
1170
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
1171
+        attachment = volume_attachment.VolumeAttachmentList(
1172
+            objects=[attach_object])
1173
+        vol.volume_attachment = attachment
1174
+        self.iscsi_terminate_connection_snapshot(vol, connector)
1175
+
1176
+        connector = {'wwpns': ["10000090FAA0786A", "10000090FAA0786B"],
1177
+                     'host': 'DummyHost'}
1178
+        attachment = {
1179
+            'id': constants.ATTACHMENT_ID,
1180
+            'volume_id': vol.id,
1181
+            'connector': connector
1182
+        }
1183
+        attach_object = volume_attachment.VolumeAttachment(**attachment)
1184
+        attachment = volume_attachment.VolumeAttachmentList(
1185
+            objects=[attach_object])
1186
+        vol.volume_attachment = attachment
1187
+        ret = self.fc_terminate_connection_snapshot(vol, connector)
1018 1188
         self.assertEqual('fibre_channel', ret['driver_volume_type'])
1019 1189
 
1020 1190
     def test_remove_export_snapshot(self):

+ 46
- 4
cinder/volume/drivers/nec/volume.py View File

@@ -26,10 +26,31 @@ from cinder.zonemanager import utils as fczm_utils
26 26
 @interface.volumedriver
27 27
 class MStorageISCSIDriver(volume_helper.MStorageDSVDriver,
28 28
                           driver.ISCSIDriver):
29
-    """M-Series Storage Snapshot iSCSI Driver."""
29
+    """M-Series Storage Snapshot iSCSI Driver.
30
+
31
+    .. code-block:: none
32
+
33
+      Version history:
34
+
35
+        1.8.1 - First open source driver version.
36
+        1.8.2 - Code refactoring.
37
+        1.9.1 - Support optimal path for non-disruptive backup.
38
+        1.9.2 - Support manage/unmanage and manage/unmanage snapshot.
39
+                Delete an unused configuration
40
+                parameter (ldset_controller_node_name).
41
+                Fixed bug #1705001: driver fails to start.
42
+        1.10.1 - Support automatic configuration of SAN access control.
43
+                 Fixed bug #1753375: SAN access remains permitted on the
44
+                 source node.
45
+        1.10.2 - Delete max volumes per pool limit.
46
+        1.10.3 - Add faster clone status check.
47
+                 Fixed bug #1777385: driver removed access permission from
48
+                 the destination node after live-migraion.
49
+                 Fixed bug #1778669: LUNs of detached volumes are never reused.
50
+    """
30 51
 
31 52
     VERSION = '1.10.3'
32
-    WIKI_NAME = 'NEC_Cinder_CI'
53
+    CI_WIKI_NAME = 'NEC_Cinder_CI'
33 54
 
34 55
     def __init__(self, *args, **kwargs):
35 56
         super(MStorageISCSIDriver, self).__init__(*args, **kwargs)
@@ -72,10 +93,31 @@ class MStorageISCSIDriver(volume_helper.MStorageDSVDriver,
72 93
 @interface.volumedriver
73 94
 class MStorageFCDriver(volume_helper.MStorageDSVDriver,
74 95
                        driver.FibreChannelDriver):
75
-    """M-Series Storage Snapshot FC Driver."""
96
+    """M-Series Storage Snapshot FC Driver.
97
+
98
+    .. code-block:: none
99
+
100
+      Version history:
101
+
102
+        1.8.1 - First open source driver version.
103
+        1.8.2 - Code refactoring.
104
+        1.9.1 - Support optimal path for non-disruptive backup.
105
+        1.9.2 - Support manage/unmanage and manage/unmanage snapshot.
106
+                Delete an unused configuration
107
+                parameter (ldset_controller_node_name).
108
+                Fixed bug #1705001: driver fails to start.
109
+        1.10.1 - Support automatic configuration of SAN access control.
110
+                 Fixed bug #1753375: SAN access remains permitted on the
111
+                 source node.
112
+        1.10.2 - Delete max volumes per pool limit.
113
+        1.10.3 - Add faster clone status check.
114
+                 Fixed bug #1777385: driver removed access permission from
115
+                 the destination node after live-migraion.
116
+                 Fixed bug #1778669: LUNs of detached volumes are never reused.
117
+    """
76 118
 
77 119
     VERSION = '1.10.3'
78
-    WIKI_NAME = 'NEC_Cinder_CI'
120
+    CI_WIKI_NAME = 'NEC_Cinder_CI'
79 121
 
80 122
     def __init__(self, *args, **kwargs):
81 123
         super(MStorageFCDriver, self).__init__(*args, **kwargs)

+ 30
- 0
cinder/volume/drivers/nec/volume_helper.py View File

@@ -1143,6 +1143,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1143 1143
                             {'msgparm': msgparm, 'exception': e})
1144 1144
         return ret
1145 1145
 
1146
+    @coordination.synchronized('mstorage_iscsi_terminate_{volume.id}')
1146 1147
     def iscsi_terminate_connection(self, volume, connector):
1147 1148
         msgparm = ('Volume ID = %(id)s, Connector = %(connector)s'
1148 1149
                    % {'id': volume.id, 'connector': connector})
@@ -1166,6 +1167,9 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1166 1167
             LOG.debug('Connector is not specified. Nothing to do.')
1167 1168
             return
1168 1169
 
1170
+        if self._is_multi_attachment(volume, connector):
1171
+            return
1172
+
1169 1173
         # delete unused access control setting.
1170 1174
         xml = self._cli.view_all(self._properties['ismview_path'])
1171 1175
         pools, lds, ldsets, used_ldns, hostports, max_ld_count = (
@@ -1312,6 +1316,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1312 1316
                             '(%(msgparm)s) (%(exception)s)',
1313 1317
                             {'msgparm': msgparm, 'exception': e})
1314 1318
 
1319
+    @coordination.synchronized('mstorage_fc_terminate_{volume.id}')
1315 1320
     def fc_terminate_connection(self, volume, connector):
1316 1321
         msgparm = ('Volume ID = %(id)s, Connector = %(connector)s'
1317 1322
                    % {'id': volume.id, 'connector': connector})
@@ -1332,6 +1337,10 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1332 1337
                   '(Volume ID = %(id)s, connector = %(connector)s) Start.',
1333 1338
                   {'id': volume.id, 'connector': connector})
1334 1339
 
1340
+        if connector is not None and (
1341
+           self._is_multi_attachment(volume, connector)):
1342
+            return
1343
+
1335 1344
         xml = self._cli.view_all(self._properties['ismview_path'])
1336 1345
         pools, lds, ldsets, used_ldns, hostports, max_ld_count = (
1337 1346
             self.configs(xml))
@@ -1393,6 +1402,26 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1393 1402
                             '(%(msgparm)s) (%(exception)s)',
1394 1403
                             {'msgparm': msgparm, 'exception': e})
1395 1404
 
1405
+    def _is_multi_attachment(self, volume, connector):
1406
+        """Check the number of attached instances.
1407
+
1408
+        Returns true if the volume is attached to multiple instances.
1409
+        Returns false if the volume is attached to a single instance.
1410
+        """
1411
+        host = connector['host']
1412
+        attach_list = volume.volume_attachment
1413
+
1414
+        if attach_list is None:
1415
+            return False
1416
+
1417
+        host_list = [att.connector['host'] for att in attach_list if
1418
+                     att is not None and att.connector is not None]
1419
+        if host_list.count(host) > 1:
1420
+            LOG.info("Volume is attached to multiple instances on "
1421
+                     "this host.")
1422
+            return True
1423
+        return False
1424
+
1396 1425
     def _build_initiator_target_map(self, connector, fc_ports):
1397 1426
         target_wwns = []
1398 1427
         for port in fc_ports:
@@ -1419,6 +1448,7 @@ class MStorageDriver(volume_common.MStorageVolumeCommon):
1419 1448
         data['driver_version'] = self.VERSION
1420 1449
         data['reserved_percentage'] = self._properties['reserved_percentage']
1421 1450
         data['QoS_support'] = True
1451
+        data['multiattach'] = True
1422 1452
         data['location_info'] = (self._properties['cli_fip'] + ":"
1423 1453
                                  + (','.join(map(str,
1424 1454
                                              self._properties['pool_pools']))))

+ 1
- 1
doc/source/configuration/block-storage/drivers/nec-storage-m-series-driver.rst View File

@@ -52,7 +52,7 @@ Supported operations
52 52
 - Efficient non-disruptive volume backup.
53 53
 - Manage and unmanage a volume.
54 54
 - Manage and unmanage a snapshot.
55
-
55
+- Attach a volume to multiple instances at once (multi-attach).
56 56
 
57 57
 Preparation
58 58
 ~~~~~~~~~~~

+ 1
- 1
doc/source/reference/support-matrix.ini View File

@@ -786,7 +786,7 @@ driver.lenovo=complete
786 786
 driver.linbit_linstor=missing
787 787
 driver.lvm=complete
788 788
 driver.macrosan=missing
789
-driver.nec=missing
789
+driver.nec=complete
790 790
 driver.netapp_ontap=complete
791 791
 driver.netapp_solidfire=complete
792 792
 driver.nexenta=missing

+ 5
- 0
releasenotes/notes/nec-support-multi-attach-8aae5100f513656c.yaml View File

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

Loading…
Cancel
Save