Manage/unmanage volume in ScaleIO driver
Add support for manage/unmanage volume in the ScaleIO driver. Also fixed an error code for volume not found. DocImpact Implements: blueprint scaleio-manage-existing Closes-Bug: #1545023 Change-Id: I14ad94905aaa7ea2bef7c75011a40c5d057e1cc0
This commit is contained in:
parent
bd70ce6f4d
commit
eac894c09f
@ -59,9 +59,6 @@ class ScaleIODriver(scaleio.ScaleIODriver):
|
||||
def reenable_replication(self, context, volume):
|
||||
pass
|
||||
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
pass
|
||||
|
||||
def promote_replica(self, context, volume):
|
||||
pass
|
||||
|
||||
@ -78,9 +75,6 @@ class ScaleIODriver(scaleio.ScaleIODriver):
|
||||
def create_consistencygroup(self, context, group):
|
||||
pass
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_ref):
|
||||
pass
|
||||
|
||||
def unmanage(self, volume):
|
||||
pass
|
||||
|
||||
|
@ -84,7 +84,7 @@ class TestDeleteSnapShot(scaleio.TestScaleIODriver):
|
||||
def test_delete_invalid_snapshot_force_delete(self):
|
||||
self.driver.configuration.set_override('sio_force_delete',
|
||||
override=True)
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
|
||||
self.driver.delete_snapshot(self.snapshot)
|
||||
|
||||
def test_delete_invalid_snapshot(self):
|
||||
|
@ -0,0 +1,126 @@
|
||||
# Copyright (c) 2016 EMC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.emc import scaleio
|
||||
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
|
||||
from cinder.volume import volume_types
|
||||
from mock import patch
|
||||
from six.moves import urllib
|
||||
|
||||
|
||||
class TestManageExisting(scaleio.TestScaleIODriver):
|
||||
"""Test cases for ``ScaleIODriver.manage_existing()``"""
|
||||
|
||||
def setUp(self):
|
||||
"""Setup a test case environment.
|
||||
|
||||
Creates a fake volume object and sets up the required API responses.
|
||||
"""
|
||||
super(TestManageExisting, self).setUp()
|
||||
ctx = context.RequestContext('fake', 'fake', auth_token=True)
|
||||
self.volume = fake_volume.fake_volume_obj(
|
||||
ctx, **{'provider_id': 'pid_1'})
|
||||
self.volume_attached = fake_volume.fake_volume_obj(
|
||||
ctx, **{'provider_id': 'pid_2'})
|
||||
self.volume_no_provider_id = fake_volume.fake_volume_obj(ctx)
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.volume.id))
|
||||
)
|
||||
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
self.RESPONSE_MODE.Valid: {
|
||||
'instances/Volume::' + self.volume['provider_id']:
|
||||
mocks.MockHTTPSResponse({
|
||||
'id': 'pid_1',
|
||||
'sizeInKb': 8388608,
|
||||
'mappedSdcInfo': None
|
||||
}, 200)
|
||||
},
|
||||
self.RESPONSE_MODE.BadStatus: {
|
||||
'instances/Volume::' + self.volume['provider_id']:
|
||||
mocks.MockHTTPSResponse({
|
||||
'errorCode': 401,
|
||||
'message': 'BadStatus Volume Test',
|
||||
}, 401),
|
||||
'instances/Volume::' + self.volume_attached['provider_id']:
|
||||
mocks.MockHTTPSResponse({
|
||||
'id': 'pid_2',
|
||||
'sizeInKb': 8388608,
|
||||
'mappedSdcInfo': 'Mapped'
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
def test_no_source_id(self):
|
||||
existing_ref = {'source-name': 'scaleioVolName'}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing, self.volume,
|
||||
existing_ref)
|
||||
|
||||
def test_no_type_id(self):
|
||||
self.volume['volume_type_id'] = None
|
||||
existing_ref = {'source-id': 'pid_1'}
|
||||
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
|
||||
self.driver.manage_existing, self.volume,
|
||||
existing_ref)
|
||||
|
||||
@patch.object(
|
||||
volume_types,
|
||||
'get_volume_type',
|
||||
return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})
|
||||
def test_volume_not_found(self, _mock_volume_type):
|
||||
self.volume['volume_type_id'] = 'ScaleIO'
|
||||
existing_ref = {'source-id': 'pid_1'}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing, self.volume,
|
||||
existing_ref)
|
||||
|
||||
@patch.object(
|
||||
volume_types,
|
||||
'get_volume_type',
|
||||
return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})
|
||||
def test_volume_attached(self, _mock_volume_type):
|
||||
self.volume_attached['volume_type_id'] = 'ScaleIO'
|
||||
existing_ref = {'source-id': 'pid_2'}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing, self.volume_attached,
|
||||
existing_ref)
|
||||
|
||||
@patch.object(
|
||||
volume_types,
|
||||
'get_volume_type',
|
||||
return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})
|
||||
def test_manage_get_size_calc(self, _mock_volume_type):
|
||||
self.volume['volume_type_id'] = 'ScaleIO'
|
||||
existing_ref = {'source-id': 'pid_1'}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
|
||||
result = self.driver.manage_existing_get_size(self.volume,
|
||||
existing_ref)
|
||||
self.assertEqual(8, result)
|
||||
|
||||
@patch.object(
|
||||
volume_types,
|
||||
'get_volume_type',
|
||||
return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})
|
||||
def test_manage_existing_valid(self, _mock_volume_type):
|
||||
self.volume['volume_type_id'] = 'ScaleIO'
|
||||
existing_ref = {'source-id': 'pid_1'}
|
||||
result = self.driver.manage_existing(self.volume, existing_ref)
|
||||
self.assertEqual('pid_1', result['provider_id'])
|
@ -86,7 +86,7 @@ QOS_BANDWIDTH_LIMIT = 'maxBWS'
|
||||
|
||||
BLOCK_SIZE = 8
|
||||
OK_STATUS_CODE = 200
|
||||
VOLUME_NOT_FOUND_ERROR = 78
|
||||
VOLUME_NOT_FOUND_ERROR = 79
|
||||
VOLUME_NOT_MAPPED_ERROR = 84
|
||||
VOLUME_ALREADY_MAPPED_ERROR = 81
|
||||
|
||||
@ -126,8 +126,7 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
if self.configuration.sio_storage_pools:
|
||||
self.storage_pools = [
|
||||
e.strip() for e in
|
||||
self.configuration.sio_storage_pools.split(',')
|
||||
]
|
||||
self.configuration.sio_storage_pools.split(',')]
|
||||
|
||||
self.storage_pool_name = self.configuration.sio_storage_pool_name
|
||||
self.storage_pool_id = self.configuration.sio_storage_pool_id
|
||||
@ -240,11 +239,11 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
extraspecs_limit = storage_type.get(extraspecs_key)
|
||||
if extraspecs_limit is not None:
|
||||
if qos_limit is not None:
|
||||
LOG.warning(_LW("QoS specs are overriding extra_specs."))
|
||||
LOG.warning(_LW("QoS specs are overriding extraspecs"))
|
||||
else:
|
||||
LOG.info(_LI("Using extra_specs for defining QoS specs "
|
||||
"will be deprecated in the N release "
|
||||
"of OpenStack. Please use QoS specs."))
|
||||
LOG.info(_LI("Using extraspecs for defining QoS specs "
|
||||
"will be deprecated in the next "
|
||||
"version of OpenStack, please use QoS specs"))
|
||||
return qos_limit if qos_limit is not None else extraspecs_limit
|
||||
|
||||
def _id_to_base64(self, id):
|
||||
@ -261,9 +260,8 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
encoded_name = base64.b64encode(encoded_name)
|
||||
if six.PY3:
|
||||
encoded_name = encoded_name.decode('ascii')
|
||||
LOG.debug(
|
||||
"Converted id %(id)s to scaleio name %(name)s.",
|
||||
{'id': id, 'name': encoded_name})
|
||||
LOG.debug("Converted id %(id)s to scaleio name %(name)s.",
|
||||
{'id': id, 'name': encoded_name})
|
||||
return encoded_name
|
||||
|
||||
def create_volume(self, volume):
|
||||
@ -430,8 +428,7 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
if not round_volume_capacity:
|
||||
exception_msg = (_(
|
||||
"Cannot create volume of size %s: "
|
||||
"not multiple of 8GB.") %
|
||||
size)
|
||||
"not multiple of 8GB.") % size)
|
||||
LOG.error(exception_msg)
|
||||
raise exception.VolumeBackendAPIException(data=exception_msg)
|
||||
|
||||
@ -651,18 +648,15 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
if r.status_code != OK_STATUS_CODE:
|
||||
response = r.json()
|
||||
error_code = response['errorCode']
|
||||
if error_code == 78:
|
||||
force_delete = self.configuration.sio_force_delete
|
||||
if force_delete:
|
||||
LOG.warning(_LW(
|
||||
"Ignoring error in delete volume %s:"
|
||||
" volume not found "
|
||||
"due to force delete settings."), vol_id)
|
||||
else:
|
||||
msg = (_("Error deleting volume %s: volume not found.") %
|
||||
vol_id)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
if error_code == VOLUME_NOT_FOUND_ERROR:
|
||||
LOG.warning(_LW(
|
||||
"Ignoring error in delete volume %s:"
|
||||
" Volume not found."), vol_id)
|
||||
elif vol_id is None:
|
||||
LOG.warning(_LW(
|
||||
"Volume does not have provider_id thus does not "
|
||||
"map to a ScaleIO volume. "
|
||||
"Allowing deletion to proceed."))
|
||||
else:
|
||||
msg = (_("Error deleting volume %(vol)s: %(err)s.") %
|
||||
{'vol': vol_id,
|
||||
@ -1019,6 +1013,80 @@ class ScaleIODriver(driver.VolumeDriver):
|
||||
"%(new_name)s."),
|
||||
{'vol': vol_id, 'new_name': new_name})
|
||||
|
||||
def manage_existing(self, volume, existing_ref):
|
||||
"""Manage an existing ScaleIO volume.
|
||||
|
||||
existing_ref is a dictionary of the form:
|
||||
{'source-id': <id of ScaleIO volume>}
|
||||
"""
|
||||
request = self._create_scaleio_get_volume_request(volume, existing_ref)
|
||||
r, response = self._execute_scaleio_get_request(request)
|
||||
LOG.info(_LI("Get Volume response: %s"), response)
|
||||
self._manage_existing_check_legal_response(r, existing_ref)
|
||||
if response['mappedSdcInfo'] is not None:
|
||||
reason = ("manage_existing cannot manage a volume "
|
||||
"connected to hosts. Please disconnect this volume "
|
||||
"from existing hosts before importing")
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref,
|
||||
reason=reason
|
||||
)
|
||||
return {'provider_id': response['id']}
|
||||
|
||||
def manage_existing_get_size(self, volume, existing_ref):
|
||||
request = self._create_scaleio_get_volume_request(volume, existing_ref)
|
||||
r, response = self._execute_scaleio_get_request(request)
|
||||
LOG.info(_LI("Get Volume response: %s"), response)
|
||||
self._manage_existing_check_legal_response(r, existing_ref)
|
||||
return int(response['sizeInKb'] / units.Mi)
|
||||
|
||||
def _execute_scaleio_get_request(self, request):
|
||||
r = requests.get(
|
||||
request,
|
||||
auth=(
|
||||
self.server_username,
|
||||
self.server_token),
|
||||
verify=self._get_verify_cert())
|
||||
r = self._check_response(r, request)
|
||||
response = r.json()
|
||||
return r, response
|
||||
|
||||
def _create_scaleio_get_volume_request(self, volume, existing_ref):
|
||||
"""Throws an exception if the input is invalid for manage existing.
|
||||
|
||||
if the input is valid - return a request.
|
||||
"""
|
||||
type_id = volume.get('volume_type_id')
|
||||
if 'source-id' not in existing_ref:
|
||||
reason = _("Reference must contain source-id.")
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref,
|
||||
reason=reason
|
||||
)
|
||||
if type_id is None:
|
||||
reason = _("Volume must have a volume type")
|
||||
raise exception.ManageExistingVolumeTypeMismatch(
|
||||
existing_ref=existing_ref,
|
||||
reason=reason
|
||||
)
|
||||
vol_id = existing_ref['source-id']
|
||||
req_vars = {'server_ip': self.server_ip,
|
||||
'server_port': self.server_port,
|
||||
'id': vol_id}
|
||||
request = ("https://%(server_ip)s:%(server_port)s"
|
||||
"/api/instances/Volume::%(id)s" % req_vars)
|
||||
LOG.info(_LI("ScaleIO get volume by id request: %s."), request)
|
||||
return request
|
||||
|
||||
def _manage_existing_check_legal_response(self, response, existing_ref):
|
||||
if response.status_code != OK_STATUS_CODE:
|
||||
reason = (_("Error managing volume: %s.") % response.json()[
|
||||
'message'])
|
||||
raise exception.ManageExistingInvalidReference(
|
||||
existing_ref=existing_ref,
|
||||
reason=reason
|
||||
)
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
pass
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Adds support for manage/unmanage volume in the ScaleIO driver.
|
Loading…
x
Reference in New Issue
Block a user