--- /dev/null
+# Copyright (c) 2016 EMC Corporation.\r
+# All Rights Reserved.\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
+# not use this file except in compliance with the License. You may obtain\r
+# a copy of the License at\r
+#\r
+# http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+# License for the specific language governing permissions and limitations\r
+# under the License.\r
+\r
+from cinder import context\r
+from cinder import exception\r
+from cinder.tests.unit import fake_volume\r
+from cinder.tests.unit.volume.drivers.emc import scaleio\r
+from cinder.tests.unit.volume.drivers.emc.scaleio import mocks\r
+from cinder.volume import volume_types\r
+from mock import patch\r
+from six.moves import urllib\r
+\r
+\r
+class TestManageExisting(scaleio.TestScaleIODriver):\r
+ """Test cases for ``ScaleIODriver.manage_existing()``"""\r
+\r
+ def setUp(self):\r
+ """Setup a test case environment.\r
+\r
+ Creates a fake volume object and sets up the required API responses.\r
+ """\r
+ super(TestManageExisting, self).setUp()\r
+ ctx = context.RequestContext('fake', 'fake', auth_token=True)\r
+ self.volume = fake_volume.fake_volume_obj(\r
+ ctx, **{'provider_id': 'pid_1'})\r
+ self.volume_attached = fake_volume.fake_volume_obj(\r
+ ctx, **{'provider_id': 'pid_2'})\r
+ self.volume_no_provider_id = fake_volume.fake_volume_obj(ctx)\r
+ self.volume_name_2x_enc = urllib.parse.quote(\r
+ urllib.parse.quote(self.driver._id_to_base64(self.volume.id))\r
+ )\r
+\r
+ self.HTTPS_MOCK_RESPONSES = {\r
+ self.RESPONSE_MODE.Valid: {\r
+ 'instances/Volume::' + self.volume['provider_id']:\r
+ mocks.MockHTTPSResponse({\r
+ 'id': 'pid_1',\r
+ 'sizeInKb': 8388608,\r
+ 'mappedSdcInfo': None\r
+ }, 200)\r
+ },\r
+ self.RESPONSE_MODE.BadStatus: {\r
+ 'instances/Volume::' + self.volume['provider_id']:\r
+ mocks.MockHTTPSResponse({\r
+ 'errorCode': 401,\r
+ 'message': 'BadStatus Volume Test',\r
+ }, 401),\r
+ 'instances/Volume::' + self.volume_attached['provider_id']:\r
+ mocks.MockHTTPSResponse({\r
+ 'id': 'pid_2',\r
+ 'sizeInKb': 8388608,\r
+ 'mappedSdcInfo': 'Mapped'\r
+ }, 200)\r
+ }\r
+ }\r
+\r
+ def test_no_source_id(self):\r
+ existing_ref = {'source-name': 'scaleioVolName'}\r
+ self.assertRaises(exception.ManageExistingInvalidReference,\r
+ self.driver.manage_existing, self.volume,\r
+ existing_ref)\r
+\r
+ def test_no_type_id(self):\r
+ self.volume['volume_type_id'] = None\r
+ existing_ref = {'source-id': 'pid_1'}\r
+ self.assertRaises(exception.ManageExistingVolumeTypeMismatch,\r
+ self.driver.manage_existing, self.volume,\r
+ existing_ref)\r
+\r
+ @patch.object(\r
+ volume_types,\r
+ 'get_volume_type',\r
+ return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})\r
+ def test_volume_not_found(self, _mock_volume_type):\r
+ self.volume['volume_type_id'] = 'ScaleIO'\r
+ existing_ref = {'source-id': 'pid_1'}\r
+ self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)\r
+ self.assertRaises(exception.ManageExistingInvalidReference,\r
+ self.driver.manage_existing, self.volume,\r
+ existing_ref)\r
+\r
+ @patch.object(\r
+ volume_types,\r
+ 'get_volume_type',\r
+ return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})\r
+ def test_volume_attached(self, _mock_volume_type):\r
+ self.volume_attached['volume_type_id'] = 'ScaleIO'\r
+ existing_ref = {'source-id': 'pid_2'}\r
+ self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)\r
+ self.assertRaises(exception.ManageExistingInvalidReference,\r
+ self.driver.manage_existing, self.volume_attached,\r
+ existing_ref)\r
+\r
+ @patch.object(\r
+ volume_types,\r
+ 'get_volume_type',\r
+ return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})\r
+ def test_manage_get_size_calc(self, _mock_volume_type):\r
+ self.volume['volume_type_id'] = 'ScaleIO'\r
+ existing_ref = {'source-id': 'pid_1'}\r
+ self.set_https_response_mode(self.RESPONSE_MODE.Valid)\r
+ result = self.driver.manage_existing_get_size(self.volume,\r
+ existing_ref)\r
+ self.assertEqual(8, result)\r
+\r
+ @patch.object(\r
+ volume_types,\r
+ 'get_volume_type',\r
+ return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})\r
+ def test_manage_existing_valid(self, _mock_volume_type):\r
+ self.volume['volume_type_id'] = 'ScaleIO'\r
+ existing_ref = {'source-id': 'pid_1'}\r
+ result = self.driver.manage_existing(self.volume, existing_ref)\r
+ self.assertEqual('pid_1', result['provider_id'])\r
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
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
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):
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):
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)
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,
"%(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