]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR with pool-aware-cinder-scheduler
authorMark Sturdevant <mark.sturdevant@hp.com>
Mon, 9 Jun 2014 02:57:21 +0000 (10:57 +0800)
committerMark Sturdevant <mark.sturdevant@hp.com>
Fri, 10 Oct 2014 17:55:03 +0000 (10:55 -0700)
HP 3PAR support for the pool-aware scheduler.
Adds support for a list of CPGs (pools).  Uses
model update when our volume-type CPG is used
instead of the scheduler selected CPG.

In cinder.conf, hp3par_cpg now accepts a list of CPGs.

DocImpact
Implements: blueprint pool-aware-cinder-scheduler-hp3par
Change-Id: I74ef0af5390311eb1a529e0d5d4875c4f7d04156

cinder/tests/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
etc/cinder/cinder.conf.sample

index 8a0a70a95676298f0fa09fbb843e65df8f36f60b..a547bed3ba2f5baa28494494e12e178c0e17530f 100644 (file)
@@ -31,6 +31,7 @@ from cinder.volume.drivers.san.hp import hp_3par_common as hpcommon
 from cinder.volume.drivers.san.hp import hp_3par_fc as hpfcdriver
 from cinder.volume.drivers.san.hp import hp_3par_iscsi as hpdriver
 from cinder.volume import qos_specs
+from cinder.volume import utils as volume_utils
 from cinder.volume import volume_types
 
 hpexceptions = hp3parclient.hpexceptions
@@ -40,6 +41,8 @@ LOG = logging.getLogger(__name__)
 CONF = cfg.CONF
 
 HP3PAR_CPG = 'OpenStackCPG'
+HP3PAR_CPG2 = 'fakepool'
+HP3PAR_CPG_QOS = 'qospool'
 HP3PAR_CPG_SNAP = 'OpenStackCPGSnap'
 HP3PAR_USER_NAME = 'testUser'
 HP3PAR_USER_PASS = 'testPassword'
@@ -109,6 +112,14 @@ class HP3PARBaseDriver(object):
               'volume_type': None,
               'volume_type_id': None}
 
+    volume_pool = {'name': VOLUME_NAME,
+                   'id': VOLUME_ID,
+                   'display_name': 'Foo Volume',
+                   'size': 2,
+                   'host': volume_utils.append_host(FAKE_HOST, HP3PAR_CPG2),
+                   'volume_type': None,
+                   'volume_type_id': None}
+
     volume_qos = {'name': VOLUME_NAME,
                   'id': VOLUME_ID,
                   'display_name': 'Foo Volume',
@@ -140,7 +151,8 @@ class HP3PARBaseDriver(object):
     volume_type = {'name': 'gold',
                    'deleted': False,
                    'updated_at': None,
-                   'extra_specs': {'qos:maxIOPS': '1000',
+                   'extra_specs': {'cpg': HP3PAR_CPG2,
+                                   'qos:maxIOPS': '1000',
                                    'qos:maxBWS': '50',
                                    'qos:minIOPS': '100',
                                    'qos:minBWS': '25',
@@ -259,7 +271,7 @@ class HP3PARBaseDriver(object):
         'name': 'blue',
         'id': RETYPE_VOLUME_TYPE_ID,
         'extra_specs': {
-            'cpg': HP3PAR_CPG,
+            'cpg': HP3PAR_CPG_QOS,
             'snap_cpg': HP3PAR_CPG_SNAP,
             'vvs': RETYPE_VVS_NAME,
             'qos': RETYPE_QOS_SPECS,
@@ -353,7 +365,7 @@ class HP3PARBaseDriver(object):
         configuration.hp3par_username = HP3PAR_USER_NAME
         configuration.hp3par_password = HP3PAR_USER_PASS
         configuration.hp3par_api_url = 'https://1.1.1.1/api/v1'
-        configuration.hp3par_cpg = HP3PAR_CPG
+        configuration.hp3par_cpg = [HP3PAR_CPG, HP3PAR_CPG2]
         configuration.hp3par_cpg_snap = HP3PAR_CPG_SNAP
         configuration.iscsi_ip_address = '1.1.1.2'
         configuration.iscsi_port = '1234'
@@ -411,6 +423,7 @@ class HP3PARBaseDriver(object):
                 conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
         mock_client.assert_has_calls(expected)
 
@@ -441,6 +454,7 @@ class HP3PARBaseDriver(object):
                 conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
         mock_client.assert_has_calls(expected)
 
@@ -471,6 +485,7 @@ class HP3PARBaseDriver(object):
                 conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
         mock_client.assert_has_calls(expected)
 
@@ -520,6 +535,30 @@ class HP3PARBaseDriver(object):
 
         mock_client.assert_has_calls(expected)
 
+    def test_create_volume_in_pool(self):
+
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        mock_client = self.setup_driver()
+        return_model = self.driver.create_volume(self.volume_pool)
+        comment = (
+            '{"display_name": "Foo Volume", "type": "OpenStack",'
+            ' "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",'
+            ' "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}')
+        expected = [
+            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.createVolume(
+                self.VOLUME_3PAR_NAME,
+                HP3PAR_CPG2,
+                1907, {
+                    'comment': comment,
+                    'tpvv': True,
+                    'snapCPG': HP3PAR_CPG_SNAP}),
+            mock.call.logout()]
+
+        mock_client.assert_has_calls(expected)
+        self.assertEqual(return_model, None)
+
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_create_volume_qos(self, _mock_volume_types):
         # setup_mock_client drive with default configuration
@@ -529,14 +568,14 @@ class HP3PARBaseDriver(object):
         _mock_volume_types.return_value = {
             'name': 'gold',
             'extra_specs': {
-                'cpg': HP3PAR_CPG,
+                'cpg': HP3PAR_CPG_QOS,
                 'snap_cpg': HP3PAR_CPG_SNAP,
                 'vvs_name': self.VVS_NAME,
                 'qos': self.QOS,
                 'tpvv': True,
                 'volume_type': self.volume_type}}
 
-        self.driver.create_volume(self.volume_qos)
+        return_model = self.driver.create_volume(self.volume_qos)
         comment = (
             '{"volume_type_name": "gold", "display_name": "Foo Volume"'
             ', "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7'
@@ -545,9 +584,10 @@ class HP3PARBaseDriver(object):
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.getCPG(HP3PAR_CPG_QOS),
             mock.call.createVolume(
                 self.VOLUME_3PAR_NAME,
-                HP3PAR_CPG,
+                HP3PAR_CPG_QOS,
                 1907, {
                     'comment': comment,
                     'tpvv': True,
@@ -555,6 +595,9 @@ class HP3PARBaseDriver(object):
             mock.call.logout()]
 
         mock_client.assert_has_calls(expected)
+        self.assertEqual(return_model,
+                         {'host': volume_utils.append_host(self.FAKE_HOST,
+                                                           HP3PAR_CPG_QOS)})
 
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_retype_not_3par(self, _mock_volume_types):
@@ -803,7 +846,9 @@ class HP3PARBaseDriver(object):
 
         volume = {'id': HP3PARBaseDriver.CLONE_ID}
 
-        self.driver.retype(self.ctxt, volume, type_ref, None, self.RETYPE_HOST)
+        retyped = self.driver.retype(
+            self.ctxt, volume, type_ref, None, self.RETYPE_HOST)
+        self.assertTrue(retyped)
 
         expected = [
             mock.call.modifyVolume('osv-0DM4qZEVSKON-AAAAAAAAA',
@@ -876,18 +921,51 @@ class HP3PARBaseDriver(object):
                   'id': HP3PARBaseDriver.CLONE_ID,
                   'display_name': 'Foo Volume',
                   'size': 2,
-                  'host': HP3PARBaseDriver.FAKE_HOST,
+                  'host': volume_utils.append_host(self.FAKE_HOST,
+                                                   HP3PAR_CPG2),
                   'source_volid': HP3PARBaseDriver.VOLUME_ID}
         src_vref = {}
         model_update = self.driver.create_cloned_volume(volume, src_vref)
-        self.assertIsNotNone(model_update)
+        self.assertIsNone(model_update)
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.copyVolume(
                 self.VOLUME_3PAR_NAME,
                 'osv-0DM4qZEVSKON-AAAAAAAAA',
-                HP3PAR_CPG,
+                HP3PAR_CPG2,
+                {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
+                 'online': True}),
+            mock.call.logout()]
+
+        mock_client.assert_has_calls(expected)
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_create_cloned_qos_volume(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_2
+        mock_client = self.setup_driver()
+        mock_client.copyVolume.return_value = {'taskid': 1}
+
+        src_vref = {}
+        volume = self.volume_qos.copy()
+        host = "TEST_HOST"
+        pool = "TEST_POOL"
+        volume_host = volume_utils.append_host(host, pool)
+        expected_cpg = self.RETYPE_VOLUME_TYPE_2['extra_specs']['cpg']
+        expected_volume_host = volume_utils.append_host(host, expected_cpg)
+        volume['id'] = HP3PARBaseDriver.CLONE_ID
+        volume['host'] = volume_host
+        volume['source_volid'] = HP3PARBaseDriver.VOLUME_ID
+        model_update = self.driver.create_cloned_volume(volume, src_vref)
+        self.assertEqual(model_update, {'host': expected_volume_host})
+
+        expected = [
+            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.getCPG(expected_cpg),
+            mock.call.copyVolume(
+                self.VOLUME_3PAR_NAME,
+                'osv-0DM4qZEVSKON-AAAAAAAAA',
+                expected_cpg,
                 {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
                  'online': True}),
             mock.call.logout()]
@@ -1146,7 +1224,9 @@ class HP3PARBaseDriver(object):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
-        self.driver.create_volume_from_snapshot(self.volume, self.snapshot)
+        model_update = self.driver.create_volume_from_snapshot(self.volume,
+                                                               self.snapshot)
+        self.assertIsNone(model_update)
 
         comment = (
             '{"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",'
@@ -1185,7 +1265,11 @@ class HP3PARBaseDriver(object):
 
         volume = self.volume.copy()
         volume['size'] = self.volume['size'] + 10
-        self.driver.create_volume_from_snapshot(volume, self.snapshot)
+        model_update = self.driver.create_volume_from_snapshot(volume,
+                                                               self.snapshot)
+        self.assertEqual(model_update,
+                         {'host': volume_utils.append_host(self.FAKE_HOST,
+                                                           HP3PAR_CPG)})
 
         comment = (
             '{"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",'
@@ -1204,7 +1288,68 @@ class HP3PARBaseDriver(object):
                 {
                     'comment': comment,
                     'readOnly': False}),
-            mock.call.copyVolume(osv_matcher, omv_matcher, mock.ANY, mock.ANY),
+            mock.call.copyVolume(
+                osv_matcher, omv_matcher, HP3PAR_CPG, mock.ANY),
+            mock.call.getTask(mock.ANY),
+            mock.call.getVolume(osv_matcher),
+            mock.call.deleteVolume(osv_matcher),
+            mock.call.modifyVolume(omv_matcher, {'newName': osv_matcher}),
+            mock.call.growVolume(osv_matcher, 10 * 1024),
+            mock.call.logout()]
+
+        mock_client.assert_has_calls(expected)
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_create_volume_from_snapshot_and_extend_with_qos(
+            self, _mock_volume_types):
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        conf = {
+            'getTask.return_value': {
+                'status': 1},
+            'copyVolume.return_value': {'taskid': 1},
+            'getVolume.return_value': {}
+        }
+
+        mock_client = self.setup_driver(mock_conf=conf)
+        _mock_volume_types.return_value = {
+            'name': 'gold',
+            'extra_specs': {
+                'cpg': HP3PAR_CPG_QOS,
+                'snap_cpg': HP3PAR_CPG_SNAP,
+                'vvs_name': self.VVS_NAME,
+                'qos': self.QOS,
+                'tpvv': True,
+                'volume_type': self.volume_type}}
+
+        volume = self.volume_qos.copy()
+        volume['size'] = self.volume['size'] + 10
+        model_update = self.driver.create_volume_from_snapshot(volume,
+                                                               self.snapshot)
+        self.assertEqual(model_update,
+                         {'host': volume_utils.append_host(self.FAKE_HOST,
+                                                           HP3PAR_CPG_QOS)})
+
+        comment = (
+            '{"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",'
+            ' "display_name": "Foo Volume",'
+            ' "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}')
+
+        volume_name_3par = self.driver.common._encode_name(volume['id'])
+        osv_matcher = 'osv-' + volume_name_3par
+        omv_matcher = 'omv-' + volume_name_3par
+
+        expected = [
+            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+            mock.call.createSnapshot(
+                self.VOLUME_3PAR_NAME,
+                'oss-L4I73ONuTci9Fd4ceij-MQ',
+                {
+                    'comment': comment,
+                    'readOnly': False}),
+            mock.call.getCPG(HP3PAR_CPG_QOS),
+            mock.call.copyVolume(
+                osv_matcher, omv_matcher, HP3PAR_CPG_QOS, mock.ANY),
             mock.call.getTask(mock.ANY),
             mock.call.getVolume(osv_matcher),
             mock.call.deleteVolume(osv_matcher),
@@ -1537,6 +1682,7 @@ class HP3PARBaseDriver(object):
                        "type": "OpenStack"}
 
         volume = {'display_name': None,
+                  'host': 'my-stack1@3parxxx#CPGNOTUSED',
                   'volume_type': 'gold',
                   'volume_type_id': 'acfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
                   'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
@@ -1552,7 +1698,8 @@ class HP3PARBaseDriver(object):
 
         obj = self.driver.manage_existing(volume, existing_ref)
 
-        expected_obj = {'display_name': 'Foo Volume'}
+        expected_obj = {'display_name': 'Foo Volume',
+                        'host': 'my-stack1@3parxxx#fakepool'}
 
         expected_manage = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
@@ -1593,10 +1740,11 @@ class HP3PARBaseDriver(object):
                  'bwMinGoalKB': 25600, 'priority': 1, 'latencyGoal': 25,
                  'bwMaxLimitKB': 51200}),
             mock.call.addVolumeToVolumeSet(vvs_matcher, osv_matcher),
-            mock.call.modifyVolume(osv_matcher,
-                                   {'action': 6, 'userCPG': 'OpenStackCPG',
-                                    'conversionOperation': 1,
-                                    'tuneOperation': 1}),
+            mock.call.modifyVolume(
+                osv_matcher,
+                {'action': 6,
+                 'userCPG': self.volume_type['extra_specs']['cpg'],
+                 'conversionOperation': 1, 'tuneOperation': 1}),
             mock.call.getTask(1),
             mock.call.logout()
         ]
@@ -1624,6 +1772,7 @@ class HP3PARBaseDriver(object):
                        "type": "OpenStack"}
 
         volume = {'display_name': 'Test Volume',
+                  'host': 'my-stack1@3parxxx#CPGNOTUSED',
                   'volume_type': 'gold',
                   'volume_type_id': 'acfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
                   'id': id}
@@ -1636,7 +1785,8 @@ class HP3PARBaseDriver(object):
 
         obj = self.driver.manage_existing(volume, existing_ref)
 
-        expected_obj = {'display_name': 'Test Volume'}
+        expected_obj = {'display_name': 'Test Volume',
+                        'host': 'my-stack1@3parxxx#qospool'}
         expected_manage = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getVolume(existing_ref['source-name']),
@@ -1661,7 +1811,8 @@ class HP3PARBaseDriver(object):
             mock.call.deleteVolumeSet(vvs_matcher),
             mock.call.addVolumeToVolumeSet(vvs, osv_matcher),
             mock.call.modifyVolume(osv_matcher,
-                                   {'action': 6, 'userCPG': 'OpenStackCPG',
+                                   {'action': 6, 'userCPG':
+                                    test_volume_type['extra_specs']['cpg'],
                                     'conversionOperation': 1,
                                     'tuneOperation': 1}),
             mock.call.getTask(1),
@@ -1976,6 +2127,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
                 conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
         mock_client.assert_has_calls(expected)
         mock_client.reset_mock()
@@ -2264,30 +2416,33 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
         mock_client.getCPG.return_value = self.cpgs[0]
-        totalCapacityMiB = 8000
-        freeCapacityMiB = 4000
         mock_client.getStorageSystemInfo.return_value = {
             'serialNumber': '1234',
-            'totalCapacityMiB': totalCapacityMiB,
-            'freeCapacityMiB': freeCapacityMiB
+            'freeCapacityMiB': 1024.0 * 2,
+            'totalCapacityMiB': 1024.0 * 123
         }
         stats = self.driver.get_volume_stats(True)
         const = 0.0009765625
         self.assertEqual(stats['storage_protocol'], 'FC')
-        self.assertEqual(stats['total_capacity_gb'], totalCapacityMiB * const)
-        self.assertEqual(stats['free_capacity_gb'], freeCapacityMiB * const)
+        self.assertEqual(stats['total_capacity_gb'], 0)
+        self.assertEqual(stats['free_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['total_capacity_gb'], 123.0)
+        self.assertEqual(stats['pools'][0]['free_capacity_gb'], 2.0)
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getStorageSystemInfo(),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
 
         mock_client.assert_has_calls(expected)
         stats = self.driver.get_volume_stats(True)
         self.assertEqual(stats['storage_protocol'], 'FC')
-        self.assertEqual(stats['total_capacity_gb'], totalCapacityMiB * const)
-        self.assertEqual(stats['free_capacity_gb'], freeCapacityMiB * const)
+        self.assertEqual(stats['total_capacity_gb'], 0)
+        self.assertEqual(stats['free_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['total_capacity_gb'], 123.0)
+        self.assertEqual(stats['pools'][0]['free_capacity_gb'], 2.0)
 
         cpg2 = self.cpgs[0].copy()
         cpg2.update({'SDGrowth': {'limitMiB': 8192}})
@@ -2296,10 +2451,14 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
         stats = self.driver.get_volume_stats(True)
         self.assertEqual(stats['storage_protocol'], 'FC')
         total_capacity_gb = 8192 * const
-        self.assertEqual(stats['total_capacity_gb'], total_capacity_gb)
+        self.assertEqual(stats['total_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['total_capacity_gb'],
+                         total_capacity_gb)
         free_capacity_gb = int(
             (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const)
-        self.assertEqual(stats['free_capacity_gb'], free_capacity_gb)
+        self.assertEqual(stats['free_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['free_capacity_gb'],
+                         free_capacity_gb)
         self.driver.common.client.deleteCPG(HP3PAR_CPG)
         self.driver.common.client.createCPG(HP3PAR_CPG, {})
 
@@ -2501,6 +2660,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
                 conn_timeout=HP3PAR_SAN_SSH_CON_TIMEOUT),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout(),
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getPorts(),
@@ -2557,23 +2717,24 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
         mock_client.getCPG.return_value = self.cpgs[0]
-        totalCapacityMiB = 8000
-        freeCapacityMiB = 4000
         mock_client.getStorageSystemInfo.return_value = {
             'serialNumber': '1234',
-            'totalCapacityMiB': totalCapacityMiB,
-            'freeCapacityMiB': freeCapacityMiB
+            'freeCapacityMiB': 1024.0 * 2,
+            'totalCapacityMiB': 1024.0 * 123
         }
         stats = self.driver.get_volume_stats(True)
         const = 0.0009765625
         self.assertEqual(stats['storage_protocol'], 'iSCSI')
-        self.assertEqual(stats['total_capacity_gb'], totalCapacityMiB * const)
-        self.assertEqual(stats['free_capacity_gb'], freeCapacityMiB * const)
+        self.assertEqual(stats['total_capacity_gb'], 0)
+        self.assertEqual(stats['free_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['total_capacity_gb'], 123.0)
+        self.assertEqual(stats['pools'][0]['free_capacity_gb'], 2.0)
 
         expected = [
             mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
             mock.call.getStorageSystemInfo(),
             mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2),
             mock.call.logout()]
 
         mock_client.assert_has_calls(expected)
@@ -2585,10 +2746,14 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
         stats = self.driver.get_volume_stats(True)
         self.assertEqual(stats['storage_protocol'], 'iSCSI')
         total_capacity_gb = 8192 * const
-        self.assertEqual(stats['total_capacity_gb'], total_capacity_gb)
+        self.assertEqual(stats['total_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['total_capacity_gb'],
+                         total_capacity_gb)
         free_capacity_gb = int(
             (8192 - self.cpgs[0]['UsrUsage']['usedMiB']) * const)
-        self.assertEqual(stats['free_capacity_gb'], free_capacity_gb)
+        self.assertEqual(stats['free_capacity_gb'], 0)
+        self.assertEqual(stats['pools'][0]['free_capacity_gb'],
+                         free_capacity_gb)
 
     def test_create_host(self):
         # setup_mock_client drive with default configuration
@@ -3325,6 +3490,24 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
         mock_client.assert_has_calls(expected)
         self.assertEqual(model, expected_model)
 
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_get_volume_settings_default_pool(self, _mock_volume_types):
+        _mock_volume_types.return_value = {
+            'name': 'gold',
+            'id': 'gold-id',
+            'extra_specs': {}}
+        self.setup_driver()
+        volume = {'host': 'test-host@3pariscsi#pool_foo',
+                  'id': 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7'}
+        model = self.driver.common.get_volume_settings_from_type_id('gold-id',
+                                                                    volume)
+        self.assertEqual(model['cpg'], 'pool_foo')
+
+    def test_get_model_update(self):
+        self.setup_driver()
+        model_update = self.driver.common._get_model_update('xxx@yyy#zzz',
+                                                            'CPG')
+        self.assertEqual(model_update, {'host': 'xxx@yyy#CPG'})
 
 VLUNS5_RET = ({'members':
                [{'portPos': {'node': 0, 'slot': 8, 'cardPort': 2},
index 7681635261c33a327af73f567da2f3a421811f1b..1f0157883cd72cd16e8610846d7dd1bdb2a07d6c 100644 (file)
@@ -59,6 +59,7 @@ from cinder.openstack.common import log as logging
 from cinder.openstack.common import loopingcall
 from cinder.openstack.common import units
 from cinder.volume import qos_specs
+from cinder.volume import utils as volume_utils
 from cinder.volume import volume_types
 
 import taskflow.engines
@@ -81,13 +82,13 @@ hp3par_opts = [
                default='',
                help="3PAR Super user password",
                secret=True),
-    cfg.StrOpt('hp3par_cpg',
-               default="OpenStack",
-               help="The CPG to use for volume creation"),
+    cfg.ListOpt('hp3par_cpg',
+                default=["OpenStack"],
+                help="List of the CPG(s) to use for volume creation"),
     cfg.StrOpt('hp3par_cpg_snap',
                default="",
                help="The CPG to use for Snapshots for volumes. "
-                    "If empty hp3par_cpg will be used"),
+                    "If empty the userCPG will be used."),
     cfg.StrOpt('hp3par_snapshot_retention',
                default="",
                help="The time in hours to retain a snapshot.  "
@@ -151,10 +152,11 @@ class HP3PARCommon(object):
         2.0.21 - Remove bogus invalid snapCPG=None exception
         2.0.22 - HP 3PAR drivers should not claim to have 'infinite' space
         2.0.23 - Increase the hostname size from 23 to 31  Bug #1371242
+        2.0.24 - Add pools (hp3par_cpg now accepts a list of CPGs)
 
     """
 
-    VERSION = "2.0.23"
+    VERSION = "2.0.24"
 
     stats = {}
 
@@ -270,8 +272,10 @@ class HP3PARCommon(object):
         self.client_login()
 
         try:
-            # make sure the default CPG exists
-            self.validate_cpg(self.config.hp3par_cpg)
+            cpg_names = self.config.hp3par_cpg
+            for cpg_name in cpg_names:
+                self.validate_cpg(cpg_name)
+
         finally:
             self.client_logout()
 
@@ -365,12 +369,15 @@ class HP3PARCommon(object):
         LOG.info(_("Virtual volume '%(ref)s' renamed to '%(new)s'.") %
                  {'ref': existing_ref['source-name'], 'new': new_vol_name})
 
+        retyped = False
+        model_update = None
         if volume_type:
             LOG.info(_("Virtual volume %(disp)s '%(new)s' is being retyped.") %
                      {'disp': display_name, 'new': new_vol_name})
 
             try:
-                self._retype_from_no_type(volume, volume_type)
+                retyped, model_update = self._retype_from_no_type(volume,
+                                                                  volume_type)
                 LOG.info(_("Virtual volume %(disp)s successfully retyped to "
                            "%(new_type)s.") %
                          {'disp': display_name,
@@ -386,11 +393,16 @@ class HP3PARCommon(object):
                         {'newName': existing_ref['source-name'],
                          'comment': old_comment_str})
 
+        updates = {'display_name': display_name}
+        if retyped and model_update:
+            updates.update(model_update)
+
         LOG.info(_("Virtual volume %(disp)s '%(new)s' is now being managed.") %
                  {'disp': display_name, 'new': new_vol_name})
 
-        # Return display name to update the name displayed in the GUI.
-        return {'display_name': display_name}
+        # Return display name to update the name displayed in the GUI and
+        # any model updates from retype.
+        return updates
 
     def manage_existing_get_size(self, volume, existing_ref):
         """Return size of volume to be managed by manage_existing.
@@ -439,30 +451,33 @@ class HP3PARCommon(object):
 
     def _extend_volume(self, volume, volume_name, growth_size_mib,
                        _convert_to_base=False):
+        model_update = None
         try:
             if _convert_to_base:
                 LOG.debug("Converting to base volume prior to growing.")
-                self._convert_to_base_volume(volume)
+                model_update = self._convert_to_base_volume(volume)
             self.client.growVolume(volume_name, growth_size_mib)
         except Exception as ex:
             with excutils.save_and_reraise_exception() as ex_ctxt:
                 if (not _convert_to_base and
                     isinstance(ex, hpexceptions.HTTPForbidden) and
                         ex.get_code() == 150):
-                    # Error code 150 means 'invalid operation: Cannot grow
-                    # this type of volume'.
-                    # Suppress raising this exception because we can
-                    # resolve it by converting it into a base volume.
-                    # Afterwards, extending the volume should succeed, or
-                    # fail with a different exception/error code.
-                    ex_ctxt.reraise = False
-                    self._extend_volume(volume, volume_name,
-                                        growth_size_mib,
-                                        _convert_to_base=True)
+                        # Error code 150 means 'invalid operation: Cannot grow
+                        # this type of volume'.
+                        # Suppress raising this exception because we can
+                        # resolve it by converting it into a base volume.
+                        # Afterwards, extending the volume should succeed, or
+                        # fail with a different exception/error code.
+                        ex_ctxt.reraise = False
+                        model_update = self._extend_volume(
+                            volume, volume_name,
+                            growth_size_mib,
+                            _convert_to_base=True)
                 else:
                     LOG.error(_("Error extending volume: %(vol)s. "
                                 "Exception: %(ex)s") %
                               {'vol': volume_name, 'ex': ex})
+        return model_update
 
     def _get_3par_vol_name(self, volume_id):
         """Get converted 3PAR volume name.
@@ -616,40 +631,47 @@ class HP3PARCommon(object):
 
         # storage_protocol and volume_backend_name are
         # set in the child classes
-        stats = {'driver_version': '1.0',
-                 'free_capacity_gb': 'unknown',
-                 'reserved_percentage': 0,
-                 'storage_protocol': None,
-                 'total_capacity_gb': 'unknown',
-                 'QoS_support': True,
-                 'vendor_name': 'Hewlett-Packard',
-                 'volume_backend_name': None}
 
+        pools = []
         info = self.client.getStorageSystemInfo()
-        try:
-            cpg = self.client.getCPG(self.config.hp3par_cpg)
-            if 'limitMiB' not in cpg['SDGrowth']:
-                # System capacity is best we can do for now.
-                total_capacity = info['totalCapacityMiB'] * const
-                free_capacity = info['freeCapacityMiB'] * const
-            else:
-                total_capacity = int(cpg['SDGrowth']['limitMiB'] * const)
-                free_capacity = int((cpg['SDGrowth']['limitMiB'] -
-                                    cpg['UsrUsage']['usedMiB']) * const)
-
-            stats['total_capacity_gb'] = total_capacity
-            stats['free_capacity_gb'] = free_capacity
-        except hpexceptions.HTTPNotFound:
-            err = (_("CPG (%s) doesn't exist on array")
-                   % self.config.hp3par_cpg)
-            LOG.error(err)
-            raise exception.InvalidInput(reason=err)
+        for cpg_name in self.config.hp3par_cpg:
+            try:
+                cpg = self.client.getCPG(cpg_name)
+                if 'limitMiB' not in cpg['SDGrowth']:
+                    # System capacity is best we can do for now.
+                    total_capacity = info['totalCapacityMiB'] * const
+                    free_capacity = info['freeCapacityMiB'] * const
+                else:
+                    total_capacity = int(cpg['SDGrowth']['limitMiB'] * const)
+                    free_capacity = int((cpg['SDGrowth']['limitMiB'] -
+                                         cpg['UsrUsage']['usedMiB']) * const)
 
-        stats['location_info'] = ('HP3PARDriver:%(sys_id)s:%(dest_cpg)s' %
-                                  {'sys_id': info['serialNumber'],
-                                   'dest_cpg': self.config.safe_get(
-                                       'hp3par_cpg')})
-        self.stats = stats
+            except hpexceptions.HTTPNotFound:
+                err = (_("CPG (%s) doesn't exist on array")
+                       % cpg_name)
+                LOG.error(err)
+                raise exception.InvalidInput(reason=err)
+
+            pool = {'pool_name': cpg_name,
+                    'total_capacity_gb': total_capacity,
+                    'free_capacity_gb': free_capacity,
+                    'QoS_support': True,
+                    'reserved_percentage': 0,
+                    'location_info': ('HP3PARDriver:%(sys_id)s:%(dest_cpg)s' %
+                                      {'sys_id': info['serialNumber'],
+                                       'dest_cpg': cpg_name})
+                    }
+            pools.append(pool)
+
+        self.stats = {'driver_version': '1.0',
+                      'storage_protocol': None,
+                      'vendor_name': 'Hewlett-Packard',
+                      'volume_backend_name': None,
+                      # Use zero capacities here so we always use a pool.
+                      'total_capacity_gb': 0,
+                      'free_capacity_gb': 0,
+                      'reserved_percentage': 0,
+                      'pools': pools}
 
     def _get_vlun(self, volume_name, hostname, lun_id=None):
         """find a VLUN on a 3PAR host."""
@@ -921,11 +943,13 @@ class HP3PARCommon(object):
                 qos = self._get_qos_by_volume_type(volume_type)
         return hp3par_keys, qos, volume_type, vvs_name
 
-    def get_volume_settings_from_type_id(self, type_id):
+    def get_volume_settings_from_type_id(self, type_id, volume):
         """Get 3PAR volume settings given a type_id.
 
         Combines type info and config settings to return a dictionary
         describing the 3PAR volume settings.  Does some validation (CPG).
+        Uses volume['host'] to determine default cpg (when not specified in
+        volume type specs).
 
         :param type_id:
         :return: dict
@@ -933,9 +957,22 @@ class HP3PARCommon(object):
 
         hp3par_keys, qos, volume_type, vvs_name = self.get_type_info(type_id)
 
-        cpg = self._get_key_value(hp3par_keys, 'cpg',
-                                  self.config.hp3par_cpg)
-        if cpg is not self.config.hp3par_cpg:
+        # Default to 1st configured CPG unless we can extract pool from host.
+        default_cpg = self.config.hp3par_cpg[0]
+        try:
+            pool = volume_utils.extract_host(volume['host'], 'pool')
+            if pool:
+                default_cpg = pool
+                LOG.debug("Default CPG from volume['host'] is (%s)" %
+                          default_cpg)
+            else:
+                LOG.debug("Default CPG from volume['host'] not found")
+        except Exception as ex:
+            LOG.debug("Default CPG from volume['host'] not found due to (%s)" %
+                      ex)
+
+        cpg = self._get_key_value(hp3par_keys, 'cpg', default_cpg)
+        if cpg not in self.config.hp3par_cpg:
             # The cpg was specified in a volume type extra spec so it
             # needs to be validated that it's in the correct domain.
             self.validate_cpg(cpg)
@@ -987,7 +1024,8 @@ class HP3PARCommon(object):
 
         type_id = volume.get('volume_type_id', None)
 
-        volume_settings = self.get_volume_settings_from_type_id(type_id)
+        volume_settings = self.get_volume_settings_from_type_id(type_id,
+                                                                volume)
 
         # check for valid persona even if we don't use it until
         # attach time, this will give the end user notice that the
@@ -1060,6 +1098,8 @@ class HP3PARCommon(object):
             LOG.error(ex)
             raise exception.CinderException(ex)
 
+        return self._get_model_update(volume['host'], cpg)
+
     def _copy_volume(self, src_name, dest_name, cpg, snap_cpg=None,
                      tpvv=True):
         # Virtual volume sets are not supported with the -online option
@@ -1088,6 +1128,32 @@ class HP3PARCommon(object):
             return comment_dict[key]
         return None
 
+    def _get_model_update(self, volume_host, cpg):
+        """Get model_update dict to use when we select a pool.
+
+        The pools implementation uses a volume['host'] suffix of :poolname.
+        When the volume comes in with this selected pool, we sometimes use
+        a different pool (e.g. because the type says to use a different pool).
+        So in the several places that we do this, we need to return a model
+        update so that the volume will have the actual pool name in the host
+        suffix after the operation.
+
+        Given a volume_host, which should (might) have the pool suffix, and
+        given the CPG we actually chose to use, return a dict to use for a
+        model update iff an update is needed.
+
+        :param volume_host: The volume's host string.
+        :param cpg: The actual pool (cpg) used, for example from the type.
+        :return: dict Model update if we need to update volume host, else None
+        """
+        model_update = None
+        host = volume_utils.extract_host(volume_host, 'backend')
+        host_and_pool = volume_utils.append_host(host, cpg)
+        if volume_host != host_and_pool:
+            # Since we selected a pool based on type, update the model.
+            model_update = {'host': host_and_pool}
+        return model_update
+
     def create_cloned_volume(self, volume, src_vref):
         try:
             orig_name = self._get_3par_vol_name(volume['source_volid'])
@@ -1097,10 +1163,13 @@ class HP3PARCommon(object):
 
             # make the 3PAR copy the contents.
             # can't delete the original until the copy is done.
-            self._copy_volume(orig_name, vol_name, cpg=type_info['cpg'],
+            cpg = type_info['cpg']
+            self._copy_volume(orig_name, vol_name, cpg=cpg,
                               snap_cpg=type_info['snap_cpg'],
                               tpvv=type_info['tpvv'])
-            return None
+
+            return self._get_model_update(volume['host'], cpg)
+
         except hpexceptions.HTTPForbidden:
             raise exception.NotAuthorized()
         except hpexceptions.HTTPNotFound:
@@ -1188,6 +1257,7 @@ class HP3PARCommon(object):
                   (pprint.pformat(volume['display_name']),
                    pprint.pformat(snapshot['display_name'])))
 
+        model_update = None
         if volume['size'] < snapshot['volume_size']:
             err = ("You cannot reduce size of the volume.  It must "
                    "be greater than or equal to the snapshot.")
@@ -1225,7 +1295,7 @@ class HP3PARCommon(object):
                 try:
                     LOG.debug('Converting to base volume type: %s.' %
                               volume['id'])
-                    self._convert_to_base_volume(volume)
+                    model_update = self._convert_to_base_volume(volume)
                     growth_size_mib = growth_size * units.Gi / units.Mi
                     LOG.debug('Growing volume: %(id)s by %(size)s GiB.' %
                               {'id': volume['id'], 'size': growth_size})
@@ -1238,11 +1308,11 @@ class HP3PARCommon(object):
                     raise exception.CinderException(ex)
 
             if qos or vvs_name is not None:
-                cpg = self._get_key_value(hp3par_keys, 'cpg',
-                                          self.config.hp3par_cpg)
+                cpg_names = self._get_key_value(hp3par_keys, 'cpg',
+                                                self.config.hp3par_cpg)
                 try:
                     self._add_volume_to_volume_set(volume, volume_name,
-                                                   cpg, vvs_name, qos)
+                                                   cpg_names[0], vvs_name, qos)
                 except Exception as ex:
                     # Delete the volume if unable to add it to the volume set
                     self.client.deleteVolume(volume_name)
@@ -1257,6 +1327,7 @@ class HP3PARCommon(object):
         except Exception as ex:
             LOG.error(ex)
             raise exception.CinderException(ex)
+        return model_update
 
     def create_snapshot(self, snapshot):
         LOG.debug("Create Snapshot\n%s" % pprint.pformat(snapshot))
@@ -1489,6 +1560,8 @@ class HP3PARCommon(object):
             LOG.error(ex)
             raise exception.CinderException(ex)
 
+        return self._get_model_update(volume['host'], cpg)
+
     def delete_snapshot(self, snapshot):
         LOG.debug("Delete Snapshot id %s %s" % (snapshot['id'],
                                                 pprint.pformat(snapshot)))
@@ -1733,7 +1806,7 @@ class HP3PARCommon(object):
         new_type_name = new_type['name']
         new_type_id = new_type['id']
         new_volume_settings = self.get_volume_settings_from_type_id(
-            new_type_id)
+            new_type_id, volume)
         new_cpg = new_volume_settings['cpg']
         new_snap_cpg = new_volume_settings['snap_cpg']
         new_tpvv = new_volume_settings['tpvv']
@@ -1765,7 +1838,11 @@ class HP3PARCommon(object):
                      host, new_persona, old_cpg, new_cpg,
                      old_snap_cpg, new_snap_cpg, old_tpvv, new_tpvv,
                      old_vvs, new_vvs, old_qos, new_qos, old_comment)
-        return True
+
+        if host:
+            return True, self._get_model_update(host['host'], new_cpg)
+        else:
+            return True, self._get_model_update(volume['host'], new_cpg)
 
     def _retype_from_no_type(self, volume, new_type):
         """Convert the volume to be of the new type.  Starting from no type.
@@ -1777,7 +1854,8 @@ class HP3PARCommon(object):
                        volume-type is not used here. This method uses None.
         :param new_type: A dictionary describing the volume type to convert to
         """
-        none_type_settings = self.get_volume_settings_from_type_id(None)
+        none_type_settings = self.get_volume_settings_from_type_id(
+            None, volume)
         return self._retype_from_old_to_new(volume, new_type,
                                             none_type_settings, None)
 
index abfa7767522022a9d6a0def64630334713496a63..38101065c4c90751f36a3ca821d696b45c1ffd11 100644 (file)
@@ -34,6 +34,7 @@ try:
 except ImportError:
     hpexceptions = None
 
+from cinder import exception
 from cinder.i18n import _
 from cinder.openstack.common import log as logging
 from cinder import utils
@@ -69,10 +70,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
         2.0.7 - Only one FC port is used when a single FC path
                 is present.  bug #1360001
         2.0.8 - Fixing missing login/logout around attach/detach bug #1367429
+        2.0.9 - Add support for pools with model update
 
     """
 
-    VERSION = "2.0.8"
+    VERSION = "2.0.9"
 
     def __init__(self, *args, **kwargs):
         super(HP3PARFCDriver, self).__init__(*args, **kwargs)
@@ -118,8 +120,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
     def create_volume(self, volume):
         self.common.client_login()
         try:
-            metadata = self.common.create_volume(volume)
-            return {'metadata': metadata}
+            return self.common.create_volume(volume)
         finally:
             self.common.client_logout()
 
@@ -127,8 +128,7 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
     def create_cloned_volume(self, volume, src_vref):
         self.common.client_login()
         try:
-            new_vol = self.common.create_cloned_volume(volume, src_vref)
-            return {'metadata': new_vol}
+            return self.common.create_cloned_volume(volume, src_vref)
         finally:
             self.common.client_logout()
 
@@ -148,9 +148,8 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
         """
         self.common.client_login()
         try:
-            metadata = self.common.create_volume_from_snapshot(volume,
-                                                               snapshot)
-            return {'metadata': metadata}
+            return self.common.create_volume_from_snapshot(volume,
+                                                           snapshot)
         finally:
             self.common.client_logout()
 
@@ -466,3 +465,14 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
             return self.common.migrate_volume(volume, host)
         finally:
             self.common.client_logout()
+
+    def get_pool(self, volume):
+        self.common.client_login()
+        try:
+            return self.common.get_cpg(volume)
+        except hpexceptions.HTTPNotFound:
+            reason = (_("Volume %s doesn't exist on array.") % volume)
+            LOG.error(reason)
+            raise exception.InvalidVolume(reason)
+        finally:
+            self.common.client_logout()
index 3027df043b5e7a9accd09e5f992da61498f2bab6..3500d9e7103e85ccf37cba00dc3c78481b40ed96 100644 (file)
@@ -74,10 +74,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
         2.0.5 - Added CHAP support, requires 3.1.3 MU1 firmware
                 and hp3parclient 3.1.0.
         2.0.6 - Fixing missing login/logout around attach/detach bug #1367429
+        2.0.7 - Add support for pools with model update
 
     """
 
-    VERSION = "2.0.6"
+    VERSION = "2.0.7"
 
     def __init__(self, *args, **kwargs):
         super(HP3PARISCSIDriver, self).__init__(*args, **kwargs)
@@ -189,8 +190,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
     def create_volume(self, volume):
         self.common.client_login()
         try:
-            metadata = self.common.create_volume(volume)
-            return {'metadata': metadata}
+            return self.common.create_volume(volume)
         finally:
             self.common.client_logout()
 
@@ -199,8 +199,7 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
         """Clone an existing volume."""
         self.common.client_login()
         try:
-            new_vol = self.common.create_cloned_volume(volume, src_vref)
-            return {'metadata': new_vol}
+            return self.common.create_cloned_volume(volume, src_vref)
         finally:
             self.common.client_logout()
 
@@ -220,9 +219,8 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
         """
         self.common.client_login()
         try:
-            metadata = self.common.create_volume_from_snapshot(volume,
-                                                               snapshot)
-            return {'metadata': metadata}
+            return self.common.create_volume_from_snapshot(volume,
+                                                           snapshot)
         finally:
             self.common.client_logout()
 
@@ -674,3 +672,14 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
             return self.common.migrate_volume(volume, host)
         finally:
             self.common.client_logout()
+
+    def get_pool(self, volume):
+        self.common.client_login()
+        try:
+            return self.common.get_cpg(volume)
+        except hpexceptions.HTTPNotFound:
+            reason = (_("Volume %s doesn't exist on array.") % volume)
+            LOG.error(reason)
+            raise exception.InvalidVolume(reason)
+        finally:
+            self.common.client_logout()
index 4f3fda0c50afc70df5a9b5a3020352debb3ae926..02c212c451e71838d6e229db97583db31225c474 100644 (file)
 # 3PAR Super user password (string value)
 #hp3par_password=
 
-# The CPG to use for volume creation (string value)
+# List of the CPG(s) to use for volume creation (list value)
 #hp3par_cpg=OpenStack
 
-# The CPG to use for Snapshots for volumes. If empty
-# hp3par_cpg will be used (string value)
+# The CPG to use for Snapshots for volumes. If empty the
+# userCPG will be used. (string value)
 #hp3par_cpg_snap=
 
 # The time in hours to retain a snapshot.  You can't delete it