Merge "3PAR: Provide an option duing creation of volume from snapshot"

This commit is contained in:
Zuul 2019-04-25 16:45:38 +00:00 committed by Gerrit Code Review
commit 2c14226392
2 changed files with 285 additions and 58 deletions

View File

@ -254,6 +254,14 @@ class HPE3PARBaseDriver(test.TestCase):
'volume_type': None,
'volume_type_id': VOLUME_TYPE_ID_FLASH_CACHE}
volume_hos = {'name': VOLUME_NAME,
'id': VOLUME_ID,
'display_name': 'Foo Volume',
'size': 2,
'host': FAKE_CINDER_HOST,
'volume_type': None,
'volume_type_id': 'hos'}
snapshot = {'name': SNAPSHOT_NAME,
'id': SNAPSHOT_ID,
'user_id': USER_ID,
@ -342,6 +350,13 @@ class HPE3PARBaseDriver(test.TestCase):
'deleted_at': None,
'id': VOLUME_TYPE_ID_FLASH_CACHE}
volume_type_hos = {'name': 'hos',
'deleted': False,
'updated_at': None,
'extra_specs': {'convert_to_base': False},
'deleted_at': None,
'id': 'hos'}
flash_cache_3par_keys = {'flash_cache': 'true'}
cpgs = [
@ -3215,31 +3230,22 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
def test_delete_snapshot_in_use(self):
# 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_client = self.setup_driver()
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
self.driver._login()
volume = self.volume.copy()
model_update = self.driver.create_volume_from_snapshot(
self.volume,
volume,
self.snapshot)
self.assertIsNone(model_update)
self.assertEqual(model_update, {})
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
volume_name_3par = common._encode_name(volume['id'])
osv_matcher = 'osv-' + volume_name_3par
omv_matcher = 'omv-' + volume_name_3par
expected = [
mock.call.createSnapshot(
@ -3247,13 +3253,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False}),
mock.call.copyVolume(
osv_matcher, omv_matcher, HPE3PAR_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})]
'readOnly': False})]
mock_client.assert_has_calls(
self.standard_login +
@ -3290,31 +3290,22 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
def test_create_volume_from_snapshot(self):
# 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_client = self.setup_driver()
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
self.driver._login()
volume = self.volume.copy()
model_update = self.driver.create_volume_from_snapshot(
self.volume,
volume,
self.snapshot)
self.assertIsNone(model_update)
self.assertEqual(model_update, {})
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
volume_name_3par = common._encode_name(volume['id'])
osv_matcher = 'osv-' + volume_name_3par
omv_matcher = 'omv-' + volume_name_3par
expected = [
mock.call.createSnapshot(
@ -3322,13 +3313,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False}),
mock.call.copyVolume(
osv_matcher, omv_matcher, HPE3PAR_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})]
'readOnly': False})]
mock_client.assert_has_calls(
self.standard_login +
@ -3481,13 +3466,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
def test_create_volume_from_snapshot_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_client = self.setup_driver()
_mock_volume_types.return_value = {
'name': 'gold',
'extra_specs': {
@ -3501,10 +3480,90 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
self.driver._login()
volume = self.volume_qos.copy()
model_update = self.driver.create_volume_from_snapshot(
volume, self.snapshot)
self.assertEqual(model_update, {})
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
expected = [
mock.call.createSnapshot(
self.VOLUME_3PAR_NAME,
'oss-L4I73ONuTci9Fd4ceij-MQ', {
'comment': comment,
'readOnly': False})]
mock_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
@mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_from_snapshot_as_child(self, _mock_volume_types):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
mock_client = self.setup_driver()
volume_type_hos = self.volume_type_hos
volume_type_hos['extra_specs']['convert_to_base'] = False
_mock_volume_types.return_value = volume_type_hos
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
self.driver._login()
volume = self.volume_hos.copy()
model_update = self.driver.create_volume_from_snapshot(
volume,
self.snapshot)
self.assertEqual(model_update, {})
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
expected = [
mock.call.createSnapshot(
self.VOLUME_3PAR_NAME,
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False})]
mock_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
@mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_from_snapshot_as_base(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)
volume_type_hos = self.volume_type_hos
volume_type_hos['extra_specs']['convert_to_base'] = True
_mock_volume_types.return_value = volume_type_hos
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
volume = self.volume_hos.copy()
model_update = self.driver.create_volume_from_snapshot(
volume,
self.snapshot)
self.assertIsNone(model_update)
comment = Comment({
@ -3519,10 +3578,10 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
expected = [
mock.call.createSnapshot(
self.VOLUME_3PAR_NAME,
'oss-L4I73ONuTci9Fd4ceij-MQ', {
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False}),
mock.call.getCPG(HPE3PAR_CPG),
mock.call.copyVolume(
osv_matcher, omv_matcher, HPE3PAR_CPG, mock.ANY),
mock.call.getTask(mock.ANY),
@ -3535,6 +3594,116 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
expected +
self.standard_logout)
@mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_from_snapshot_as_child_and_extend(
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)
volume_type_hos = self.volume_type_hos
volume_type_hos['extra_specs']['convert_to_base'] = False
_mock_volume_types.return_value = volume_type_hos
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
volume = self.volume_hos.copy()
volume['size'] = self.volume['size'] + 10
model_update = self.driver.create_volume_from_snapshot(
volume,
self.snapshot)
self.assertIsNone(model_update)
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
volume_name_3par = common._encode_name(volume['id'])
osv_matcher = 'osv-' + volume_name_3par
omv_matcher = 'omv-' + volume_name_3par
expected = [
mock.call.createSnapshot(
self.VOLUME_3PAR_NAME,
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False}),
mock.call.copyVolume(
osv_matcher, omv_matcher, HPE3PAR_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_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
@mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_from_snapshot_as_base_and_extend(
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)
volume_type_hos = self.volume_type_hos
volume_type_hos['extra_specs']['convert_to_base'] = True
_mock_volume_types.return_value = volume_type_hos
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
volume = self.volume_hos.copy()
volume['size'] = self.volume['size'] + 10
model_update = self.driver.create_volume_from_snapshot(
volume,
self.snapshot)
self.assertIsNone(model_update)
comment = Comment({
"snapshot_id": "2f823bdc-e36e-4dc8-bd15-de1c7a28ff31",
"display_name": "Foo Volume",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
})
volume_name_3par = common._encode_name(volume['id'])
osv_matcher = 'osv-' + volume_name_3par
omv_matcher = 'omv-' + volume_name_3par
expected = [
mock.call.createSnapshot(
self.VOLUME_3PAR_NAME,
'oss-L4I73ONuTci9Fd4ceij-MQ',
{
'comment': comment,
'readOnly': False}),
mock.call.copyVolume(
osv_matcher, omv_matcher, HPE3PAR_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_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
def test_terminate_connection_from_primary_when_failed_over(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client

View File

@ -273,11 +273,12 @@ class HPE3PARCommon(object):
4.0.9 - Set proper backend on subsequent operation, after group
failover. bug #1773069
4.0.10 - Added retry in delete_volume. bug #1783934
4.0.11 - Added extra spec hpe3par:convert_to_base
"""
VERSION = "4.0.10"
VERSION = "4.0.11"
stats = {}
@ -331,7 +332,8 @@ class HPE3PARCommon(object):
'priority']
qos_priority_level = {'low': 1, 'normal': 2, 'high': 3}
hpe3par_valid_keys = ['cpg', 'snap_cpg', 'provisioning', 'persona', 'vvs',
'flash_cache', 'compression', 'group_replication']
'flash_cache', 'compression', 'group_replication',
'convert_to_base']
def __init__(self, config, active_backend_id=None):
self.config = config
@ -1797,6 +1799,16 @@ class HPE3PARCommon(object):
else:
return default
def _get_boolean_key_value(self, hpe3par_keys, key, default=False):
value = self._get_key_value(
hpe3par_keys, key, default)
if isinstance(value, six.string_types):
if value.lower() == 'true':
value = True
else:
value = False
return value
def _get_qos_value(self, qos, key, default=None):
if key in qos:
return qos[key]
@ -2086,6 +2098,10 @@ class HPE3PARCommon(object):
hpe3par_tiramisu = (
self._get_key_value(hpe3par_keys, 'group_replication'))
# by default, set convert_to_base to False
convert_to_base = self._get_boolean_key_value(
hpe3par_keys, 'convert_to_base', False)
# if provisioning is not set use thin
default_prov = self.valid_prov_values[0]
prov_value = self._get_key_value(hpe3par_keys, 'provisioning',
@ -2121,7 +2137,8 @@ class HPE3PARCommon(object):
'vvs_name': vvs_name, 'qos': qos,
'tpvv': tpvv, 'tdvv': tdvv,
'volume_type': volume_type,
'group_replication': hpe3par_tiramisu}
'group_replication': hpe3par_tiramisu,
'convert_to_base': convert_to_base}
def get_volume_settings_from_type(self, volume, host=None):
"""Get 3PAR volume settings given a volume.
@ -2624,13 +2641,23 @@ class HPE3PARCommon(object):
self.client.createSnapshot(volume_name, snap_name, optional)
# Convert snapshot volume to base volume type
LOG.debug('Converting to base volume type: %s.',
volume['id'])
model_update = self._convert_to_base_volume(volume)
# by default, set convert_to_base to False
convert_to_base = self._get_boolean_key_value(
hpe3par_keys, 'convert_to_base', False)
LOG.debug("convert_to_base: %(convert)s",
{'convert': convert_to_base})
# Grow the snapshot if needed
growth_size = volume['size'] - snapshot['volume_size']
LOG.debug("growth_size: %(size)s", {'size': growth_size})
if growth_size > 0 or convert_to_base:
# Convert snapshot volume to base volume type
LOG.debug('Converting to base volume type: %(id)s.',
{'id': volume['id']})
model_update = self._convert_to_base_volume(volume)
else:
LOG.debug("volume is created as child of snapshot")
if growth_size > 0:
try:
growth_size_mib = growth_size * units.Gi / units.Mi
@ -2926,6 +2953,37 @@ class HPE3PARCommon(object):
"can't be deleted at this time.")
raise exception.SnapshotIsBusy(message=msg)
if snap.startswith('osv-'):
LOG.info(
"Found a volume %(name)s",
{'name': snap})
# Get details of original volume v1
# These details would be required to form v2
s1_detail = self.client.getVolume(snap_name)
v1_name = s1_detail.get('copyOf')
v1 = self.client.getVolume(v1_name)
# Get details of volume v2,
# which is child of snapshot s1
v2_name = snap
v2 = self.client.getVolume(v2_name)
# Update v2 object as required for
# _convert_to_base function
v2['volume_type_id'] = \
self._get_3par_vol_comment_value(
v1['comment'], 'volume_type_id')
v2['id'] = self._get_3par_vol_comment_value(
v2['comment'], 'volume_id')
v2['host'] = '#' + v1['userCPG']
LOG.debug('Converting to base volume type: '
'%(id)s.', {'id': v2['id']})
self._convert_to_base_volume(v2)
try:
self.client.deleteVolume(snap_name)
except Exception: