diff --git a/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py b/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py index 8b9cb07766b..bc47882752e 100644 --- a/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py +++ b/cinder/tests/unit/volume/drivers/solidfire/test_solidfire.py @@ -150,6 +150,26 @@ class SolidFireVolumeTestCase(test.TestCase): 'qos': None, 'iqn': 'super_fake_iqn'} + self.fake_primary_cluster = ( + {'endpoint': { + 'passwd': 'admin', + 'port': 443, + 'url': 'https://192.168.139.11:443', + 'svip': '10.10.8.11', + 'mvip': '10.10.8.12', + 'login': 'admin'}, + 'name': 'volume-f0632d53-d836-474c-a5bc-478ef18daa32', + 'clusterPairID': 33, + 'uuid': 'f0632d53-d836-474c-a5bc-478ef18daa32', + 'svip': '10.10.8.11', + 'mvipNodeID': 1, + 'repCount': 1, + 'encryptionAtRestState': 'disabled', + 'attributes': {}, + 'mvip': '10.10.8.12', + 'ensemble': ['10.10.5.130'], + 'svipNodeID': 1}) + self.cluster_pairs = ( [{'uniqueID': 'lu9f', 'endpoint': {'passwd': 'admin', 'port': 443, @@ -3121,6 +3141,61 @@ class SolidFireVolumeTestCase(test.TestCase): mock_create_cluster_reference.assert_called() mock_get_sfvol_by_cinder_vref.assert_called() + @mock.patch.object(solidfire.SolidFireDriver, '_set_cluster_pairs') + @mock.patch.object(solidfire.SolidFireDriver, 'failover') + @mock.patch.object(solidfire.SolidFireDriver, 'failover_completed') + def test_failover_host(self, mock_failover_completed, + mock_failover, + mock_set_cluster_pairs): + + fake_context = None + fake_cinder_vols = [{'id': 'testvol1'}, {'id': 'testvol2'}] + + fake_failover_updates = [{'volume_id': 'testvol1', + 'updates': { + 'replication_status': 'failed-over'}}, + {'volume_id': 'testvol2', + 'updates': { + 'replication_status': 'failed-over'}}] + + mock_failover.return_value = "secondary", fake_failover_updates, [] + + drv_args = {'active_backend_id': None} + sfv = solidfire.SolidFireDriver(configuration=self.configuration, + **drv_args) + + cluster_id, updates, _ = sfv.failover_host( + fake_context, fake_cinder_vols, secondary_id='secondary', + groups=None) + + mock_failover.called_with(fake_context, fake_cinder_vols, "secondary", + None) + mock_failover_completed.called_with(fake_context, "secondary") + self.assertEqual(cluster_id, "secondary") + self.assertEqual(fake_failover_updates, updates) + + @mock.patch.object(solidfire.SolidFireDriver, '_set_cluster_pairs') + @mock.patch.object(solidfire.SolidFireDriver, '_create_cluster_reference') + def test_failover_completed(self, mock_create_cluster_reference, + mock_set_cluster_pairs): + + ctx = context.get_admin_context() + drv_args = {'active_backend_id': None} + sfv = solidfire.SolidFireDriver(configuration=self.configuration, + **drv_args) + + sfv.cluster_pairs = self.cluster_pairs + + sfv.failover_completed(ctx, "secondary") + self.assertTrue(sfv.failed_over) + self.assertDictEqual(sfv.active_cluster, sfv.cluster_pairs[0]) + + mock_create_cluster_reference.return_value = self.fake_primary_cluster + sfv.failover_completed(ctx, '') + self.assertFalse(sfv.failed_over) + mock_create_cluster_reference.assert_called() + self.assertDictEqual(sfv.active_cluster, self.fake_primary_cluster) + @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') @mock.patch.object(solidfire.SolidFireDriver, '_create_cluster_reference') @mock.patch.object(solidfire.SolidFireDriver, '_set_cluster_pairs') @@ -3130,15 +3205,15 @@ class SolidFireVolumeTestCase(test.TestCase): @mock.patch.object(solidfire.SolidFireDriver, '_failover_volume') @mock.patch.object(solidfire.SolidFireDriver, '_get_create_account') @mock.patch.object(solidfire.SolidFireDriver, '_get_remote_info_by_id') - def test_failover_host(self, mock_get_remote_info_by_id, - mock_get_create_account, - mock_failover_volume, - mock_map_sf_volumes, - mock_get_cluster_info, - mock_update_cluster_status, - mock_set_cluster_pairs, - mock_create_cluster_reference, - mock_issue_api_request): + def test_failover(self, mock_get_remote_info_by_id, + mock_get_create_account, + mock_failover_volume, + mock_map_sf_volumes, + mock_get_cluster_info, + mock_update_cluster_status, + mock_set_cluster_pairs, + mock_create_cluster_reference, + mock_issue_api_request): all_mocks = locals() @@ -3176,7 +3251,7 @@ class SolidFireVolumeTestCase(test.TestCase): **drv_args) self.assertRaises(exception.UnableToFailOver, - sfv.failover_host, ctx, cinder_vols, 'fake', None) + sfv.failover, ctx, cinder_vols, 'fake', None) mock_map_sf_volumes.assert_not_called() fake_replication_device = {'backend_id': 'fake', @@ -3187,26 +3262,28 @@ class SolidFireVolumeTestCase(test.TestCase): self.configuration.replication_device = [fake_replication_device] reset_mocks() - drv_args = {'active_backend_id': None} + drv_args = {'active_backend_id': ''} sfv = solidfire.SolidFireDriver(configuration=self.configuration, **drv_args) + sfv.replication_enabled = True self.assertRaises(exception.InvalidReplicationTarget, - sfv.failover_host, ctx, cinder_vols, 'default', None) + sfv.failover, ctx, cinder_vols, 'default', None) mock_map_sf_volumes.assert_not_called() reset_mocks() drv_args = {'active_backend_id': None} sfv = solidfire.SolidFireDriver(configuration=self.configuration, **drv_args) + sfv.replication_enabled = True self.assertRaises(exception.InvalidReplicationTarget, - sfv.failover_host, ctx, cinder_vols, + sfv.failover, ctx, cinder_vols, secondary_id='not_fake_id', groups=None) mock_map_sf_volumes.assert_not_called() mock_create_cluster_reference.return_value = self.cluster_pairs[0] reset_mocks() - drv_args = {'active_backend_id': 'secondary'} + drv_args = {'active_backend_id': 'fake'} sfv = solidfire.SolidFireDriver(configuration=self.configuration, **drv_args) sfv.cluster_pairs = self.cluster_pairs @@ -3223,7 +3300,6 @@ class SolidFireVolumeTestCase(test.TestCase): mock_failover_volume.assert_called() mock_map_sf_volumes.assert_called() mock_update_cluster_status.assert_called() - mock_set_cluster_pairs.assert_called() mock_create_cluster_reference.assert_called() reset_mocks() @@ -3233,7 +3309,7 @@ class SolidFireVolumeTestCase(test.TestCase): sfv.cluster_pairs = self.cluster_pairs sfv.cluster_pairs[0]['backend_id'] = 'fake' sfv.replication_enabled = True - cluster_id, updates, _ = sfv.failover_host( + cluster_id, updates, _ = sfv.failover( ctx, cinder_vols, secondary_id='fake', groups=None) self.assertEqual(5, len(updates)) for update in updates: @@ -3245,7 +3321,6 @@ class SolidFireVolumeTestCase(test.TestCase): mock_failover_volume.assert_called() mock_map_sf_volumes.assert_called() mock_update_cluster_status.assert_called() - mock_set_cluster_pairs.assert_called() mock_create_cluster_reference.assert_called() @mock.patch.object(solidfire.SolidFireDriver, '_issue_api_request') diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index ba5f8a5e713..f85b4acb94f 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -225,9 +225,14 @@ class SolidFireDriver(san.SanISCSIDriver): SnapshotsOnly) 2.0.17 - Fix bug #1859653 SolidFire fails to failback when volume service is restarted + 2.1.0 - Add Cinder Active/Active support + - Enable Active/Active support flag + - Implement Active/Active replication support """ - VERSION = '2.0.17' + VERSION = '2.1.0' + + SUPPORTS_ACTIVE_ACTIVE = True # ThirdPartySystems wiki page CI_WIKI_NAME = "NetApp_SolidFire_CI" @@ -2347,7 +2352,7 @@ class SolidFireDriver(san.SanISCSIDriver): self._issue_api_request('ModifyVolume', params, endpoint=tgt_cluster['endpoint']) - def failover_host(self, context, volumes, secondary_id=None, groups=None): + def failover(self, context, volumes, secondary_id=None, groups=None): """Failover to replication target. In order to do failback, you MUST specify the original/default cluster @@ -2484,10 +2489,7 @@ class SolidFireDriver(san.SanISCSIDriver): } } vol_updates['updates'].update(conn_info) - volume_updates.append(vol_updates) - LOG.debug("Updates for volume: %(id)s %(updates)s", - {'id': v.id, 'updates': vol_updates}) except Exception as e: volume_updates.append({'volume_id': v['id'], @@ -2500,18 +2502,37 @@ class SolidFireDriver(san.SanISCSIDriver): volume_updates.append({'volume_id': v['id'], 'updates': {'status': 'error', }}) - self.active_cluster = remote + return '' if failback else remote['backend_id'], volume_updates, [] - if failback: - active_cluster_id = '' + def failover_completed(self, context, active_backend_id=None): + """Update volume node when `failover` is completed. + + Expects the following scenarios: + 1) active_backend_id='' when failing back + 2) active_backend_id= when failing over + 3) When `failover` raises an Exception, this will be called + with the previous active_backend_id (Will be empty string + in case backend wasn't in failed-over state). + """ + if not active_backend_id: + LOG.info("Failback completed. " + "Switching active cluster back to default.") + self.active_cluster = self._create_cluster_reference() self.failed_over = False # Recreating cluster pairs after a successful failback self._set_cluster_pairs() else: - active_cluster_id = remote['backend_id'] + LOG.info("Failover completed. " + "Switching active cluster to %s.", active_backend_id) + self.active_cluster = self.cluster_pairs[0] self.failed_over = True - return active_cluster_id, volume_updates, [] + def failover_host(self, context, volumes, secondary_id=None, groups=None): + """Failover to replication target in non-clustered deployment.""" + active_cluster_id, volume_updates, group_updates = ( + self.failover(context, volumes, secondary_id, groups)) + self.failover_completed(context, active_cluster_id) + return active_cluster_id, volume_updates, group_updates def freeze_backend(self, context): """Freeze backend notification.""" diff --git a/releasenotes/notes/solidfire-active-active-replication-support-f77e0e12320f8b21.yaml b/releasenotes/notes/solidfire-active-active-replication-support-f77e0e12320f8b21.yaml new file mode 100644 index 00000000000..72cc5481eb9 --- /dev/null +++ b/releasenotes/notes/solidfire-active-active-replication-support-f77e0e12320f8b21.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + NetApp SolidFire driver: Enabled support for Active/Active + (including replication) to the SolidFire driver. This allows + users to configure SolidFire backends in clustered environments. \ No newline at end of file