]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR: Adding Consistency Group Support
authorAlex O'Rourke <alex.orourke@hp.com>
Mon, 20 Jul 2015 17:05:40 +0000 (10:05 -0700)
committerAlex O'Rourke <alex.orourke@hp.com>
Mon, 17 Aug 2015 20:46:16 +0000 (13:46 -0700)
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
cinder/volume/drivers/san/hp/hp_3par_common.py
cinder/volume/drivers/san/hp/hp_3par_fc.py
cinder/volume/drivers/san/hp/hp_3par_iscsi.py

index 83177c925551cc06cfba33650539e425cef5164e..40451afe386216b53bbfbc384948d0d4d4d4a6e2 100644 (file)
@@ -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):
 
index 04b8e257d3a789351f083f16b88bd37aec716202..ea31a735dc6421f7acbf7097d1dd1ede96b4b4d4 100644 (file)
@@ -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:
index fc1e7ae355e45be65d95fb0654f015e70f79daf4..6d9f8cf39d19f4d227292ad487ef8914233bae99 100644 (file)
@@ -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:
index 9c53da42cad03a7439833abc6efc213a75475ee8..65ed7376394aa831a27f465a809cbc915505ed5e 100644 (file)
@@ -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: