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
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'
'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',
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',
'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,
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'
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)
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)
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.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
_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'
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,
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):
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',
'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()]
# 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",'
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",'
{
'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),
"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'}
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),
'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()
]
"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}
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']),
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),
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()
# 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}})
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, {})
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(),
# 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)
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
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},
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
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. "
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 = {}
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()
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,
{'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.
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.
# 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."""
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
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)
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
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
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'])
# 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:
(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.")
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})
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)
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))
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)))
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']
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.
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)