Tiramisu: Add groups param to failover_host

failover_host is the interface for Cheesecake.
Currently it passes volumes to the failover_host
interface in the driver. If a backend supports both
Cheesecase and Tiramisu, it makes sense for the driver
to failover a group instead of individual volumes if a
volume is in a replication group. So this patch passes
groups to the failover_host interface in the driver in
addition to volumes so driver can decide whether to
failover a replication group.

Change-Id: I9842eec1a50ffe65a9490e2ac0c00b468f18b30a
Partially-Implements: blueprint replication-cg
This commit is contained in:
xing-yang 2017-05-03 02:21:02 -04:00
parent 544d13ef0a
commit 32e67f3119
46 changed files with 376 additions and 227 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = '<is> 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('<is> 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 = '<is> 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(), [],

View File

@ -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(), [],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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': <cinder-uuid>,
# '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."""

View File

@ -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.') %

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, []

View File

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

View File

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

View File

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

View File

@ -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: <cinder-volid>, updates: {'provider_id': xxxx....}},
# {volume_id: <cinder-volid>, 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: <cinder-grpid>, updates: {'xxxx': xxxx....}},
# {group_id: <cinder-grpid>, 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

View File

@ -935,29 +935,3 @@ def is_group_a_type(group, key):
)
return spec == "<is> 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