EMC ScaleIO Cinder Driver

This patch adds a Cinder volume driver for EMC ScaleIO product.
The ScaleIO driver supports the following Cinder features:
* Create/delete volume
* Create/delete snapshot
* Create volume from snapshot
* Create cloned volume
* Extend volume

implements blueprint scaleio-cinder-volume-driver

Change-Id: I7007e4ec36586d98cbf53e4f322ab25aa9b414c6
This commit is contained in:
Xing Yang 2015-05-16 00:31:51 -04:00
parent e2de169c6e
commit 185abac1c2
12 changed files with 2252 additions and 0 deletions

View File

@ -0,0 +1,161 @@
# Copyright (c) 2013 - 2015 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.
import copy
import requests
from cinder import test
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
class CustomResponseMode(object):
"""A context manager to define a custom set of per-request response modes.
Example:
with CustomResponseMode(self, **{
'some/api/path': RESPONSE_MODE.Valid,
'another/api/path': RESPONSE_MODE.BadStatus,
'last/api/path': MockResponse('some data',
status_code=403),
}):
self.assertRaises(SomeException, self.driver.api_call, data)
"""
def __init__(self, test_instance, **kwargs):
self.test_instance = test_instance
self.custom_responses = kwargs
self.current_responses = None
def __enter__(self):
self.current_responses = self.test_instance.HTTPS_MOCK_RESPONSES
https_responses = copy.deepcopy(
self.test_instance.HTTPS_MOCK_RESPONSES
)
current_mode = self.test_instance.current_https_response_mode
for call, new_mode in self.custom_responses.items():
if isinstance(new_mode, mocks.MockHTTPSResponse):
https_responses[current_mode][call] = new_mode
else:
https_responses[current_mode][call] = \
self.test_instance.get_https_response(call, new_mode)
self.test_instance.HTTPS_MOCK_RESPONSES = https_responses
def __exit__(self, exc_type, exc_val, exc_tb):
self.test_instance.HTTPS_MOCK_RESPONSES = self.current_responses
class TestScaleIODriver(test.TestCase):
"""Base ``TestCase`` subclass for the ``ScaleIODriver``"""
RESPONSE_MODE = type(str('ResponseMode'), (object, ), dict(
Valid='0',
Invalid='1',
BadStatus='2',
))
__RESPONSE_MODE_NAMES = {
'0': 'Valid',
'1': 'Invalid',
'2': 'BadStatus',
}
BAD_STATUS_RESPONSE = mocks.MockHTTPSResponse(
{
'errorCode': 500,
'message': 'BadStatus Response Test',
}, 500
)
HTTPS_MOCK_RESPONSES = {}
__COMMON_HTTPS_MOCK_RESPONSES = {
RESPONSE_MODE.Valid: {
'login': 'login_token',
},
RESPONSE_MODE.BadStatus: {
'login': mocks.MockHTTPSResponse(
{
'errorCode': 403,
'message': 'Bad Login Response Test',
}, 403
),
},
}
__https_response_mode = RESPONSE_MODE.Valid
log = None
def setUp(self):
"""Setup a test case environment.
Creates a ``ScaleIODriver`` instance
Mocks the ``requests.get/post`` methods to return
``MockHTTPSResponse``'s instead.
"""
super(TestScaleIODriver, self).setUp()
self.driver = mocks.ScaleIODriver()
self.mock_object(requests, 'get', self.do_request)
self.mock_object(requests, 'post', self.do_request)
def do_request(self, url, *args, **kwargs):
"""Do a fake GET/POST API request.
Splits `url` on '/api/' to get the what API call is, then returns
the value of `self.HTTPS_MOCK_RESPONSES[<response_mode>][<api_call>]`
converting to a `MockHTTPSResponse` if necessary.
:raises test.TestingException: If the current mode/api_call does not
exist.
:returns MockHTTPSResponse:
"""
return self.get_https_response(url.split('/api/')[1])
def set_https_response_mode(self, mode=RESPONSE_MODE.Valid):
"""Set the HTTPS response mode.
RESPONSE_MODE.Valid: Respond with valid data
RESPONSE_MODE.Invalid: Respond with invalid data
RESPONSE_MODE.BadStatus: Response with not-OK status code.
"""
self.__https_response_mode = mode
def get_https_response(self, api_path, mode=None):
if mode is None:
mode = self.__https_response_mode
try:
response = self.HTTPS_MOCK_RESPONSES[mode][api_path]
except KeyError:
try:
response = self.__COMMON_HTTPS_MOCK_RESPONSES[mode][api_path]
except KeyError:
raise test.TestingException(
'Mock API Endpoint not implemented: [{}]{}'.format(
self.__RESPONSE_MODE_NAMES[mode], api_path
)
)
if not isinstance(response, mocks.MockHTTPSResponse):
return mocks.MockHTTPSResponse(response, 200)
return response
@property
def current_https_response_mode(self):
return self.__https_response_mode
def https_response_mode_name(self, mode):
return self.__RESPONSE_MODE_NAMES[mode]
def custom_response_mode(self, **kwargs):
return CustomResponseMode(self, **kwargs)

