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 f71a9a81693..b3f0ebdca84 100644 --- a/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py +++ b/cinder/tests/unit/volume/drivers/huawei/test_huawei_drivers.py @@ -25,9 +25,10 @@ from xml.dom import minidom from cinder import context from cinder import exception +from cinder.objects import fields from cinder import test -from cinder.tests.unit.consistencygroup import fake_cgsnapshot -from cinder.tests.unit.consistencygroup import fake_consistencygroup +from cinder.tests.unit import fake_group +from cinder.tests.unit import fake_group_snapshot from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_volume from cinder.tests.unit import utils @@ -42,6 +43,7 @@ from cinder.volume.drivers.huawei import replication from cinder.volume.drivers.huawei import rest_client from cinder.volume.drivers.huawei import smartx from cinder.volume import qos_specs +from cinder.volume import utils as volume_utils from cinder.volume import volume_types admin_contex = context.get_admin_context() @@ -382,7 +384,7 @@ FAKE_POOLS_SUPPORT_REPORT = { 'max_over_subscription_ratio': 20.0, 'luncopy': True, 'hypermetro': True, - 'consistencygroup_support': True + 'consistent_group_snapshot_enabled': True } FAKE_LUN_GET_SUCCESS_RESPONSE = """ @@ -2020,6 +2022,15 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate/portgroup?TYPE=245&ASSOC' REPLICA_BACKEND_ID = 'huawei-replica-1' +def cg_or_cg_snapshot(func): + def wrapper(self, *args, **kwargs): + self.mock_object(volume_utils, + 'is_group_a_cg_snapshot_type', + return_value=True) + return func(self, *args, **kwargs) + return wrapper + + class FakeHuaweiConf(huawei_conf.HuaweiConf): def __init__(self, conf, protocol): self.conf = conf @@ -2241,10 +2252,10 @@ class HuaweiTestBase(test.TestCase): admin_contex, id=ID, provider_location=PROVIDER_LOCATION, name_id=ID) - self.cgsnapshot = fake_cgsnapshot.fake_cgsnapshot_obj( - admin_contex, id=ID, consistencygroup_id=ID, status='available') + self.group_snapshot = fake_group_snapshot.fake_group_snapshot_obj( + admin_contex, id=ID, group_id=ID, status='available') - self.cg = fake_consistencygroup.fake_consistencyobject_obj( + self.group = fake_group.fake_group_obj( admin_contex, id=ID, status='available') def test_encode_name(self): @@ -4286,49 +4297,52 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase): iqn = self.driver.client._get_tgt_iqn_from_rest(ip) self.assertIsNone(iqn) - def test_create_cgsnapshot(self): + @cg_or_cg_snapshot + def test_create_group_snapshot(self): test_snapshots = [self.snapshot] ctxt = context.get_admin_context() - model, snapshots = self.driver.create_cgsnapshot(ctxt, - self.cgsnapshot, - test_snapshots) + model, snapshots = ( + self.driver.create_group_snapshot(ctxt, self.group_snapshot, + test_snapshots)) snapshots_model_update = [{'id': '21ec7341-9256-497b-97d9' '-ef48edcf0635', 'status': 'available', 'provider_location': 11}] self.assertEqual(snapshots_model_update, snapshots) - self.assertEqual('available', model['status']) + self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE, model['status']) - def test_create_cgsnapshot_create_snapshot_fail(self): + @cg_or_cg_snapshot + def test_create_group_snapshot_with_create_snapshot_fail(self): test_snapshots = [self.snapshot] ctxt = context.get_admin_context() self.mock_object(rest_client.RestClient, 'create_snapshot', side_effect=( exception.VolumeBackendAPIException(data='err'))) self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_cgsnapshot, + self.driver.create_group_snapshot, ctxt, - self.cgsnapshot, + self.group_snapshot, test_snapshots) - def test_create_cgsnapshot_active_snapshot_fail(self): + @cg_or_cg_snapshot + def test_create_group_snapshot_with_active_snapshot_fail(self): test_snapshots = [self.snapshot] ctxt = context.get_admin_context() self.mock_object(rest_client.RestClient, 'activate_snapshot', side_effect=( exception.VolumeBackendAPIException(data='err'))) self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_cgsnapshot, + self.driver.create_group_snapshot, ctxt, - self.cgsnapshot, + self.group_snapshot, test_snapshots) - def test_delete_cgsnapshot(self): + @cg_or_cg_snapshot + def test_delete_group_snapshot(self): test_snapshots = [self.snapshot] ctxt = context.get_admin_context() - self.driver.delete_cgsnapshot(ctxt, - self.cgsnapshot, - test_snapshots) + self.driver.delete_group_snapshot(ctxt, self.group_snapshot, + test_snapshots) class FCSanLookupService(object): @@ -5109,95 +5123,81 @@ class HuaweiFCDriverTestCase(HuaweiTestBase): self.assertFalse(res) @mock.patch.object(huawei_driver.HuaweiBaseDriver, - '_get_consistencygroup_type', - return_value={"hypermetro": "true"}) - def test_create_hypermetro_consistencygroup_success(self, mock_grouptype): - """Test that create_consistencygroup return successfully.""" + '_get_group_type', + return_value=[{"hypermetro": "true"}]) + @cg_or_cg_snapshot + def test_create_hypermetro_group_success(self, mock_grouptype): + """Test that create_group return successfully.""" ctxt = context.get_admin_context() - # Create consistency group - model_update = self.driver.create_consistencygroup(ctxt, self.cg) + # Create group + model_update = self.driver.create_group(ctxt, self.group) - self.assertEqual('available', + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update['status'], - "Consistency Group created failed") + "Group created failed") @mock.patch.object(huawei_driver.HuaweiBaseDriver, - '_get_consistencygroup_type', - return_value={"hypermetro": "false"}) - def test_create_normal_consistencygroup_success(self, - mock_grouptype): - """Test that create_consistencygroup return successfully.""" + '_get_group_type', + return_value=[{"hypermetro": "false"}]) + @cg_or_cg_snapshot + def test_create_normal_group_success(self, mock_grouptype): + """Test that create_group return successfully.""" ctxt = context.get_admin_context() - # Create consistency group - model_update = self.driver.create_consistencygroup(ctxt, self.cg) + # Create group + model_update = self.driver.create_group(ctxt, self.group) - self.assertEqual('available', + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update['status'], - "Consistency Group created failed") + "Group created failed") @mock.patch.object(huawei_driver.HuaweiBaseDriver, - '_get_consistencygroup_type', - return_value={"hypermetro": "true"}) - def test_delete_hypermetro_consistencygroup_success(self, mock_grouptype): - """Test that create_consistencygroup return successfully.""" + '_get_group_type', + return_value=[{"hypermetro": "true"}]) + @cg_or_cg_snapshot + def test_delete_hypermetro_group_success(self, mock_grouptype): + """Test that delete_group return successfully.""" test_volumes = [self.volume] ctxt = context.get_admin_context() - # Create consistency group - model, volumes = self.driver.delete_consistencygroup(ctxt, - self.cg, - test_volumes) - self.assertEqual('available', + # Delete group + model, volumes = self.driver.delete_group(ctxt, self.group, + test_volumes) + self.assertEqual(fields.GroupStatus.DELETED, model['status'], - "Consistency Group created failed") - - def test_delete_normal_consistencygroup_success(self): - ctxt = context.get_admin_context() - test_volumes = [self.volume] - self.mock_object(huawei_driver.HuaweiBaseDriver, - '_get_consistencygroup_type', - return_value={"hypermetro": "false"}) - - model, volumes = self.driver.delete_consistencygroup(ctxt, - self.cg, - test_volumes) - self.assertEqual('available', - model['status'], - "Consistency Group created failed") + "Group deleted failed") @mock.patch.object(huawei_driver.HuaweiBaseDriver, - '_get_consistencygroup_type', - return_value={"hypermetro": "true"}) + '_get_group_type', + return_value=[{"hypermetro": "false"}]) + @cg_or_cg_snapshot + def test_delete_normal_group_success(self, mock_grouptype): + """Test that delete_group return successfully.""" + ctxt = context.get_admin_context() + test_volumes = [self.volume] + # Delete group + model, volumes = self.driver.delete_group(ctxt, self.group, + test_volumes) + self.assertEqual(fields.GroupStatus.DELETED, + model['status'], + "Group deleted failed") + + @mock.patch.object(huawei_driver.HuaweiBaseDriver, + '_get_group_type', + return_value=[{"hypermetro": "true"}]) @mock.patch.object(huawei_driver.huawei_utils, 'get_volume_metadata', return_value={'hypermetro_id': '3400a30d844d0007', 'remote_lun_id': '59'}) - def test_update_consistencygroup_success(self, - mock_grouptype, - mock_metadata): - """Test that create_consistencygroup return successfully.""" + @cg_or_cg_snapshot + def test_update_group_success(self, mock_grouptype, mock_metadata): + """Test that update_group return successfully.""" ctxt = context.get_admin_context() add_volumes = [self.volume] remove_volumes = [self.volume] - # Create consistency group - model_update = self.driver.update_consistencygroup(ctxt, - self.cg, - add_volumes, - remove_volumes) - self.assertEqual('available', + # Update group + model_update = self.driver.update_group(ctxt, self.group, + add_volumes, remove_volumes) + self.assertEqual(fields.GroupStatus.AVAILABLE, model_update[0]['status'], - "Consistency Group update failed") - - def test_create_hypermetro_consistencygroup_success_2(self): - ctxt = context.get_admin_context() - # Create consistency group - temp_cg = copy.deepcopy(self.cg) - temp_cg['volume_type_id'] = '550c089b-bfdd-4f7f-86e1-3ba88125555c,' - self.mock_object(volume_types, 'get_volume_type', - return_value=test_hypermetro_type) - model_update = self.driver.create_consistencygroup(ctxt, temp_cg) - - self.assertEqual('available', - model_update['status'], - "Consistency Group created failed") + "Group update failed") def test_is_initiator_associated_to_host_raise(self): self.assertRaises(exception.VolumeBackendAPIException, diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index f1f43fa6f91..73103503da5 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -29,6 +29,7 @@ from cinder import context from cinder import exception from cinder.i18n import _ from cinder import interface +from cinder.objects import fields from cinder import utils from cinder.volume import driver from cinder.volume.drivers.huawei import constants @@ -191,6 +192,7 @@ class HuaweiBaseDriver(driver.VolumeDriver): pool['thick_provisioning_support'] = True pool['thin_provisioning_support'] = True pool['smarttier'] = True + pool['consistent_group_snapshot_enabled'] = True if self.configuration.san_product == "Dorado": pool['smarttier'] = False @@ -198,8 +200,6 @@ class HuaweiBaseDriver(driver.VolumeDriver): if self.metro_flag: pool['hypermetro'] = self.check_func_support("HyperMetroPair") - pool['consistencygroup_support'] = ( - self.check_func_support("HyperMetro_ConsistentGroup")) # Asign the support function to global paramenter. self.support_func = pool @@ -224,17 +224,28 @@ class HuaweiBaseDriver(driver.VolumeDriver): opts = self._get_volume_params_from_specs(specs) return opts - def _get_consistencygroup_type(self, group): - specs = {} - opts = {} - type_id = group.volume_type_id.split(",") - if type_id[0] and len(type_id) == 2: - ctxt = context.get_admin_context() - volume_type = volume_types.get_volume_type(ctxt, type_id[0]) - specs = dict(volume_type).get('extra_specs') - opts = self._get_volume_params_from_specs(specs) + def _get_group_type(self, group): + opts = [] + vol_types = group.volume_types + + for vol_type in vol_types: + specs = vol_type.extra_specs + opts.append(self._get_volume_params_from_specs(specs)) + return opts + def _check_volume_type_support(self, opts, vol_type): + if not opts: + return False + + support = True + for opt in opts: + if opt.get(vol_type) != 'true': + support = False + break + + return support + def _get_volume_params_from_specs(self, specs): """Return the volume parameters from extra specs.""" opts_capability = { @@ -1599,24 +1610,26 @@ class HuaweiBaseDriver(driver.VolumeDriver): self.client.is_host_associated_to_hostgroup(host_id)): self.client.remove_host(host_id) - def create_consistencygroup(self, context, group): - """Creates a consistencygroup.""" - model_update = {'status': 'available'} - opts = self._get_consistencygroup_type(group) - if (opts.get('hypermetro') == 'true'): + @huawei_utils.check_whether_operate_consistency_group + def create_group(self, context, group): + """Creates a group.""" + model_update = {'status': fields.GroupStatus.AVAILABLE} + opts = self._get_group_type(group) + if self._check_volume_type_support(opts, 'hypermetro'): metro = hypermetro.HuaweiHyperMetro(self.client, self.rmt_client, self.configuration) metro.create_consistencygroup(group) return model_update - # Array will create CG at create_cgsnapshot time. Cinder will - # maintain the CG and volumes relationship in the db. + # Array will create group at create_group_snapshot time. Cinder will + # maintain the group and volumes relationship in the db. return model_update - def delete_consistencygroup(self, context, group, volumes): - opts = self._get_consistencygroup_type(group) - if opts.get('hypermetro') == 'true': + @huawei_utils.check_whether_operate_consistency_group + def delete_group(self, context, group, volumes): + opts = self._get_group_type(group) + if self._check_volume_type_support(opts, 'hypermetro'): metro = hypermetro.HuaweiHyperMetro(self.client, self.rmt_client, self.configuration) @@ -1624,7 +1637,7 @@ class HuaweiBaseDriver(driver.VolumeDriver): model_update = {} volumes_model_update = [] - model_update.update({'status': group.status}) + model_update.update({'status': fields.GroupStatus.DELETED}) for volume_ref in volumes: try: @@ -1637,12 +1650,12 @@ class HuaweiBaseDriver(driver.VolumeDriver): return model_update, volumes_model_update - def update_consistencygroup(self, context, group, - add_volumes, - remove_volumes): - model_update = {'status': 'available'} - opts = self._get_consistencygroup_type(group) - if opts.get('hypermetro') == 'true': + @huawei_utils.check_whether_operate_consistency_group + def update_group(self, context, group, + add_volumes=None, remove_volumes=None): + model_update = {'status': fields.GroupStatus.AVAILABLE} + opts = self._get_group_type(group) + if self._check_volume_type_support(opts, 'hypermetro'): metro = hypermetro.HuaweiHyperMetro(self.client, self.rmt_client, self.configuration) @@ -1651,15 +1664,23 @@ class HuaweiBaseDriver(driver.VolumeDriver): remove_volumes) return model_update, None, None - # Array will create CG at create_cgsnapshot time. Cinder will - # maintain the CG and volumes relationship in the db. + # Array will create group at create_group_snapshot time. Cinder will + # maintain the group and volumes relationship in the db. return model_update, None, None - def create_cgsnapshot(self, context, cgsnapshot, snapshots): - """Create cgsnapshot.""" - LOG.info('Create cgsnapshot for consistency group' - ': %(group_id)s', - {'group_id': cgsnapshot.consistencygroup_id}) + @huawei_utils.check_whether_operate_consistency_group + def create_group_from_src(self, context, group, volumes, + group_snapshot=None, snapshots=None, + source_group=None, source_vols=None): + err_msg = _("Huawei Storage doesn't support create_group_from_src.") + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + @huawei_utils.check_whether_operate_consistency_group + def create_group_snapshot(self, context, group_snapshot, snapshots): + """Create group snapshot.""" + LOG.info('Create group snapshot for group' + ': %(group_id)s', {'group_id': group_snapshot.group_id}) model_update = {} snapshots_model_update = [] @@ -1682,48 +1703,50 @@ class HuaweiBaseDriver(driver.VolumeDriver): info = self.client.create_snapshot(lun_id, snapshot_name, snapshot_description) - snapshot_model_update = {'id': snapshot.id, - 'status': 'available', - 'provider_location': info['ID']} - snapshots_model_update.append(snapshot_model_update) + snap_model_update = {'id': snapshot.id, + 'status': fields.SnapshotStatus.AVAILABLE, + 'provider_location': info['ID']} + snapshots_model_update.append(snap_model_update) added_snapshots_info.append(info) except Exception: with excutils.save_and_reraise_exception(): - LOG.error("Create cgsnapshots failed. " - "Cgsnapshot id: %s.", cgsnapshot.id) + LOG.error("Create group snapshots failed. " + "Group snapshot id: %s.", group_snapshot.id) snapshot_ids = [added_snapshot['ID'] for added_snapshot in added_snapshots_info] try: self.client.activate_snapshot(snapshot_ids) except Exception: with excutils.save_and_reraise_exception(): - LOG.error("Active cgsnapshots failed. " - "Cgsnapshot id: %s.", cgsnapshot.id) + LOG.error("Active group snapshots failed. " + "Group snapshot id: %s.", group_snapshot.id) - model_update['status'] = 'available' + model_update['status'] = fields.GroupSnapshotStatus.AVAILABLE return model_update, snapshots_model_update - def delete_cgsnapshot(self, context, cgsnapshot, snapshots): - """Delete consistency group snapshot.""" - LOG.info('Delete cgsnapshot %(snap_id)s for consistency group: ' + @huawei_utils.check_whether_operate_consistency_group + def delete_group_snapshot(self, context, group_snapshot, snapshots): + """Delete group snapshot.""" + LOG.info('Delete group snapshot %(snap_id)s for group: ' '%(group_id)s', - {'snap_id': cgsnapshot.id, - 'group_id': cgsnapshot.consistencygroup_id}) + {'snap_id': group_snapshot.id, + 'group_id': group_snapshot.group_id}) model_update = {} snapshots_model_update = [] - model_update['status'] = cgsnapshot.status + model_update['status'] = fields.GroupSnapshotStatus.DELETED for snapshot in snapshots: try: self.delete_snapshot(snapshot) - snapshots_model_update.append({'id': snapshot.id, - 'status': 'deleted'}) + snapshot_model = {'id': snapshot.id, + 'status': fields.SnapshotStatus.DELETED} + snapshots_model_update.append(snapshot_model) except Exception: with excutils.save_and_reraise_exception(): - LOG.error("Delete cg snapshots failed. " - "Cgsnapshot id: %s", cgsnapshot.id) + LOG.error("Delete group snapshot failed. " + "Group snapshot id: %s", group_snapshot.id) return model_update, snapshots_model_update diff --git a/cinder/volume/drivers/huawei/huawei_utils.py b/cinder/volume/drivers/huawei/huawei_utils.py index 7166a3112d8..012f1e7be13 100644 --- a/cinder/volume/drivers/huawei/huawei_utils.py +++ b/cinder/volume/drivers/huawei/huawei_utils.py @@ -24,6 +24,7 @@ from cinder import exception from cinder.i18n import _ from cinder import objects from cinder.volume.drivers.huawei import constants +from cinder.volume import utils LOG = logging.getLogger(__name__) @@ -112,3 +113,14 @@ def get_snapshot_metadata_value(snapshot): return {item['key']: item['value'] for item in metadata} return {} + + +def check_whether_operate_consistency_group(func): + def wrapper(self, context, group, *args, **kwargs): + if not utils.is_group_a_cg_snapshot_type(group): + msg = _("%s, the group or group snapshot is not cg or " + "cg_snapshot") % func.__name__ + LOG.debug(msg) + raise NotImplementedError(msg) + return func(self, context, group, *args, **kwargs) + return wrapper diff --git a/cinder/volume/drivers/huawei/hypermetro.py b/cinder/volume/drivers/huawei/hypermetro.py index 9608ef53d94..0ccefc84194 100644 --- a/cinder/volume/drivers/huawei/hypermetro.py +++ b/cinder/volume/drivers/huawei/hypermetro.py @@ -18,6 +18,7 @@ from oslo_log import log as logging from cinder import exception from cinder.i18n import _ +from cinder.objects import fields from cinder.volume.drivers.huawei import constants from cinder.volume.drivers.huawei import huawei_utils @@ -286,7 +287,7 @@ class HuaweiHyperMetro(object): {'group': group.id}) model_update = {} volumes_model_update = [] - model_update['status'] = group.status + model_update['status'] = fields.GroupStatus.DELETED metrogroup_id = self.check_consistencygroup_need_to_stop(group) if metrogroup_id: self.client.delete_metrogroup(metrogroup_id) @@ -304,8 +305,6 @@ class HuaweiHyperMetro(object): LOG.info("Update Consistency Group: %(group)s. " "This adds or removes volumes from a CG.", {'group': group.id}) - model_update = {} - model_update['status'] = group.status metrogroup_id = self.check_consistencygroup_need_to_stop(group) if metrogroup_id: # Deal with add volumes to CG diff --git a/releasenotes/notes/huawei-generic-group-bc3fb7236efc58e7.yaml b/releasenotes/notes/huawei-generic-group-bc3fb7236efc58e7.yaml new file mode 100644 index 00000000000..d9c7fc3a771 --- /dev/null +++ b/releasenotes/notes/huawei-generic-group-bc3fb7236efc58e7.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add CG capability to generic volume groups in Huawei driver.