From 1899d7f80b190474908d88b4251bbd12fc10e392 Mon Sep 17 00:00:00 2001 From: Alex O'Rourke Date: Mon, 20 Jul 2015 10:05:40 -0700 Subject: [PATCH] 3PAR: Adding Consistency Group Support Adds support for Consistency Groups to the HP 3PAR driver Implements: blueprint hp-3par-add-consistency-groups Change-Id: I31e74b55db0f9b813a9295378271715df4b839f4 --- cinder/tests/unit/test_hp3par.py | 508 ++++++++++++++++++ .../volume/drivers/san/hp/hp_3par_common.py | 185 ++++++- cinder/volume/drivers/san/hp/hp_3par_fc.py | 51 +- cinder/volume/drivers/san/hp/hp_3par_iscsi.py | 51 +- 4 files changed, 788 insertions(+), 7 deletions(-) diff --git a/cinder/tests/unit/test_hp3par.py b/cinder/tests/unit/test_hp3par.py index 83177c925..40451afe3 100644 --- a/cinder/tests/unit/test_hp3par.py +++ b/cinder/tests/unit/test_hp3par.py @@ -94,6 +94,10 @@ class HP3PARBaseDriver(object): SNAPSHOT_NAME = 'snapshot-2f823bdc-e36e-4dc8-bd15-de1c7a28ff31' VOLUME_3PAR_NAME = 'osv-0DM4qZEVSKON-DXN-NwVpw' SNAPSHOT_3PAR_NAME = 'oss-L4I73ONuTci9Fd4ceij-MQ' + CONSIS_GROUP_ID = '6044fedf-c889-4752-900f-2039d247a5df' + CONSIS_GROUP_NAME = 'vvs-YET.38iJR1KQDyA50kel3w' + CGSNAPSHOT_ID = 'e91c5ed5-daee-4e84-8724-1c9e31e7a1f2' + CGSNAPSHOT_BASE_NAME = 'oss-6Rxe1druToSHJByeMeeh8g' # fake host on the 3par FAKE_HOST = 'fakehost' FAKE_CINDER_HOST = 'fakehost@foo#' + HP3PAR_CPG @@ -264,6 +268,11 @@ class HP3PARBaseDriver(object): 'state': 1, 'uuid': '29c214aa-62b9-41c8-b198-543f6cf24edf'}] + cgsnapshot = {'consistencygroup_id': CONSIS_GROUP_ID, + 'description': 'cgsnapshot', + 'id': CGSNAPSHOT_ID, + 'readOnly': False} + TASK_DONE = 1 TASK_ACTIVE = 2 STATUS_DONE = {'status': 1} @@ -2947,6 +2956,505 @@ class HP3PARBaseDriver(object): safe_host = common._safe_hostname(long_hostname) self.assertEqual(fixed_hostname, safe_host) + @mock.patch('hp3parclient.version', "3.2.2") + def test_create_consistency_group(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + + comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_create_consistency_group_from_src(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + volume = self.volume + + cgsnap_optional = ( + {'comment': '{"consistency_group_id":' + ' "6044fedf-c889-4752-900f-2039d247a5df",' + ' "description": "cgsnapshot",' + ' "cgsnapshot_id": "e91c5ed5-daee-4e84-8724-1c9e31e7a1f2"}', + 'readOnly': False}) + + cg_comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=cg_comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # add a volume to the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[volume], + remove_volumes=[]) + + expected = [ + mock.call.addVolumeToVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # create a snapshot of the consistency group + self.driver.create_cgsnapshot(context.get_admin_context(), + self.cgsnapshot) + + expected = [ + mock.call.createSnapshotOfVolumeSet( + self.CGSNAPSHOT_BASE_NAME + "-@count@", + self.CONSIS_GROUP_NAME, + optional=cgsnap_optional)] + + # create a consistency group from the cgsnapshot + self.driver.create_consistencygroup_from_src( + context.get_admin_context(), group, + [volume], cgsnapshot=self.cgsnapshot, + snapshots=[self.snapshot]) + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_delete_consistency_group(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + + comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # remove the consistency group + group.status = 'deleting' + self.driver.delete_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.deleteVolumeSet( + self.CONSIS_GROUP_NAME)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_update_consistency_group_add_vol(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + volume = self.volume + + comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # add a volume to the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[volume], + remove_volumes=[]) + + expected = [ + mock.call.addVolumeToVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_update_consistency_group_remove_vol(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + volume = self.volume + + comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # add a volume to the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[volume], + remove_volumes=[]) + + expected = [ + mock.call.addVolumeToVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # remove the volume from the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[], + remove_volumes=[volume]) + + expected = [ + mock.call.removeVolumeFromVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_create_cgsnapshot(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + volume = self.volume + + cg_comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + cgsnap_optional = ( + {'comment': '{"consistency_group_id":' + ' "6044fedf-c889-4752-900f-2039d247a5df",' + ' "description": "cgsnapshot",' + ' "cgsnapshot_id": "e91c5ed5-daee-4e84-8724-1c9e31e7a1f2"}', + 'readOnly': False}) + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=cg_comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # add a volume to the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[volume], + remove_volumes=[]) + + expected = [ + mock.call.addVolumeToVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # create a snapshot of the consistency group + self.driver.create_cgsnapshot(context.get_admin_context(), + self.cgsnapshot) + + expected = [ + mock.call.createSnapshotOfVolumeSet( + self.CGSNAPSHOT_BASE_NAME + "-@count@", + self.CONSIS_GROUP_NAME, + optional=cgsnap_optional)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + + @mock.patch('hp3parclient.version', "3.2.2") + def test_delete_cgsnapshot(self): + class fake_consitencygroup_object(object): + volume_type_id = '49fa96b5-828e-4653-b622-873a1b7e6f1c' + name = 'cg_name' + cgsnapshot_id = None + host = self.FAKE_CINDER_HOST + id = self.CONSIS_GROUP_ID + description = 'consistency group' + + mock_client = self.setup_driver() + volume = self.volume + cgsnapshot = self.cgsnapshot + + cg_comment = ( + "{'display_name': 'cg_name'," + " 'consistency_group_id':" + " '" + self.CONSIS_GROUP_ID + "'," + " 'description': 'consistency group'}") + + cgsnap_optional = ( + {'comment': '{"consistency_group_id":' + ' "6044fedf-c889-4752-900f-2039d247a5df",' + ' "description": "cgsnapshot",' + ' "cgsnapshot_id": "e91c5ed5-daee-4e84-8724-1c9e31e7a1f2"}', + 'readOnly': False}) + + with mock.patch.object(hpcommon.HP3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + mock_client.getCPG.return_value = {'domain': None} + + # create a consistency group + group = fake_consitencygroup_object() + self.driver.create_consistencygroup(context.get_admin_context(), + group) + + expected = [ + mock.call.getCPG(HP3PAR_CPG), + mock.call.createVolumeSet( + self.CONSIS_GROUP_NAME, + domain=None, + comment=cg_comment)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # add a volume to the consistency group + self.driver.update_consistencygroup(context.get_admin_context(), + group, + add_volumes=[volume], + remove_volumes=[]) + + expected = [ + mock.call.addVolumeToVolumeSet( + self.CONSIS_GROUP_NAME, + self.VOLUME_NAME_3PAR)] + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + mock_client.reset_mock() + + # create a snapshot of the consistency group + self.driver.create_cgsnapshot(context.get_admin_context(), + cgsnapshot) + + expected = [ + mock.call.createSnapshotOfVolumeSet( + self.CGSNAPSHOT_BASE_NAME + "-@count@", + self.CONSIS_GROUP_NAME, + optional=cgsnap_optional)] + + # delete the snapshot of the consistency group + cgsnapshot['status'] = 'deleting' + self.driver.delete_cgsnapshot(context.get_admin_context(), + cgsnapshot) + + mock_client.assert_has_calls( + [mock.call.getWsApiVersion()] + + self.standard_login + + expected + + self.standard_logout) + class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): diff --git a/cinder/volume/drivers/san/hp/hp_3par_common.py b/cinder/volume/drivers/san/hp/hp_3par_common.py index 04b8e257d..ea31a735d 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -61,6 +61,7 @@ from cinder import context from cinder import exception from cinder import flow_utils from cinder.i18n import _, _LE, _LI, _LW +from cinder import objects from cinder.volume import qos_specs from cinder.volume import utils as volume_utils from cinder.volume import volume_types @@ -72,6 +73,7 @@ LOG = logging.getLogger(__name__) MIN_CLIENT_VERSION = '3.1.2' GETCPGSTATDATA_VERSION = '3.2.2' +MIN_CG_CLIENT_VERSION = '3.2.2' DEDUP_API_VERSION = 30201120 FLASH_CACHE_API_VERSION = 30201200 SRSTATLD_API_VERSION = 30201200 @@ -198,10 +200,11 @@ class HP3PARCommon(object): 2.0.48 - Adding changes to support 3PAR iSCSI multipath. 2.0.49 - Added client CPG stats to driver volume stats. bug #1482741 2.0.50 - Add over subscription support + 2.0.51 - Adds consistency group support """ - VERSION = "2.0.50" + VERSION = "2.0.51" stats = {} @@ -242,6 +245,7 @@ class HP3PARCommon(object): self.config = config self.client = None self.uuid = uuid.uuid4() + self.db = importutils.import_module('cinder.db') def get_version(self): return self.VERSION @@ -373,6 +377,167 @@ class HP3PARCommon(object): growth_size_mib = growth_size * units.Ki self._extend_volume(volume, volume_name, growth_size_mib) + def create_consistencygroup(self, context, group): + """Creates a consistencygroup.""" + + pool = volume_utils.extract_host(group.host, level='pool') + domain = self.get_domain(pool) + cg_name = self._get_3par_vvs_name(group.id) + + extra = {'consistency_group_id': group.id} + extra['description'] = group.description + extra['display_name'] = group.name + if group.cgsnapshot_id: + extra['cgsnapshot_id'] = group.cgsnapshot_id + + self.client.createVolumeSet(cg_name, domain=domain, + comment=six.text_type(extra)) + + model_update = {'status': 'available'} + return model_update + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None, + source_cg=None, source_vols=None): + + if cgsnapshot and snapshots: + self.create_consistencygroup(context, group) + vvs_name = self._get_3par_vvs_name(group.id) + cgsnap_name = self._get_3par_snap_name(cgsnapshot['id']) + for i, (volume, snapshot) in enumerate(zip(volumes, snapshots)): + snap_name = cgsnap_name + "-" + six.text_type(i) + volume_name = self._get_3par_vol_name(volume['id']) + type_info = self.get_volume_settings_from_type(volume) + cpg = type_info['cpg'] + optional = {'online': True, 'snapCPG': cpg} + self.client.copyVolume(snap_name, volume_name, cpg, optional) + self.client.addVolumeToVolumeSet(vvs_name, volume_name) + else: + msg = _("create_consistencygroup_from_src only supports a" + " cgsnapshot source, other sources cannot be used.") + raise exception.InvalidInput(reason=msg) + + return None, None + + def delete_consistencygroup(self, context, group): + """Deletes a consistency group.""" + + try: + cg_name = self._get_3par_vvs_name(group.id) + self.client.deleteVolumeSet(cg_name) + except hpexceptions.HTTPNotFound: + err = (_LW("Virtual Volume Set '%s' doesn't exist on array.") % + cg_name) + LOG.warning(err) + except hpexceptions.HTTPConflict as e: + err = (_LE("Conflict detected in Virtual Volume Set" + " %(volume_set): %(error)"), + {"volume_set": cg_name, + "error": e}) + LOG.error(err) + + volumes = self.db.volume_get_all_by_group(context, group.id) + for volume in volumes: + self.delete_volume(volume) + volume.status = 'deleted' + + model_update = {'status': group.status} + + return model_update, volumes + + def update_consistencygroup(self, context, group, + add_volumes=None, remove_volumes=None): + + volume_set_name = self._get_3par_vvs_name(group.id) + + for volume in add_volumes: + volume_name = self._get_3par_vol_name(volume['id']) + try: + self.client.addVolumeToVolumeSet(volume_set_name, volume_name) + except hpexceptions.HTTPNotFound: + msg = (_LE('Virtual Volume Set %s does not exist.') % + volume_set_name) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + for volume in remove_volumes: + volume_name = self._get_3par_vol_name(volume['id']) + try: + self.client.removeVolumeFromVolumeSet( + volume_set_name, volume_name) + except hpexceptions.HTTPNotFound: + msg = (_LE('Virtual Volume Set %s does not exist.') % + volume_set_name) + LOG.error(msg) + raise exception.InvalidInput(reason=msg) + + return None, None, None + + def create_cgsnapshot(self, context, cgsnapshot): + """Creates a cgsnapshot.""" + + cg_id = cgsnapshot['consistencygroup_id'] + snap_shot_name = self._get_3par_snap_name(cgsnapshot['id']) + ( + "-@count@") + copy_of_name = self._get_3par_vvs_name(cg_id) + + extra = {'cgsnapshot_id': cgsnapshot['id']} + extra['consistency_group_id'] = cg_id + extra['description'] = cgsnapshot['description'] + + optional = {'comment': json.dumps(extra), + 'readOnly': False} + if self.config.hp3par_snapshot_expiration: + optional['expirationHours'] = ( + int(self.config.hp3par_snapshot_expiration)) + + if self.config.hp3par_snapshot_retention: + optional['retentionHours'] = ( + int(self.config.hp3par_snapshot_retention)) + + self.client.createSnapshotOfVolumeSet(snap_shot_name, copy_of_name, + optional=optional) + + snapshots = objects.SnapshotList().get_all_for_cgsnapshot( + context, cgsnapshot['id']) + + for snapshot in snapshots: + snapshot.status = 'available' + + model_update = {'status': 'available'} + + return model_update, snapshots + + def delete_cgsnapshot(self, context, cgsnapshot): + """Deletes a cgsnapshot.""" + + cgsnap_name = self._get_3par_snap_name(cgsnapshot['id']) + + snapshots = objects.SnapshotList().get_all_for_cgsnapshot( + context, cgsnapshot['id']) + + for i, snapshot in enumerate(snapshots): + try: + snap_name = cgsnap_name + "-" + six.text_type(i) + self.client.deleteVolume(snap_name) + except hpexceptions.HTTPForbidden as ex: + LOG.error(_LE("Exception: %s."), ex) + raise exception.NotAuthorized() + except hpexceptions.HTTPNotFound as ex: + # We'll let this act as if it worked + # it helps clean up the cinder entries. + LOG.warning(_LW("Delete Snapshot id not found. Removing from " + "cinder: %(id)s Ex: %(msg)s"), + {'id': snapshot['id'], 'msg': ex}) + except hpexceptions.HTTPConflict as ex: + LOG.error(_LE("Exception: %s."), ex) + raise exception.SnapshotIsBusy(snapshot_name=snapshot['id']) + snapshot['status'] = 'deleted' + + model_update = {'status': cgsnapshot['status']} + + return model_update, snapshots + def manage_existing(self, volume, existing_ref): """Manage an existing 3PAR volume. @@ -817,6 +982,9 @@ class HP3PARCommon(object): 'multiattach': True, } + if hp3parclient.version >= MIN_CG_CLIENT_VERSION: + pool['consistencygroup_support'] = True + pools.append(pool) self.stats = {'driver_version': '1.0', @@ -1191,7 +1359,6 @@ class HP3PARCommon(object): cpg = pool self.validate_cpg(cpg) - # Look to see if the snap_cpg was specified in volume type # extra spec, if not use hp3par_cpg_snap from config as the # default. @@ -1292,6 +1459,10 @@ class HP3PARCommon(object): tdvv = type_info['tdvv'] flash_cache = self.get_flash_cache_policy(type_info['hp3par_keys']) + cg_id = volume.get('consistencygroup_id', None) + if cg_id: + vvs_name = self._get_3par_vvs_name(cg_id) + type_id = volume.get('volume_type_id', None) if type_id is not None: comments['volume_type_name'] = volume_type.get('name') @@ -1494,7 +1665,8 @@ class HP3PARCommon(object): LOG.error(_LE("Exception: %s"), ex) raise exception.CinderException(ex) - def create_volume_from_snapshot(self, volume, snapshot): + def create_volume_from_snapshot(self, volume, snapshot, snap_name=None, + vvs_name=None): """Creates a volume from a snapshot. """ @@ -1510,7 +1682,8 @@ class HP3PARCommon(object): raise exception.InvalidInput(reason=err) try: - snap_name = self._get_3par_snap_name(snapshot['id']) + if not snap_name: + snap_name = self._get_3par_snap_name(snapshot['id']) volume_name = self._get_3par_vol_name(volume['id']) extra = {'volume_id': volume['id'], @@ -1518,8 +1691,10 @@ class HP3PARCommon(object): type_id = volume.get('volume_type_id', None) - hp3par_keys, qos, _volume_type, vvs_name = self.get_type_info( + hp3par_keys, qos, _volume_type, vvs = self.get_type_info( type_id) + if vvs: + vvs_name = vvs name = volume.get('display_name', None) if name: diff --git a/cinder/volume/drivers/san/hp/hp_3par_fc.py b/cinder/volume/drivers/san/hp/hp_3par_fc.py index fc1e7ae35..6d9f8cf39 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_fc.py +++ b/cinder/volume/drivers/san/hp/hp_3par_fc.py @@ -80,10 +80,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): 2.0.16 - Added encrypted property to initialize_connection #1439917 2.0.17 - Improved VLUN creation and deletion logic. #1469816 2.0.18 - Changed initialize_connection to use getHostVLUNs. #1475064 + 2.0.19 - Adds consistency group support """ - VERSION = "2.0.18" + VERSION = "2.0.19" def __init__(self, *args, **kwargs): super(HP3PARFCDriver, self).__init__(*args, **kwargs) @@ -427,6 +428,54 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver): finally: self._logout(common) + def create_consistencygroup(self, context, group): + common = self._login() + try: + return common.create_consistencygroup(context, group) + finally: + self._logout(common) + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None, + source_cg=None, source_vols=None): + common = self._login() + try: + return common.create_consistencygroup_from_src( + context, group, volumes, cgsnapshot, snapshots, source_cg, + source_vols) + finally: + self._logout(common) + + def delete_consistencygroup(self, context, group): + common = self._login() + try: + return common.delete_consistencygroup(context, group) + finally: + self._logout(common) + + def update_consistencygroup(self, context, group, + add_volumes=None, remove_volumes=None): + common = self._login() + try: + return common.update_consistencygroup(context, group, add_volumes, + remove_volumes) + finally: + self._logout(common) + + def create_cgsnapshot(self, context, cgsnapshot): + common = self._login() + try: + return common.create_cgsnapshot(context, cgsnapshot) + finally: + self._logout(common) + + def delete_cgsnapshot(self, context, cgsnapshot): + common = self._login() + try: + return common.delete_cgsnapshot(context, cgsnapshot) + finally: + self._logout(common) + def manage_existing(self, volume, existing_ref): common = self._login() try: diff --git a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py index 9c53da42c..65ed73763 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_iscsi.py +++ b/cinder/volume/drivers/san/hp/hp_3par_iscsi.py @@ -90,10 +90,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): 2.0.18 - Improved VLUN creation and deletion logic. #1469816 2.0.19 - Changed initialize_connection to use getHostVLUNs. #1475064 2.0.20 - Adding changes to support 3PAR iSCSI multipath. + 2.0.21 - Adds consistency group support """ - VERSION = "2.0.20" + VERSION = "2.0.21" def __init__(self, *args, **kwargs): super(HP3PARISCSIDriver, self).__init__(*args, **kwargs) @@ -724,6 +725,54 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver): finally: self._logout(common) + def create_consistencygroup(self, context, group): + common = self._login() + try: + common.create_consistencygroup(context, group) + finally: + self._logout(common) + + def create_consistencygroup_from_src(self, context, group, volumes, + cgsnapshot=None, snapshots=None, + source_cg=None, source_vols=None): + common = self._login() + try: + return common.create_consistencygroup_from_src( + context, group, volumes, cgsnapshot, snapshots, source_cg, + source_vols) + finally: + self._logout(common) + + def delete_consistencygroup(self, context, group): + common = self._login() + try: + return common.delete_consistencygroup(context, group) + finally: + self._logout(common) + + def update_consistencygroup(self, context, group, + add_volumes=None, remove_volumes=None): + common = self._login() + try: + return common.update_consistencygroup(context, group, add_volumes, + remove_volumes) + finally: + self._logout(common) + + def create_cgsnapshot(self, context, cgsnapshot): + common = self._login() + try: + return common.create_cgsnapshot(context, cgsnapshot) + finally: + self._logout(common) + + def delete_cgsnapshot(self, context, cgsnapshot): + common = self._login() + try: + return common.delete_cgsnapshot(context, cgsnapshot) + finally: + self._logout(common) + def manage_existing(self, volume, existing_ref): common = self._login() try: -- 2.45.2