View File

@ -0,0 +1,116 @@
# Copyright (c) 2013 - 2015 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.
import json
import requests
import six
from cinder.volume import configuration as conf
from cinder.volume.drivers.emc import scaleio
from oslo_config import cfg
class ScaleIODriver(scaleio.ScaleIODriver):
"""Mock ScaleIO Driver class.
Provides some fake configuration options
"""
def __init__(self, *args, **kwargs):
configuration = conf.Configuration(
[
cfg.StrOpt('fake', default=None),
],
None
)
# Override the defaults to fake values
configuration.set_override('san_ip', override='127.0.0.1')
configuration.set_override('sio_rest_server_port', override='8888')
configuration.set_override('san_login', override='test')
configuration.set_override('san_password', override='pass')
configuration.set_override('sio_storage_pool_id', override='test_pool')
configuration.set_override('sio_protection_domain_id',
override='test_domain')
configuration.set_override('sio_storage_pools',
override='test_domain:test_pool')
super(ScaleIODriver, self).__init__(configuration=configuration,
*args,
**kwargs)
def update_consistencygroup(self, context, group, add_volumes=None,
remove_volumes=None):
pass
def local_path(self, volume):
pass
def reenable_replication(self, context, volume):
pass
def manage_existing(self, volume, existing_ref):
pass
def promote_replica(self, context, volume):
pass
def delete_consistencygroup(self, context, group):
pass
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None):
pass
def create_replica_test_volume(self, volume, src_vref):
pass
def create_consistencygroup(self, context, group):
pass
def manage_existing_get_size(self, volume, existing_ref):
pass
def unmanage(self, volume):
pass
def create_cgsnapshot(self, context, cgsnapshot):
pass
def delete_cgsnapshot(self, context, cgsnapshot):
pass
class MockHTTPSResponse(requests.Response):
"""Mock HTTP Response
Defines the https replies from the mocked calls to do_request()
"""
def __init__(self, content, status_code=200):
super(MockHTTPSResponse, self).__init__()
self._content = content
self.status_code = status_code
def json(self, **kwargs):
if isinstance(self._content, six.string_types):
return super(MockHTTPSResponse, self).json(**kwargs)
return self._content
@property
def text(self):
if not isinstance(self._content, six.string_types):
return json.dumps(self._content)
return super(MockHTTPSResponse, self).text

View File

