From 45263d54dc1638a3f942d61f9660808e1d5a982e Mon Sep 17 00:00:00 2001 From: Nahim Alves de Souza Date: Wed, 19 Apr 2023 12:22:30 +0000 Subject: [PATCH] NetApp ONTAP: Added support to Active/Active mode in NFS driver This patch enables Active/Active support in NetApp NFS driver. To support replication in A/A mode, the method failover_host was splitted in two phases (failover and failover_completed) as required by the spec [1]. [1] https://specs.openstack.org/openstack/cinder-specs/specs/ocata/ha-aa-replication.html Change-Id: I401ca89440d44e04f460741981cf24a42e5264a0 --- .../netapp/dataontap/test_nfs_cmode.py | 66 +++++++++++++++++++ .../dataontap/utils/test_data_motion.py | 4 +- .../drivers/netapp/dataontap/nfs_cmode.py | 23 ++++++- .../netapp/dataontap/utils/data_motion.py | 42 ++++++++++++ doc/source/reference/support-matrix.ini | 15 ++++- ...etapp-nfs-aa-support-477ddf585c5aa578.yaml | 5 ++ 6 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/netapp-nfs-aa-support-477ddf585c5aa578.yaml diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py index 1f986a2f6d0..aad6a15afeb 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py @@ -1573,6 +1573,72 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): self.assertEqual('dev1', actual_active) self.assertEqual([], vol_updates) + @ddt.data({'secondary_id': 'dev0', 'configured_targets': ['dev1']}, + {'secondary_id': 'dev3', 'configured_targets': ['dev1', 'dev2']}, + {'secondary_id': 'dev1', 'configured_targets': []}, + {'secondary_id': None, 'configured_targets': []}) + @ddt.unpack + def test_failover_invalid_replication_target(self, secondary_id, + configured_targets): + """This tests executes a method in the DataMotionMixin.""" + self.driver.backend_name = 'dev0' + self.mock_object(data_motion.DataMotionMixin, + 'get_replication_backend_names', + return_value=configured_targets) + complete_failover_call = self.mock_object( + data_motion.DataMotionMixin, '_complete_failover') + + self.assertRaises(exception.InvalidReplicationTarget, + self.driver.failover, 'fake_context', [], + secondary_id=secondary_id) + self.assertFalse(complete_failover_call.called) + + def test_failover_unable_to_failover(self): + """This tests executes a method in the DataMotionMixin.""" + self.driver.backend_name = 'dev0' + self.mock_object(data_motion.DataMotionMixin, '_complete_failover', + side_effect=na_utils.NetAppDriverException) + self.mock_object(data_motion.DataMotionMixin, + 'get_replication_backend_names', + return_value=['dev1', 'dev2']) + self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names', + return_value=fake_ssc.SSC.keys()) + self.mock_object(self.driver, '_update_zapi_client') + + self.assertRaises(exception.UnableToFailOver, + self.driver.failover, 'fake_context', [], + secondary_id='dev1') + data_motion.DataMotionMixin._complete_failover.assert_called_once_with( + 'dev0', ['dev1', 'dev2'], fake_ssc.SSC.keys(), [], + failover_target='dev1') + self.assertFalse(self.driver._update_zapi_client.called) + + def test_failover(self): + """This tests executes a method in the DataMotionMixin.""" + self.driver.backend_name = 'dev0' + self.mock_object(data_motion.DataMotionMixin, '_complete_failover', + return_value=('dev1', [])) + self.mock_object(data_motion.DataMotionMixin, + 'get_replication_backend_names', + return_value=['dev1', 'dev2']) + self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names', + return_value=fake_ssc.SSC.keys()) + self.mock_object(self.driver, '_update_zapi_client') + + actual_active, vol_updates, __ = self.driver.failover( + 'fake_context', [], secondary_id='dev1', groups=[]) + + data_motion.DataMotionMixin._complete_failover.assert_called_once_with( + 'dev0', ['dev1', 'dev2'], fake_ssc.SSC.keys(), [], + failover_target='dev1') + + def test_failover_completed(self): + self.mock_object(self.driver, '_update_zapi_client') + self.driver.failover_completed('fake_context', secondary_id='dev1') + self.driver._update_zapi_client.assert_called_once_with('dev1') + self.assertTrue(self.driver.failed_over) + self.assertEqual('dev1', self.driver.failed_over_backend_name) + def test_delete_group_snapshot(self): mock_delete_backing_file = self.mock_object( self.driver, '_delete_backing_file_for_snapshot') diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py index c71ad392722..3043720d0e0 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py @@ -1027,7 +1027,7 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase): self.assertEqual('fallback2', target) self.assertFalse(mock_debug_log.called) - def test__failover_host_no_suitable_target(self): + def test__complete_failover_no_suitable_target(self): flexvols = ['nvol1', 'nvol2'] replication_backends = ['fallback1', 'fallback2'] self.mock_object(self.dm_mixin, '_choose_failover_target', @@ -1045,7 +1045,7 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase): self.assertFalse(self.dm_mixin.break_snapmirrors.called) @ddt.data('fallback1', None) - def test__failover_host(self, failover_target): + def test__complete_failover(self, failover_target): flexvols = ['nvol1', 'nvol2', 'nvol3'] replication_backends = ['fallback1', 'fallback2'] volumes = [ diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py index 7b546cb93db..abebed28820 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py @@ -66,12 +66,15 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, Implement FlexGroup pool 3.0.0 - Add support for Intra-cluster Storage assisted volume migration Add support for revert to snapshot + 4.0.0 - Add Cinder Active/Active support (High Availability) + Implement Active/Active replication support """ - VERSION = "3.0.0" + VERSION = "4.0.0" REQUIRED_CMODE_FLAGS = ['netapp_vserver'] + SUPPORTS_ACTIVE_ACTIVE = True def __init__(self, *args, **kwargs): super(NetAppCmodeNfsDriver, self).__init__(*args, **kwargs) @@ -867,9 +870,23 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, super(NetAppCmodeNfsDriver, self).unmanage(volume) def failover_host(self, context, volumes, secondary_id=None, groups=None): - """Failover a backend to a secondary replication target.""" + """Failover a backend to a secondary replication target. - return self._failover_host(volumes, secondary_id=secondary_id) + This function combines failover() and failover_completed() + to perform failover when Active/Active is not enabled. + """ + active_backend_name, volume_updates, group_updates = ( + self._failover(context, volumes, secondary_id, groups)) + self._failover_completed(context, active_backend_name) + return active_backend_name, volume_updates, group_updates + + def failover(self, context, volumes, secondary_id=None, groups=None): + """Failover to replication target.""" + return self._failover(context, volumes, secondary_id, groups) + + def failover_completed(self, context, secondary_id=None): + """Update volume node when `failover` is completed.""" + return self._failover_completed(context, secondary_id) def _get_backing_flexvol_names(self): """Returns a list of backing flexvol names.""" diff --git a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py index 5148ef36262..8b6372e834c 100644 --- a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py +++ b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py @@ -787,6 +787,48 @@ class DataMotionMixin(object): return active_backend_name, volume_updates, [] + def _failover(self, context, volumes, secondary_id=None, groups=None): + """Failover to replication target.""" + if secondary_id == self.backend_name: + msg = _("Cannot failover to the same host as the primary.") + raise exception.InvalidReplicationTarget(reason=msg) + + replication_targets = self.get_replication_backend_names( + self.configuration) + + if not replication_targets: + msg = _("No replication targets configured for backend " + "%s. Cannot failover.") + raise exception.InvalidReplicationTarget(reason=msg % self.host) + elif secondary_id and secondary_id not in replication_targets: + msg = _("%(target)s is not among replication targets configured " + "for back end %(host)s. Cannot failover.") + payload = { + 'target': secondary_id, + 'host': self.host, + } + raise exception.InvalidReplicationTarget(reason=msg % payload) + + flexvols = self.ssc_library.get_ssc_flexvol_names() + + try: + active_backend_name, volume_updates = self._complete_failover( + self.backend_name, replication_targets, flexvols, volumes, + failover_target=secondary_id) + except na_utils.NetAppDriverException as e: + msg = _("Could not complete failover: %s") % e + raise exception.UnableToFailOver(reason=msg) + + return active_backend_name, volume_updates, [] + + def _failover_completed(self, context, secondary_id=None): + """Update volume node when `failover` is completed.""" + # Update the ZAPI client to the backend we failed over to + self._update_zapi_client(secondary_id) + + self.failed_over = True + self.failed_over_backend_name = secondary_id + def _get_replication_volume_online_timeout(self): return self.configuration.netapp_replication_volume_online_timeout diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index dc4da1bcbff..c21d1d04ef1 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -157,7 +157,10 @@ title=NEC Storage M Series Driver (iSCSI, FC) title=NEC Storage V Series Driver (iSCSI, FC) [driver.netapp_ontap] -title=NetApp Data ONTAP Driver (iSCSI, NFS, FC, NVMe/TCP) +title=NetApp Data ONTAP Driver (iSCSI, FC, NVMe/TCP) + +[driver.netapp_ontap_nfs] +title=NetApp Data ONTAP Driver (NFS) [driver.netapp_solidfire] title=NetApp Solidfire Driver (iSCSI) @@ -286,6 +289,7 @@ driver.macrosan=complete driver.nec=complete driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=complete driver.nfs=complete @@ -363,6 +367,7 @@ driver.macrosan=complete driver.nec=complete driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=complete driver.nfs=missing @@ -443,6 +448,7 @@ driver.macrosan=complete driver.nec=complete driver.nec_v=missing driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -522,6 +528,7 @@ driver.macrosan=complete driver.nec=missing driver.nec_v=missing driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -602,6 +609,7 @@ driver.macrosan=missing driver.nec=missing driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -681,6 +689,7 @@ driver.macrosan=complete driver.nec=complete driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=complete @@ -761,6 +770,7 @@ driver.macrosan=complete driver.nec=complete driver.nec_v=missing driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -841,6 +851,7 @@ driver.macrosan=missing driver.nec=complete driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -918,6 +929,7 @@ driver.macrosan=missing driver.nec=complete driver.nec_v=complete driver.netapp_ontap=complete +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing @@ -999,6 +1011,7 @@ driver.macrosan=complete driver.nec=missing driver.nec_v=missing driver.netapp_ontap=missing +driver.netapp_ontap_nfs=complete driver.netapp_solidfire=complete driver.nexenta=missing driver.nfs=missing diff --git a/releasenotes/notes/netapp-nfs-aa-support-477ddf585c5aa578.yaml b/releasenotes/notes/netapp-nfs-aa-support-477ddf585c5aa578.yaml new file mode 100644 index 00000000000..b1a8b110a96 --- /dev/null +++ b/releasenotes/notes/netapp-nfs-aa-support-477ddf585c5aa578.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + NetApp ONTAP NFS driver: Enabled support for Active/Active environments + in the NetApp NFS driver (including replication).