host, None)
self.assertEqual(expected_info, vlun_info)
+ @mock.patch.object(volume_types, 'get_volume_type')
+ def test_manage_existing(self, _mock_volume_types):
+ mock_client = self.setup_driver()
+
+ _mock_volume_types.return_value = {
+ 'name': 'gold',
+ 'extra_specs': {
+ 'cpg': HP3PAR_CPG,
+ 'snap_cpg': HP3PAR_CPG_SNAP,
+ 'vvs_name': self.VVS_NAME,
+ 'qos': self.QOS,
+ 'tpvv': True,
+ 'volume_type': self.volume_type}}
+ comment = (
+ '{"display_name": "Foo Volume"}')
+ new_comment = (
+ '{"volume_type_name": "gold",'
+ ' "display_name": "Foo Volume",'
+ ' "name": "volume-007dbfce-7579-40bc-8f90-a20b3902283e",'
+ ' "volume_type_id": "acfa9fa4-54a0-4340-a3d8-bfcf19aea65e",'
+ ' "volume_id": "007dbfce-7579-40bc-8f90-a20b3902283e",'
+ ' "qos": {},'
+ ' "type": "OpenStack"}')
+ volume = {'display_name': None,
+ 'volume_type': 'gold',
+ 'volume_type_id': 'acfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
+ 'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
+
+ mock_client.getVolume.return_value = {'comment': comment}
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ osv_matcher = self.driver.common._get_3par_vol_name(volume['id'])
+ existing_ref = {'name': unm_matcher}
+
+ obj = self.driver.manage_existing(volume, existing_ref)
+
+ expected_obj = {'display_name': 'Foo Volume'}
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.modifyVolume(existing_ref['name'],
+ {'newName': osv_matcher,
+ 'comment': new_comment}),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+ self.assertEqual(expected_obj, obj)
+
+ volume['display_name'] = 'Test Volume'
+
+ obj = self.driver.manage_existing(volume, existing_ref)
+
+ expected_obj = {'display_name': 'Test Volume'}
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.modifyVolume(existing_ref['name'],
+ {'newName': osv_matcher,
+ 'comment': new_comment}),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+ self.assertEqual(expected_obj, obj)
+
+ def test_manage_existing_no_volume_type(self):
+ mock_client = self.setup_driver()
+
+ comment = (
+ '{"display_name": "Foo Volume"}')
+ new_comment = (
+ '{"type": "OpenStack",'
+ ' "display_name": "Foo Volume",'
+ ' "name": "volume-007dbfce-7579-40bc-8f90-a20b3902283e",'
+ ' "volume_id": "007dbfce-7579-40bc-8f90-a20b3902283e"}')
+ volume = {'display_name': None,
+ 'volume_type': None,
+ 'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
+
+ mock_client.getVolume.return_value = {'comment': comment}
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ osv_matcher = self.driver.common._get_3par_vol_name(volume['id'])
+ existing_ref = {'name': unm_matcher}
+
+ obj = self.driver.manage_existing(volume, existing_ref)
+
+ expected_obj = {'display_name': 'Foo Volume'}
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.modifyVolume(existing_ref['name'],
+ {'newName': osv_matcher,
+ 'comment': new_comment}),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+ self.assertEqual(expected_obj, obj)
+
+ volume['display_name'] = 'Test Volume'
+
+ obj = self.driver.manage_existing(volume, existing_ref)
+
+ expected_obj = {'display_name': 'Test Volume'}
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.modifyVolume(existing_ref['name'],
+ {'newName': osv_matcher,
+ 'comment': new_comment}),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+ self.assertEqual(expected_obj, obj)
+
+ mock_client.getVolume.return_value = {}
+ volume['display_name'] = None
+
+ obj = self.driver.manage_existing(volume, existing_ref)
+
+ expected_obj = {'display_name': None}
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.modifyVolume(existing_ref['name'],
+ {'newName': osv_matcher,
+ 'comment': new_comment}),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+ self.assertEqual(expected_obj, obj)
+
+ def test_manage_existing_invalid_input(self):
+ mock_client = self.setup_driver()
+
+ volume = {'display_name': None,
+ 'volume_type': None,
+ 'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
+
+ mock_client.getVolume.side_effect = hpexceptions.HTTPNotFound('fake')
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ existing_ref = {'name': unm_matcher}
+
+ self.assertRaises(exception.InvalidInput,
+ self.driver.manage_existing,
+ volume=volume,
+ existing_ref=existing_ref)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+
+ def test_manage_existing_volume_type_exception(self):
+ mock_client = self.setup_driver()
+
+ comment = (
+ '{"display_name": "Foo Volume"}')
+ volume = {'display_name': None,
+ 'volume_type': 'gold',
+ 'volume_type_id': 'bcfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
+ 'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
+
+ mock_client.getVolume.return_value = {'comment': comment}
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ existing_ref = {'name': unm_matcher}
+
+ self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
+ self.driver.manage_existing,
+ volume=volume,
+ existing_ref=existing_ref)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+
+ def test_manage_existing_get_size(self):
+ mock_client = self.setup_driver()
+ mock_client.getVolume.return_value = {'sizeMiB': 2048}
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ volume = {}
+ existing_ref = {'name': unm_matcher}
+
+ size = self.driver.manage_existing_get_size(volume, existing_ref)
+
+ expected_size = 2
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected, True)
+ self.assertEqual(expected_size, size)
+
+ def test_manage_existing_get_size_invalid_reference(self):
+ mock_client = self.setup_driver()
+ volume = {}
+ existing_ref = {'name': self.VOLUME_3PAR_NAME}
+
+ self.assertRaises(exception.ManageExistingInvalidReference,
+ self.driver.manage_existing_get_size,
+ volume=volume,
+ existing_ref=existing_ref)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+
+ existing_ref = {}
+
+ self.assertRaises(exception.ManageExistingInvalidReference,
+ self.driver.manage_existing_get_size,
+ volume=volume,
+ existing_ref=existing_ref)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+
+ def test_manage_existing_get_size_invalid_input(self):
+ mock_client = self.setup_driver()
+ mock_client.getVolume.side_effect = hpexceptions.HTTPNotFound('fake')
+
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+ volume = {}
+ existing_ref = {'name': unm_matcher}
+
+ self.assertRaises(exception.InvalidInput,
+ self.driver.manage_existing_get_size,
+ volume=volume,
+ existing_ref=existing_ref)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(existing_ref['name']),
+ mock.call.logout()
+ ]
+
+ mock_client.assert_has_calls(expected)
+
+ def test_unmanage(self):
+ mock_client = self.setup_driver()
+
+ self.driver.unmanage(self.volume)
+
+ osv_matcher = self.driver.common._get_3par_vol_name(self.volume['id'])
+ unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.modifyVolume(osv_matcher, {'newName': unm_matcher}),
+ mock.call.logout()]
+
+ mock_client.assert_has_calls(expected)
+
class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
import ast
import base64
import json
+import math
import pprint
import re
import uuid
2.0.10 - Fixed an issue with 3PAR vlun location bug #1315542
2.0.11 - Remove hp3parclient requirement from unit tests #1315195
2.0.12 - Volume detach hangs when host is in a host set bug #1317134
+ 2.0.13 - Added support for managing/unmanaging of volumes
"""
- VERSION = "2.0.12"
+ VERSION = "2.0.13"
stats = {}
growth_size_mib = growth_size * units.Ki
self._extend_volume(volume, volume_name, growth_size_mib)
+ def manage_existing(self, volume, existing_ref):
+ """Manage an existing 3PAR volume."""
+ # Check for the existence of the virtual volume.
+ try:
+ vol = self.client.getVolume(existing_ref['name'])
+ except hpexceptions.HTTPNotFound:
+ err = (_("Virtual volume '%s' doesn't exist on array.") %
+ existing_ref['name'])
+ LOG.error(err)
+ raise exception.InvalidInput(reason=err)
+
+ new_comment = {}
+
+ # Use the display name from the existing volume if no new name
+ # was chosen by the user.
+ if volume['display_name']:
+ display_name = volume['display_name']
+ new_comment['display_name'] = volume['display_name']
+ elif 'comment' in vol:
+ display_name = self._get_3par_vol_comment_value(vol['comment'],
+ 'display_name')
+ if display_name:
+ new_comment['display_name'] = display_name
+ else:
+ display_name = None
+
+ # Generate the new volume information based off of the new ID.
+ new_vol_name = self._get_3par_vol_name(volume['id'])
+ name = 'volume-' + volume['id']
+
+ new_comment['volume_id'] = volume['id']
+ new_comment['name'] = name
+ new_comment['type'] = 'OpenStack'
+
+ # Create new comments for the existing volume depending on
+ # whether the user's volume type choice.
+ # TODO(Anthony) when retype is available handle retyping of
+ # a volume.
+ if volume['volume_type']:
+ try:
+ settings = self.get_volume_settings_from_type(volume)
+ except Exception:
+ reason = (_("Volume type ID '%s' is invalid.") %
+ volume['volume_type_id'])
+ raise exception.ManageExistingVolumeTypeMismatch(reason=reason)
+
+ volume_type = self._get_volume_type(volume['volume_type_id'])
+
+ new_comment['volume_type_name'] = volume_type['name']
+ new_comment['volume_type_id'] = volume['volume_type_id']
+ new_comment['qos'] = settings['qos']
+
+ # Update the existing volume with the new name and comments.
+ self.client.modifyVolume(existing_ref['name'],
+ {'newName': new_vol_name,
+ 'comment': json.dumps(new_comment)})
+
+ LOG.info(_("Virtual volume '%(ref)s' renamed to '%(new)s'.") %
+ {'ref': existing_ref['name'], 'new': new_vol_name})
+ 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}
+
+ def manage_existing_get_size(self, volume, existing_ref):
+ """Return size of volume to be managed by manage_existing.
+
+ existing_ref is a dictionary of the form:
+ {'name': <name of the virtual volume>}
+ """
+ # Check that a valid reference was provided.
+ if 'name' not in existing_ref:
+ reason = _("Reference must contain name element.")
+ raise exception.ManageExistingInvalidReference(
+ existing_ref=existing_ref,
+ reason=reason)
+
+ # Make sure the reference is not in use.
+ if re.match('osv-*|oss-*|vvs-*', existing_ref['name']):
+ reason = _("Reference must be for an unmanaged virtual volume.")
+ raise exception.ManageExistingInvalidReference(
+ existing_ref=existing_ref,
+ reason=reason)
+
+ # Check for the existence of the virtual volume.
+ try:
+ vol = self.client.getVolume(existing_ref['name'])
+ except hpexceptions.HTTPNotFound:
+ err = (_("Virtual volume '%s' doesn't exist on array.") %
+ existing_ref['name'])
+ LOG.error(err)
+ raise exception.InvalidInput(reason=err)
+
+ return int(math.ceil(float(vol['sizeMiB']) / units.Ki))
+
+ def unmanage(self, volume):
+ """Removes the specified volume from Cinder management."""
+ # Rename the volume's name to unm-* format so that it can be
+ # easily found later.
+ vol_name = self._get_3par_vol_name(volume['id'])
+ new_vol_name = self._get_3par_unm_name(volume['id'])
+ self.client.modifyVolume(vol_name, {'newName': new_vol_name})
+
+ LOG.info(_("Virtual volume %(disp)s '%(vol)s' is no longer managed. "
+ "Volume renamed to '%(new)s'.") %
+ {'disp': volume['display_name'],
+ 'vol': vol_name,
+ 'new': new_vol_name})
+
def _extend_volume(self, volume, volume_name, growth_size_mib,
_convert_to_base=False):
try:
vvs_name = self._encode_name(volume_id)
return "vvs-%s" % vvs_name
+ def _get_3par_unm_name(self, volume_id):
+ unm_name = self._encode_name(volume_id)
+ return "unm-%s" % unm_name
+
def _encode_name(self, name):
uuid_str = name.replace("-", "")
vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)