From: Mike Perez Date: Fri, 5 Jun 2015 23:14:33 +0000 (-0700) Subject: Revert "Adds drivers for DotHill Storage Arrays." X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=2e77756ce03f4bb6eb87bb6cc6224d21167d78d5;p=openstack-build%2Fcinder-build.git Revert "Adds drivers for DotHill Storage Arrays." Coordinating with the DotHill folks on some corrections that need to be made for this patch. This reverts commit f09c4b1aa0d76d5063d63b12a597da7fead0b93e. Change-Id: I24a8892ae97b7d874fdfeab50065577f7857c8c6 --- diff --git a/cinder/exception.py b/cinder/exception.py index 011577938..5f4a9f5ca 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -901,28 +901,3 @@ class WebDAVClientError(CinderException): # XtremIO Drivers class XtremIOAlreadyMappedError(CinderException): message = _("Volume to Initiator Group mapping already exists") - - -# DOTHILL -class DotHillInvalidBackend(CinderException): - message = _("Backend doesn't exist (%(backend)s)") - - -class DotHillConnectionError(CinderException): - message = _("%(message)s") - - -class DotHillAuthenticationError(CinderException): - message = _("%(message)s") - - -class DotHillNotEnoughSpace(CinderException): - message = _("Not enough space on backend (%(backend)s)") - - -class DotHillRequestError(CinderException): - message = _("%(message)s") - - -class DotHillNotTargetPortal(CinderException): - message = _("No active iscsi portals with supplied iscsi ips") diff --git a/cinder/tests/unit/volume/drivers/test_dothill.py b/cinder/tests/unit/volume/drivers/test_dothill.py deleted file mode 100644 index f0e71c5b4..000000000 --- a/cinder/tests/unit/volume/drivers/test_dothill.py +++ /dev/null @@ -1,735 +0,0 @@ -# (c) Copyright 2015 DotHill Systems -# -# 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. - -"""Unit tests for OpenStack Cinder DotHill driver.""" - -import urllib2 - -from lxml import etree -import mock - -from cinder import exception -from cinder import test -from cinder.volume.drivers.dothill import dothill_client as dothill -from cinder.volume.drivers.dothill import dothill_common -from cinder.volume.drivers.dothill import dothill_fc -from cinder.volume.drivers.dothill import dothill_iscsi -from cinder.zonemanager import utils as fczm_utils - -session_key = '12a1626754554a21d85040760c81b' -resp_login = ''' - success - 0 - 12a1626754554a21d85040760c81b - 1''' -resp_badlogin = ''' - error - 1 - Authentication failure - 1''' -response_ok = ''' - some data - 0 - ''' -response_not_ok = ''' - Error Message - 1 - ''' -response_stats_linear = ''' - 3863830528 - 3863830528 - ''' -response_stats_realstor = ''' - 3863830528 - 3863830528 - ''' -response_no_lun = '''''' -response_lun = ''' - 1 - - 4''' -response_ports = ''' - - FC - id1 - Disconnected - - FC - id2 - Up - - iSCSI - id3 - 10.0.0.10 - Disconnected - - iSCSI - id4 - 10.0.0.11 - Up - - iSCSI - id5 - 10.0.0.12 - Up - ''' - -response_ports_linear = response_ports % {'ip': 'primary-ip-address'} -response_ports_realstor = response_ports % {'ip': 'ip-address'} - - -invalid_xml = '''''' -malformed_xml = '''''' -fake_xml = '''''' - -stats_low_space = {'free_capacity_gb': 10, 'total_capacity_gb': 100} -stats_large_space = {'free_capacity_gb': 90, 'total_capacity_gb': 100} - -vol_id = 'fceec30e-98bc-4ce5-85ff-d7309cc17cc2' -test_volume = {'id': vol_id, 'name_id': None, - 'display_name': 'test volume', 'name': 'volume', 'size': 10} -test_retype_volume = {'attach_status': 'available', 'id': vol_id, - 'name_id': None, 'display_name': 'test volume', - 'name': 'volume', 'size': 10} -test_host = {'capabilities': {'location_info': - 'DotHillVolumeDriver:xxxxx:dg02:A'}} -test_snap = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', - 'volume': {'name_id': None}, - 'volume_id': vol_id, - 'display_name': 'test volume', 'name': 'volume', 'size': 10} -encoded_volid = 'v_O7DDpi8TOWF_9cwnMF' -encoded_snapid = 's_O7DDpi8TOWF_9cwnMF' -dest_volume = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', - 'source_volid': vol_id, - 'display_name': 'test volume', 'name': 'volume', 'size': 10} -attached_volume = {'id': vol_id, - 'display_name': 'test volume', 'name': 'volume', - 'size': 10, 'status': 'in-use', - 'attach_status': 'attached'} -attaching_volume = {'id': vol_id, - 'display_name': 'test volume', 'name': 'volume', - 'size': 10, 'status': 'attaching', - 'attach_status': 'attached'} -detached_volume = {'id': vol_id, 'name_id': None, - 'display_name': 'test volume', 'name': 'volume', - 'size': 10, 'status': 'available', - 'attach_status': 'detached'} - -connector = {'ip': '10.0.0.2', - 'initiator': 'iqn.1993-08.org.debian:01:222', - 'wwpns': ["111111111111111", "111111111111112"], - 'wwnns': ["211111111111111", "211111111111112"], - 'host': 'fakehost'} -invalid_connector = {'ip': '10.0.0.2', - 'initiator': '', - 'wwpns': [], - 'wwnns': [], - 'host': 'fakehost'} - - -class TestDotHillClient(test.TestCase): - def setUp(self): - super(TestDotHillClient, self).setUp() - self.login = 'manage' - self.passwd = '!manage' - self.ip = '10.0.0.1' - self.protocol = 'http' - self.client = dothill.DotHillClient(self.ip, self.login, self.passwd, - self.protocol) - - @mock.patch('urllib2.urlopen') - def test_login(self, mock_url_open): - m = mock.Mock() - m.read.side_effect = [resp_login] - mock_url_open.return_value = m - self.client.login() - self.assertEqual(session_key, self.client._session_key) - m.read.side_effect = [resp_badlogin] - self.assertRaises(exception.DotHillAuthenticationError, - self.client.login) - - def test_build_request_url(self): - url = self.client._build_request_url('/path') - self.assertEqual('http://10.0.0.1/api/path', url) - url = self.client._build_request_url('/path', arg1='val1') - self.assertEqual('http://10.0.0.1/api/path/arg1/val1', url) - url = self.client._build_request_url('/path', arg_1='val1') - self.assertEqual('http://10.0.0.1/api/path/arg-1/val1', url) - url = self.client._build_request_url('/path', 'arg1') - self.assertEqual('http://10.0.0.1/api/path/arg1', url) - url = self.client._build_request_url('/path', 'arg1', arg2='val2') - self.assertEqual('http://10.0.0.1/api/path/arg2/val2/arg1', url) - url = self.client._build_request_url('/path', 'arg1', 'arg3', - arg2='val2') - self.assertEqual('http://10.0.0.1/api/path/arg2/val2/arg1/arg3', url) - - @mock.patch('urllib2.urlopen') - def test_request(self, mock_url_open): - self.client._session_key = session_key - - m = mock.Mock() - m.read.side_effect = [response_ok, malformed_xml, - urllib2.URLError("error")] - mock_url_open.return_value = m - ret = self.client._request('/path') - self.assertTrue(type(ret) == etree._Element) - self.assertRaises(exception.DotHillConnectionError, - self.client._request, - '/path') - self.assertRaises(exception.DotHillConnectionError, - self.client._request, - '/path') - - def test_assert_response_ok(self): - ok_tree = etree.XML(response_ok) - not_ok_tree = etree.XML(response_not_ok) - invalid_tree = etree.XML(invalid_xml) - ret = self.client._assert_response_ok(ok_tree) - self.assertEqual(None, ret) - self.assertRaises(exception.DotHillRequestError, - self.client._assert_response_ok, - not_ok_tree) - self.assertRaises(exception.DotHillRequestError, - self.client._assert_response_ok, invalid_tree) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_backend_exists(self, mock_request): - mock_request.side_effect = [exception.DotHillRequestError, - fake_xml] - self.assertEqual(False, self.client.backend_exists('backend_name', - 'linear')) - self.assertEqual(True, self.client.backend_exists('backend_name', - 'linear')) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_backend_stats(self, mock_request): - stats = {'free_capacity_gb': 1979, - 'total_capacity_gb': 1979} - linear = etree.XML(response_stats_linear) - realstor = etree.XML(response_stats_realstor) - mock_request.side_effect = [linear, realstor] - - self.assertEqual(stats, self.client.backend_stats('OpenStack', - 'linear')) - self.assertEqual(stats, self.client.backend_stats('OpenStack', - 'realstor')) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_get_lun(self, mock_request): - mock_request.side_effect = [etree.XML(response_no_lun), - etree.XML(response_lun)] - ret = self.client._get_first_available_lun_for_host("fakehost") - self.assertEqual(1, ret) - ret = self.client._get_first_available_lun_for_host("fakehost") - self.assertEqual(2, ret) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_get_ports(self, mock_request): - mock_request.side_effect = [etree.XML(response_ports)] - ret = self.client.get_active_target_ports() - self.assertEqual([{'port-type': 'FC', - 'target-id': 'id2', - 'status': 'Up'}, - {'port-type': 'iSCSI', - 'target-id': 'id4', - 'status': 'Up'}, - {'port-type': 'iSCSI', - 'target-id': 'id5', - 'status': 'Up'}], ret) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_get_fc_ports(self, mock_request): - mock_request.side_effect = [etree.XML(response_ports)] - ret = self.client.get_active_fc_target_ports() - self.assertEqual(['id2'], ret) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_get_iscsi_iqns(self, mock_request): - mock_request.side_effect = [etree.XML(response_ports)] - ret = self.client.get_active_iscsi_target_iqns() - self.assertEqual(['id4', 'id5'], ret) - - @mock.patch.object(dothill.DotHillClient, '_request') - def test_get_iscsi_portals(self, mock_request): - portals = {'10.0.0.12': 'Up', '10.0.0.11': 'Up'} - mock_request.side_effect = [etree.XML(response_ports_linear), - etree.XML(response_ports_realstor)] - ret = self.client.get_active_iscsi_target_portals('linear') - self.assertEqual(portals, ret) - ret = self.client.get_active_iscsi_target_portals('realstor') - self.assertEqual(portals, ret) - - -class FakeConfiguration1(object): - dothill_backend_name = 'OpenStack' - dothill_backend_type = 'linear' - san_ip = '10.0.0.1' - san_login = 'manage' - san_password = '!manage' - dothill_wbi_protocol = 'http' - - def safe_get(self, key): - return 'fakevalue' - - -class FakeConfiguration2(FakeConfiguration1): - dothill_iscsi_ips = ['10.0.0.11'] - use_chap_auth = None - - -class TestFCDotHillCommon(test.TestCase): - def setUp(self): - super(TestFCDotHillCommon, self).setUp() - self.config = FakeConfiguration1() - self.common = dothill_common.DotHillCommon(self.config) - self.common.client_login = mock.MagicMock() - self.common.client_logout = mock.MagicMock() - self.common.serialNumber = "xxxxx" - self.common.owner = "A" - self.connector_element = "wwpns" - - @mock.patch.object(dothill.DotHillClient, 'get_serial_number') - @mock.patch.object(dothill.DotHillClient, 'get_owner_info') - @mock.patch.object(dothill.DotHillClient, 'backend_exists') - def test_do_setup(self, mock_backend_exists, - mock_owner_info, mock_serial_number): - mock_backend_exists.side_effect = [False, True] - mock_owner_info.return_value = "A" - mock_serial_number.return_value = "xxxxx" - self.assertRaises(exception.DotHillInvalidBackend, - self.common.do_setup, None) - self.assertEqual(None, self.common.do_setup(None)) - mock_backend_exists.assert_called_with(self.common.backend_name, - self.common.backend_type) - mock_owner_info.assert_called_with(self.common.backend_name) - - def test_vol_name(self): - self.assertEqual(encoded_volid, self.common._get_vol_name(vol_id)) - self.assertEqual(encoded_snapid, self.common._get_snap_name(vol_id)) - - def test_check_flags(self): - class FakeOptions(object): - def __init__(self, d): - for k, v in d.items(): - self.__dict__[k] = v - - options = FakeOptions({'opt1': 'val1', 'opt2': 'val2'}) - required_flags = ['opt1', 'opt2'] - ret = self.common.check_flags(options, required_flags) - self.assertEqual(None, ret) - - options = FakeOptions({'opt1': 'val1', 'opt2': 'val2'}) - required_flags = ['opt1', 'opt2', 'opt3'] - self.assertRaises(exception.Invalid, self.common.check_flags, - options, required_flags) - - def test_assert_connector_ok(self): - self.assertRaises(exception.InvalidInput, - self.common._assert_connector_ok, invalid_connector, - self.connector_element) - self.assertIsNone(self.common._assert_connector_ok( - connector, - self.connector_element)) - - @mock.patch.object(dothill.DotHillClient, 'backend_stats') - def test_update_volume_stats(self, mock_stats): - mock_stats.side_effect = [exception.DotHillRequestError, - stats_large_space] - - self.assertRaises(exception.Invalid, self.common._update_volume_stats) - mock_stats.assert_called_with(self.common.backend_name, - self.common.backend_type) - ret = self.common._update_volume_stats() - - self.assertEqual(None, ret) - self.assertEqual({'driver_version': self.common.VERSION, - 'pools': [{'QoS_support': False, - 'free_capacity_gb': 90, - 'location_info': - 'DotHillVolumeDriver:xxxxx:OpenStack:A', - 'pool_name': 'OpenStack', - 'total_capacity_gb': 100}], - 'storage_protocol': None, - 'vendor_name': 'DotHill', - 'volume_backend_name': None}, self.common.stats) - - @mock.patch.object(dothill.DotHillClient, 'create_volume') - def test_create_volume(self, mock_create): - mock_create.side_effect = [exception.DotHillRequestError, None] - - self.assertRaises(exception.Invalid, self.common.create_volume, - test_volume) - ret = self.common.create_volume(test_volume) - self.assertEqual(None, ret) - mock_create.assert_called_with(encoded_volid, - "%sGB" % test_volume['size'], - self.common.backend_name, - self.common.backend_type) - - @mock.patch.object(dothill.DotHillClient, 'delete_volume') - def test_delete_volume(self, mock_delete): - not_found_e = exception.DotHillRequestError( - 'The volume was not found on this system.') - mock_delete.side_effect = [not_found_e, exception.DotHillRequestError, - None] - self.assertEqual(None, self.common.delete_volume(test_volume)) - self.assertRaises(exception.Invalid, self.common.delete_volume, - test_volume) - self.assertEqual(None, self.common.delete_volume(test_volume)) - mock_delete.assert_called_with(encoded_volid) - - @mock.patch.object(dothill.DotHillClient, 'copy_volume') - @mock.patch.object(dothill.DotHillClient, 'backend_stats') - def test_create_cloned_volume(self, mock_stats, mock_copy): - mock_stats.side_effect = [stats_low_space, stats_large_space, - stats_large_space] - - self.assertRaises(exception.DotHillNotEnoughSpace, - self.common.create_cloned_volume, - dest_volume, detached_volume) - self.assertFalse(mock_copy.called) - - mock_copy.side_effect = [exception.DotHillRequestError, None] - self.assertRaises(exception.Invalid, - self.common.create_cloned_volume, - dest_volume, detached_volume) - - ret = self.common.create_cloned_volume(dest_volume, detached_volume) - self.assertEqual(None, ret) - - mock_copy.assert_called_with(encoded_volid, - 'vqqqqqqqqqqqqqqqqqqq', - 0, self.common.backend_name) - - @mock.patch.object(dothill.DotHillClient, 'copy_volume') - @mock.patch.object(dothill.DotHillClient, 'backend_stats') - def test_create_volume_from_snapshot(self, mock_stats, mock_copy): - mock_stats.side_effect = [stats_low_space, stats_large_space, - stats_large_space] - - self.assertRaises(exception.DotHillNotEnoughSpace, - self.common.create_volume_from_snapshot, - dest_volume, test_snap) - - mock_copy.side_effect = [exception.DotHillRequestError, None] - self.assertRaises(exception.Invalid, - self.common.create_volume_from_snapshot, - dest_volume, test_snap) - - ret = self.common.create_volume_from_snapshot(dest_volume, test_snap) - self.assertEqual(None, ret) - mock_copy.assert_called_with('sqqqqqqqqqqqqqqqqqqq', - 'vqqqqqqqqqqqqqqqqqqq', - 0, self.common.backend_name) - - @mock.patch.object(dothill.DotHillClient, 'extend_volume') - def test_extend_volume(self, mock_extend): - mock_extend.side_effect = [exception.DotHillRequestError, None] - - self.assertRaises(exception.Invalid, self.common.extend_volume, - test_volume, 20) - ret = self.common.extend_volume(test_volume, 20) - self.assertEqual(None, ret) - mock_extend.assert_called_with(encoded_volid, '10GB') - - @mock.patch.object(dothill.DotHillClient, 'create_snapshot') - def test_create_snapshot(self, mock_create): - mock_create.side_effect = [exception.DotHillRequestError, None] - - self.assertRaises(exception.Invalid, self.common.create_snapshot, - test_snap) - ret = self.common.create_snapshot(test_snap) - self.assertEqual(None, ret) - mock_create.assert_called_with(encoded_volid, 'sqqqqqqqqqqqqqqqqqqq') - - @mock.patch.object(dothill.DotHillClient, 'delete_snapshot') - def test_delete_snapshot(self, mock_delete): - not_found_e = exception.DotHillRequestError( - 'The volume was not found on this system.') - mock_delete.side_effect = [not_found_e, exception.DotHillRequestError, - None] - - self.assertEqual(None, self.common.delete_snapshot(test_snap)) - self.assertRaises(exception.Invalid, self.common.delete_snapshot, - test_snap) - self.assertEqual(None, self.common.delete_snapshot(test_snap)) - mock_delete.assert_called_with('sqqqqqqqqqqqqqqqqqqq') - - @mock.patch.object(dothill.DotHillClient, 'map_volume') - def test_map_volume(self, mock_map): - mock_map.side_effect = [exception.DotHillRequestError, 10] - - self.assertRaises(exception.Invalid, self.common.map_volume, - test_volume, connector, self.connector_element) - lun = self.common.map_volume(test_volume, connector, - self.connector_element) - self.assertEqual(10, lun) - mock_map.assert_called_with(encoded_volid, - connector, self.connector_element) - - @mock.patch.object(dothill.DotHillClient, 'unmap_volume') - def test_unmap_volume(self, mock_unmap): - mock_unmap.side_effect = [exception.DotHillRequestError, None] - - self.assertRaises(exception.Invalid, self.common.unmap_volume, - test_volume, connector, self.connector_element) - ret = self.common.unmap_volume(test_volume, connector, - self.connector_element) - self.assertEqual(None, ret) - mock_unmap.assert_called_with(encoded_volid, connector, - self.connector_element) - - @mock.patch.object(dothill.DotHillClient, 'copy_volume') - @mock.patch.object(dothill.DotHillClient, 'delete_volume') - @mock.patch.object(dothill.DotHillClient, 'modify_volume_name') - def test_retype(self, mock_modify, mock_delete, mock_copy): - mock_copy.side_effect = [exception.DotHillRequestError, None] - mock_modify.side_effect = None - mock_delete.side_effect = None - self.assertRaises(exception.Invalid, self.common.migrate_volume, - test_retype_volume, test_host) - ret = self.common.migrate_volume(test_retype_volume, test_host) - self.assertEqual((True, None), ret) - ret = self.common.migrate_volume(test_retype_volume, - {'capabilities': {}}) - self.assertEqual((False, None), ret) - - @mock.patch.object(dothill_common.DotHillCommon, '_get_vol_name') - @mock.patch.object(dothill.DotHillClient, 'modify_volume_name') - def test_manage_existing(self, mock_modify, mock_volume): - existing_ref = {'source-name': 'xxxx'} - mock_volume.side_effect = None - mock_modify.side_effect = [exception.DotHillRequestError, None] - self.assertRaises(exception.Invalid, self.common.manage_existing, - test_volume, existing_ref) - ret = self.common.manage_existing(test_volume, existing_ref) - self.assertEqual(None, ret) - - @mock.patch.object(dothill.DotHillClient, 'get_volume_size') - def test_manage_existing_get_size(self, mock_volume): - existing_ref = {'source-name': 'xxxx'} - mock_volume.side_effect = [exception.DotHillRequestError, 1] - self.assertRaises(exception.Invalid, - self.common.manage_existing_get_size, - None, existing_ref) - ret = self.common.manage_existing_get_size(None, existing_ref) - self.assertEqual(1, ret) - - -class TestISCSIDotHillCommon(TestFCDotHillCommon): - def setUp(self): - super(TestISCSIDotHillCommon, self).setUp() - self.connector_element = 'initiator' - - -class TestDotHillFC(test.TestCase): - @mock.patch.object(dothill_common.DotHillCommon, 'do_setup') - def setUp(self, mock_setup): - super(TestDotHillFC, self).setUp() - self.vendor_name = 'DotHill' - - mock_setup.return_value = True - - def fake_init(self, *args, **kwargs): - super(dothill_fc.DotHillFCDriver, self).__init__() - self.common = None - self.configuration = FakeConfiguration1() - self.lookup_service = fczm_utils.create_lookup_service() - - dothill_fc.DotHillFCDriver.__init__ = fake_init - self.driver = dothill_fc.DotHillFCDriver() - self.driver.do_setup(None) - - def _test_with_mock(self, mock, method, args, expected=None): - func = getattr(self.driver, method) - mock.side_effect = [exception.Invalid(), None] - self.assertRaises(exception.Invalid, func, *args) - self.assertEqual(expected, func(*args)) - - @mock.patch.object(dothill_common.DotHillCommon, 'create_volume') - def test_create_volume(self, mock_create): - self._test_with_mock(mock_create, 'create_volume', [None], - {'metadata': None}) - - @mock.patch.object(dothill_common.DotHillCommon, - 'create_cloned_volume') - def test_create_cloned_volume(self, mock_create): - self._test_with_mock(mock_create, 'create_cloned_volume', [None, None], - {'metadata': None}) - - @mock.patch.object(dothill_common.DotHillCommon, - 'create_volume_from_snapshot') - def test_create_volume_from_snapshot(self, mock_create): - self._test_with_mock(mock_create, 'create_volume_from_snapshot', - [None, None], None) - - @mock.patch.object(dothill_common.DotHillCommon, 'delete_volume') - def test_delete_volume(self, mock_delete): - self._test_with_mock(mock_delete, 'delete_volume', [None]) - - @mock.patch.object(dothill_common.DotHillCommon, 'create_snapshot') - def test_create_snapshot(self, mock_create): - self._test_with_mock(mock_create, 'create_snapshot', [None]) - - @mock.patch.object(dothill_common.DotHillCommon, 'delete_snapshot') - def test_delete_snapshot(self, mock_delete): - self._test_with_mock(mock_delete, 'delete_snapshot', [None]) - - @mock.patch.object(dothill_common.DotHillCommon, 'extend_volume') - def test_extend_volume(self, mock_extend): - self._test_with_mock(mock_extend, 'extend_volume', [None, 10]) - - @mock.patch.object(dothill_common.DotHillCommon, 'client_logout') - @mock.patch.object(dothill_common.DotHillCommon, - 'get_active_fc_target_ports') - @mock.patch.object(dothill_common.DotHillCommon, 'map_volume') - @mock.patch.object(dothill_common.DotHillCommon, 'client_login') - def test_initialize_connection(self, mock_login, mock_map, mock_ports, - mock_logout): - mock_login.return_value = None - mock_logout.return_value = None - mock_map.side_effect = [exception.Invalid, 1] - mock_ports.side_effect = [['id1']] - - self.assertRaises(exception.Invalid, - self.driver.initialize_connection, test_volume, - connector) - mock_map.assert_called_with(test_volume, connector, 'wwpns') - - ret = self.driver.initialize_connection(test_volume, connector) - self.assertEqual({'driver_volume_type': 'fibre_channel', - 'data': {'initiator_target_map': { - '111111111111111': ['id1'], - '111111111111112': ['id1']}, - 'target_wwn': ['id1'], - 'target_lun': 1, - 'target_discovered': True}}, ret) - - @mock.patch.object(dothill_common.DotHillCommon, 'unmap_volume') - @mock.patch.object(dothill.DotHillClient, 'list_luns_for_host') - def test_terminate_connection(self, mock_list, mock_unmap): - mock_unmap.side_effect = [exception.Invalid, 1] - mock_list.side_effect = ['yes'] - actual = {'driver_volume_type': 'fibre_channel', 'data': {}} - self.assertRaises(exception.Invalid, - self.driver.terminate_connection, test_volume, - connector) - mock_unmap.assert_called_with(test_volume, connector, 'wwpns') - - ret = self.driver.terminate_connection(test_volume, connector) - self.assertEqual(actual, ret) - - @mock.patch.object(dothill_common.DotHillCommon, 'get_volume_stats') - def test_get_volume_stats(self, mock_stats): - stats = {'storage_protocol': None, - 'driver_version': self.driver.VERSION, - 'volume_backend_name': None, - 'vendor_name': self.vendor_name, - 'pools': [{'free_capacity_gb': 90, - 'reserved_percentage': 0, - 'total_capacity_gb': 100, - 'QoS_support': False, - 'location_info': 'xx:xx:xx:xx', - 'pool_name': 'x'}]} - mock_stats.side_effect = [exception.Invalid, stats, stats] - - self.assertRaises(exception.Invalid, self.driver.get_volume_stats, - False) - ret = self.driver.get_volume_stats(False) - self.assertEqual(stats, ret) - - ret = self.driver.get_volume_stats(True) - self.assertEqual(stats, ret) - mock_stats.assert_called_with(True) - - @mock.patch.object(dothill_common.DotHillCommon, 'retype') - def test_retype(self, mock_retype): - mock_retype.side_effect = [exception.Invalid, True, False] - args = [None, None, None, None, None] - self.assertRaises(exception.Invalid, self.driver.retype, *args) - self.assertEqual(True, self.driver.retype(*args)) - self.assertEqual(False, self.driver.retype(*args)) - - @mock.patch.object(dothill_common.DotHillCommon, 'manage_existing') - def test_manage_existing(self, mock_manage_existing): - self._test_with_mock(mock_manage_existing, 'manage_existing', - [None, None]) - - @mock.patch.object(dothill_common.DotHillCommon, - 'manage_existing_get_size') - def test_manage_size(self, mock_manage_size): - mock_manage_size.side_effect = [exception.Invalid, 1] - self.assertRaises(exception.Invalid, - self.driver.manage_existing_get_size, - None, None) - self.assertEqual(1, self.driver.manage_existing_get_size(None, None)) - - -class TestDotHillISCSI(TestDotHillFC): - @mock.patch.object(dothill_common.DotHillCommon, 'do_setup') - def setUp(self, mock_setup): - super(TestDotHillISCSI, self).setUp() - self.vendor_name = 'DotHill' - mock_setup.return_value = True - - def fake_init(self, *args, **kwargs): - super(dothill_iscsi.DotHillISCSIDriver, self).__init__() - self.common = None - self.configuration = FakeConfiguration2() - self.iscsi_ips = ['10.0.0.11'] - - dothill_iscsi.DotHillISCSIDriver.__init__ = fake_init - self.driver = dothill_iscsi.DotHillISCSIDriver() - self.driver.do_setup(None) - - @mock.patch.object(dothill_common.DotHillCommon, 'client_logout') - @mock.patch.object(dothill_common.DotHillCommon, - 'get_active_iscsi_target_portals') - @mock.patch.object(dothill_common.DotHillCommon, - 'get_active_iscsi_target_iqns') - @mock.patch.object(dothill_common.DotHillCommon, 'map_volume') - @mock.patch.object(dothill_common.DotHillCommon, 'client_login') - def test_initialize_connection(self, mock_login, mock_map, mock_iqns, - mock_portals, mock_logout): - mock_login.return_value = None - mock_logout.return_value = None - mock_map.side_effect = [exception.Invalid, 1] - self.driver.iscsi_ips = ['10.0.0.11'] - self.driver.initialize_iscsi_ports() - mock_iqns.side_effect = [['id2']] - mock_portals.side_effect = {'10.0.0.11': 'Up', '10.0.0.12': 'Up'} - - self.assertRaises(exception.Invalid, - self.driver.initialize_connection, test_volume, - connector) - mock_map.assert_called_with(test_volume, connector, 'initiator') - - ret = self.driver.initialize_connection(test_volume, connector) - self.assertEqual({'driver_volume_type': 'iscsi', - 'data': {'target_iqn': 'id2', - 'target_lun': 1, - 'target_discovered': True, - 'target_portal': '10.0.0.11:3260'}}, ret) - - @mock.patch.object(dothill_common.DotHillCommon, 'unmap_volume') - def test_terminate_connection(self, mock_unmap): - mock_unmap.side_effect = [exception.Invalid, 1] - - self.assertRaises(exception.Invalid, - self.driver.terminate_connection, test_volume, - connector) - mock_unmap.assert_called_with(test_volume, connector, 'initiator') - - ret = self.driver.terminate_connection(test_volume, connector) - self.assertEqual(None, ret) diff --git a/cinder/volume/drivers/dothill/__init__.py b/cinder/volume/drivers/dothill/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cinder/volume/drivers/dothill/dothill_client.py b/cinder/volume/drivers/dothill/dothill_client.py deleted file mode 100644 index 27466471e..000000000 --- a/cinder/volume/drivers/dothill/dothill_client.py +++ /dev/null @@ -1,334 +0,0 @@ -# Copyright 2015 DotHill Systems -# -# 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 hashlib import md5 -import math -import time -import urllib2 - -from lxml import etree -from oslo_log import log as logging - -from cinder import exception - -LOG = logging.getLogger(__name__) - - -class DotHillClient(object): - def __init__(self, host, login, password, protocol): - self._login = login - self._password = password - self._base_url = "%s://%s/api" % (protocol, host) - self._session_key = None - - def _get_auth_token(self, xml): - """Parse an XML authentication reply to extract the session key.""" - self._session_key = None - tree = etree.XML(xml) - if tree.findtext(".//PROPERTY[@name='response-type']") == "success": - self._session_key = tree.findtext(".//PROPERTY[@name='response']") - - def login(self): - """Authenticates the service on the device.""" - hash_ = md5("%s_%s" % (self._login, self._password)) - digest = hash_.hexdigest() - - url = self._base_url + "/login/" + digest - try: - xml = urllib2.urlopen(url).read() - except urllib2.URLError: - raise exception.DotHillConnectionError - - self._get_auth_token(xml) - - if self._session_key is None: - raise exception.DotHillAuthenticationError - - def _assert_response_ok(self, tree): - """Parses the XML returned by the device to check the return code. - - Raises a DotHillRequestError error if the return code is not 0. - """ - return_code = tree.findtext(".//PROPERTY[@name='return-code']") - if return_code and return_code != '0': - raise exception.DotHillRequestError( - message=tree.findtext(".//PROPERTY[@name='response']")) - elif not return_code: - raise exception.DotHillRequestError(message="No status found") - - def _build_request_url(self, path, *args, **kargs): - url = self._base_url + path - if kargs: - url += '/' + '/'.join(["%s/%s" % (k.replace('_', '-'), v) - for (k, v) in kargs.items()]) - if args: - url += '/' + '/'.join(args) - - return url - - def _request(self, path, *args, **kargs): - """Performs an HTTP request on the device. - - Raises a DotHillRequestError if the device returned but the status is - not 0. The device error message will be used in the exception message. - - If the status is OK, returns the XML data for further processing. - """ - - url = self._build_request_url(path, *args, **kargs) - headers = {'dataType': 'api', 'sessionKey': self._session_key} - req = urllib2.Request(url, headers=headers) - try: - xml = urllib2.urlopen(req).read() - tree = etree.XML(xml) - except Exception: - raise exception.DotHillConnectionError - - if path == "/show/volumecopy-status": - return tree - self._assert_response_ok(tree) - return tree - - def logout(self): - url = self._base_url + '/exit' - try: - urllib2.urlopen(url) - return True - except Exception: - return False - - def create_volume(self, name, size, backend_name, backend_type): - # NOTE: size is in this format: [0-9]+GB - path_dict = {'size': size} - if backend_type == "linear": - path_dict['vdisk'] = backend_name - else: - path_dict['pool'] = backend_name - - self._request("/create/volume", name, **path_dict) - return None - - def delete_volume(self, name): - self._request("/delete/volumes", name) - - def extend_volume(self, name, added_size): - self._request("/expand/volume", name, size=added_size) - - def create_snapshot(self, volume_name, snap_name): - self._request("/create/snapshots", snap_name, volumes=volume_name) - - def delete_snapshot(self, snap_name): - self._request("/delete/snapshot", "cleanup", snap_name) - - def backend_exists(self, backend_name, backend_type): - try: - if backend_type == "linear": - path = "/show/vdisks" - else: - path = "/show/pools" - self._request(path, backend_name) - return True - except exception.DotHillRequestError: - return False - - def _get_size(self, size): - return int(math.ceil(float(size) * 512 / (10 ** 9))) - - def backend_stats(self, backend_name, backend_type): - stats = {'free_capacity_gb': 0, - 'total_capacity_gb': 0} - prop_list = [] - if backend_type == "linear": - path = "/show/vdisks" - prop_list = ["size-numeric", "freespace-numeric"] - else: - path = "/show/pools" - prop_list = ["total-size-numeric", "total-avail-numeric"] - tree = self._request(path, backend_name) - - size = tree.findtext(".//PROPERTY[@name='%s']" % prop_list[0]) - if size: - stats['total_capacity_gb'] = self._get_size(size) - - size = tree.findtext(".//PROPERTY[@name='%s']" % prop_list[1]) - if size: - stats['free_capacity_gb'] = self._get_size(size) - return stats - - def list_luns_for_host(self, host): - tree = self._request("/show/host-maps", host) - return [int(prop.text) for prop in tree.xpath( - "//PROPERTY[@name='lun']")] - - def _get_first_available_lun_for_host(self, host): - luns = self.list_luns_for_host(host) - lun = 1 - while True: - if lun not in luns: - return lun - lun += 1 - - def map_volume(self, volume_name, connector, connector_element): - if connector_element == 'wwpns': - lun = self._get_first_available_lun_for_host(connector['wwpns'][0]) - host = ",".join(connector['wwpns']) - else: - host = connector['initiator'] - host_status = self._check_host(host) - if host_status != 0: - hostname = self._safe_hostname(connector['host']) - self._request("/create/host", hostname, id=host) - lun = self._get_first_available_lun_for_host(host) - - self._request("/map/volume", - volume_name, - lun=str(lun), - host=host, - access="rw") - return lun - - def unmap_volume(self, volume_name, connector, connector_element): - if connector_element == 'wwpns': - host = ",".join(connector['wwpns']) - else: - host = connector['initiator'] - self._request("/unmap/volume", volume_name, host=host) - - def get_active_target_ports(self): - ports = [] - tree = self._request("/show/ports") - - for obj in tree.xpath("//OBJECT[@basetype='port']"): - port = {prop.get('name'): prop.text - for prop in obj.iter("PROPERTY") - if prop.get('name') in - ["port-type", "target-id", "status"]} - if port['status'] == 'Up': - ports.append(port) - return ports - - def get_active_fc_target_ports(self): - return [port['target-id'] for port in self.get_active_target_ports() - if port['port-type'] == "FC"] - - def get_active_iscsi_target_iqns(self): - return [port['target-id'] for port in self.get_active_target_ports() - if port['port-type'] == "iSCSI"] - - def copy_volume(self, src_name, dest_name, same_bknd, dest_bknd_name): - self._request("/volumecopy", - dest_name, - dest_vdisk=dest_bknd_name, - source_volume=src_name, - prompt='yes') - - if same_bknd == 0: - return - - count = 0 - while True: - tree = self._request("/show/volumecopy-status") - return_code = tree.findtext(".//PROPERTY[@name='return-code']") - - if return_code == '0': - status = tree.findtext(".//PROPERTY[@name='progress']") - progress = False - if status: - progress = True - LOG.debug("Volume copy is in progress %s", status) - if not progress: - LOG.debug("Volume copy completed %s", status) - break - else: - if count >= 5: - LOG.debug("Error in copying volume %s", src_name) - raise exception.DotHillRequestError - break - time.sleep(1) - count += 1 - - time.sleep(5) - - def _check_host(self, host): - host_status = -1 - tree = self._request("/show/hosts") - for prop in tree.xpath("//PROPERTY[@name='host-id' and text()='%s']" - % host): - host_status = 0 - return host_status - - def _safe_hostname(self, hostname): - """DotHill hostname restrictions. - - A host name cannot include " , \ in linear and " , < > \ in realstor - and can have a max of 15 bytes in linear and 32 bytes in realstor. - """ - for ch in [',', '"', '\\', '<', '>']: - if ch in hostname: - hostname = hostname.replace(ch, '') - index = len(hostname) - if index > 15: - index = 15 - return hostname[:index] - - def get_active_iscsi_target_portals(self, backend_type): - # This function returns {'ip': status,} - portals = {} - prop = "" - tree = self._request("/show/ports") - if backend_type == "linear": - prop = "primary-ip-address" - else: - prop = "ip-address" - - iscsi_ips = [ip.text for ip in tree.xpath( - "//PROPERTY[@name='%s']" % prop)] - if not iscsi_ips: - return portals - for index, port_type in enumerate(tree.xpath( - "//PROPERTY[@name='port-type' and text()='iSCSI']")): - status = port_type.getparent().findtext("PROPERTY[@name='status']") - if status == 'Up': - portals[iscsi_ips[index]] = status - return portals - - def get_chap_record(self, initiator_name): - tree = self._request("/show/chap-records") - for prop in tree.xpath("//PROPERTY[@name='initiator-name' and " - "text()='%s']" % initiator_name): - chap_secret = prop.getparent().findtext("PROPERTY[@name='initiator" - "-secret']") - return chap_secret - - def create_chap_record(self, initiator_name, chap_secret): - self._request("/create/chap-record", - name=initiator_name, - secret=chap_secret) - - def get_serial_number(self): - tree = self._request("/show/system") - return tree.findtext(".//PROPERTY[@name='midplane-serial-number']") - - def get_owner_info(self, backend_name): - tree = self._request("/show/vdisks", backend_name) - return tree.findtext(".//PROPERTY[@name='owner']") - - def modify_volume_name(self, old_name, new_name): - self._request("/set/volume", old_name, name=new_name) - - def get_volume_size(self, volume_name): - tree = self._request("/show/volumes", volume_name) - size = tree.findtext(".//PROPERTY[@name='size-numeric']") - return self._get_size(size) diff --git a/cinder/volume/drivers/dothill/dothill_common.py b/cinder/volume/drivers/dothill/dothill_common.py deleted file mode 100644 index 81ab2e721..000000000 --- a/cinder/volume/drivers/dothill/dothill_common.py +++ /dev/null @@ -1,539 +0,0 @@ -# Copyright 2015 DotHill Systems -# -# 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. -# -""" -Volume driver common utilities for DotHill Storage array -""" - -import base64 -import six -import uuid - -from oslo_config import cfg -from oslo_log import log as logging - -from cinder import exception -from cinder.i18n import _, _LE -from cinder.volume.drivers.dothill import dothill_client as dothill - -LOG = logging.getLogger(__name__) - -common_opt = [ - cfg.StrOpt('dothill_backend_name', - default='OpenStack', - help="VDisk or Pool name to use for volume creation."), - cfg.StrOpt('dothill_backend_type', - choices=['linear', 'realstor'], - help="linear(for VDisk) or realstor(for Pool)"), - cfg.StrOpt('dothill_wbi_protocol', - choices=['http', 'https'], - help="DotHill web interface protocol"), -] - -iscsi_opt = [ - cfg.ListOpt('dothill_iscsi_ips', - default=[], - help="List of target iSCSI addresses"), -] - -CONF = cfg.CONF -CONF.register_opts(common_opt) -CONF.register_opts(iscsi_opt) - - -class DotHillCommon(object): - VERSION = "0.1" - - stats = {} - - def __init__(self, config): - self.config = config - self.vendor_name = "DotHill" - self.backend_name = self.config.dothill_backend_name - self.backend_type = self.config.dothill_backend_type - self.client = dothill.DotHillClient(self.config.san_ip, - self.config.san_login, - self.config.san_password, - self.config.dothill_wbi_protocol) - - def get_version(self): - return self.VERSION - - def do_setup(self, context): - self.client_login() - self._validate_backend() - if (self.backend_type == "linear" or - (self.backend_type == "realstor" and - self.backend_name not in ['A', 'B'])): - self._get_owner_info(self.backend_name) - self._get_serial_number() - self.client_logout() - - def client_login(self): - LOG.debug("Connecting to %s Array.", self.vendor_name) - try: - self.client.login() - except exception.DotHillConnectionError as ex: - msg = _("Failed to connect to %(vendor_name)s Array %(host)s: " - "%(err)s") % {'vendor_name': self.vendor_name, - 'host': self.config.san_ip, - 'err': six.text_type(ex)} - LOG.error(msg) - raise exception.DotHillConnectionError(message=msg) - except exception.DotHillAuthenticationError: - msg = _("Failed to log on %s Array " - "(invalid login?).") % self.vendor_name - LOG.error(msg) - raise exception.DotHillAuthenticationError(message=msg) - - def _get_serial_number(self): - self.serialNumber = self.client.get_serial_number() - - def _get_owner_info(self, backend_name): - self.owner = self.client.get_owner_info(backend_name) - - def _validate_backend(self): - if not self.client.backend_exists(self.backend_name, - self.backend_type): - self.client_logout() - raise exception.DotHillInvalidBackend(backend=self.backend_name) - - def client_logout(self): - self.client.logout() - LOG.debug("Disconnected from %s Array.", self.vendor_name) - - def _get_vol_name(self, volume_id): - volume_name = self._encode_name(volume_id) - return "v%s" % volume_name - - def _get_snap_name(self, snapshot_id): - snapshot_name = self._encode_name(snapshot_id) - return "s%s" % snapshot_name - - def _encode_name(self, name): - """Get converted DotHill volume name. - - Converts the openstack volume id from - fceec30e-98bc-4ce5-85ff-d7309cc17cc2 - to - v_O7DDpi8TOWF_9cwnMF - - We convert the 128(32*4) bits of the uuid into a 24character long - base64 encoded string. This still exceeds the limit of 20 characters - so we truncate the name later. - """ - uuid_str = name.replace("-", "") - vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str) - vol_encoded = base64.b64encode(vol_uuid.bytes) - vol_encoded = vol_encoded.replace('=', '') - - # + is not a valid character for DotHill - vol_encoded = vol_encoded.replace('+', '.') - # since we use http URLs to send paramters, '/' is not an acceptable - # parameter - vol_encoded = vol_encoded.replace('/', '_') - - # NOTE:we limit the size to 20 characters since the array - # doesn't support more than that for now. Duplicates should happen very - # rarely. - # We return 19 chars here because the _get_{vol,snap}_name functions - # prepend a character - return vol_encoded[:19] - - def check_flags(self, options, required_flags): - for flag in required_flags: - if not getattr(options, flag, None): - msg = _('%s configuration option is not set.') % flag - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def create_volume(self, volume): - self.client_login() - # Use base64 to encode the volume name (UUID is too long for DotHill) - volume_name = self._get_vol_name(volume['id']) - volume_size = "%dGB" % volume['size'] - LOG.debug("Create Volume %(display_name)s %(name)s %(id)s %(size)s", - {'display_name': volume['display_name'], - 'name': volume['name'], - 'id': volume_name, - 'size': volume_size, }) - try: - metadata = self.client.create_volume(volume_name, - volume_size, - self.backend_name, - self.backend_type) - return metadata - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Creation of volume %s failed"), volume['id']) - raise exception.Invalid(ex) - - finally: - self.client_logout() - - def _assert_enough_space_for_copy(self, volume_size): - """The DotHill creates a snap pool before trying to copy the volume. - The pool is 5.27GB or 20% of the volume size, whichever is larger. - - Verify that we have enough space for the pool and then copy - """ - pool_size = max(volume_size * 0.2, 5.27) - required_size = pool_size + volume_size - - if required_size > self.stats['pools'][0]['free_capacity_gb']: - raise exception.DotHillNotEnoughSpace(backend=self.backend_name) - - def _assert_source_detached(self, volume): - """The DotHill requires a volume to be dettached to clone it. - - Make sure that the volume is not in use when trying to copy it. - """ - if (volume['status'] != "available" or - volume['attach_status'] == "attached"): - LOG.error(_LE("Volume must be detached for clone operation.")) - raise exception.VolumeAttached(volume_id=volume['id']) - - def create_cloned_volume(self, volume, src_vref): - if self.backend_type == "realstor" and self.backend_name in ["A", "B"]: - msg = _("Create volume from volume(clone) does not support " - "for virtual pool A and B.") - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - self.get_volume_stats(True) - self._assert_enough_space_for_copy(volume['size']) - self._assert_source_detached(src_vref) - LOG.debug("Cloning Volume %(source_id)s (%(dest_id)s)", - {'source_id': volume['source_volid'], - 'dest_id': volume['id'], }) - - if src_vref['name_id']: - orig_name = self._get_vol_name(src_vref['name_id']) - else: - orig_name = self._get_vol_name(volume['source_volid']) - dest_name = self._get_vol_name(volume['id']) - - self.client_login() - try: - self.client.copy_volume(orig_name, dest_name, 0, self.backend_name) - return None - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Cloning of volume %s failed"), - volume['source_volid']) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def create_volume_from_snapshot(self, volume, snapshot): - if self.backend_type == "realstor" and self.backend_name in ["A", "B"]: - msg = _('Create volume from snapshot does not support ' - 'for virtual pool A and B.') - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - self.get_volume_stats(True) - self._assert_enough_space_for_copy(volume['size']) - LOG.debug("Creating Volume from snapshot %(source_id)s (%(dest_id)s)", - {'source_id': snapshot['id'], 'dest_id': volume['id'], }) - - orig_name = self._get_snap_name(snapshot['id']) - dest_name = self._get_vol_name(volume['id']) - self.client_login() - try: - self.client.copy_volume(orig_name, dest_name, 0, self.backend_name) - return None - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Create volume failed from snapshot %s"), - snapshot['id']) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def delete_volume(self, volume): - LOG.debug("Deleting Volume %s", volume['id']) - if volume['name_id']: - volume_name = self._get_vol_name(volume['name_id']) - else: - volume_name = self._get_vol_name(volume['id']) - - self.client_login() - try: - self.client.delete_volume(volume_name) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Deletion of volume %s failed"), volume['id']) - # if the volume wasn't found, ignore the error - if 'The volume was not found on this system.' in ex: - return - raise exception.Invalid(ex) - finally: - self.client_logout() - - def get_volume_stats(self, refresh): - if refresh: - self.client_login() - try: - self._update_volume_stats() - finally: - self.client_logout() - return self.stats - - def _update_volume_stats(self): - # storage_protocol and volume_backend_name are - # set in the child classes - stats = {'driver_version': self.VERSION, - 'storage_protocol': None, - 'vendor_name': self.vendor_name, - 'volume_backend_name': None, - 'pools': []} - - pool = {'QoS_support': False} - try: - src_type = "%sVolumeDriver" % self.vendor_name - backend_stats = self.client.backend_stats(self.backend_name, - self.backend_type) - pool.update(backend_stats) - if (self.backend_type == "linear" or - (self.backend_type == "realstor" and - self.backend_name not in ['A', 'B'])): - pool['location_info'] = ('%s:%s:%s:%s' % - (src_type, - self.serialNumber, - self.backend_name, - self.owner)) - pool['pool_name'] = self.backend_name - except exception.DotHillRequestError: - err = (_("Unable to get stats for backend_name %s") % - self.backend_name) - LOG.exception(err) - raise exception.Invalid(reason=err) - - stats['pools'].append(pool) - self.stats = stats - - def _assert_connector_ok(self, connector, connector_element): - if not connector[connector_element]: - msg = _("Connector does not provide %s") % connector_element - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def map_volume(self, volume, connector, connector_element): - self._assert_connector_ok(connector, connector_element) - if volume['name_id']: - volume_name = self._get_vol_name(volume['name_id']) - else: - volume_name = self._get_vol_name(volume['id']) - try: - data = self.client.map_volume(volume_name, - connector, - connector_element) - return data - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error mapping volume %s"), volume_name) - raise exception.Invalid(ex) - - def unmap_volume(self, volume, connector, connector_element): - self._assert_connector_ok(connector, connector_element) - if volume['name_id']: - volume_name = self._get_vol_name(volume['name_id']) - else: - volume_name = self._get_vol_name(volume['id']) - - self.client_login() - try: - self.client.unmap_volume(volume_name, - connector, - connector_element) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error unmapping volume %s"), volume_name) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def get_active_fc_target_ports(self): - try: - return self.client.get_active_fc_target_ports() - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error getting active FC target ports.")) - raise exception.Invalid(ex) - - def get_active_iscsi_target_iqns(self): - try: - return self.client.get_active_iscsi_target_iqns() - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error getting active ISCSI target iqns.")) - raise exception.Invalid(ex) - - def get_active_iscsi_target_portals(self): - try: - return self.client.get_active_iscsi_target_portals( - self.backend_type) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error getting active ISCSI target portals.")) - raise exception.Invalid(ex) - - def create_snapshot(self, snapshot): - LOG.debug("Creating snapshot from %(volume_id)s (%(snap_id)s)", - {'volume_id': snapshot['volume_id'], - 'snap_id': snapshot['id'], }) - if snapshot['volume']['name_id']: - vol_name = self._get_vol_name(snapshot['volume']['name_id']) - else: - vol_name = self._get_vol_name(snapshot['volume_id']) - snap_name = self._get_snap_name(snapshot['id']) - - self.client_login() - try: - self.client.create_snapshot(vol_name, snap_name) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Creation of snapshot failed for volume %s"), - snapshot['volume_id']) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def delete_snapshot(self, snapshot): - snap_name = self._get_snap_name(snapshot['id']) - LOG.debug("Deleting snapshot (%s)", snapshot['id']) - - self.client_login() - try: - self.client.delete_snapshot(snap_name) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Deleting snapshot %s failed"), snapshot['id']) - # if the volume wasn't found, ignore the error - if 'The volume was not found on this system.' in ex: - return - raise exception.Invalid(ex) - finally: - self.client_logout() - - def extend_volume(self, volume, new_size): - if volume['name_id']: - volume_name = self._get_vol_name(volume['name_id']) - else: - volume_name = self._get_vol_name(volume['id']) - old_size = volume['size'] - growth_size = int(new_size) - old_size - LOG.debug("Extending Volume %(volume_name)s from %(old_size)s to " - "%(new_size)s, by %(growth_size)s GB.", - {'volume_name': volume_name, - 'old_size': old_size, - 'new_size': new_size, - 'growth_size': growth_size, }) - self.client_login() - try: - self.client.extend_volume(volume_name, "%dGB" % growth_size) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Extension of volume %s failed"), volume['id']) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def get_chap_record(self, initiator_name): - try: - return self.client.get_chap_record(initiator_name) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error getting chap record.")) - raise exception.Invalid(ex) - - def create_chap_record(self, initiator_name, chap_secret): - try: - self.client.create_chap_record(initiator_name, chap_secret) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error creating chap record.")) - raise exception.Invalid(ex) - - def migrate_volume(self, volume, host): - """Migrate directly if source and dest are managed by same storage. - - :param volume: A dictionary describing the volume to migrate - :param host: A dictionary describing the host to migrate to, where - host['host'] is its name, and host['capabilities'] is a - dictionary of its reported capabilities. - :returns (False, None) if the driver does not support migration, - (True, None) if successful - - """ - false_ret = (False, None) - if volume['attach_status'] == "attached": - return false_ret - if 'location_info' not in host['capabilities']: - return false_ret - info = host['capabilities']['location_info'] - try: - (dest_type, dest_id, - dest_back_name, dest_owner) = info.split(':') - except ValueError: - return false_ret - - if not (dest_type == 'DotHillVolumeDriver' and - dest_id == self.serialNumber and - dest_owner == self.owner): - return false_ret - if volume['name_id']: - source_name = self._get_vol_name(volume['name_id']) - else: - source_name = self._get_vol_name(volume['id']) - # DotHill Array does not support duplicate names - dest_name = "m%s" % source_name[1:] - - self.client_login() - try: - self.client.copy_volume(source_name, dest_name, 1, dest_back_name) - self.client.delete_volume(source_name) - self.client.modify_volume_name(dest_name, source_name) - return (True, None) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error migrating volume %s"), source_name) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def retype(self, volume, new_type, diff, host): - ret = self.migrate_volume(volume, host) - return ret[0] - - def manage_existing(self, volume, existing_ref): - """Manage an existing non-openstack DotHill volume - - existing_ref is a dictionary of the form: - {'source-name': } - """ - target_vol_name = existing_ref['source-name'] - modify_target_vol_name = self._get_vol_name(volume['id']) - - self.client_login() - try: - self.client.modify_volume_name(target_vol_name, - modify_target_vol_name) - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error manage existing volume.")) - raise exception.Invalid(ex) - finally: - self.client_logout() - - def manage_existing_get_size(self, volume, existing_ref): - """Return size of volume to be managed by manage_existing. - - existing_ref is a dictionary of the form: - {'source-name': } - """ - target_vol_name = existing_ref['source-name'] - - self.client_login() - try: - size = self.client.get_volume_size(target_vol_name) - return size - except exception.DotHillRequestError as ex: - LOG.exception(_LE("Error manage existing get volume size.")) - raise exception.Invalid(ex) - finally: - self.client_logout() diff --git a/cinder/volume/drivers/dothill/dothill_fc.py b/cinder/volume/drivers/dothill/dothill_fc.py deleted file mode 100644 index cb15ea94d..000000000 --- a/cinder/volume/drivers/dothill/dothill_fc.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright 2015 DotHill Systems -# -# 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 oslo_log import log as logging - -import cinder.volume.driver -from cinder.volume.drivers.dothill import dothill_common -from cinder.volume.drivers.san import san -from cinder.zonemanager import utils as fczm_utils - -LOG = logging.getLogger(__name__) - - -class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver): - VERSION = "1.0" - - def __init__(self, *args, **kwargs): - super(DotHillFCDriver, self).__init__(*args, **kwargs) - self.common = None - self.configuration.append_config_values(dothill_common.common_opt) - self.configuration.append_config_values(san.san_opts) - self.lookup_service = fczm_utils.create_lookup_service() - - def _init_common(self): - return dothill_common.DotHillCommon(self.configuration) - - def _check_flags(self): - required_flags = ['san_ip', 'san_login', 'san_password'] - self.common.check_flags(self.configuration, required_flags) - - def do_setup(self, context): - self.common = self._init_common() - self._check_flags() - self.common.do_setup(context) - - def check_for_setup_error(self): - self._check_flags() - - def create_volume(self, volume): - return {'metadata': self.common.create_volume(volume)} - - def create_volume_from_snapshot(self, volume, src_vref): - self.common.create_volume_from_snapshot(volume, src_vref) - - def create_cloned_volume(self, volume, src_vref): - return {'metadata': self.common.create_cloned_volume(volume, src_vref)} - - def delete_volume(self, volume): - self.common.delete_volume(volume) - - @fczm_utils.AddFCZone - def initialize_connection(self, volume, connector): - self.common.client_login() - try: - data = {} - data['target_lun'] = self.common.map_volume(volume, - connector, - 'wwpns') - - ports, init_targ_map = self.get_init_targ_map(connector) - data['target_discovered'] = True - data['target_wwn'] = ports - data['initiator_target_map'] = init_targ_map - info = {'driver_volume_type': 'fibre_channel', - 'data': data} - return info - finally: - self.common.client_logout() - - @fczm_utils.RemoveFCZone - def terminate_connection(self, volume, connector, **kwargs): - self.common.unmap_volume(volume, connector, 'wwpns') - info = {'driver_volume_type': 'fibre_channel', 'data': {}} - if not self.common.client.list_luns_for_host(connector['wwpns'][0]): - ports, init_targ_map = self.get_init_targ_map(connector) - info['data'] = {'target_wwn': ports, - 'initiator_target_map': init_targ_map} - return info - - def get_init_targ_map(self, connector): - init_targ_map = {} - target_wwns = [] - ports = self.common.get_active_fc_target_ports() - if self.lookup_service is not None: - dev_map = self.lookup_service.get_device_mapping_from_network( - connector['wwpns'], - ports) - for fabric_name in dev_map: - fabric = dev_map[fabric_name] - target_wwns += fabric['target_port_wwn_list'] - for initiator in fabric['initiator_port_wwn_list']: - if initiator not in init_targ_map: - init_targ_map[initiator] = [] - init_targ_map[initiator] += fabric['target_port_wwn_list'] - init_targ_map[initiator] = list(set( - init_targ_map[initiator])) - target_wwns = list(set(target_wwns)) - else: - initiator_wwns = connector['wwpns'] - target_wwns = ports - for initiator in initiator_wwns: - init_targ_map[initiator] = target_wwns - - return target_wwns, init_targ_map - - def get_volume_stats(self, refresh=False): - stats = self.common.get_volume_stats(refresh) - stats['storage_protocol'] = 'FC' - stats['driver_version'] = self.VERSION - backend_name = self.configuration.safe_get('volume_backend_name') - stats['volume_backend_name'] = (backend_name or - self.__class__.__name__) - return stats - - def create_export(self, context, volume): - pass - - def ensure_export(self, context, volume): - pass - - def remove_export(self, context, volume): - pass - - def create_snapshot(self, snapshot): - self.common.create_snapshot(snapshot) - - def delete_snapshot(self, snapshot): - self.common.delete_snapshot(snapshot) - - def extend_volume(self, volume, new_size): - self.common.extend_volume(volume, new_size) - - def retype(self, context, volume, new_type, diff, host): - return self.common.retype(volume, new_type, diff, host) - - def manage_existing(self, volume, existing_ref): - self.common.manage_existing(volume, existing_ref) - - def manage_existing_get_size(self, volume, existing_ref): - return self.common.manage_existing_get_size(volume, existing_ref) - - def unmanage(self, volume): - pass diff --git a/cinder/volume/drivers/dothill/dothill_iscsi.py b/cinder/volume/drivers/dothill/dothill_iscsi.py deleted file mode 100644 index f8ea90c10..000000000 --- a/cinder/volume/drivers/dothill/dothill_iscsi.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2015 DotHill Systems -# -# 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 oslo_log import log as logging - -from cinder import exception -from cinder.i18n import _ -import cinder.volume.driver -from cinder.volume.drivers.dothill import dothill_common as dothillcommon -from cinder.volume.drivers.san import san - - -DEFAULT_ISCSI_PORT = "3260" -LOG = logging.getLogger(__name__) - - -class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver): - VERSION = "1.0" - - def __init__(self, *args, **kwargs): - super(DotHillISCSIDriver, self).__init__(*args, **kwargs) - self.common = None - self.configuration.append_config_values(dothillcommon.common_opt) - self.configuration.append_config_values(dothillcommon.iscsi_opt) - self.configuration.append_config_values(san.san_opts) - self.iscsi_ips = self.configuration.dothill_iscsi_ips - - def _init_common(self): - return dothillcommon.DotHillCommon(self.configuration) - - def _check_flags(self): - required_flags = ['san_ip', 'san_login', 'san_password'] - self.common.check_flags(self.configuration, required_flags) - - def do_setup(self, context): - self.common = self._init_common() - self._check_flags() - self.common.do_setup(context) - self.initialize_iscsi_ports() - - def initialize_iscsi_ports(self): - iscsi_ips = [] - if self.iscsi_ips: - for ip_addr in self.iscsi_ips: - ip = ip_addr.split(':') - if len(ip) == 1: - iscsi_ips.append([ip_addr, DEFAULT_ISCSI_PORT]) - elif len(ip) == 2: - iscsi_ips.append([ip[0], ip[1]]) - else: - msg = _("Invalid IP address format '%s'") % ip_addr - LOG.error(msg) - raise exception.InvalidInput(reason=(msg)) - self.iscsi_ips = iscsi_ips - else: - msg = _('At least one valid iSCSI IP address must be set.') - LOG.error(msg) - raise exception.InvalidInput(reason=(msg)) - - def check_for_setup_error(self): - self._check_flags() - - def create_volume(self, volume): - return {'metadata': self.common.create_volume(volume)} - - def create_volume_from_snapshot(self, volume, src_vref): - self.common.create_volume_from_snapshot(volume, src_vref) - - def create_cloned_volume(self, volume, src_vref): - return {'metadata': self.common.create_cloned_volume(volume, src_vref)} - - def delete_volume(self, volume): - self.common.delete_volume(volume) - - def initialize_connection(self, volume, connector): - self.common.client_login() - try: - data = {} - data['target_lun'] = self.common.map_volume(volume, - connector, - 'initiator') - iqns = self.common.get_active_iscsi_target_iqns() - data['target_discovered'] = True - data['target_iqn'] = iqns[0] - iscsi_portals = self.common.get_active_iscsi_target_portals() - - for ip_port in self.iscsi_ips: - if (ip_port[0] in iscsi_portals): - data['target_portal'] = ":".join(ip_port) - break - - if 'target_portal' not in data: - raise exception.DotHillNotTargetPortal() - - if self.configuration.use_chap_auth: - chap_secret = self.common.get_chap_record( - connector['initiator'] - ) - if not chap_secret: - chap_secret = self.create_chap_record( - connector['initiator'] - ) - data['auth_password'] = chap_secret - data['auth_username'] = connector['initiator'] - data['auth_method'] = 'CHAP' - - info = {'driver_volume_type': 'iscsi', - 'data': data} - return info - finally: - self.common.client_logout() - - def terminate_connection(self, volume, connector, **kwargs): - self.common.unmap_volume(volume, connector, 'initiator') - - def get_volume_stats(self, refresh=False): - stats = self.common.get_volume_stats(refresh) - stats['storage_protocol'] = 'iSCSI' - stats['driver_version'] = self.VERSION - backend_name = self.configuration.safe_get('volume_backend_name') - stats['volume_backend_name'] = (backend_name or - self.__class__.__name__) - return stats - - def create_export(self, context, volume): - pass - - def ensure_export(self, context, volume): - pass - - def remove_export(self, context, volume): - pass - - def create_snapshot(self, snapshot): - self.common.create_snapshot(snapshot) - - def delete_snapshot(self, snapshot): - self.common.delete_snapshot(snapshot) - - def extend_volume(self, volume, new_size): - self.common.extend_volume(volume, new_size) - - def create_chap_record(self, initiator_name): - chap_secret = self.configuration.chap_password - # Chap secret length should be 12 to 16 characters - if 12 <= len(chap_secret) <= 16: - self.common.create_chap_record(initiator_name, chap_secret) - else: - msg = _('chap secret should be 12-16 bytes') - LOG.error(msg) - raise exception.InvalidInput(reason=(msg)) - return chap_secret - - def retype(self, context, volume, new_type, diff, host): - return self.common.retype(volume, new_type, diff, host) - - def manage_existing(self, volume, existing_ref): - self.common.manage_existing(volume, existing_ref) - - def manage_existing_get_size(self, volume, existing_ref): - return self.common.manage_existing_get_size(volume, existing_ref) - - def unmanage(self, volume): - pass