@ -0,0 +1,91 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
from cinder import context
from cinder import exception
from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.emc import scaleio
class TestCreateClonedVolume(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.create_cloned_volume()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
def setUp(self):
"""Setup a test case environment.
Creates fake volume objects and sets up the required API responses.
"""
super(TestCreateClonedVolume, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.src_volume = fake_volume.fake_volume_obj(ctx)
self.src_volume_name_2x_enc = urllib.quote(
urllib.quote(
self.driver.id_to_base64(self.src_volume.id)
)
)
self.new_volume = fake_volume.fake_volume_obj(
ctx, **{'id': 'cloned', 'name': 'cloned_volume'}
)
self.new_volume_name_2x_enc = urllib.quote(
urllib.quote(
self.driver.id_to_base64(self.new_volume.id)
)
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.src_volume_name_2x_enc: self.src_volume.id,
'instances/System/action/snapshotVolumes': '"{}"'.format(
self.new_volume.id
),
},
self.RESPONSE_MODE.BadStatus: {
'instances/System/action/snapshotVolumes::':
self.BAD_STATUS_RESPONSE,
'types/Volume/instances/getByName::' +
self.src_volume_name_2x_enc: self.BAD_STATUS_RESPONSE,
},
self.RESPONSE_MODE.Invalid: {
'types/Volume/instances/getByName::' +
self.src_volume_name_2x_enc: None,
},
}
def test_bad_login(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cloned_volume,
self.new_volume, self.src_volume)
def test_invalid_source_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cloned_volume,
self.new_volume, self.src_volume)
def test_create_cloned_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.driver.create_cloned_volume(self.new_volume, self.src_volume)

View File

@ -0,0 +1,103 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
from cinder import context
from cinder import exception
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit.volume.drivers.emc import scaleio
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
class TestCreateSnapShot(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.create_snapshot()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
def setUp(self):
"""Setup a test case environment.
Creates fake volume and snapshot objects and sets up the required
API responses.
"""
super(TestCreateSnapShot, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.snapshot = fake_snapshot.fake_snapshot_obj(ctx)
self.volume_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.snapshot.volume_id))
)
self.snapshot_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.snapshot.id))
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: '"{}"'.format(
self.snapshot.volume_id
),
'instances/System/action/snapshotVolumes': self.snapshot.id,
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: self.snapshot.id,
},
self.RESPONSE_MODE.BadStatus: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: self.BAD_STATUS_RESPONSE,
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: mocks.MockHTTPSResponse(
{
'errorCode': 401,
'message': 'BadStatus Snapshot Test',
}, 401
),
},
self.RESPONSE_MODE.Invalid: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: None,
'instances/System/action/snapshotVolumes':
mocks.MockHTTPSResponse(
{
'errorCode': 400,
'message': 'Invalid Volume Snapshot Test'
}, 400
),
},
}
def test_bad_login(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.create_snapshot,
self.snapshot
)
def test_invalid_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.create_snapshot,
self.snapshot
)
def test_create_snapshot(self):
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.driver.create_snapshot(self.snapshot)

View File

