From 1f516ee6958781e37dd59b884b8efeb29b82a5d9 Mon Sep 17 00:00:00 2001 From: Mike Perez Date: Wed, 18 Mar 2015 21:56:39 -0700 Subject: [PATCH] Removing Coraid driver for no reported CI CI deadlines were set and pushed since last year. An email about this requirement and the deadline of March 19th 2015 has been sent to each individual driver maintainer, as well as the mailing list [1]. This driver is being removed because the maintainer has chosen not to respond to the CI deadline email and report a CI to ensure their driver integration is successful. Therfore, we can not validate the driver is working in Cinder today in a continuous way. DocImpact [1] - http://lists.openstack.org/pipermail/openstack-dev/2015-January/054614.html Change-Id: I033960c21db91c3150daeffa3dae38005b7ef39b --- cinder/tests/test_coraid.py | 904 -------------------------------- cinder/volume/drivers/coraid.py | 564 -------------------- 2 files changed, 1468 deletions(-) delete mode 100644 cinder/tests/test_coraid.py delete mode 100644 cinder/volume/drivers/coraid.py diff --git a/cinder/tests/test_coraid.py b/cinder/tests/test_coraid.py deleted file mode 100644 index 736bd6e18..000000000 --- a/cinder/tests/test_coraid.py +++ /dev/null @@ -1,904 +0,0 @@ - -# Copyright 2012 OpenStack Foundation -# 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 math - -import mock -import mox -from oslo_config import cfg -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import units - -from cinder.brick.initiator import connector -from cinder import exception -from cinder.image import image_utils -from cinder import test -from cinder import utils -from cinder.volume import configuration as conf -from cinder.volume.drivers import coraid -from cinder.volume import volume_types - - -CONF = cfg.CONF -LOG = logging.getLogger(__name__) - - -def to_coraid_kb(gb): - return math.ceil(float(gb) * units.Gi / 1000) - - -def coraid_volume_size(gb): - return '{0}K'.format(to_coraid_kb(gb)) - - -fake_esm_ipaddress = "192.168.0.1" -fake_esm_username = "darmok" -fake_esm_group = "tanagra" -fake_esm_group_id = 1 -fake_esm_password = "12345678" - -fake_coraid_repository_key = 'repository_key' - -fake_volume_name = "volume-12345678-1234-1234-1234-1234567890ab" -fake_clone_name = "volume-ffffffff-1234-1234-1234-1234567890ab" -fake_volume_size = 10 -fake_repository_name = "A-B:C:D" -fake_pool_name = "FakePool" -fake_aoetarget = 4081 -fake_shelf = 16 -fake_lun = 241 - -fake_str_aoetarget = str(fake_aoetarget) -fake_lun_addr = {"shelf": fake_shelf, "lun": fake_lun} - -fake_volume_type = {'id': 1} - -fake_volume = {"id": fake_volume_name, - "name": fake_volume_name, - "size": fake_volume_size, - "volume_type": fake_volume_type} - -fake_clone_volume = {"name": fake_clone_name, - "size": fake_volume_size, - "volume_type": fake_volume_type} - -fake_big_clone_volume = {"name": fake_clone_name, - "size": fake_volume_size + 1, - "volume_type": fake_volume_type} - -fake_volume_info = {"pool": fake_pool_name, - "repo": fake_repository_name, - "vsxidx": fake_aoetarget, - "index": fake_lun, - "shelf": fake_shelf} - -fake_lun_info = {"shelf": fake_shelf, "lun": fake_lun} - -fake_snapshot_name = "snapshot-12345678-8888-8888-1234-1234567890ab" -fake_snapshot_id = "12345678-8888-8888-1234-1234567890ab" -fake_volume_id = "12345678-1234-1234-1234-1234567890ab" -fake_snapshot = {"id": fake_snapshot_id, - "name": fake_snapshot_name, - "volume_id": fake_volume_id, - "volume_name": fake_volume_name, - "volume_size": int(fake_volume_size) - 1, - "volume": fake_volume} - -fake_configure_data = [{"addr": "cms", "data": "FAKE"}] - -fake_esm_fetch = [[ - {"command": "super_fake_command"}, - {"reply": [ - {"lv": - {"containingPool": fake_pool_name, - "lunIndex": fake_aoetarget, - "name": fake_volume_name, - "lvStatus": - {"exportedLun": - {"lun": fake_lun, - "shelf": fake_shelf}} - }, - "repoName": fake_repository_name}]}]] - -fake_esm_fetch_no_volume = [[ - {"command": "super_fake_command"}, - {"reply": []}]] - -fake_esm_success = {"category": "provider", - "tracking": False, - "configState": "completedSuccessfully", - "heldPending": False, - "metaCROp": "noAction", - "message": None} - -fake_group_fullpath = "admin group:%s" % (fake_esm_group) -fake_group_id = 4 -fake_login_reply = {"values": [ - {"fullPath": fake_group_fullpath, - "groupId": fake_group_id}], - "message": "", - "state": "adminSucceed", - "metaCROp": "noAction"} - -fake_group_fail_fullpath = "fail group:%s" % (fake_esm_group) -fake_group_fail_id = 5 -fake_login_reply_group_fail = {"values": [ - {"fullPath": fake_group_fail_fullpath, - "groupId": fake_group_fail_id}], - "message": "", - "state": "adminSucceed", - "metaCROp": "noAction"} - - -def compare(a, b): - if type(a) != type(b): - return False - if type(a) == list or type(a) == tuple: - if len(a) != len(b): - return False - return all(map(lambda t: compare(t[0], t[1]), zip(a, b))) - elif type(a) == dict: - if len(a) != len(b): - return False - for k, v in a.items(): - if not compare(v, b[k]): - return False - return True - else: - return a == b - - -def pack_data(request): - request['data'] = jsonutils.dumps(request['data']) - - -class FakeRpcBadRequest(Exception): - pass - - -class FakeRpcIsNotCalled(Exception): - def __init__(self, handle, url_params, data): - self.handle = handle - self.url_params = url_params - self.data = data - - def __str__(self): - return 'Fake Rpc handle for {0}/{1}/{2} not found'.format( - self.handle, self.url_params, self.data) - - -class FakeRpcHandle(object): - def __init__(self, handle, url_params, data, result): - self.handle = handle - self.url_params = url_params - self.data = data - self.result = result - self._is_called = False - - def set_called(self): - self._is_called = True - - def __call__(self, handle, url_params, data, - allow_empty_response=False): - if handle != self.handle: - raise FakeRpcBadRequest( - 'Unexpected handle name {0}. Expected {1}.' - .format(handle, self.handle)) - if not compare(url_params, self.url_params): - raise FakeRpcBadRequest('Unexpected url params: {0} / {1}' - .format(url_params, self.url_params)) - if not compare(data, self.data): - raise FakeRpcBadRequest('Unexpected data: {0}/{1}' - .format(data, self.data)) - if callable(self.result): - return self.result() - else: - return self.result - - -class FakeRpc(object): - def __init__(self): - self._handles = [] - - def handle(self, handle, url_params, data, result): - self._handles.append(FakeRpcHandle(handle, url_params, data, result)) - - def __call__(self, handle_name, url_params, data, - allow_empty_response=False): - for handle in self._handles: - if (handle.handle == handle_name and - compare(handle.url_params, url_params) and - compare(handle.data, handle.data)): - handle.set_called() - return handle(handle_name, url_params, data, - allow_empty_response) - raise FakeRpcIsNotCalled(handle_name, url_params, data) - - -class CoraidDriverTestCase(test.TestCase): - def setUp(self): - super(CoraidDriverTestCase, self).setUp() - configuration = mox.MockObject(conf.Configuration) - configuration.append_config_values(mox.IgnoreArg()) - configuration.coraid_default_repository = 'default_repository' - configuration.coraid_esm_address = fake_esm_ipaddress - configuration.coraid_user = fake_esm_username - configuration.coraid_group = fake_esm_group - configuration.coraid_password = fake_esm_password - configuration.volume_name_template = "volume-%s" - configuration.snapshot_name_template = "snapshot-%s" - configuration.coraid_repository_key = fake_coraid_repository_key - configuration.use_multipath_for_image_xfer = False - configuration.enforce_multipath_for_image_xfer = False - configuration.num_volume_device_scan_tries = 3 - configuration.volume_dd_blocksize = '1M' - self.fake_rpc = FakeRpc() - - self.stubs.Set(coraid.CoraidRESTClient, 'rpc', self.fake_rpc) - - self.driver = coraid.CoraidDriver(configuration=configuration) - self.driver.do_setup({}) - - def mock_volume_types(self, repositories=None): - if not repositories: - repositories = [fake_repository_name] - self.mox.StubOutWithMock(volume_types, 'get_volume_type_extra_specs') - for repository in repositories: - (volume_types - .get_volume_type_extra_specs(fake_volume_type['id'], - fake_coraid_repository_key) - .AndReturn(' {0}'.format(repository))) - - -class CoraidDriverLoginSuccessTestCase(CoraidDriverTestCase): - def setUp(self): - super(CoraidDriverLoginSuccessTestCase, self).setUp() - - login_results = {'state': 'adminSucceed', - 'values': [ - {'fullPath': - 'admin group:{0}'.format(fake_esm_group), - 'groupId': fake_esm_group_id - }]} - - self.fake_rpc.handle('admin', {'op': 'login', - 'username': fake_esm_username, - 'password': fake_esm_password}, - 'Login', login_results) - - self.fake_rpc.handle('admin', {'op': 'setRbacGroup', - 'groupId': fake_esm_group_id}, - 'Group', {'state': 'adminSucceed'}) - - -class CoraidDriverApplianceTestCase(CoraidDriverLoginSuccessTestCase): - def test_resize_volume(self): - new_volume_size = int(fake_volume_size) + 1 - - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - reply = {'configState': 'completedSuccessfully'} - - resize_volume_request = {'addr': 'cms', - 'data': { - 'lvName': fake_volume_name, - 'newLvName': fake_volume_name + '-resize', - 'size': - coraid_volume_size(new_volume_size), - 'repoName': fake_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'resize'} - pack_data(resize_volume_request) - self.fake_rpc.handle('configure', {}, [resize_volume_request], - reply) - - real_reply = self.driver.appliance.resize_volume(fake_volume_name, - new_volume_size) - - self.assertEqual(reply['configState'], real_reply['configState']) - - -class CoraidDriverIntegrationalTestCase(CoraidDriverLoginSuccessTestCase): - def setUp(self): - super(CoraidDriverIntegrationalTestCase, self).setUp() - self.appliance = self.driver.appliance - # NOTE(nsobolevsky) prevent re-creation esm appliance - self.stubs.Set(coraid.CoraidDriver, 'appliance', self.appliance) - - def test_create_volume(self): - self.mock_volume_types() - - create_volume_request = {'addr': 'cms', - 'data': { - 'servers': [], - 'size': - coraid_volume_size(fake_volume_size), - 'repoName': fake_repository_name, - 'lvName': fake_volume_name}, - 'op': 'orchStrLun', - 'args': 'add'} - pack_data(create_volume_request) - - self.fake_rpc.handle('configure', {}, [create_volume_request], - {'configState': 'completedSuccessfully', - 'firstParam': 'fake_first_param'}) - - self.mox.ReplayAll() - - self.driver.create_volume(fake_volume) - - self.mox.VerifyAll() - - @mock.patch.object(volume_types, 'get_volume_type_extra_specs') - def test_create_volume_volume_type_no_repo_key(self, volume_specs_mock): - """Test volume creation without repo specified in volume type.""" - volume_specs_mock.return_value = None - - create_volume_request = {'addr': 'cms', - 'data': { - 'servers': [], - 'size': - coraid_volume_size(fake_volume_size), - 'repoName': 'default_repository', - 'lvName': fake_volume_name}, - 'op': 'orchStrLun', - 'args': 'add'} - pack_data(create_volume_request) - - self.fake_rpc.handle('configure', {}, [create_volume_request], - {'configState': 'completedSuccessfully', - 'firstParam': 'fake_first_param'}) - - self.driver.create_volume(fake_volume) - - @mock.patch.object(volume_types, 'get_volume_type_extra_specs') - def test_create_volume_volume_type_no_repo_data(self, volume_specs_mock): - """Test volume creation w/o repo in volume type nor config.""" - volume_specs_mock.return_value = None - self.driver.configuration.coraid_default_repository = None - - create_volume_request = {'addr': 'cms', - 'data': { - 'servers': [], - 'size': - coraid_volume_size(fake_volume_size), - 'repoName': 'default_repository', - 'lvName': fake_volume_name}, - 'op': 'orchStrLun', - 'args': 'add'} - pack_data(create_volume_request) - - self.fake_rpc.handle('configure', {}, [create_volume_request], - {'configState': 'completedSuccessfully', - 'firstParam': 'fake_first_param'}) - - self.assertRaises(exception.CoraidException, - self.driver.create_volume, fake_volume) - - def test_delete_volume(self): - delete_volume_request = {'addr': 'cms', - 'data': { - 'repoName': fake_repository_name, - 'lvName': fake_volume_name}, - 'op': 'orchStrLun/verified', - 'args': 'delete'} - pack_data(delete_volume_request) - - self.fake_rpc.handle('configure', {}, [delete_volume_request], - {'configState': 'completedSuccessfully'}) - - self.fake_rpc.handle('fetch', {'orchStrRepo': '', - 'shelf': 'cms', - 'lv': fake_volume_name}, - None, - fake_esm_fetch) - - self.mox.ReplayAll() - - self.driver.delete_volume(fake_volume) - - self.mox.VerifyAll() - - def test_ping_ok(self): - self.fake_rpc.handle('fetch', {}, None, '') - - self.mox.ReplayAll() - - self.driver.appliance.ping() - - self.mox.VerifyAll() - - def test_ping_failed(self): - def rpc(handle, url_params, data, - allow_empty_response=True): - raise test.TestingException("Some exception") - - self.stubs.Set(self.driver.appliance, 'rpc', rpc) - self.mox.ReplayAll() - - self.assertRaises(exception.CoraidESMNotAvailable, - self.driver.appliance.ping) - - self.mox.VerifyAll() - - def test_delete_not_existing_lun(self): - delete_volume_request = {'addr': 'cms', - 'data': { - 'repoName': fake_repository_name, - 'lvName': fake_volume_name}, - 'op': 'orchStrLun/verified', - 'args': 'delete'} - pack_data(delete_volume_request) - - self.fake_rpc.handle('configure', {}, [delete_volume_request], - {'configState': 'completedSuccessfully'}) - - self.fake_rpc.handle('fetch', {'orchStrRepo': '', - 'shelf': 'cms', - 'lv': fake_volume_name}, - None, - fake_esm_fetch_no_volume) - - self.mox.ReplayAll() - - self.assertRaises( - exception.VolumeNotFound, - self.driver.appliance.delete_lun, - fake_volume['name']) - - self.mox.VerifyAll() - - def test_delete_not_existing_volumeappliance_is_ok(self): - def delete_lun(volume_name): - raise exception.VolumeNotFound(volume_id=fake_volume['name']) - - self.stubs.Set(self.driver.appliance, 'delete_lun', delete_lun) - - def ping(): - pass - - self.stubs.Set(self.driver.appliance, 'ping', ping) - - self.mox.ReplayAll() - - self.driver.delete_volume(fake_volume) - - self.mox.VerifyAll() - - def test_delete_not_existing_volume_sleepingappliance(self): - def delete_lun(volume_name): - raise exception.VolumeNotFound(volume_id=fake_volume['name']) - - self.stubs.Set(self.driver.appliance, 'delete_lun', delete_lun) - - def ping(): - raise exception.CoraidESMNotAvailable(reason="Any reason") - - self.stubs.Set(self.driver.appliance, 'ping', ping) - - self.driver.appliance.ping = ping - - self.mox.ReplayAll() - - self.assertRaises(exception.CoraidESMNotAvailable, - self.driver.delete_volume, - fake_volume) - - self.mox.VerifyAll() - - def test_create_snapshot(self): - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - create_snapshot_request = {'addr': 'cms', - 'data': { - 'repoName': fake_repository_name, - 'lvName': fake_volume_name, - 'newLvName': fake_snapshot_name}, - 'op': 'orchStrLunMods', - 'args': 'addClSnap'} - pack_data(create_snapshot_request) - self.fake_rpc.handle('configure', {}, [create_snapshot_request], - {'configState': 'completedSuccessfully'}) - - self.mox.ReplayAll() - - self.driver.create_snapshot(fake_snapshot) - - self.mox.VerifyAll() - - def test_delete_snapshot(self): - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_snapshot_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - delete_snapshot_request = {'addr': 'cms', - 'data': { - 'repoName': fake_repository_name, - 'lvName': fake_snapshot_name, - 'newLvName': 'noop'}, - 'op': 'orchStrLunMods', - 'args': 'delClSnap'} - pack_data(delete_snapshot_request) - self.fake_rpc.handle('configure', {}, [delete_snapshot_request], - {'configState': 'completedSuccessfully'}) - - self.mox.ReplayAll() - - self.driver.delete_snapshot(fake_snapshot) - - self.mox.VerifyAll() - - def test_create_volume_from_snapshot(self): - self.mock_volume_types() - - self.mox.StubOutWithMock(self.driver.appliance, 'resize_volume') - self.driver.appliance.resize_volume(fake_volume_name, - fake_volume['size'])\ - .AndReturn(None) - - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_snapshot_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - create_clone_request = {'addr': 'cms', - 'data': { - 'lvName': fake_snapshot_name, - 'repoName': fake_repository_name, - 'newLvName': fake_volume_name, - 'newRepoName': fake_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'addClone'} - pack_data(create_clone_request) - self.fake_rpc.handle('configure', {}, [create_clone_request], - {'configState': 'completedSuccessfully'}) - - self.mox.ReplayAll() - - self.driver.create_volume_from_snapshot(fake_volume, fake_snapshot) - - self.mox.VerifyAll() - - def test_initialize_connection(self): - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - self.mox.ReplayAll() - - connection = self.driver.initialize_connection(fake_volume, {}) - - self.mox.VerifyAll() - - self.assertEqual(connection['driver_volume_type'], 'aoe') - self.assertEqual(connection['data']['target_shelf'], fake_shelf) - self.assertEqual(connection['data']['target_lun'], fake_lun) - - def test_get_repository_capabilities(self): - reply = [[{}, {'reply': [ - {'name': 'repo1', - 'profile': - {'fullName': 'Bronze-Bronze:Profile1'}}, - {'name': 'repo2', - 'profile': - {'fullName': 'Bronze-Bronze:Profile2'}}]}]] - - self.fake_rpc.handle('fetch', {'orchStrRepo': ''}, None, - reply) - - self.mox.ReplayAll() - - capabilities = self.driver.get_volume_stats(refresh=True) - - self.mox.VerifyAll() - - self.assertEqual( - capabilities[fake_coraid_repository_key], - 'Bronze-Bronze:Profile1:repo1 Bronze-Bronze:Profile2:repo2') - - def test_create_cloned_volume(self): - self.mock_volume_types([fake_repository_name]) - - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - shelf_lun = '{0}.{1}'.format(fake_shelf, fake_lun) - create_clone_request = {'addr': 'cms', - 'data': { - 'shelfLun': shelf_lun, - 'lvName': fake_volume_name, - 'repoName': fake_repository_name, - 'newLvName': fake_clone_name, - 'newRepoName': fake_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'addClone'} - pack_data(create_clone_request) - self.fake_rpc.handle('configure', {}, [create_clone_request], - {'configState': 'completedSuccessfully'}) - - self.mox.ReplayAll() - - self.driver.create_cloned_volume(fake_clone_volume, fake_volume) - - self.mox.VerifyAll() - - def test_create_cloned_volume_with_resize(self): - self.mock_volume_types([fake_repository_name]) - - self.mox.StubOutWithMock(self.driver.appliance, 'resize_volume') - self.driver.appliance.resize_volume(fake_big_clone_volume['name'], - fake_big_clone_volume['size'])\ - .AndReturn(None) - - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - shelf_lun = '{0}.{1}'.format(fake_shelf, fake_lun) - create_clone_request = {'addr': 'cms', - 'data': { - 'shelfLun': shelf_lun, - 'lvName': fake_volume_name, - 'repoName': fake_repository_name, - 'newLvName': fake_clone_name, - 'newRepoName': fake_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'addClone'} - pack_data(create_clone_request) - self.fake_rpc.handle('configure', {}, [create_clone_request], - {'configState': 'completedSuccessfully'}) - - self.mox.ReplayAll() - - self.driver.create_cloned_volume(fake_big_clone_volume, fake_volume) - - self.mox.VerifyAll() - - def test_create_cloned_volume_in_different_repository(self): - self.mock_volume_types([fake_repository_name + '_another']) - - fetch_request = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': fake_volume_name} - self.fake_rpc.handle('fetch', fetch_request, None, - fake_esm_fetch) - - self.mox.ReplayAll() - - self.assertRaises( - exception.CoraidException, - self.driver.create_cloned_volume, - fake_clone_volume, - fake_volume) - - self.mox.VerifyAll() - - def test_extend_volume(self): - self.mox.StubOutWithMock(self.driver.appliance, 'resize_volume') - self.driver.appliance.resize_volume(fake_volume_name, 10)\ - .AndReturn(None) - - self.mox.ReplayAll() - - self.driver.extend_volume(fake_volume, 10) - - self.mox.VerifyAll() - - -class AutoReloginCoraidTestCase(test.TestCase): - def setUp(self): - super(AutoReloginCoraidTestCase, self).setUp() - self.rest_client = coraid.CoraidRESTClient('https://fake') - self.appliance = coraid.CoraidAppliance(self.rest_client, - 'fake_username', - 'fake_password', - 'fake_group') - - def _test_auto_relogin_fail(self, state): - self.mox.StubOutWithMock(self.rest_client, 'rpc') - - self.rest_client.rpc('fake_handle', {}, None, False).\ - AndReturn({'state': state, - 'metaCROp': 'reboot'}) - - self.rest_client.rpc('fake_handle', {}, None, False).\ - AndReturn({'state': state, - 'metaCROp': 'reboot'}) - - self.rest_client.rpc('fake_handle', {}, None, False).\ - AndReturn({'state': state, - 'metaCROp': 'reboot'}) - - self.mox.StubOutWithMock(self.appliance, '_ensure_session') - self.appliance._ensure_session().AndReturn(None) - - self.mox.StubOutWithMock(self.appliance, '_relogin') - self.appliance._relogin().AndReturn(None) - self.appliance._relogin().AndReturn(None) - - self.mox.ReplayAll() - - self.assertRaises(exception.CoraidESMReloginFailed, - self.appliance.rpc, - 'fake_handle', {}, None, False) - - self.mox.VerifyAll() - - def test_auto_relogin_fail_admin(self): - self._test_auto_relogin_fail('GeneralAdminFailure') - - def test_auto_relogin_fail_inactivity(self): - self._test_auto_relogin_fail('passwordInactivityTimeout') - - def test_auto_relogin_fail_absolute(self): - self._test_auto_relogin_fail('passwordAbsoluteTimeout') - - def test_auto_relogin_success(self): - self.mox.StubOutWithMock(self.rest_client, 'rpc') - - self.rest_client.rpc('fake_handle', {}, None, False).\ - AndReturn({'state': 'GeneralAdminFailure', - 'metaCROp': 'reboot'}) - - self.rest_client.rpc('fake_handle', {}, None, False).\ - AndReturn({'state': 'ok'}) - - self.mox.StubOutWithMock(self.appliance, '_ensure_session') - self.appliance._ensure_session().AndReturn(None) - - self.mox.StubOutWithMock(self.appliance, '_relogin') - self.appliance._relogin().AndReturn(None) - - self.mox.ReplayAll() - - reply = self.appliance.rpc('fake_handle', {}, None, False) - - self.mox.VerifyAll() - - self.assertEqual(reply['state'], 'ok') - - -class CoraidDriverImageTestCases(CoraidDriverTestCase): - def setUp(self): - super(CoraidDriverImageTestCases, self).setUp() - - self.fake_dev_path = '/dev/ether/fake_dev' - - self.fake_connection = {'driver_volume_type': 'aoe', - 'data': {'target_shelf': fake_shelf, - 'target_lun': fake_lun}} - - self.fake_volume_info = { - 'shelf': self.fake_connection['data']['target_shelf'], - 'lun': self.fake_connection['data']['target_lun']} - - self.mox.StubOutWithMock(self.driver, 'initialize_connection') - self.driver.initialize_connection(fake_volume, {})\ - .AndReturn(self.fake_connection) - - self.mox.StubOutWithMock(self.driver, 'terminate_connection') - self.driver.terminate_connection(fake_volume, mox.IgnoreArg(), - force=False).AndReturn(None) - - root_helper = 'sudo cinder-rootwrap /etc/cinder/rootwrap.conf' - - self.mox.StubOutWithMock(connector, 'get_connector_properties') - connector.get_connector_properties(root_helper, - CONF.my_ip, False, False).\ - AndReturn({}) - - self.mox.StubOutWithMock(utils, 'brick_get_connector') - - aoe_initiator = self.mox.CreateMockAnything() - - utils.brick_get_connector('aoe', - device_scan_attempts=3, - use_multipath=False, - conn=mox.IgnoreArg()).\ - AndReturn(aoe_initiator) - - aoe_initiator\ - .connect_volume(self.fake_connection['data'])\ - .AndReturn({'path': self.fake_dev_path}) - - aoe_initiator.check_valid_device(self.fake_dev_path, mox.IgnoreArg())\ - .AndReturn(True) - - aoe_initiator.disconnect_volume( - {'target_shelf': self.fake_volume_info['shelf'], - 'target_lun': self.fake_volume_info['lun']}, mox.IgnoreArg()) - - def test_copy_volume_to_image(self): - fake_image_service = 'fake-image-service' - fake_image_meta = 'fake-image-meta' - - self.mox.StubOutWithMock(image_utils, 'upload_volume') - image_utils.upload_volume({}, - fake_image_service, - fake_image_meta, - self.fake_dev_path) - - self.mox.ReplayAll() - self.driver.copy_volume_to_image({}, - fake_volume, - fake_image_service, - fake_image_meta) - - self.mox.VerifyAll() - - def test_copy_image_to_volume(self): - fake_image_service = 'fake-image-service' - fake_image_id = 'fake-image-id;' - - self.mox.StubOutWithMock(image_utils, 'fetch_to_raw') - image_utils.fetch_to_raw({}, - fake_image_service, - fake_image_id, - self.fake_dev_path, - mox.IgnoreArg(), - size=fake_volume_size) - - self.mox.ReplayAll() - - self.driver.copy_image_to_volume({}, - fake_volume, - fake_image_service, - fake_image_id) - - self.mox.VerifyAll() - - -class CoraidResetConnectionTestCase(CoraidDriverTestCase): - def test_create_new_appliance_for_every_request(self): - self.mox.StubOutWithMock(coraid, 'CoraidRESTClient') - self.mox.StubOutWithMock(coraid, 'CoraidAppliance') - - coraid.CoraidRESTClient(mox.IgnoreArg()) - coraid.CoraidRESTClient(mox.IgnoreArg()) - - coraid.CoraidAppliance(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn('fake_app1') - coraid.CoraidAppliance(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn('fake_app2') - self.mox.ReplayAll() - - self.assertEqual(self.driver.appliance, 'fake_app1') - self.assertEqual(self.driver.appliance, 'fake_app2') - - self.mox.VerifyAll() diff --git a/cinder/volume/drivers/coraid.py b/cinder/volume/drivers/coraid.py deleted file mode 100644 index c625a396e..000000000 --- a/cinder/volume/drivers/coraid.py +++ /dev/null @@ -1,564 +0,0 @@ -# Copyright 2012 Alyseo. -# 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. -""" -Desc : Driver to store volumes on Coraid Appliances. -Require : Coraid EtherCloud ESM, Coraid VSX and Coraid SRX. -Author : Jean-Baptiste RANSY -Author : Alex Zasimov -Author : Nikolay Sobolevsky -Contrib : Larry Matter -""" - -import cookielib -import math -import urllib -import urllib2 - -from oslo_concurrency import lockutils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import units -import six.moves.urllib.parse as urlparse - -from cinder import exception -from cinder.i18n import _ -from cinder.volume import driver -from cinder.volume import volume_types - -LOG = logging.getLogger(__name__) - -coraid_opts = [ - cfg.StrOpt('coraid_esm_address', - default='', - help='IP address of Coraid ESM'), - cfg.StrOpt('coraid_user', - default='admin', - help='User name to connect to Coraid ESM'), - cfg.StrOpt('coraid_group', - default='admin', - help='Name of group on Coraid ESM to which coraid_user belongs' - ' (must have admin privilege)'), - cfg.StrOpt('coraid_password', - default='password', - help='Password to connect to Coraid ESM', - secret=True), - cfg.StrOpt('coraid_repository_key', - default='coraid_repository', - help='Volume Type key name to store ESM Repository Name'), - cfg.StrOpt('coraid_default_repository', - help='ESM Repository Name to use if not specified in ' - 'Volume Type keys'), -] - -CONF = cfg.CONF -CONF.register_opts(coraid_opts) - - -ESM_SESSION_EXPIRED_STATES = ['GeneralAdminFailure', - 'passwordInactivityTimeout', - 'passwordAbsoluteTimeout'] - - -class CoraidRESTClient(object): - """Executes REST RPC requests on Coraid ESM EtherCloud Appliance.""" - - def __init__(self, esm_url): - self._check_esm_url(esm_url) - self._esm_url = esm_url - self._cookie_jar = cookielib.CookieJar() - self._url_opener = urllib2.build_opener( - urllib2.HTTPCookieProcessor(self._cookie_jar)) - - def _check_esm_url(self, esm_url): - splitted = urlparse.urlsplit(esm_url) - if splitted.scheme != 'https': - raise ValueError( - _('Invalid ESM url scheme "%s". Supported https only.') % - splitted.scheme) - - @lockutils.synchronized('coraid_rpc', 'cinder-', False) - def rpc(self, handle, url_params, data, allow_empty_response=False): - return self._rpc(handle, url_params, data, allow_empty_response) - - def _rpc(self, handle, url_params, data, allow_empty_response): - """Execute REST RPC using url /handle?url_params. - - Send JSON encoded data in body of POST request. - - Exceptions: - urllib2.URLError - 1. Name or service not found (e.reason is socket.gaierror) - 2. Socket blocking operation timeout (e.reason is - socket.timeout) - 3. Network IO error (e.reason is socket.error) - - urllib2.HTTPError - 1. HTTP 404, HTTP 500 etc. - - CoraidJsonEncodeFailure - bad REST response - """ - # Handle must be simple path, for example: - # /configure - if '?' in handle or '&' in handle: - raise ValueError(_('Invalid REST handle name. Expected path.')) - - # Request url includes base ESM url, handle path and optional - # URL params. - rest_url = urlparse.urljoin(self._esm_url, handle) - encoded_url_params = urllib.urlencode(url_params) - if encoded_url_params: - rest_url += '?' + encoded_url_params - - if data is None: - json_request = None - else: - json_request = jsonutils.dumps(data) - - request = urllib2.Request(rest_url, json_request) - response = self._url_opener.open(request).read() - - try: - if not response and allow_empty_response: - reply = {} - else: - reply = jsonutils.loads(response) - except (TypeError, ValueError) as exc: - msg = (_('Call to json.loads() failed: %(ex)s.' - ' Response: %(resp)s') % - {'ex': exc, 'resp': response}) - raise exception.CoraidJsonEncodeFailure(msg) - - return reply - - -def to_coraid_kb(gb): - return math.ceil(float(gb) * units.Gi / 1000) - - -def coraid_volume_size(gb): - return '{0}K'.format(to_coraid_kb(gb)) - - -class CoraidAppliance(object): - def __init__(self, rest_client, username, password, group): - self._rest_client = rest_client - self._username = username - self._password = password - self._group = group - self._logined = False - - def _login(self): - """Login into ESM. - - Perform login request and return available groups. - - :returns: dict -- map with group_name to group_id - """ - ADMIN_GROUP_PREFIX = 'admin group:' - - url_params = {'op': 'login', - 'username': self._username, - 'password': self._password} - reply = self._rest_client.rpc('admin', url_params, 'Login') - if reply['state'] != 'adminSucceed': - raise exception.CoraidESMBadCredentials() - - # Read groups map from login reply. - groups_map = {} - for group_info in reply.get('values', []): - full_group_name = group_info['fullPath'] - if full_group_name.startswith(ADMIN_GROUP_PREFIX): - group_name = full_group_name[len(ADMIN_GROUP_PREFIX):] - groups_map[group_name] = group_info['groupId'] - - return groups_map - - def _set_effective_group(self, groups_map, group): - """Set effective group. - - Use groups_map returned from _login method. - """ - try: - group_id = groups_map[group] - except KeyError: - raise exception.CoraidESMBadGroup(group_name=group) - - url_params = {'op': 'setRbacGroup', - 'groupId': group_id} - reply = self._rest_client.rpc('admin', url_params, 'Group') - if reply['state'] != 'adminSucceed': - raise exception.CoraidESMBadCredentials() - - self._logined = True - - def _ensure_session(self): - if not self._logined: - groups_map = self._login() - self._set_effective_group(groups_map, self._group) - - def _relogin(self): - self._logined = False - self._ensure_session() - - def rpc(self, handle, url_params, data, allow_empty_response=False): - self._ensure_session() - - relogin_attempts = 3 - # Do action, relogin if needed and repeat action. - while True: - reply = self._rest_client.rpc(handle, url_params, data, - allow_empty_response) - - if self._is_session_expired(reply): - relogin_attempts -= 1 - if relogin_attempts <= 0: - raise exception.CoraidESMReloginFailed() - LOG.debug('Session is expired. Relogin on ESM.') - self._relogin() - else: - return reply - - def _is_session_expired(self, reply): - return ('state' in reply and - reply['state'] in ESM_SESSION_EXPIRED_STATES and - reply['metaCROp'] == 'reboot') - - def _is_bad_config_state(self, reply): - return (not reply or - 'configState' not in reply or - reply['configState'] != 'completedSuccessfully') - - def configure(self, json_request): - reply = self.rpc('configure', {}, json_request) - if self._is_bad_config_state(reply): - # Calculate error message - if not reply: - reason = _('Reply is empty.') - else: - reason = reply.get('message', _('Error message is empty.')) - raise exception.CoraidESMConfigureError(reason=reason) - return reply - - def esm_command(self, request): - request['data'] = jsonutils.dumps(request['data']) - return self.configure([request]) - - def get_volume_info(self, volume_name): - """Retrieve volume information for a given volume name.""" - url_params = {'shelf': 'cms', - 'orchStrRepo': '', - 'lv': volume_name} - reply = self.rpc('fetch', url_params, None) - try: - volume_info = reply[0][1]['reply'][0] - except (IndexError, KeyError): - raise exception.VolumeNotFound(volume_id=volume_name) - return {'pool': volume_info['lv']['containingPool'], - 'repo': volume_info['repoName'], - 'lun': volume_info['lv']['lvStatus']['exportedLun']['lun'], - 'shelf': volume_info['lv']['lvStatus']['exportedLun']['shelf']} - - def get_volume_repository(self, volume_name): - volume_info = self.get_volume_info(volume_name) - return volume_info['repo'] - - def get_all_repos(self): - reply = self.rpc('fetch', {'orchStrRepo': ''}, None) - try: - return reply[0][1]['reply'] - except (IndexError, KeyError): - return [] - - def ping(self): - try: - self.rpc('fetch', {}, None, allow_empty_response=True) - except Exception as e: - LOG.debug('Coraid Appliance ping failed: %s', e) - raise exception.CoraidESMNotAvailable(reason=e) - - def create_lun(self, repository_name, volume_name, volume_size_in_gb): - request = {'addr': 'cms', - 'data': { - 'servers': [], - 'repoName': repository_name, - 'lvName': volume_name, - 'size': coraid_volume_size(volume_size_in_gb)}, - 'op': 'orchStrLun', - 'args': 'add'} - esm_result = self.esm_command(request) - LOG.debug('Volume "%(name)s" created with VSX LUN "%(lun)s"' % - {'name': volume_name, - 'lun': esm_result['firstParam']}) - return esm_result - - def delete_lun(self, volume_name): - repository_name = self.get_volume_repository(volume_name) - request = {'addr': 'cms', - 'data': { - 'repoName': repository_name, - 'lvName': volume_name}, - 'op': 'orchStrLun/verified', - 'args': 'delete'} - esm_result = self.esm_command(request) - LOG.debug('Volume "%s" deleted.', volume_name) - return esm_result - - def resize_volume(self, volume_name, new_volume_size_in_gb): - LOG.debug('Resize volume "%(name)s" to %(size)s GB.' % - {'name': volume_name, - 'size': new_volume_size_in_gb}) - repository = self.get_volume_repository(volume_name) - LOG.debug('Repository for volume "%(name)s" found: "%(repo)s"' % - {'name': volume_name, - 'repo': repository}) - - request = {'addr': 'cms', - 'data': { - 'lvName': volume_name, - 'newLvName': volume_name + '-resize', - 'size': coraid_volume_size(new_volume_size_in_gb), - 'repoName': repository}, - 'op': 'orchStrLunMods', - 'args': 'resize'} - esm_result = self.esm_command(request) - - LOG.debug('Volume "%(name)s" resized. New size is %(size)s GB.' % - {'name': volume_name, - 'size': new_volume_size_in_gb}) - return esm_result - - def create_snapshot(self, volume_name, snapshot_name): - volume_repository = self.get_volume_repository(volume_name) - request = {'addr': 'cms', - 'data': { - 'repoName': volume_repository, - 'lvName': volume_name, - 'newLvName': snapshot_name}, - 'op': 'orchStrLunMods', - 'args': 'addClSnap'} - esm_result = self.esm_command(request) - return esm_result - - def delete_snapshot(self, snapshot_name): - repository_name = self.get_volume_repository(snapshot_name) - request = {'addr': 'cms', - 'data': { - 'repoName': repository_name, - 'lvName': snapshot_name, - # NOTE(novel): technically, the 'newLvName' is not - # required for 'delClSnap' command. However, some - # versions of ESM have a bug that fails validation - # if we don't specify that. Hence, this fake value. - 'newLvName': "noop"}, - 'op': 'orchStrLunMods', - 'args': 'delClSnap'} - esm_result = self.esm_command(request) - return esm_result - - def create_volume_from_snapshot(self, - snapshot_name, - volume_name, - dest_repository_name): - snapshot_repo = self.get_volume_repository(snapshot_name) - request = {'addr': 'cms', - 'data': { - 'lvName': snapshot_name, - 'repoName': snapshot_repo, - 'newLvName': volume_name, - 'newRepoName': dest_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'addClone'} - esm_result = self.esm_command(request) - return esm_result - - def clone_volume(self, - src_volume_name, - dst_volume_name, - dst_repository_name): - src_volume_info = self.get_volume_info(src_volume_name) - - if src_volume_info['repo'] != dst_repository_name: - raise exception.CoraidException( - _('Cannot create clone volume in different repository.')) - - request = {'addr': 'cms', - 'data': { - 'shelfLun': '{0}.{1}'.format(src_volume_info['shelf'], - src_volume_info['lun']), - 'lvName': src_volume_name, - 'repoName': src_volume_info['repo'], - 'newLvName': dst_volume_name, - 'newRepoName': dst_repository_name}, - 'op': 'orchStrLunMods', - 'args': 'addClone'} - return self.esm_command(request) - - -class CoraidDriver(driver.VolumeDriver): - """This is the Class to set in cinder.conf (volume_driver).""" - - VERSION = '1.0.0' - - def __init__(self, *args, **kwargs): - super(CoraidDriver, self).__init__(*args, **kwargs) - self.configuration.append_config_values(coraid_opts) - - self._stats = {'driver_version': self.VERSION, - 'free_capacity_gb': 'unknown', - 'reserved_percentage': 0, - 'storage_protocol': 'aoe', - 'total_capacity_gb': 'unknown', - 'vendor_name': 'Coraid'} - backend_name = self.configuration.safe_get('volume_backend_name') - self._stats['volume_backend_name'] = backend_name or 'EtherCloud ESM' - - @property - def appliance(self): - # NOTE(nsobolevsky): This is workaround for bug in the ESM appliance. - # If there is a lot of request with the same session/cookie/connection, - # the appliance could corrupt all following request in session. - # For that purpose we just create a new appliance. - esm_url = "https://{0}:8443".format( - self.configuration.coraid_esm_address) - - return CoraidAppliance(CoraidRESTClient(esm_url), - self.configuration.coraid_user, - self.configuration.coraid_password, - self.configuration.coraid_group) - - def check_for_setup_error(self): - """Return an error if prerequisites aren't met.""" - self.appliance.ping() - - def _get_repository(self, volume_type): - """Get the ESM Repository from the Volume Type. - - The ESM Repository is stored into a volume_type_extra_specs key. - """ - volume_type_id = volume_type['id'] - repository_key_name = self.configuration.coraid_repository_key - repository = (volume_types.get_volume_type_extra_specs( - volume_type_id, repository_key_name) or - self.configuration.coraid_default_repository) - - # if there's no repository still, we cannot move forward - if not repository: - message = ("The Coraid repository not specified neither " - "in Volume Type '%s' key nor in the " - "'coraid_default_repository' config option" % - self.configuration.coraid_repository_key) - raise exception.CoraidException(message=message) - - # Remove keyword from repository name if needed - if repository.startswith(' '): - return repository[len(' '):] - else: - return repository - - def create_volume(self, volume): - """Create a Volume.""" - repository = self._get_repository(volume['volume_type']) - self.appliance.create_lun(repository, volume['name'], volume['size']) - - def create_cloned_volume(self, volume, src_vref): - dst_volume_repository = self._get_repository(volume['volume_type']) - - self.appliance.clone_volume(src_vref['name'], - volume['name'], - dst_volume_repository) - - if volume['size'] != src_vref['size']: - self.appliance.resize_volume(volume['name'], volume['size']) - - def delete_volume(self, volume): - """Delete a Volume.""" - try: - self.appliance.delete_lun(volume['name']) - except exception.VolumeNotFound: - self.appliance.ping() - - def create_snapshot(self, snapshot): - """Create a Snapshot.""" - volume_name = snapshot['volume_name'] - snapshot_name = snapshot['name'] - self.appliance.create_snapshot(volume_name, snapshot_name) - - def delete_snapshot(self, snapshot): - """Delete a Snapshot.""" - snapshot_name = snapshot['name'] - self.appliance.delete_snapshot(snapshot_name) - - def create_volume_from_snapshot(self, volume, snapshot): - """Create a Volume from a Snapshot.""" - snapshot_name = snapshot['name'] - repository = self._get_repository(volume['volume_type']) - self.appliance.create_volume_from_snapshot(snapshot_name, - volume['name'], - repository) - if volume['size'] > snapshot['volume_size']: - self.appliance.resize_volume(volume['name'], volume['size']) - - def extend_volume(self, volume, new_size): - """Extend an existing volume.""" - self.appliance.resize_volume(volume['name'], new_size) - - def initialize_connection(self, volume, connector): - """Return connection information.""" - volume_info = self.appliance.get_volume_info(volume['name']) - - shelf = volume_info['shelf'] - lun = volume_info['lun'] - - LOG.debug('Initialize connection %(shelf)s/%(lun)s for %(name)s' % - {'shelf': shelf, - 'lun': lun, - 'name': volume['name']}) - - aoe_properties = {'target_shelf': shelf, - 'target_lun': lun} - - return {'driver_volume_type': 'aoe', - 'data': aoe_properties} - - def _get_repository_capabilities(self): - repos_list = map(lambda i: i['profile']['fullName'] + ':' + i['name'], - self.appliance.get_all_repos()) - return ' '.join(repos_list) - - def update_volume_stats(self): - capabilities = self._get_repository_capabilities() - self._stats[self.configuration.coraid_repository_key] = capabilities - - def get_volume_stats(self, refresh=False): - """Return Volume Stats.""" - if refresh: - self.update_volume_stats() - return self._stats - - def local_path(self, volume): - pass - - def create_export(self, context, volume): - pass - - def remove_export(self, context, volume): - pass - - def terminate_connection(self, volume, connector, **kwargs): - pass - - def ensure_export(self, context, volume): - pass -- 2.45.2