diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py b/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py index 92ac401baa3..a6f7ee4cd2b 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpelefthand.py @@ -182,18 +182,18 @@ class HPELeftHandBaseDriver(object): class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): CONSIS_GROUP_ID = '3470cc4c-63b3-4c7a-8120-8a0693b45838' - CGSNAPSHOT_ID = '5351d914-6c90-43e7-9a8e-7e84610927da' + GROUPSNAPSHOT_ID = '5351d914-6c90-43e7-9a8e-7e84610927da' - class fake_consistencygroup_object(object): - volume_type_id = '371c64d5-b92a-488c-bc14-1e63cef40e08' - name = 'cg_name' - cgsnapshot_id = None + class fake_group_object(object): + volume_type_ids = '371c64d5-b92a-488c-bc14-1e63cef40e08' + name = 'group_name' + groupsnapshot_id = None id = '3470cc4c-63b3-4c7a-8120-8a0693b45838' - description = 'consistency group' + description = 'group' - class fake_cgsnapshot_object(object): - consistencygroup_id = '3470cc4c-63b3-4c7a-8120-8a0693b45838' - description = 'cgsnapshot' + class fake_groupsnapshot_object(object): + group_id = '3470cc4c-63b3-4c7a-8120-8a0693b45838' + description = 'groupsnapshot' id = '5351d914-6c90-43e7-9a8e-7e84610927da' readOnly = False @@ -2638,8 +2638,15 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): mock_client.assert_has_calls(expected) - def test_create_consistencygroup(self): + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_group(self, cg_ss_enabled, mock_get_volume_type): + cg_ss_enabled.side_effect = [False, True, True] ctxt = context.get_admin_context() + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2647,15 +2654,39 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) + # create a group object + group = self.fake_group_object() - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group with consistent_group_snapshot_enabled flag to + # False + self.assertRaises(NotImplementedError, + self.driver.create_group, ctxt, group) - def test_delete_consistencygroup(self): + # create a group with consistent_group_snapshot_enabled flag to + # True + model_update = self.driver.create_group(ctxt, group) + + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) + + # create a group with replication enabled on volume type + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' True'}} + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.ERROR, + model_update['status']) + + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_delete_group(self, cg_ss_enabled, mock_get_volume_type): + cg_ss_enabled.return_value = True ctxt = context.get_admin_context() + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2666,21 +2697,29 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group + group = self.fake_group_object() + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) - # delete the consistency group - group.status = fields.ConsistencyGroupStatus.DELETING - cg, vols = self.driver.delete_consistencygroup(ctxt, group, - volumes) - self.assertEqual(fields.ConsistencyGroupStatus.DELETING, - cg['status']) + # delete the group + group.status = fields.GroupStatus.DELETING + model_update, vols = self.driver.delete_group(ctxt, group, + volumes) + self.assertEqual(fields.GroupStatus.DELETING, + model_update['status']) - def test_update_consistencygroup_add_vol_delete_cg(self): + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_update_group_add_vol_delete_group(self, cg_ss_enabled, + mock_get_volume_type): + cg_ss_enabled.return_value = True ctxt = context.get_admin_context() + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2698,26 +2737,33 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group + group = self.fake_group_object() + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) - # add volume to consistency group - cg = self.driver.update_consistencygroup( + # add volume to group + model_update = self.driver.update_group( ctxt, group, add_volumes=[self.volume], remove_volumes=None) - # delete the consistency group - group.status = fields.ConsistencyGroupStatus.DELETING - cg, vols = self.driver.delete_consistencygroup(ctxt, group, - volumes) - self.assertEqual(fields.ConsistencyGroupStatus.DELETING, - cg['status']) + # delete the group + group.status = fields.GroupStatus.DELETING + model_update, vols = self.driver.delete_group(ctxt, group, + volumes) + self.assertEqual(fields.GroupStatus.DELETING, + model_update['status']) - def test_update_consistencygroup_remove_vol_delete_cg(self): + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_update_group_remove_vol_delete_group(self, cg_ss_enabled, + mock_get_volume_type): + cg_ss_enabled.return_value = True ctxt = context.get_admin_context() - + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2734,30 +2780,36 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group + group = self.fake_group_object() + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) - # add volume to consistency group - cg = self.driver.update_consistencygroup( + # add volume to group + model_update = self.driver.update_group( ctxt, group, add_volumes=[self.volume], remove_volumes=None) - # remove volume from consistency group - cg = self.driver.update_consistencygroup( + # remove volume from group + model_update = self.driver.update_group( ctxt, group, add_volumes=None, remove_volumes=[self.volume]) - # delete the consistency group - group.status = fields.ConsistencyGroupStatus.DELETING - cg, vols = self.driver.delete_consistencygroup(ctxt, group, - volumes) - self.assertEqual(fields.ConsistencyGroupStatus.DELETING, - cg['status']) + # delete the group + group.status = fields.GroupStatus.DELETING + model_update, vols = self.driver.delete_group(ctxt, group, + volumes) + self.assertEqual(fields.GroupStatus.DELETING, + model_update['status']) - def test_create_cgsnapshot(self): + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_create_groupsnapshot(self, cg_ss_enabled, mock_get_volume_type): + cg_ss_enabled.return_value = True ctxt = context.get_admin_context() - + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2772,21 +2824,21 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group + group = self.fake_group_object() + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) - # create volume and add it to the consistency group - self.driver.update_consistencygroup( + # create volume and add it to the group + self.driver.update_group( ctxt, group, add_volumes=[self.volume], remove_volumes=None) - # create the conistency group snapshot - cgsnapshot = self.fake_cgsnapshot_object() - cgsnap, snaps = self.driver.create_cgsnapshot( - ctxt, cgsnapshot, expected_snaps) - self.assertEqual('available', cgsnap['status']) + # create the group snapshot + groupsnapshot = self.fake_groupsnapshot_object() + madel_update, snaps = self.driver.create_group_snapshot( + ctxt, groupsnapshot, expected_snaps) + self.assertEqual('available', madel_update['status']) # mock HTTPServerError (array failure) mock_client.createSnapshotSet.side_effect = ( @@ -2794,8 +2846,8 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): # ensure the raised exception is a cinder exception self.assertRaises( exception.VolumeBackendAPIException, - self.driver.create_cgsnapshot, - ctxt, cgsnapshot, expected_snaps) + self.driver.create_group_snapshot, + ctxt, groupsnapshot, expected_snaps) # mock HTTPServerError (array failure) mock_client.getVolumeByName.side_effect = ( @@ -2803,12 +2855,18 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): # ensure the raised exception is a cinder exception self.assertRaises( exception.VolumeBackendAPIException, - self.driver.create_cgsnapshot, - ctxt, cgsnapshot, expected_snaps) + self.driver.create_group_snapshot, + ctxt, groupsnapshot, expected_snaps) - def test_delete_cgsnapshot(self): + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type') + def test_delete_groupsnapshot(self, cg_ss_enabled, mock_get_volume_type): + cg_ss_enabled.return_value = True ctxt = context.get_admin_context() - + mock_get_volume_type.return_value = { + 'name': 'gold', + 'extra_specs': { + 'replication_enabled': ' False'}} # set up driver with default config mock_client = self.setup_driver() @@ -2823,22 +2881,22 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): '_create_client') as mock_do_setup: mock_do_setup.return_value = mock_client - # create a consistency group - group = self.fake_consistencygroup_object() - cg = self.driver.create_consistencygroup(ctxt, group) - self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, - cg['status']) + # create a group + group = self.fake_group_object() + model_update = self.driver.create_group(ctxt, group) + self.assertEqual(fields.GroupStatus.AVAILABLE, + model_update['status']) - # create volume and add it to the consistency group - self.driver.update_consistencygroup( + # create volume and add it to the group + self.driver.update_group( ctxt, group, add_volumes=[self.volume], remove_volumes=None) - # delete the consistency group snapshot - cgsnapshot = self.fake_cgsnapshot_object() - cgsnapshot.status = 'deleting' - cgsnap, snaps = self.driver.delete_cgsnapshot( - ctxt, cgsnapshot, expected_snaps) - self.assertEqual('deleting', cgsnap['status']) + # delete the group snapshot + groupsnapshot = self.fake_groupsnapshot_object() + groupsnapshot.status = 'deleting' + model_update, snaps = self.driver.delete_group_snapshot( + ctxt, groupsnapshot, expected_snaps) + self.assertEqual('deleting', model_update['status']) # mock HTTPServerError ex = hpeexceptions.HTTPServerError({ @@ -2847,16 +2905,16 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase): ' duh.'}) mock_client.getSnapshotByName.side_effect = ex # ensure the raised exception is a cinder exception - cgsnap, snaps = self.driver.delete_cgsnapshot( - ctxt, cgsnapshot, expected_snaps) + cgsnap, snaps = self.driver.delete_group_snapshot( + ctxt, groupsnapshot, expected_snaps) self.assertEqual('error', snaps[0]['status']) # mock HTTP other errors ex = hpeexceptions.HTTPConflict({'message': 'Some message.'}) mock_client.getSnapshotByName.side_effect = ex # ensure the raised exception is a cinder exception - cgsnap, snaps = self.driver.delete_cgsnapshot( - ctxt, cgsnapshot, expected_snaps) + cgsnap, snaps = self.driver.delete_group_snapshot( + ctxt, groupsnapshot, expected_snaps) self.assertEqual('error', snaps[0]['status']) @mock.patch.object(volume_types, 'get_volume_type') diff --git a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py index 0ba62a8dd5f..35f4993c162 100644 --- a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py @@ -161,9 +161,10 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): 2.0.9 - Fix terminate connection on failover 2.0.10 - Add entry point tracing 2.0.11 - Fix extend volume if larger than snapshot bug #1560654 + 2.0.12 - add CG capability to generic volume groups. """ - VERSION = "2.0.11" + VERSION = "2.0.12" CI_WIKI_NAME = "HPE_Storage_CI" @@ -469,24 +470,41 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): self._logout(client) @cinder_utils.trace - def create_consistencygroup(self, context, group): - """Creates a consistencygroup.""" - model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} - return model_update + def create_group(self, context, group): + """Creates a group.""" + LOG.debug("Creating group.") + if not utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + for vol_type_id in group.volume_type_ids: + replication_type = self._volume_of_replicated_type( + None, vol_type_id) + if replication_type: + # An unsupported configuration + LOG.error('Unable to create group: create group with ' + 'replication volume type is not supported.') + model_update = {'status': fields.GroupStatus.ERROR} + return model_update + + return {'status': fields.GroupStatus.AVAILABLE} @cinder_utils.trace - def create_consistencygroup_from_src(self, context, group, volumes, - cgsnapshot=None, snapshots=None, - source_cg=None, source_vols=None): - """Creates a consistency group from a source""" - msg = _("Creating a consistency group from a source is not " - "currently supported.") - LOG.error(msg) - raise NotImplementedError(msg) + def create_group_from_src(self, context, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + """Creates a group from a source""" + msg = _("Creating a group from a source is not " + "supported when consistent_group_snapshot_enabled to true.") + if not utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + else: + raise exception.VolumeBackendAPIException(data=msg) @cinder_utils.trace - def delete_consistencygroup(self, context, group, volumes): - """Deletes a consistency group.""" + def delete_group(self, context, group, volumes): + """Deletes a group.""" + if not utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + volume_model_updates = [] for volume in volumes: volume_update = {'id': volume.id} @@ -506,25 +524,31 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): return model_update, volume_model_updates @cinder_utils.trace - def update_consistencygroup(self, context, group, - add_volumes=None, remove_volumes=None): - """Updates a consistency group. + def update_group(self, context, group, add_volumes=None, + remove_volumes=None): + """Updates a group. Because the backend has no concept of volume grouping, cinder will - maintain all volume/consistency group relationships. Because of this + maintain all volume/group relationships. Because of this functionality, there is no need to make any client calls; instead simply returning out of this function allows cinder to properly - add/remove volumes from the consistency group. + add/remove volumes from the group. """ + LOG.debug("Updating group.") + if not utils.is_group_a_cg_snapshot_type(group): + raise NotImplementedError() + return None, None, None @cinder_utils.trace - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Creates a consistency group snapshot.""" + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Creates a group snapshot.""" + if not utils.is_group_a_cg_snapshot_type(group_snapshot): + raise NotImplementedError() client = self._login() try: snap_set = [] - snapshot_base_name = "snapshot-" + cgsnapshot.id + snapshot_base_name = "snapshot-" + group_snapshot.id snapshot_model_updates = [] for i, snapshot in enumerate(snapshots): volume = snapshot.volume @@ -551,7 +575,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): source_volume_id = snap_set[0]['volumeId'] optional = {'inheritAccess': True} - description = cgsnapshot.description + description = group_snapshot.description if description: optional['description'] = description @@ -574,11 +598,12 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): return model_update, snapshot_model_updates @cinder_utils.trace - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Deletes a consistency group snapshot.""" - + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Deletes a group snapshot.""" + if not utils.is_group_a_cg_snapshot_type(group_snapshot): + raise NotImplementedError() client = self._login() - snap_name_base = "snapshot-" + cgsnapshot.id + snap_name_base = "snapshot-" + group_snapshot.id snapshot_model_updates = [] for i, snapshot in enumerate(snapshots): @@ -605,7 +630,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): self._logout(client) - model_update = {'status': cgsnapshot.status} + model_update = {'status': group_snapshot.status} return model_update, snapshot_model_updates @@ -705,7 +730,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): data['total_volumes'] = total_volumes data['filter_function'] = self.get_filter_function() data['goodness_function'] = self.get_goodness_function() - data['consistencygroup_support'] = True + data['consistent_group_snapshot_enabled'] = True data['replication_enabled'] = self._replication_enabled data['replication_type'] = ['periodic'] data['replication_count'] = len(self._replication_targets) @@ -1747,9 +1772,12 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver): rep_flag = False return rep_flag - def _volume_of_replicated_type(self, volume): + def _volume_of_replicated_type(self, volume, vol_type_id=None): + # TODO(kushal) : we will use volume.volume_types when we re-write + # the design for unit tests to use objects instead of dicts. replicated_type = False - volume_type_id = volume.get('volume_type_id') + volume_type_id = vol_type_id if vol_type_id else volume.get( + 'volume_type_id') if volume_type_id: volume_type = self._get_volume_type(volume_type_id) diff --git a/releasenotes/notes/Lefthand-generic-volume-group-570d07b4786b93c2.yaml b/releasenotes/notes/Lefthand-generic-volume-group-570d07b4786b93c2.yaml new file mode 100644 index 00000000000..dfb4e1fa837 --- /dev/null +++ b/releasenotes/notes/Lefthand-generic-volume-group-570d07b4786b93c2.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add consistent group capability to generic volume groups in Lefthand + driver.