@ -0,0 +1,138 @@
# Copyright (c) 2013 - 2015 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
class TestCreateVolume(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.create_volume()``"""
PROTECTION_DOMAIN_ID = 'test_prot_id'
PROTECTION_DOMAIN_NAME = 'test_prot_name'
STORAGE_POOL_ID = 'test_pool_id'
STORAGE_POOL_NAME = 'test_pool_name'
def setUp(self):
"""Setup a test case environment.
Creates a fake volume object and sets up the required API responses.
"""
super(TestCreateVolume, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.volume = fake_volume.fake_volume_obj(ctx)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.volume.name: '"{}"'.format(self.volume.id),
'types/Volume/instances': [
{
'Id': self.volume.id,
'Name': self.volume.name,
'volumeSizeInKb': self.volume.size,
'isObfuscated': False,
'creationTime': self.volume.launched_at,
'mappingToAllSdcsEnabled': False,
'mappingSdcInfoList': [],
'mappingScsiInitiatorList': [],
'ancestorVolumeId': '',
'vtreeId': '',
'storagePoolId': self.STORAGE_POOL_ID,
'useRmcache': False,
}
],
'types/Domain/instances/getByName::' +
self.PROTECTION_DOMAIN_NAME:
'"{}"'.format(self.PROTECTION_DOMAIN_ID),
'types/Pool/instances/getByName::{},{}'.format(
self.PROTECTION_DOMAIN_ID,
self.STORAGE_POOL_NAME
): '"{}"'.format(self.STORAGE_POOL_ID),
},
self.RESPONSE_MODE.Invalid: {
'types/Domain/instances/getByName::' +
self.PROTECTION_DOMAIN_NAME: None,
'types/Pool/instances/getByName::{},{}'.format(
self.PROTECTION_DOMAIN_ID,
self.STORAGE_POOL_NAME
): None,
},
self.RESPONSE_MODE.BadStatus: {
'types/Volume/instances': self.BAD_STATUS_RESPONSE,
'types/Domain/instances/getByName::' +
self.PROTECTION_DOMAIN_NAME: self.BAD_STATUS_RESPONSE,
'types/Pool/instances/getByName::{},{}'.format(
self.PROTECTION_DOMAIN_ID,
self.STORAGE_POOL_NAME
): self.BAD_STATUS_RESPONSE,
},
}
def test_no_domain(self):
"""No protection domain name or ID provided."""
self.driver.protection_domain_name = None
self.driver.protection_domain_id = None
self.assertRaises(exception.VolumeBackendAPIException,
self.test_create_volume)
def test_no_domain_id(self):
"""Only protection domain name provided."""
self.driver.protection_domain_id = None
self.driver.protection_domain_name = self.PROTECTION_DOMAIN_NAME
self.driver.storage_pool_name = None
self.driver.storage_pool_id = self.STORAGE_POOL_ID
self.test_create_volume()
def test_no_domain_id_invalid_response(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.test_no_domain_id)
def test_no_domain_id_badstatus_response(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.test_no_domain_id)
def test_no_storage_id(self):
"""Only protection domain name provided."""
self.driver.storage_pool_id = None
self.driver.storage_pool_name = self.STORAGE_POOL_NAME
self.driver.protection_domain_id = self.PROTECTION_DOMAIN_ID
self.driver.protection_domain_name = None
self.test_create_volume()
def test_no_storage_id_invalid_response(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.test_no_storage_id)
def test_no_storage_id_badstatus_response(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.test_no_storage_id)
def test_create_volume(self):
"""Valid create volume parameters"""
self.driver.create_volume(self.volume)
def test_create_volume_badstatus_response(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.test_create_volume)

View File

@ -0,0 +1,91 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
from cinder import context
from cinder import exception
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.emc import scaleio
class TestCreateVolumeFromSnapShot(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.create_volume_from_snapshot()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
def setUp(self):
"""Setup a test case environment.
Creates fake volume and snapshot objects and sets up the required
API responses.
"""
super(TestCreateVolumeFromSnapShot, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.snapshot = fake_snapshot.fake_snapshot_obj(ctx)
self.snapshot_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.snapshot.id))
)
self.volume = fake_volume.fake_volume_obj(ctx)
self.volume_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.volume.id))
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: self.snapshot.id,
'instances/System/action/snapshotVolumes': self.volume.id,
},
self.RESPONSE_MODE.BadStatus: {
'instances/System/action/snapshotVolumes::':
self.BAD_STATUS_RESPONSE,
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: self.BAD_STATUS_RESPONSE,
self.snapshot_name_2x_enc: self.BAD_STATUS_RESPONSE,
},
self.RESPONSE_MODE.Invalid: {
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: None,
},
}
def test_bad_login(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
self.volume,
self.snapshot
)
def test_invalid_snapshot(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(
exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
self.volume,
self.snapshot
)
def test_create_volume_from_snapshot(self):
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.driver.create_volume_from_snapshot(self.volume, self.snapshot)

View File

@ -0,0 +1,99 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
from cinder import context
from cinder import exception
from cinder.tests.unit.fake_snapshot import fake_snapshot_obj
from cinder.tests.unit.volume.drivers.emc import scaleio
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
class TestDeleteSnapShot(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.delete_snapshot()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
VOLUME_NOT_FOUND_ERROR = 3
def setUp(self):
"""Setup a test case environment.
Creates fake volume and snapshot objects and sets up the required
API responses.
"""
super(TestDeleteSnapShot, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.snapshot = fake_snapshot_obj(ctx)
self.snapshot_name_2x_enc = urllib.quote(
urllib.quote(
self.driver.id_to_base64(self.snapshot.id)
)
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: self.snapshot.id,
'instances/Volume::{}/action/removeMappedSdc'.format(
self.snapshot.id
): self.snapshot.id,
'instances/Volume::{}/action/removeVolume'.format(
self.snapshot.id
): self.snapshot.id,
},
self.RESPONSE_MODE.BadStatus: {
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: self.BAD_STATUS_RESPONSE,
},
self.RESPONSE_MODE.Invalid: {
'types/Volume/instances/getByName::' +
self.snapshot_name_2x_enc: mocks.MockHTTPSResponse(
{
'errorCode': self.VOLUME_NOT_FOUND_ERROR,
'message': 'Test Delete Invalid Snapshot',
}, 400
),
},
}
def test_bad_login(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_snapshot, self.snapshot)
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.driver.delete_snapshot(self.snapshot)
def test_delete_invalid_snapshot(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_snapshot, self.snapshot)
def test_delete_snapshot(self):
"""Setting the unmap volume before delete flag for tests """
self.driver.configuration.set_override(
'sio_unmap_volume_before_deletion',
override=True)
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.driver.delete_snapshot(self.snapshot)

View File

@ -0,0 +1,79 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
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
class TestDeleteVolume(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.delete_volume()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
def setUp(self):
"""Setup a test case environment.
Creates a fake volume object and sets up the required API responses.
"""
super(TestDeleteVolume, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.volume = fake_volume.fake_volume_obj(ctx)
self.volume_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.volume.id))
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: self.volume.id,
'instances/Volume::{}/action/removeMappedSdc'.format(
self.volume.id): self.volume.id,
'instances/Volume::{}/action/removeVolume'.format(
self.volume.id
): self.volume.id,
},
self.RESPONSE_MODE.BadStatus: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: mocks.MockHTTPSResponse(
{
'errorCode': 401,
'message': 'BadStatus Volume Test',
}, 401
),
},
}
def test_bad_login_and_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_volume,
self.volume)
def test_delete_volume(self):
"""Setting the unmap volume before delete flag for tests """
self.driver.configuration.set_override(
'sio_unmap_volume_before_deletion',
override=True)
self.driver.delete_volume(self.volume)

View File

@ -0,0 +1,110 @@
# Copyright (c) 2013 - 2015 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.
import urllib
import six
from cinder import context
from cinder import exception
from cinder.tests.unit.fake_volume import fake_volume_obj
from cinder.tests.unit.volume.drivers.emc import scaleio
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
class TestExtendVolume(scaleio.TestScaleIODriver):
"""Test cases for ``ScaleIODriver.extend_volume()``"""
STORAGE_POOL_ID = six.text_type('1')
STORAGE_POOL_NAME = 'SP1'
PROT_DOMAIN_ID = six.text_type('1')
PROT_DOMAIN_NAME = 'PD1'
""" New sizes for the volume.
Since ScaleIO has a granularity of 8 GB, multiples of 8 always work.
The 7 size should be either rounded up to 8 or raise an exception
based on the round_volume_capacity config setting.
"""
NEW_SIZE = 8
BAD_SIZE = 7
def setUp(self):
"""Setup a test case environment.
Creates fake volume object and sets up the required API responses.
"""
super(TestExtendVolume, self).setUp()
ctx = context.RequestContext('fake', 'fake', auth_token=True)
self.volume = fake_volume_obj(ctx, **{'id': 'fake_volume'})
self.volume_name_2x_enc = urllib.quote(
urllib.quote(self.driver.id_to_base64(self.volume.id))
)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: '"{}"'.format(self.volume.id),
'instances/Volume::{}/action/setVolumeSize'.format(
self.volume.id
): 'OK',
},
self.RESPONSE_MODE.BadStatus: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: self.BAD_STATUS_RESPONSE,
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: mocks.MockHTTPSResponse(
{
'errorCode': 401,
'message': 'BadStatus Extend Volume Test',
}, 401
),
},
self.RESPONSE_MODE.Invalid: {
'types/Volume/instances/getByName::' +
self.volume_name_2x_enc: None,
},
}
def test_bad_login(self):
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.extend_volume,
self.volume,
self.NEW_SIZE)
def test_invalid_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.extend_volume,
self.volume,
self.NEW_SIZE)
def test_extend_volume_bad_size_no_round(self):
self.driver.configuration.set_override('sio_round_volume_capacity',
override=False)
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.extend_volume,
self.volume,
self.BAD_SIZE)
def test_extend_volume_bad_size_round(self):
self.driver.configuration.set_override('sio_round_volume_capacity',
override=True)
self.driver.extend_volume(self.volume, self.BAD_SIZE)
def test_extend_volume(self):
self.set_https_response_mode(self.RESPONSE_MODE.Valid)
self.driver.extend_volume(self.volume, self.NEW_SIZE)

View File

@ -0,0 +1,110 @@
# Copyright (c) 2013 - 2015 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.
import urllib
from cinder import exception
from cinder.tests.unit.volume.drivers.emc import scaleio
class TestMisc(scaleio.TestScaleIODriver):
DOMAIN_NAME = 'PD1'
POOL_NAME = 'SP1'
STORAGE_POOLS = ['{}:{}'.format(DOMAIN_NAME, POOL_NAME)]
def setUp(self):
"""Set up the test case environment.
Defines the mock HTTPS responses for the REST API calls.
"""
super(TestMisc, self).setUp()
self.domain_name_enc = urllib.quote(self.DOMAIN_NAME)
self.pool_name_enc = urllib.quote(self.POOL_NAME)
self.HTTPS_MOCK_RESPONSES = {
self.RESPONSE_MODE.Valid: {
'types/Domain/instances/getByName::' +
self.domain_name_enc: '"{}"'.format(self.DOMAIN_NAME).encode(
'ascii',
'ignore'
),
'types/Pool/instances/getByName::{},{}'.format(
self.DOMAIN_NAME,
self.POOL_NAME
): '"{}"'.format(self.POOL_NAME).encode('ascii', 'ignore'),
'types/StoragePool/instances/action/querySelectedStatistics': {
'"{}"'.format(self.POOL_NAME): {
'capacityInUseInKb': 502,
'capacityLimitInKb': 1024,
},
},
},
self.RESPONSE_MODE.BadStatus: {
'types/Domain/instances/getByName::' +
self.domain_name_enc: self.BAD_STATUS_RESPONSE,
},
self.RESPONSE_MODE.Invalid: {
'types/Domain/instances/getByName::' +
self.domain_name_enc: None,
},
}
def test_valid_configuration(self):
self.driver.check_for_setup_error()
def test_both_storage_pool(self):
"""Both storage name and ID provided."""
self.driver.storage_pool_id = "test_pool_id"
self.driver.storage_pool_name = "test_pool_name"
self.assertRaises(exception.InvalidInput,
self.driver.check_for_setup_error)
def test_no_storage_pool(self):
"""No storage name or ID provided."""
self.driver.storage_pool_name = None
self.driver.storage_pool_id = None
self.assertRaises(exception.InvalidInput,
self.driver.check_for_setup_error)
def test_both_domain(self):
self.driver.protection_domain_name = "test_domain_name"
self.driver.protection_domain_id = "test_domain_id"
self.assertRaises(exception.InvalidInput,
self.driver.check_for_setup_error)
def test_volume_size_round_true(self):
self.driver._check_volume_size(1)
def test_volume_size_round_false(self):
self.driver.configuration.set_override('sio_round_volume_capacity',
override=False)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver._check_volume_size, 1)
def test_get_volume_stats_bad_status(self):
self.driver.storage_pools = self.STORAGE_POOLS
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.get_volume_stats, True)
def test_get_volume_stats_invalid_domain(self):
self.driver.storage_pools = self.STORAGE_POOLS
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.get_volume_stats, True)
def test_get_volume_stats(self):
self.driver.storage_pools = self.STORAGE_POOLS
self.driver.get_volume_stats(True)

File diff suppressed because it is too large Load Diff