diff --git a/cinder/group/api.py b/cinder/group/api.py index 1abbe5d0179..79d473be9e5 100644 --- a/cinder/group/api.py +++ b/cinder/group/api.py @@ -902,7 +902,7 @@ class API(base.Base): gsnapshot.save() def _check_type(self, group): - if not vol_utils.is_group_a_replication_group_type(group): + if not group.is_replicated: msg = _("Group %s is not a replication group type.") % group.id LOG.error(msg) raise exception.InvalidGroupType(reason=msg) diff --git a/cinder/objects/group.py b/cinder/objects/group.py index 4ed7e38cc54..120438bae8c 100644 --- a/cinder/objects/group.py +++ b/cinder/objects/group.py @@ -21,6 +21,7 @@ from cinder.i18n import _ from cinder import objects from cinder.objects import base from cinder.objects import fields as c_fields +from cinder.volume import utils as vol_utils @base.CinderObjectRegistry.register @@ -177,6 +178,14 @@ class Group(base.CinderPersistentObject, base.CinderObject, with self.obj_as_admin(): db.group_destroy(self._context, self.id) + @property + def is_replicated(self): + if (vol_utils.is_group_a_type(self, "group_replication_enabled") or + vol_utils.is_group_a_type( + self, "consistent_group_replication_enabled")): + return True + return False + @base.CinderObjectRegistry.register class GroupList(base.ObjectListBase, base.CinderObject): @@ -207,3 +216,18 @@ class GroupList(base.ObjectListBase, base.CinderObject): return base.obj_make_list(context, cls(context), objects.Group, groups) + + @classmethod + def get_all_replicated(cls, context, filters=None, marker=None, limit=None, + offset=None, sort_keys=None, sort_dirs=None): + groups = db.group_get_all( + context, filters=filters, marker=marker, limit=limit, + offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs) + grp_obj_list = base.obj_make_list(context, cls(context), + objects.Group, + groups) + + out_groups = [grp for grp in grp_obj_list + if grp.is_replicated] + + return out_groups diff --git a/cinder/tests/fake_driver.py b/cinder/tests/fake_driver.py index 853cb566280..f3932d35704 100644 --- a/cinder/tests/fake_driver.py +++ b/cinder/tests/fake_driver.py @@ -189,7 +189,7 @@ class FakeLoggingVolumeDriver(lvm.LVMVolumeDriver): model_update = super(FakeLoggingVolumeDriver, self).create_group( context, group) try: - if vol_utils.is_group_a_replication_group_type(group): + if group.is_replicated: # Sets the new group's replication_status to disabled model_update['replication_status'] = ( fields.ReplicationStatus.DISABLED) diff --git a/cinder/tests/unit/api/v3/test_groups.py b/cinder/tests/unit/api/v3/test_groups.py index 5e3083b8710..036d2e69297 100644 --- a/cinder/tests/unit/api/v3/test_groups.py +++ b/cinder/tests/unit/api/v3/test_groups.py @@ -1057,7 +1057,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) def test_enable_replication(self, mock_rep_grp_type, mock_rep_vol_type): req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' % @@ -1078,7 +1078,7 @@ class GroupsAPITestCase(test.TestCase): @ddt.data((True, False), (False, True), (False, False)) @ddt.unpack @mock.patch('cinder.volume.utils.is_replicated_spec') - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type') + @mock.patch('cinder.volume.utils.is_group_a_type') def test_enable_replication_wrong_type(self, is_grp_rep_type, is_vol_rep_type, mock_rep_grp_type, @@ -1097,7 +1097,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=False) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) def test_enable_replication_wrong_group_type(self, mock_rep_grp_type, mock_rep_vol_type): @@ -1113,7 +1113,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) @ddt.data((GROUP_REPLICATION_MICRO_VERSION, True, fields.GroupStatus.CREATING, @@ -1146,7 +1146,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) def test_disable_replication(self, mock_rep_grp_type, mock_rep_vol_type): req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' % @@ -1167,7 +1167,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) @ddt.data((GROUP_REPLICATION_MICRO_VERSION, True, fields.GroupStatus.CREATING, @@ -1209,7 +1209,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) def test_failover_replication(self, mock_rep_grp_type, mock_rep_vol_type): req = fakes.HTTPRequest.blank('/v3/%s/groups/%s/action' % @@ -1230,7 +1230,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) @ddt.data((GROUP_REPLICATION_MICRO_VERSION, True, fields.GroupStatus.CREATING, @@ -1272,7 +1272,7 @@ class GroupsAPITestCase(test.TestCase): @mock.patch('cinder.volume.utils.is_replicated_spec', return_value=True) - @mock.patch('cinder.volume.utils.is_group_a_replication_group_type', + @mock.patch('cinder.volume.utils.is_group_a_type', return_value=True) @mock.patch('cinder.volume.rpcapi.VolumeAPI.list_replication_targets') def test_list_replication_targets(self, mock_list_rep_targets, diff --git a/cinder/tests/unit/objects/test_group.py b/cinder/tests/unit/objects/test_group.py index b6160a94640..6aed446c7aa 100644 --- a/cinder/tests/unit/objects/test_group.py +++ b/cinder/tests/unit/objects/test_group.py @@ -179,7 +179,32 @@ class TestGroup(test_objects.BaseObjectsTestCase): self.assertEqual(is_set, converted_group.obj_attr_is_set(key)) self.assertEqual('name', converted_group.name) + @mock.patch('cinder.volume.group_types.get_group_type_specs') + def test_is_replicated_true(self, mock_get_specs): + mock_get_specs.return_value = ' True' + group = objects.Group(self.context, group_type_id=fake.GROUP_TYPE_ID) + # NOTE(xyang): Changed the following from self.assertTrue( + # group.is_replicated) to self.assertEqual(True, group.is_replicated) + # to address a review comment. This way this test will still pass + # even if is_replicated is a method and not a property. + self.assertTrue(True, group.is_replicated) + @ddt.data(' False', None, 'notASpecValueWeCareAbout') + def test_is_replicated_false(self, spec_value): + with mock.patch('cinder.volume.group_types' + '.get_group_type_specs') as mock_get_specs: + mock_get_specs.return_value = spec_value + group = objects.Group(self.context, + group_type_id=fake.GROUP_TYPE_ID) + # NOTE(xyang): Changed the following from self.assertFalse( + # group.is_replicated) to self.assertEqual(False, + # group.is_replicated) to address a review comment. This way this + # test will still pass even if is_replicated is a method and not + # a property. + self.assertEqual(False, group.is_replicated) + + +@ddt.ddt class TestGroupList(test_objects.BaseObjectsTestCase): @mock.patch('cinder.db.group_get_all', return_value=[fake_group]) @@ -224,3 +249,26 @@ class TestGroupList(test_objects.BaseObjectsTestCase): limit=1, offset=None, sort_keys='id', sort_dirs='asc') TestGroup._compare(self, fake_group, groups[0]) + + @ddt.data({'cluster_name': 'fake_cluster'}, {'host': 'fake_host'}) + @mock.patch('cinder.volume.group_types.get_group_type_specs') + @mock.patch('cinder.db.group_get_all') + def test_get_all_replicated(self, filters, mock_get_groups, + mock_get_specs): + mock_get_specs.return_value = ' True' + fake_group2 = fake_group.copy() + fake_group2['id'] = fake.GROUP2_ID + fake_group2['cluster_name'] = 'fake_cluster' + if filters.get('cluster_name'): + mock_get_groups.return_value = [fake_group2] + else: + mock_get_groups.return_value = [fake_group] + res = objects.GroupList.get_all_replicated(self.context, + filters=filters) + self.assertEqual(1, len(res)) + if filters.get('cluster_name'): + self.assertEqual(fake.GROUP2_ID, res[0].id) + self.assertEqual('fake_cluster', res[0].cluster_name) + else: + self.assertEqual(fake.GROUP_ID, res[0].id) + self.assertIsNone(res[0].cluster_name) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/sc/test_sc.py b/cinder/tests/unit/volume/drivers/dell_emc/sc/test_sc.py index 1bf3c54898c..31702555371 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/sc/test_sc.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/sc/test_sc.py @@ -3161,8 +3161,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'volume_id': fake.VOLUME2_ID, 'updates': {'replication_status': 'failed-over', 'provider_id': '2.2'}}] - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. Not all volumes replicated. @@ -3175,8 +3175,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. Not all volumes replicated. No replication_driver_data. @@ -3189,8 +3189,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. No volumes replicated. No replication_driver_data. @@ -3202,8 +3202,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Secondary not found. @@ -3214,14 +3214,15 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): self.driver.failover_host, {}, volumes, - '54321') + '54321', + []) # Already failed over. self.driver.failed_over = True self.driver.failover_host({}, volumes, 'default') mock_failback_volumes.assert_called_once_with(volumes) # Already failed over. self.assertRaises(exception.InvalidReplicationTarget, - self.driver.failover_host, {}, volumes, '67890') + self.driver.failover_host, {}, volumes, '67890', []) self.driver.replication_enabled = False @mock.patch.object(storagecenter_iscsi.SCISCSIDriver, @@ -3279,8 +3280,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'volume_id': fake.VOLUME2_ID, 'updates': {'replication_status': 'failed-over', 'provider_id': '2.2'}}] - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. Not all volumes replicated. @@ -3293,8 +3294,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. Not all volumes replicated. No replication_driver_data. @@ -3307,8 +3308,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Good run. No volumes replicated. No replication_driver_data. @@ -3320,8 +3321,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): {'status': 'error'}}] self.driver.failed_over = False self.driver.active_backend_id = None - destssn, volume_update = self.driver.failover_host( - {}, volumes, '12345') + destssn, volume_update, __ = self.driver.failover_host( + {}, volumes, '12345', []) self.assertEqual(expected_destssn, destssn) self.assertEqual(expected_volume_update, volume_update) # Secondary not found. @@ -3332,7 +3333,8 @@ class DellSCSanISCSIDriverTestCase(test.TestCase): self.driver.failover_host, {}, volumes, - '54321') + '54321', + []) # Already failed over. self.driver.failed_over = True self.driver.failover_host({}, volumes, 'default') diff --git a/cinder/tests/unit/volume/drivers/dell_emc/vnx/test_adapter.py b/cinder/tests/unit/volume/drivers/dell_emc/vnx/test_adapter.py index 10f05ff8698..3710f51e068 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/vnx/test_adapter.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/vnx/test_adapter.py @@ -1165,8 +1165,8 @@ class TestCommonAdapter(test.TestCase): fake_mirror.secondary_client.get_serial.return_value = ( device['backend_id']) fake.return_value = fake_mirror - backend_id, updates = common_adapter.failover_host( - None, [vol1], device['backend_id']) + backend_id, updates, __ = common_adapter.failover_host( + None, [vol1], device['backend_id'], []) fake_mirror.promote_image.assert_called_once_with( 'mirror_' + vol1.id) fake_mirror.secondary_client.get_serial.assert_called_with() @@ -1205,8 +1205,8 @@ class TestCommonAdapter(test.TestCase): fake_mirror.secondary_client.get_serial.return_value = ( device['backend_id']) fake.return_value = fake_mirror - backend_id, updates = common_adapter.failover_host( - None, [vol1], 'default') + backend_id, updates, __ = common_adapter.failover_host( + None, [vol1], 'default', []) fake_mirror.promote_image.assert_called_once_with( 'mirror_' + vol1.id) fake_mirror.secondary_client.get_serial.assert_called_with() diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 24dc897c8dd..37a1c993bd9 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -5116,7 +5116,8 @@ class HPE3PARBaseDriver(object): expected_model = (self.REPLICATION_BACKEND_ID, [{'updates': {'replication_status': 'failed-over'}, - 'volume_id': self.VOLUME_ID}]) + 'volume_id': self.VOLUME_ID}], + []) return_model = self.driver.failover_host( context.get_admin_context(), volumes, @@ -5173,7 +5174,8 @@ class HPE3PARBaseDriver(object): expected_model = (None, [{'updates': {'replication_status': 'available'}, - 'volume_id': self.VOLUME_ID}]) + 'volume_id': self.VOLUME_ID}], + []) self.assertEqual(expected_model, return_model) @mock.patch.object(volume_types, 'get_volume_type') diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py b/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py index 197cfdd105a..8714e7c7524 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py @@ -3067,7 +3067,8 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): 'failed-over', 'provider_location': prov_location}, - 'volume_id': 1}]) + 'volume_id': 1}], + []) self.assertEqual(expected_model, return_model) @mock.patch.object(volume_types, 'get_volume_type') @@ -3164,7 +3165,8 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): 'available', 'provider_location': prov_location}, - 'volume_id': 1}]) + 'volume_id': 1}], + []) self.assertEqual(expected_model, return_model) @mock.patch.object(volume_types, 'get_volume_type') diff --git a/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py b/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py index 41524facf48..766645a7799 100644 --- a/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py +++ b/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py @@ -3962,8 +3962,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [self.volume], 'default') + secondary_id, volumes_update, __ = driver.failover_host( + None, [self.volume], 'default', []) self.assertIn(driver.active_backend_id, ('', None)) self.assertEqual(old_client, driver.client) self.assertEqual(old_replica_client, driver.replica_client) @@ -3977,8 +3977,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [self.volume], REPLICA_BACKEND_ID) + secondary_id, volumes_update, __ = driver.failover_host( + None, [self.volume], REPLICA_BACKEND_ID, []) self.assertEqual(REPLICA_BACKEND_ID, driver.active_backend_id) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) @@ -3999,8 +3999,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [self.volume], REPLICA_BACKEND_ID) + secondary_id, volumes_update, __ = driver.failover_host( + None, [self.volume], REPLICA_BACKEND_ID, []) self.assertEqual(REPLICA_BACKEND_ID, driver.active_backend_id) self.assertEqual(old_client, driver.client) self.assertEqual(old_replica_client, driver.replica_client) @@ -4018,8 +4018,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [self.volume], 'default') + secondary_id, volumes_update, __ = driver.failover_host( + None, [self.volume], 'default', []) self.assertIn(driver.active_backend_id, ('', None)) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) @@ -4041,8 +4041,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): self.mock_object(replication.ReplicaCommonDriver, 'failover') self.mock_object(huawei_driver.HuaweiBaseDriver, '_get_volume_params', return_value={'replication_enabled': 'true'}) - secondary_id, volumes_update = driver.failover_host( - None, [self.replica_volume], REPLICA_BACKEND_ID) + secondary_id, volumes_update, __ = driver.failover_host( + None, [self.replica_volume], REPLICA_BACKEND_ID, []) self.assertEqual(REPLICA_BACKEND_ID, driver.active_backend_id) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) @@ -4071,8 +4071,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_replica = driver.replica self.mock_object(huawei_driver.HuaweiBaseDriver, '_get_volume_params', return_value={'replication_enabled': 'true'}) - secondary_id, volumes_update = driver.failover_host( - None, [volume], REPLICA_BACKEND_ID) + secondary_id, volumes_update, __ = driver.failover_host( + None, [volume], REPLICA_BACKEND_ID, []) self.assertEqual(driver.active_backend_id, REPLICA_BACKEND_ID) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) @@ -4099,8 +4099,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [volume], 'default') + secondary_id, volumes_update, __ = driver.failover_host( + None, [volume], 'default', []) self.assertIn(driver.active_backend_id, ('', None)) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) @@ -4132,8 +4132,8 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): old_client = driver.client old_replica_client = driver.replica_client old_replica = driver.replica - secondary_id, volumes_update = driver.failover_host( - None, [volume], 'default') + secondary_id, volumes_update, __ = driver.failover_host( + None, [volume], 'default', []) self.assertIn(driver.active_backend_id, ('', None)) self.assertEqual(old_client, driver.replica_client) self.assertEqual(old_replica_client, driver.client) diff --git a/cinder/tests/unit/volume/drivers/ibm/test_ds8k_proxy.py b/cinder/tests/unit/volume/drivers/ibm/test_ds8k_proxy.py index 3827707d4be..dba70795586 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_ds8k_proxy.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_ds8k_proxy.py @@ -2901,8 +2901,8 @@ class DS8KProxyTest(test.TestCase): pprc_pairs = copy.deepcopy(FAKE_GET_PPRCS_RESPONSE['data']['pprcs']) pprc_pairs[0]['state'] = 'suspended' mock_get_pprc_pairs.side_effect = [pprc_pairs] - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], TEST_TARGET_DS8K_IP) + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], TEST_TARGET_DS8K_IP, []) self.assertEqual(TEST_TARGET_DS8K_IP, secondary_id) @mock.patch.object(replication.Replication, 'do_pprc_failover') @@ -2928,7 +2928,7 @@ class DS8KProxyTest(test.TestCase): restclient.APIException('failed to do failover.')) self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, self.ctxt, - [volume], TEST_TARGET_DS8K_IP) + [volume], TEST_TARGET_DS8K_IP, []) def test_failover_host_to_invalid_target(self): """Failover host to invalid secondary should fail.""" @@ -2947,7 +2947,7 @@ class DS8KProxyTest(test.TestCase): replication_driver_data=data) self.assertRaises(exception.InvalidReplicationTarget, self.driver.failover_host, self.ctxt, - [volume], 'fake_target') + [volume], 'fake_target', []) def test_failover_host_that_has_been_failed_over(self): """Failover host that has been failed over should just return.""" @@ -2964,8 +2964,8 @@ class DS8KProxyTest(test.TestCase): volume = self._create_volume(volume_type_id=vol_type.id, provider_location=location, replication_driver_data=data) - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], TEST_TARGET_DS8K_IP) + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], TEST_TARGET_DS8K_IP, []) self.assertEqual(TEST_TARGET_DS8K_IP, secondary_id) self.assertEqual([], volume_update_list) @@ -2984,8 +2984,8 @@ class DS8KProxyTest(test.TestCase): volume = self._create_volume(volume_type_id=vol_type.id, provider_location=location, replication_driver_data=data) - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], 'default') + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], 'default', []) self.assertIsNone(secondary_id) self.assertEqual([], volume_update_list) @@ -3000,8 +3000,8 @@ class DS8KProxyTest(test.TestCase): location = six.text_type({'vol_hex_id': TEST_VOLUME_ID}) volume = self._create_volume(volume_type_id=vol_type.id, provider_location=location) - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], TEST_TARGET_DS8K_IP) + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], TEST_TARGET_DS8K_IP, []) self.assertEqual(TEST_TARGET_DS8K_IP, secondary_id) self.assertEqual('error', volume_update_list[0]['updates']['status']) @@ -3019,8 +3019,8 @@ class DS8KProxyTest(test.TestCase): }) volume = self._create_volume(volume_type_id=vol_type.id, provider_location=location) - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], 'default') + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], 'default', []) self.assertEqual('default', secondary_id) self.assertEqual('available', volume_update_list[0]['updates']['status']) @@ -3050,8 +3050,8 @@ class DS8KProxyTest(test.TestCase): mock_get_pprc_pairs.side_effect = [pprc_pairs_full_duplex, pprc_pairs_suspended, pprc_pairs_full_duplex] - secondary_id, volume_update_list = self.driver.failover_host( - self.ctxt, [volume], 'default') + secondary_id, volume_update_list, __ = self.driver.failover_host( + self.ctxt, [volume], 'default', []) self.assertEqual('default', secondary_id) @mock.patch.object(replication.Replication, 'start_pprc_failback') @@ -3074,4 +3074,4 @@ class DS8KProxyTest(test.TestCase): restclient.APIException('failed to do failback.')) self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, self.ctxt, - [volume], 'default') + [volume], 'default', []) diff --git a/cinder/tests/unit/volume/drivers/ibm/test_ibm_storage.py b/cinder/tests/unit/volume/drivers/ibm/test_ibm_storage.py index b9c95afb5a2..3c4d72f8618 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_ibm_storage.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_ibm_storage.py @@ -268,7 +268,7 @@ class IBMStorageFakeProxyDriver(object): def thaw_backend(self, context): return True - def failover_host(self, context, volumes, secondary_id): + def failover_host(self, context, volumes, secondary_id, groups=None): target_id = 'BLA' volume_update_list = [] for volume in volumes: @@ -279,7 +279,7 @@ class IBMStorageFakeProxyDriver(object): {'volume_id': volume['id'], 'updates': {'replication_status': status}}) - return target_id, volume_update_list + return target_id, volume_update_list, [] def enable_replication(self, context, group, volumes): vol_status = [] @@ -916,10 +916,11 @@ class IBMStorageVolumeDriverTest(test.TestCase): {'volume_id': REPLICATED_VOLUME['id'], 'updates': {'replication_status': 'failed-over'}}] - target_id, volume_update_list = self.driver.failover_host( + target_id, volume_update_list, __ = self.driver.failover_host( CONTEXT, [replicated_volume], - SECONDARY + SECONDARY, + [] ) self.assertEqual(expected_target_id, target_id) @@ -939,10 +940,11 @@ class IBMStorageVolumeDriverTest(test.TestCase): {'volume_id': REPLICATED_VOLUME['id'], 'updates': {'replication_status': 'error'}}] - target_id, volume_update_list = self.driver.failover_host( + target_id, volume_update_list, __ = self.driver.failover_host( CONTEXT, [replicated_volume], - SECONDARY + SECONDARY, + [] ) self.assertEqual(expected_target_id, target_id) diff --git a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py index 96dbff529a6..6fcd5305506 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_storwize_svc.py @@ -6987,7 +6987,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): volumes = [volume, non_replica_vol, gmcv_volume] # Delete volume in failover state self.driver.failover_host( - self.ctxt, volumes, self.rep_target['backend_id']) + self.ctxt, volumes, self.rep_target['backend_id'], []) # Delete non-replicate volume in a failover state self.assertRaises(exception.VolumeDriverException, self.driver.delete_volume, @@ -7001,7 +7001,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self._validate_replic_vol_deletion(gmcv_volume, True) self.driver.failover_host( - self.ctxt, volumes, 'default') + self.ctxt, volumes, 'default', []) self.driver.delete_volume(non_replica_vol) self._assert_vol_exists(non_replica_vol['name'], False) @@ -7092,11 +7092,13 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver._replica_enabled = False self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, - self.ctxt, volumes, self.rep_target['backend_id']) + self.ctxt, volumes, self.rep_target['backend_id'], + []) self.driver._replica_enabled = True self.assertRaises(exception.InvalidReplicationTarget, self.driver.failover_host, - self.ctxt, volumes, self.fake_target['backend_id']) + self.ctxt, volumes, self.fake_target['backend_id'], + []) with mock.patch.object(storwize_svc_common.StorwizeHelpers, 'get_system_info') as get_sys_info: @@ -7106,12 +7108,12 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, self.ctxt, volumes, - self.rep_target['backend_id']) + self.rep_target['backend_id'], []) self.driver._active_backend_id = self.rep_target['backend_id'] self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, - self.ctxt, volumes, 'default') + self.ctxt, volumes, 'default', []) self.driver.delete_volume(mm_vol) self.driver.delete_volume(gmcv_vol) @@ -7189,8 +7191,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase): {'replication_status': fields.ReplicationStatus.FAILED_OVER}, 'volume_id': gmcv_vol['id']}] - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, self.rep_target['backend_id']) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, self.rep_target['backend_id'], []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual(expected_list, volume_list) @@ -7206,8 +7208,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.driver.delete_volume(gm_vol) self.driver.delete_volume(gmcv_vol) - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, None) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, None, []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual([], volume_list) @@ -7258,8 +7260,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase): 'volume_id': non_replica_vol['id']}, ] - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, self.rep_target['backend_id']) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, self.rep_target['backend_id'], []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual(expected_list, volume_list) @@ -7271,15 +7273,15 @@ class StorwizeSVCReplicationTestCase(test.TestCase): self.assertTrue(update_storwize_state.called) self.assertTrue(update_volume_stats.called) - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, None) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, None, []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual([], volume_list) # Delete non-replicate volume in a failover state self.assertRaises(exception.VolumeDriverException, self.driver.delete_volume, non_replica_vol) - self.driver.failover_host(self.ctxt, volumes, 'default') + self.driver.failover_host(self.ctxt, volumes, 'default', []) self.driver.delete_volume(mm_vol) self.driver.delete_volume(gmcv_vol) self.driver.delete_volume(non_replica_vol) @@ -7360,22 +7362,22 @@ class StorwizeSVCReplicationTestCase(test.TestCase): 'status': 'available'}, 'volume_id': gmcv_vol['id']}] # Already failback - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, 'default') + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, 'default', []) self.assertIsNone(target_id) self.assertEqual([], volume_list) # fail over operation - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, self.rep_target['backend_id']) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, self.rep_target['backend_id'], []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual(failover_expect, volume_list) self.assertTrue(update_storwize_state.called) self.assertTrue(update_volume_stats.called) # fail back operation - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, 'default') + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, 'default', []) self.assertEqual('default', target_id) self.assertEqual(failback_expect, volume_list) self.assertIsNone(self.driver._active_backend_id) @@ -7450,14 +7452,14 @@ class StorwizeSVCReplicationTestCase(test.TestCase): 'volume_id': non_replica_vol2['id']}] # Already failback - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, 'default') + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, 'default', []) self.assertIsNone(target_id) self.assertEqual([], volume_list) # fail over operation - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, self.rep_target['backend_id']) + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, self.rep_target['backend_id'], []) self.assertEqual(self.rep_target['backend_id'], target_id) self.assertEqual(failover_expect, volume_list) self.assertTrue(update_storwize_state.called) @@ -7489,8 +7491,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase): {'updates': {'status': 'error', 'replication_driver_data': rep_data4}, 'volume_id': gm_vol['id']}] - target_id, volume_list = self.driver.failover_host( - self.ctxt, volumes, 'default') + target_id, volume_list, __ = self.driver.failover_host( + self.ctxt, volumes, 'default', []) self.assertEqual('default', target_id) self.assertEqual(failback_expect, volume_list) self.assertIsNone(self.driver._active_backend_id) diff --git a/cinder/tests/unit/volume/drivers/ibm/test_xiv_proxy.py b/cinder/tests/unit/volume/drivers/ibm/test_xiv_proxy.py index 8319aa02930..ac0242ab4c3 100644 --- a/cinder/tests/unit/volume/drivers/ibm/test_xiv_proxy.py +++ b/cinder/tests/unit/volume/drivers/ibm/test_xiv_proxy.py @@ -542,7 +542,7 @@ class XIVProxyTest(test.TestCase): volume = {'id': 'WTF64', 'size': 16, 'name': 'WTF32', 'volume_type_id': 'WTF'} target = REPLICA_ID - p.failover_host({}, [volume], target) + p.failover_host({}, [volume], target, []) def test_failover_host_invalid_target(self): """Test failover_host with invalid target""" @@ -559,7 +559,7 @@ class XIVProxyTest(test.TestCase): 'name': 'WTF32', 'volume_type_id': 'WTF'} target = 'Invalid' ex = getattr(p, "_get_exception")() - self.assertRaises(ex, p.failover_host, {}, [volume], target) + self.assertRaises(ex, p.failover_host, {}, [volume], target, []) @mock.patch("cinder.volume.drivers.ibm.ibm_storage." "xiv_proxy.client.XCLIClient") @@ -585,7 +585,7 @@ class XIVProxyTest(test.TestCase): 'name': 'WTF32', 'volume_type_id': 'WTF'} target = REPLICA_ID ex = getattr(p, "_get_exception")() - self.assertRaises(ex, p.failover_host, {}, [volume], target) + self.assertRaises(ex, p.failover_host, {}, [volume], target, []) @mock.patch("cinder.volume.drivers.ibm.ibm_storage." "xiv_proxy.client.XCLIClient") @@ -606,7 +606,7 @@ class XIVProxyTest(test.TestCase): volume = {'id': 'WTF64', 'size': 16, 'name': 'WTF32', 'volume_type_id': 'WTF'} target = 'default' - p.failover_host(None, [volume], target) + p.failover_host(None, [volume], target, []) def qos_test_empty_name_if_no_specs(self): """Test empty name in case no specs are specified""" diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py index ae922395960..cde8e005077 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py @@ -710,8 +710,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase): return_value=fake_utils.SSC.keys()) self.mock_object(self.library, '_update_zapi_client') - actual_active, vol_updates = self.library.failover_host( - 'fake_context', [], secondary_id='dev1') + actual_active, vol_updates, __ = self.library.failover_host( + 'fake_context', [], secondary_id='dev1', groups=[]) data_motion.DataMotionMixin._complete_failover.assert_called_once_with( 'dev0', ['dev1', 'dev2'], fake_utils.SSC.keys(), [], 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 f238e95f767..0a50a36cf98 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 @@ -1409,8 +1409,8 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): return_value=fake_ssc.SSC.keys()) self.mock_object(self.driver, '_update_zapi_client') - actual_active, vol_updates = self.driver.failover_host( - 'fake_context', [], secondary_id='dev1') + actual_active, vol_updates, __ = self.driver.failover_host( + 'fake_context', [], secondary_id='dev1', groups=[]) data_motion.DataMotionMixin._complete_failover.assert_called_once_with( 'dev0', ['dev1', 'dev2'], fake_ssc.SSC.keys(), [], diff --git a/cinder/tests/unit/volume/drivers/test_kaminario.py b/cinder/tests/unit/volume/drivers/test_kaminario.py index 360f36048a1..c20c98f7311 100644 --- a/cinder/tests/unit/volume/drivers/test_kaminario.py +++ b/cinder/tests/unit/volume/drivers/test_kaminario.py @@ -400,14 +400,16 @@ class TestKaminarioISCSI(test.TestCase): self.driver.target = FakeKrest() self.driver.target.search().total = 1 self.driver.client.search().total = 1 - backend_ip, res_volumes = self.driver.failover_host(None, volumes) + backend_ip, res_volumes, __ = self.driver.failover_host( + None, volumes, []) self.assertEqual('10.0.0.1', backend_ip) status = res_volumes[0]['updates']['replication_status'] self.assertEqual(fields.ReplicationStatus.FAILED_OVER, status) # different backend ip self.driver.configuration.san_ip = '10.0.0.2' self.driver.client.search().hits[0].state = 'in_sync' - backend_ip, res_volumes = self.driver.failover_host(None, volumes) + backend_ip, res_volumes, __ = self.driver.failover_host( + None, volumes, []) self.assertEqual('10.0.0.2', backend_ip) status = res_volumes[0]['updates']['replication_status'] self.assertEqual(fields.ReplicationStatus.DISABLED, status) diff --git a/cinder/tests/unit/volume/drivers/test_pure.py b/cinder/tests/unit/volume/drivers/test_pure.py index f5619096a4b..bc894158157 100644 --- a/cinder/tests/unit/volume/drivers/test_pure.py +++ b/cinder/tests/unit/volume/drivers/test_pure.py @@ -2111,10 +2111,11 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase): array2_v1_3.get_volume.return_value = REPLICATED_VOLUME_SNAPS context = mock.MagicMock() - new_active_id, volume_updates = self.driver.failover_host( + new_active_id, volume_updates, __ = self.driver.failover_host( context, REPLICATED_VOLUME_OBJS, - None + None, + [] ) self.assertEqual(secondary_device_id, new_active_id) diff --git a/cinder/tests/unit/volume/drivers/test_rbd.py b/cinder/tests/unit/volume/drivers/test_rbd.py index c69f414274e..580201a2a37 100644 --- a/cinder/tests/unit/volume/drivers/test_rbd.py +++ b/cinder/tests/unit/volume/drivers/test_rbd.py @@ -1555,7 +1555,7 @@ class RBDTestCase(test.TestCase): self.driver._is_replication_enabled = False self.assertRaises(exception.UnableToFailOver, self.driver.failover_host, - self.context, [self.volume_a]) + self.context, [self.volume_a], []) @ddt.data(None, 'tertiary-backend') @common_mocks @@ -1572,9 +1572,10 @@ class RBDTestCase(test.TestCase): remote = self.driver._replication_targets[1 if secondary_id else 0] mock_get_cfg.return_value = (remote['name'], remote) - res = self.driver.failover_host(self.context, volumes, secondary_id) + res = self.driver.failover_host(self.context, volumes, secondary_id, + []) - self.assertEqual((remote['name'], volumes), res) + self.assertEqual((remote['name'], volumes, []), res) self.assertEqual(remote, self.driver._active_config) mock_failover_vol.assert_has_calls( [mock.call(mock.ANY, v, remote, False, @@ -1593,9 +1594,9 @@ class RBDTestCase(test.TestCase): remote = self.driver._get_target_config('default') volumes = [self.volume_a, self.volume_b] - res = self.driver.failover_host(self.context, volumes, 'default') + res = self.driver.failover_host(self.context, volumes, 'default', []) - self.assertEqual(('default', volumes), res) + self.assertEqual(('default', volumes, []), res) self.assertEqual(remote, self.driver._active_config) mock_failover_vol.assert_has_calls( [mock.call(mock.ANY, v, remote, False, @@ -1613,7 +1614,7 @@ class RBDTestCase(test.TestCase): volumes = [self.volume_a, self.volume_b] self.assertRaises(exception.InvalidReplicationTarget, self.driver.failover_host, - self.context, volumes, None) + self.context, volumes, None, []) def test_failover_volume_non_replicated(self): self.volume_a.replication_status = fields.ReplicationStatus.DISABLED diff --git a/cinder/tests/unit/volume/test_driver.py b/cinder/tests/unit/volume/test_driver.py index 9925d21edc2..b4bfe8f012f 100644 --- a/cinder/tests/unit/volume/test_driver.py +++ b/cinder/tests/unit/volume/test_driver.py @@ -141,11 +141,13 @@ class DriverTestCase(test.TestCase): with mock.patch.object(my_driver, 'failover_host') as failover_mock: res = my_driver.failover(mock.sentinel.context, mock.sentinel.volumes, - secondary_id=mock.sentinel.secondary_id) + secondary_id=mock.sentinel.secondary_id, + groups=[]) self.assertEqual(failover_mock.return_value, res) failover_mock.assert_called_once_with(mock.sentinel.context, mock.sentinel.volumes, - mock.sentinel.secondary_id) + mock.sentinel.secondary_id, + []) class BaseDriverTestCase(test.TestCase): diff --git a/cinder/tests/unit/volume/test_replication_manager.py b/cinder/tests/unit/volume/test_replication_manager.py index a30a96aee8c..e3f95b3f5be 100644 --- a/cinder/tests/unit/volume/test_replication_manager.py +++ b/cinder/tests/unit/volume/test_replication_manager.py @@ -26,6 +26,7 @@ from cinder.common import constants from cinder import exception from cinder import objects from cinder.objects import fields +from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_service from cinder.tests.unit import utils from cinder.tests.unit import volume as base @@ -74,7 +75,8 @@ class ReplicationTestCase(base.BaseVolumeTestCase): filters={'host': self.host}) mock_failover.assert_called_once_with(self.context, [], - secondary_id=new_backend) + secondary_id=new_backend, + groups=[]) db_svc = objects.Service.get_by_id(self.context, svc.id) self.assertEqual(expected, db_svc.replication_status) @@ -269,14 +271,14 @@ class ReplicationTestCase(base.BaseVolumeTestCase): called, not_called = not_called, called called.return_value = ('secondary', [{'volume_id': vol.id, - 'updates': {'status': 'error'}}]) + 'updates': {'status': 'error'}}], []) self.volume.failover(self.context, secondary_backend_id='secondary') not_called.assert_not_called() called.assert_called_once_with(self.context, [vol], - secondary_id='secondary') + secondary_id='secondary', groups=[]) expected_update = {'replication_status': rep_field.FAILED_OVER, 'active_backend_id': 'secondary', @@ -456,6 +458,8 @@ class ReplicationTestCase(base.BaseVolumeTestCase): def _test_failover_model_updates(self, in_volumes, in_snapshots, driver_volumes, driver_result, out_volumes, out_snapshots, + in_groups=None, out_groups=None, + driver_group_result=None, secondary_id=None): host = vol_utils.extract_host(self.manager.host) utils.create_service(self.context, {'host': host, @@ -466,9 +470,13 @@ class ReplicationTestCase(base.BaseVolumeTestCase): for snapshot in in_snapshots: utils.create_snapshot(self.context, **snapshot) + for group in in_groups: + utils.create_group(self.context, self.manager.host, **group) + with mock.patch.object( self.manager.driver, 'failover_host', - return_value=(secondary_id, driver_result)) as driver_mock: + return_value=(secondary_id, driver_result, + driver_group_result)) as driver_mock: self.manager.failover_host(self.context, secondary_id) self.assertSetEqual(driver_volumes, @@ -476,27 +484,56 @@ class ReplicationTestCase(base.BaseVolumeTestCase): self._check_failover_db(objects.VolumeList, out_volumes) self._check_failover_db(objects.SnapshotList, out_snapshots) + self._check_failover_db(objects.GroupList, out_groups) - def test_failover_host_model_updates(self): + @mock.patch('cinder.volume.utils.is_group_a_type') + def test_failover_host_model_updates(self, mock_group_type): status = fields.ReplicationStatus - # IDs will be overwritten with UUIDs, but they help follow the code - in_volumes = [ - {'id': 0, 'status': 'available', - 'replication_status': status.DISABLED}, - {'id': 1, 'status': 'in-use', - 'replication_status': status.NOT_CAPABLE}, - {'id': 2, 'status': 'available', + mock_group_type.return_value = True + in_groups = [ + {'id': str(uuid.uuid4()), 'status': 'available', + 'group_type_id': fake.GROUP_TYPE_ID, + 'volume_type_ids': [fake.VOLUME_TYPE_ID], 'replication_status': status.FAILOVER_ERROR}, - {'id': 3, 'status': 'in-use', - 'replication_status': status.ENABLED}, - {'id': 4, 'status': 'available', - 'replication_status': status.FAILOVER_ERROR}, - {'id': 5, 'status': 'in-use', + {'id': str(uuid.uuid4()), 'status': 'available', + 'group_type_id': fake.GROUP_TYPE_ID, + 'volume_type_ids': [fake.VOLUME_TYPE_ID], + 'replication_status': status.ENABLED}, + ] + driver_group_result = [ + {'group_id': in_groups[0]['id'], + 'updates': {'replication_status': status.FAILOVER_ERROR}}, + {'group_id': in_groups[1]['id'], + 'updates': {'replication_status': status.FAILED_OVER}}, + ] + out_groups = [ + {'id': in_groups[0]['id'], 'status': 'error', + 'replication_status': status.FAILOVER_ERROR}, + {'id': in_groups[1]['id'], 'status': in_groups[1]['status'], + 'replication_status': status.FAILED_OVER}, + ] + + # test volumes + in_volumes = [ + {'id': str(uuid.uuid4()), 'status': 'available', + 'replication_status': status.DISABLED}, + {'id': str(uuid.uuid4()), 'status': 'in-use', + 'replication_status': status.NOT_CAPABLE}, + {'id': str(uuid.uuid4()), 'status': 'available', + 'replication_status': status.FAILOVER_ERROR}, + {'id': str(uuid.uuid4()), 'status': 'in-use', + 'replication_status': status.ENABLED}, + {'id': str(uuid.uuid4()), 'status': 'available', + 'replication_status': status.FAILOVER_ERROR}, + {'id': str(uuid.uuid4()), 'status': 'in-use', + 'replication_status': status.ENABLED}, + {'id': str(uuid.uuid4()), 'status': 'available', + 'group_id': in_groups[0]['id'], + 'replication_status': status.FAILOVER_ERROR}, + {'id': str(uuid.uuid4()), 'status': 'available', + 'group_id': in_groups[1]['id'], 'replication_status': status.ENABLED}, ] - # Generate real volume IDs - for volume in in_volumes: - volume['id'] = str(uuid.uuid4()) in_snapshots = [ {'id': v['id'], 'volume_id': v['id'], 'status': 'available'} for v in in_volumes @@ -512,6 +549,10 @@ class ReplicationTestCase(base.BaseVolumeTestCase): 'updates': {'replication_status': status.FAILOVER_ERROR}}, {'volume_id': in_volumes[5]['id'], 'updates': {'replication_status': status.FAILED_OVER}}, + {'volume_id': in_volumes[6]['id'], + 'updates': {'replication_status': status.FAILOVER_ERROR}}, + {'volume_id': in_volumes[7]['id'], + 'updates': {'replication_status': status.FAILED_OVER}}, ] out_volumes = [ {'id': in_volumes[0]['id'], 'status': 'error', @@ -530,15 +571,23 @@ class ReplicationTestCase(base.BaseVolumeTestCase): 'replication_status': status.FAILOVER_ERROR}, {'id': in_volumes[5]['id'], 'status': in_volumes[5]['status'], 'replication_status': status.FAILED_OVER}, + {'id': in_volumes[6]['id'], 'status': 'error', + 'previous_status': in_volumes[6]['status'], + 'replication_status': status.FAILOVER_ERROR}, + {'id': in_volumes[7]['id'], 'status': in_volumes[7]['status'], + 'replication_status': status.FAILED_OVER}, ] out_snapshots = [ {'id': ov['id'], 'status': 'error' if ov['status'] == 'error' else 'available'} for ov in out_volumes ] + self._test_failover_model_updates(in_volumes, in_snapshots, driver_volumes, driver_result, - out_volumes, out_snapshots) + out_volumes, out_snapshots, + in_groups, out_groups, + driver_group_result) def test_failback_host_model_updates(self): status = fields.ReplicationStatus @@ -612,4 +661,5 @@ class ReplicationTestCase(base.BaseVolumeTestCase): self._test_failover_model_updates(in_volumes, in_snapshots, driver_volumes, driver_result, out_volumes, out_snapshots, + [], [], [], self.manager.FAILBACK_SENTINEL) diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index 01a21c8608c..720657dce69 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -1458,7 +1458,7 @@ class BaseVD(object): """ return True - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover a backend to a secondary replication target. Instructs a replication capable/configured backend to failover @@ -1481,8 +1481,9 @@ class BaseVD(object): :param volumes: list of volume objects, in case the driver needs to take action on them in some way :param secondary_id: Specifies rep target backend to fail over to - :returns: ID of the backend that was failed-over to - and model update for volumes + :param groups: replication groups + :returns: ID of the backend that was failed-over to, + model update for volumes, and model update for groups """ # Example volume_updates data structure: @@ -1490,15 +1491,18 @@ class BaseVD(object): # 'updates': {'provider_id': 8, # 'replication_status': 'failed-over', # 'replication_extended_status': 'whatever',...}},] + # Example group_updates data structure: + # [{'group_id': , + # 'updates': {'replication_status': 'failed-over',...}},] raise NotImplementedError() - def failover(self, context, volumes, secondary_id=None): + def failover(self, context, volumes, secondary_id=None, groups=None): """Like failover but for a host that is clustered. Most of the time this will be the exact same behavior as failover_host, so if it's not overwritten, it is assumed to be the case. """ - return self.failover_host(context, volumes, secondary_id) + return self.failover_host(context, volumes, secondary_id, groups) def failover_completed(self, context, active_backend_id=None): """This method is called after failover for clustered backends.""" diff --git a/cinder/volume/drivers/dell_emc/sc/storagecenter_common.py b/cinder/volume/drivers/dell_emc/sc/storagecenter_common.py index 53d0e8a855f..36e53feb0e1 100644 --- a/cinder/volume/drivers/dell_emc/sc/storagecenter_common.py +++ b/cinder/volume/drivers/dell_emc/sc/storagecenter_common.py @@ -1784,7 +1784,7 @@ class SCCommonDriver(driver.ManageableVD, # Error and leave. return model_update - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover to secondary. :param context: security context @@ -1808,7 +1808,7 @@ class SCCommonDriver(driver.ManageableVD, if self.failed_over: if secondary_id == 'default': LOG.debug('failing back') - return 'default', self.failback_volumes(volumes) + return 'default', self.failback_volumes(volumes), [] raise exception.InvalidReplicationTarget( reason=_('Already failed over')) @@ -1851,7 +1851,7 @@ class SCCommonDriver(driver.ManageableVD, LOG.debug(self.failed_over) LOG.debug(self.active_backend_id) LOG.debug(self.replication_enabled) - return destssn, volume_updates + return destssn, volume_updates, [] else: raise exception.InvalidReplicationTarget(reason=( _('replication_failover failed. %s not found.') % diff --git a/cinder/volume/drivers/dell_emc/vnx/adapter.py b/cinder/volume/drivers/dell_emc/vnx/adapter.py index 6d72847c337..78faa7b3438 100644 --- a/cinder/volume/drivers/dell_emc/vnx/adapter.py +++ b/cinder/volume/drivers/dell_emc/vnx/adapter.py @@ -1210,7 +1210,8 @@ class CommonAdapter(object): raise exception.InvalidInput( reason='Invalid backend_id specified.') - def failover_host(self, context, volumes, secondary_backend_id): + def failover_host(self, context, volumes, secondary_backend_id, + groups=None): """Fails over the volume back and forth. Driver needs to update following info for failed-over volume: @@ -1269,7 +1270,7 @@ class CommonAdapter(object): # any sequential request will be redirected to it. self.client = mirror_view.secondary_client - return secondary_backend_id, volume_update_list + return secondary_backend_id, volume_update_list, [] def get_pool_name(self, volume): return self.client.get_pool_name(volume.name) diff --git a/cinder/volume/drivers/dell_emc/vnx/driver.py b/cinder/volume/drivers/dell_emc/vnx/driver.py index e14bcbd2cf4..df133f80f39 100644 --- a/cinder/volume/drivers/dell_emc/vnx/driver.py +++ b/cinder/volume/drivers/dell_emc/vnx/driver.py @@ -290,9 +290,10 @@ class VNXDriver(driver.ManageableVD, def backup_use_temp_snapshot(self): return True - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Fail-overs volumes from primary device to secondary.""" - return self.adapter.failover_host(context, volumes, secondary_id) + return self.adapter.failover_host(context, volumes, secondary_id, + groups) @utils.require_consistent_group_snapshot_enabled def create_group(self, context, group): diff --git a/cinder/volume/drivers/hpe/hpe_3par_fc.py b/cinder/volume/drivers/hpe/hpe_3par_fc.py index a4ffaa6c053..f9fdbde658b 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_fc.py +++ b/cinder/volume/drivers/hpe/hpe_3par_fc.py @@ -714,7 +714,7 @@ class HPE3PARFCDriver(driver.ManageableVD, self._logout(common) @utils.trace - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Force failover to a secondary replication target.""" common = self._login(timeout=30) try: @@ -722,6 +722,6 @@ class HPE3PARFCDriver(driver.ManageableVD, active_backend_id, volume_updates = common.failover_host( context, volumes, secondary_id) self._active_backend_id = active_backend_id - return active_backend_id, volume_updates + return active_backend_id, volume_updates, [] finally: self._logout(common) diff --git a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py index 872a4fcf8c4..ea73b75debb 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py @@ -984,7 +984,7 @@ class HPE3PARISCSIDriver(driver.ManageableVD, self._logout(common) @utils.trace - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Force failover to a secondary replication target.""" common = self._login(timeout=30) try: @@ -992,6 +992,6 @@ class HPE3PARISCSIDriver(driver.ManageableVD, active_backend_id, volume_updates = common.failover_host( context, volumes, secondary_id) self._active_backend_id = active_backend_id - return active_backend_id, volume_updates + return active_backend_id, volume_updates, [] finally: self._logout(common) diff --git a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py index c76ffd3ea04..75fed58df3d 100644 --- a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py @@ -1492,7 +1492,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): # v2 replication methods @cinder_utils.trace - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Force failover to a secondary replication target.""" if secondary_id and secondary_id == self.FAILBACK_VALUE: volume_update_list = self._replication_failback(volumes) @@ -1575,7 +1575,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): self._active_backend_id = target_id - return target_id, volume_update_list + return target_id, volume_update_list, [] def _do_replication_setup(self): default_san_ssh_port = self.configuration.hpelefthand_ssh_port diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index b04c0211d8f..09eb63d1e40 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -1838,7 +1838,7 @@ class HuaweiBaseDriver(driver.VolumeDriver): self.configuration) return secondary_id, volumes_update - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover all volumes to secondary.""" if secondary_id == 'default': secondary_id, volumes_update = self._failback(volumes) @@ -1850,7 +1850,7 @@ class HuaweiBaseDriver(driver.VolumeDriver): LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - return secondary_id, volumes_update + return secondary_id, volumes_update, [] def initialize_connection_snapshot(self, snapshot, connector, **kwargs): """Map a snapshot to a host and return target iSCSI information.""" diff --git a/cinder/volume/drivers/ibm/ibm_storage/ds8k_proxy.py b/cinder/volume/drivers/ibm/ibm_storage/ds8k_proxy.py index b21896d5f6d..94bb67ee742 100644 --- a/cinder/volume/drivers/ibm/ibm_storage/ds8k_proxy.py +++ b/cinder/volume/drivers/ibm/ibm_storage/ds8k_proxy.py @@ -1059,7 +1059,7 @@ class DS8KProxy(proxy.IBMStorageProxy): @proxy.logger @proxy._trace_time - def failover_host(self, ctxt, volumes, secondary_id): + def failover_host(self, ctxt, volumes, secondary_id, groups=None): """Fail over the volume back and forth. if secondary_id is 'default', volumes will be failed back, @@ -1070,12 +1070,12 @@ class DS8KProxy(proxy.IBMStorageProxy): if not self._active_backend_id: LOG.info("Host has been failed back. doesn't need " "to fail back again.") - return self._active_backend_id, volume_update_list + return self._active_backend_id, volume_update_list, [] else: if self._active_backend_id: LOG.info("Host has been failed over to %s.", self._active_backend_id) - return self._active_backend_id, volume_update_list + return self._active_backend_id, volume_update_list, [] backend_id = self._replication._target_helper.backend['id'] if secondary_id is None: @@ -1134,4 +1134,4 @@ class DS8KProxy(proxy.IBMStorageProxy): self._switch_backend_connection(self._active_backend_id) self._active_backend_id = "" - return secondary_id, volume_update_list + return secondary_id, volume_update_list, [] diff --git a/cinder/volume/drivers/ibm/ibm_storage/ibm_storage.py b/cinder/volume/drivers/ibm/ibm_storage/ibm_storage.py index 89fb7422c1d..973ce91616f 100644 --- a/cinder/volume/drivers/ibm/ibm_storage/ibm_storage.py +++ b/cinder/volume/drivers/ibm/ibm_storage/ibm_storage.py @@ -217,11 +217,11 @@ class IBMStorageDriver(san.SanDriver, return self.proxy.thaw_backend(context) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover a backend to a secondary replication target. """ return self.proxy.failover_host( - context, volumes, secondary_id) + context, volumes, secondary_id, groups) def get_replication_status(self, context, volume): """Return replication status.""" diff --git a/cinder/volume/drivers/ibm/ibm_storage/xiv_proxy.py b/cinder/volume/drivers/ibm/ibm_storage/xiv_proxy.py index 88c06ba82b5..b44deda92b8 100644 --- a/cinder/volume/drivers/ibm/ibm_storage/xiv_proxy.py +++ b/cinder/volume/drivers/ibm/ibm_storage/xiv_proxy.py @@ -1280,7 +1280,7 @@ class XIVProxy(proxy.IBMStorageProxy): return False, msg @proxy._trace_time - def failover_host(self, context, volumes, secondary_id): + def failover_host(self, context, volumes, secondary_id, groups=None): """Failover a full backend. Fails over the volume back and forth, if secondary_id is 'default', @@ -1300,7 +1300,7 @@ class XIVProxy(proxy.IBMStorageProxy): if self._using_default_backend(): LOG.info("Host has been failed back. No need " "to fail back again.") - return self.active_backend_id, volume_update_list + return self.active_backend_id, volume_update_list, [] pool_slave = self.storage_info[storage.FLAG_KEYS['storage_pool']] pool_master = self._get_target_params( self.active_backend_id)['san_clustername'] @@ -1308,7 +1308,7 @@ class XIVProxy(proxy.IBMStorageProxy): else: if not self._using_default_backend(): LOG.info("Already failed over. No need to failover again.") - return self.active_backend_id, volume_update_list + return self.active_backend_id, volume_update_list, [] # case: need to select a target if secondary_id is None: secondary_id = self._get_target() @@ -1393,7 +1393,7 @@ class XIVProxy(proxy.IBMStorageProxy): # set active backend id to secondary id self.active_backend_id = secondary_id - return secondary_id, volume_update_list + return secondary_id, volume_update_list, [] @proxy._trace_time def retype(self, ctxt, volume, new_type, diff, host): diff --git a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py index 3c5df69543b..6dd1b6158be 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py +++ b/cinder/volume/drivers/ibm/storwize_svc/storwize_svc_common.py @@ -2835,7 +2835,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, LOG.debug("Exit: update volume copy status.") # #### V2.1 replication methods #### # - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): LOG.debug('enter: failover_host: secondary_id=%(id)s', {'id': secondary_id}) if not self._replica_enabled: @@ -2859,7 +2859,7 @@ class StorwizeSVCCommonDriver(san.SanDriver, LOG.debug('leave: failover_host: secondary_id=%(id)s', {'id': secondary_id}) - return secondary_id, volumes_update + return secondary_id, volumes_update, [] def _replication_failback(self, ctxt, volumes): """Fail back all the volume on the secondary backend.""" diff --git a/cinder/volume/drivers/kaminario/kaminario_common.py b/cinder/volume/drivers/kaminario/kaminario_common.py index 97b22a74117..95fb94661bf 100644 --- a/cinder/volume/drivers/kaminario/kaminario_common.py +++ b/cinder/volume/drivers/kaminario/kaminario_common.py @@ -357,7 +357,7 @@ class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver): "changed to failed_over ", rsession_name) @kaminario_logger - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover to replication target.""" volume_updates = [] back_end_ip = None @@ -508,7 +508,7 @@ class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver): volume_updates.append({'volume_id': v['id'], 'updates': {'status': 'error', }}) back_end_ip = self.replica.backend_id - return back_end_ip, volume_updates + return back_end_ip, volume_updates, [] @kaminario_logger def _create_volume_replica_user_snap(self, k2, sess): diff --git a/cinder/volume/drivers/netapp/dataontap/block_cmode.py b/cinder/volume/drivers/netapp/dataontap/block_cmode.py index f1f3dec1ea5..5b3fb18c6b5 100644 --- a/cinder/volume/drivers/netapp/dataontap/block_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/block_cmode.py @@ -453,7 +453,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary, self._mark_qos_policy_group_for_deletion(qos_policy_group_info) super(NetAppBlockStorageCmodeLibrary, self).unmanage(volume) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover a backend to a secondary replication target.""" return self._failover_host(volumes, secondary_id=secondary_id) diff --git a/cinder/volume/drivers/netapp/dataontap/fc_7mode.py b/cinder/volume/drivers/netapp/dataontap/fc_7mode.py index bc4c2e501e5..7480d3c9e0d 100644 --- a/cinder/volume/drivers/netapp/dataontap/fc_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/fc_7mode.py @@ -130,5 +130,5 @@ class NetApp7modeFibreChannelDriver(driver.BaseVD, group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots, source_cg=source_cg, source_vols=source_vols) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): raise NotImplementedError() diff --git a/cinder/volume/drivers/netapp/dataontap/fc_cmode.py b/cinder/volume/drivers/netapp/dataontap/fc_cmode.py index e710a6017ae..a69de769195 100644 --- a/cinder/volume/drivers/netapp/dataontap/fc_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/fc_cmode.py @@ -130,6 +130,6 @@ class NetAppCmodeFibreChannelDriver(driver.BaseVD, group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots, source_cg=source_cg, source_vols=source_vols) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): return self.library.failover_host( context, volumes, secondary_id=secondary_id) diff --git a/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py b/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py index aa05e179c93..ff6e42e9df7 100644 --- a/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py +++ b/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py @@ -127,5 +127,5 @@ class NetApp7modeISCSIDriver(driver.BaseVD, group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots, source_cg=source_cg, source_vols=source_vols) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): raise NotImplementedError() diff --git a/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py b/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py index 147a49dd981..13f6fb3abbf 100644 --- a/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py @@ -127,6 +127,6 @@ class NetAppCmodeISCSIDriver(driver.BaseVD, group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots, source_cg=source_cg, source_vols=source_vols) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): return self.library.failover_host( context, volumes, secondary_id=secondary_id) diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py index 50734bd9b60..0d0d0a3ea11 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py @@ -709,7 +709,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, super(NetAppCmodeNfsDriver, self).unmanage(volume) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover a backend to a secondary replication target.""" return self._failover_host(volumes, secondary_id=secondary_id) diff --git a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py index daa94fe4b5e..5aba6bf2750 100644 --- a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py +++ b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py @@ -603,7 +603,7 @@ class DataMotionMixin(object): return active_backend_name, volume_updates - def _failover_host(self, volumes, secondary_id=None): + def _failover_host(self, volumes, secondary_id=None, groups=None): if secondary_id == self.backend_name: msg = _("Cannot failover to the same host as the primary.") @@ -641,4 +641,4 @@ class DataMotionMixin(object): self.failed_over = True self.failed_over_backend_name = active_backend_name - return active_backend_name, volume_updates + return active_backend_name, volume_updates, [] diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index 859ee2c88d5..f13ed3ececc 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -1364,7 +1364,7 @@ class PureBaseVolumeDriver(san.SanDriver): "message: %s", err.text) @pure_driver_debug_trace - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover backend to a secondary array This action will not affect the original volumes in any @@ -1377,7 +1377,7 @@ class PureBaseVolumeDriver(san.SanDriver): # our current array back to the primary. if self._failed_over_primary_array: self._set_current_array(self._failed_over_primary_array) - return secondary_id, [] + return secondary_id, [], [] else: msg = _('Unable to failback to "default", this can only be ' 'done after a failover has completed.') @@ -1455,7 +1455,7 @@ class PureBaseVolumeDriver(san.SanDriver): # secondary array we just failed over to. self._failed_over_primary_array = self._get_current_array() self._set_current_array(secondary_array) - return secondary_array._backend_id, model_updates + return secondary_array._backend_id, model_updates, [] def _does_pgroup_exist(self, array, pgroup_name): """Return True/False""" diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py index e29bbde8bb1..ef702f027dc 100644 --- a/cinder/volume/drivers/rbd.py +++ b/cinder/volume/drivers/rbd.py @@ -997,7 +997,7 @@ class RBDDriver(driver.CloneableImageVD, secondary_id = candidates.pop() return secondary_id, self._get_target_config(secondary_id) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover to replication target.""" LOG.info('RBD driver failover started.') if not self._is_replication_enabled: @@ -1020,7 +1020,7 @@ class RBDDriver(driver.CloneableImageVD, self._active_backend_id = secondary_id self._active_config = remote LOG.info('RBD driver failover completed.') - return secondary_id, updates + return secondary_id, updates, [] def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" diff --git a/cinder/volume/drivers/solidfire.py b/cinder/volume/drivers/solidfire.py index f755522728a..d985f21475a 100644 --- a/cinder/volume/drivers/solidfire.py +++ b/cinder/volume/drivers/solidfire.py @@ -2036,7 +2036,7 @@ class SolidFireDriver(san.SanISCSIDriver): self._issue_api_request('ModifyVolume', params, endpoint=remote['endpoint']) - def failover_host(self, context, volumes, secondary_id=None): + def failover_host(self, context, volumes, secondary_id=None, groups=None): """Failover to replication target.""" volume_updates = [] remote = None @@ -2100,7 +2100,7 @@ class SolidFireDriver(san.SanISCSIDriver): # but for now that's going to be the trade off of using replciation self.active_cluster_info = remote self.failed_over = True - return remote['mvip'], volume_updates + return remote['mvip'], volume_updates, [] def freeze_backend(self, context): """Freeze backend notification.""" diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index 2f5132d79c8..2b05b982c2c 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -2383,9 +2383,9 @@ class VolumeManager(manager.CleanableManager, # and update db if volume_stats.get('replication_status') == ( fields.ReplicationStatus.ERROR): - backend = vol_utils.extract_host(self.host, 'backend') - groups = vol_utils.get_replication_groups_by_host( - context, backend) + filters = self._get_cluster_or_host_filters() + groups = objects.GroupList.get_all_replicated( + context, filters=filters) group_model_updates, volume_model_updates = ( self.driver.get_replication_error_status(context, groups)) @@ -2811,11 +2811,15 @@ class VolumeManager(manager.CleanableManager, return vol_ref - def _get_my_resources(self, ctxt, ovo_class_list): + def _get_cluster_or_host_filters(self): if self.cluster: filters = {'cluster_name': self.cluster} else: filters = {'host': self.host} + return filters + + def _get_my_resources(self, ctxt, ovo_class_list): + filters = self._get_cluster_or_host_filters() return getattr(ovo_class_list, 'get_all')(ctxt, filters=filters) def _get_my_volumes(self, ctxt): @@ -3961,6 +3965,7 @@ class VolumeManager(manager.CleanableManager, snapshot.save() volume_update_list = None + group_update_list = None try: # For non clustered we can call v2.1 failover_host, but for # clustered we call a/a failover method. We know a/a method @@ -3971,17 +3976,30 @@ class VolumeManager(manager.CleanableManager, # expected form of volume_update_list: # [{volume_id: , updates: {'provider_id': xxxx....}}, # {volume_id: , updates: {'provider_id': xxxx....}}] - - active_backend_id, volume_update_list = failover( - context, - replicated_vols, - secondary_id=secondary_backend_id) + # It includes volumes in replication groups and those not in them + # expected form of group_update_list: + # [{group_id: , updates: {'xxxx': xxxx....}}, + # {group_id: , updates: {'xxxx': xxxx....}}] + filters = self._get_cluster_or_host_filters() + groups = objects.GroupList.get_all_replicated(context, + filters=filters) + active_backend_id, volume_update_list, group_update_list = ( + failover(context, + replicated_vols, + secondary_id=secondary_backend_id, + groups=groups)) try: update_data = {u['volume_id']: u['updates'] for u in volume_update_list} except KeyError: msg = "Update list, doesn't include volume_id" raise exception.ProgrammingError(reason=msg) + try: + update_group_data = {g['group_id']: g['updates'] + for g in group_update_list} + except KeyError: + msg = "Update list, doesn't include group_id" + raise exception.ProgrammingError(reason=msg) except Exception as exc: # NOTE(jdg): Drivers need to be aware if they fail during # a failover sequence, we're expecting them to cleanup @@ -4046,6 +4064,19 @@ class VolumeManager(manager.CleanableManager, volume.update(update) volume.save() + for grp in groups: + update = update_group_data.get(grp.id, {}) + if update.get('status', '') == 'error': + update['replication_status'] = repl_status.FAILOVER_ERROR + elif update.get('replication_status') in (None, + repl_status.FAILED_OVER): + update['replication_status'] = updates['replication_status'] + + if update['replication_status'] == repl_status.FAILOVER_ERROR: + update.setdefault('status', 'error') + grp.update(update) + grp.save() + LOG.info("Failed over to replication target successfully.") # TODO(geguileo): In P - remove this diff --git a/cinder/volume/utils.py b/cinder/volume/utils.py index 96f1a43b9d4..3f556a5c1b3 100644 --- a/cinder/volume/utils.py +++ b/cinder/volume/utils.py @@ -935,29 +935,3 @@ def is_group_a_type(group, key): ) return spec == " True" return False - - -def is_group_a_non_consistent_replication_group_type(group): - return is_group_a_type(group, "group_replication_enabled") - - -def is_group_a_consistent_replication_group_type(group): - return is_group_a_type(group, "consistent_group_replication_enabled") - - -def is_group_a_replication_group_type(group): - if (is_group_a_non_consistent_replication_group_type(group) or - is_group_a_consistent_replication_group_type(group)): - return True - return False - - -def get_replication_groups_by_host(ctxt, host): - groups = [] - filters = {'host': host, 'backend_match_level': 'backend'} - grps = objects.GroupList.get_all(ctxt, filters=filters) - for grp in grps: - if is_group_a_replication_group_type(grp): - groups.append(grp) - - return groups