From: Mike Perez Date: Thu, 19 Mar 2015 18:08:37 +0000 (-0700) Subject: Removing HP MSA driver for no reported CI X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=0223b3a97b41a771eb0bf3de78bc225a4746fa62;p=openstack-build%2Fcinder-build.git Removing HP MSA 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 it's not supported, so it won't have a CI reporting 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: Ia6681e943ffb807ff6b3064976f6ef8eb3259e65 --- diff --git a/cinder/tests/test_hp_msa.py b/cinder/tests/test_hp_msa.py deleted file mode 100644 index 94510ac29..000000000 --- a/cinder/tests/test_hp_msa.py +++ /dev/null @@ -1,549 +0,0 @@ -# (c) Copyright 2014 Objectif Libre -# -# 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 HP MSA driver.""" - -import urllib2 - -import lxml.etree as etree -import mock - -from cinder import exception -from cinder import test -from cinder.volume.drivers.san.hp import hp_msa_client as msa -from cinder.volume.drivers.san.hp import hp_msa_common -from cinder.volume.drivers.san.hp import hp_msa_fc - - -session_key = 'JSESS0004eb8a82b08fd5' -resp_login = ''' - success - 0 - JSESS0004eb8a82b08fd5 - 1''' -resp_badlogin = ''' - ''' - -response_ok = ''' - some data - 0''' -response_not_ok = ''' - Error Message - 1 - ''' -response_stats = ''' - 1756381184 - 756381184 - ''' -response_no_lun = '''''' -response_lun = ''' - 1 - - 3''' -response_ports = ''' - FC - id1 - Up - - FC - id2 - Disconnected - - iSCSI - id3 - Up''' -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 = 'ecffc30f-98cb-4cf5-85ee-d7309cc17cd2' -test_volume = {'id': vol_id, - 'display_name': 'test volume', 'name': 'volume', 'size': 10} -test_snap = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', - 'volume_id': vol_id, - 'display_name': 'test volume', 'name': 'volume', 'size': 10} -encoded_volid = 'v7P_DD5jLTPWF7tcwnMF' -encoded_snapid = 's7P_DD5jLTPWF7tcwnMF' -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, - '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': 'iqn.1993-08.org.debian:01:222', - 'wwpns': [], - 'wwnns': [], - 'host': 'fakehost'} - - -class TestHPMSAClient(test.TestCase): - def setUp(self): - super(TestHPMSAClient, self).setUp() - self.login = 'manage' - self.passwd = '!manage' - self.ip = '10.0.0.1' - self.client = msa.HPMSAClient(self.ip, self.login, self.passwd) - - @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(self.client._session_key, session_key) - - m.read.side_effect = [resp_badlogin] - self.assertRaises(msa.HPMSAAuthenticationError, - self.client.login) - - def test_build_request_url(self): - url = self.client._build_request_url('/path', None) - self.assertEqual(url, 'http://10.0.0.1/api/path') - url = self.client._build_request_url('/path', None, arg1='val1') - self.assertEqual(url, 'http://10.0.0.1/api/path/arg1/val1') - url = self.client._build_request_url('/path', 'arg1') - self.assertEqual(url, 'http://10.0.0.1/api/path/arg1') - url = self.client._build_request_url('/path', 'arg1', arg2='val2') - self.assertEqual(url, 'http://10.0.0.1/api/path/arg2/val2/arg1') - url = self.client._build_request_url('/path', ['arg1', 'arg3'], - arg2='val2') - self.assertEqual(url, 'http://10.0.0.1/api/path/arg2/val2/arg1/arg3') - - @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', None) - self.assertTrue(type(ret) == etree._Element) - self.assertRaises(msa.HPMSAConnectionError, self.client._request, - '/path', None) - self.assertRaises(msa.HPMSAConnectionError, self.client._request, - '/path', None) - - 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(ret, None) - self.assertRaises(msa.HPMSARequestError, - self.client._assert_response_ok, not_ok_tree) - self.assertRaises(msa.HPMSARequestError, - self.client._assert_response_ok, invalid_tree) - - @mock.patch.object(msa.HPMSAClient, '_request') - def test_vdisk_exists(self, mock_request): - mock_request.side_effect = [msa.HPMSARequestError, - fake_xml] - - self.assertEqual(self.client.vdisk_exists('vdisk'), False) - self.assertEqual(self.client.vdisk_exists('vdisk'), True) - - @mock.patch.object(msa.HPMSAClient, '_request') - def test_vdisk_stats(self, mock_request): - mock_request.return_value = etree.XML(response_stats) - ret = self.client.vdisk_stats('OpenStack') - self.assertEqual(ret, {'free_capacity_gb': 387, - 'total_capacity_gb': 899}) - mock_request.assert_called_with('/show/vdisks', 'OpenStack') - - @mock.patch.object(msa.HPMSAClient, '_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(ret, 1) - ret = self.client._get_first_available_lun_for_host("fakehost") - self.assertEqual(ret, 2) - - @mock.patch.object(msa.HPMSAClient, '_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(ret, [{'port-type': 'FC', - 'target-id': 'id1', - 'status': 'Up'}, - {'port-type': 'iSCSI', - 'target-id': 'id3', - 'status': 'Up'}]) - - @mock.patch.object(msa.HPMSAClient, '_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(ret, ['id1']) - - -class FakeConfiguration(object): - msa_vdisk = 'OpenStack' - san_ip = '10.0.0.1' - san_login = 'manage' - san_password = '!manage' - - def safe_get(self, key): - return 'fakevalue' - - -class TestHPMSACommon(test.TestCase): - def setUp(self): - super(TestHPMSACommon, self).setUp() - self.config = FakeConfiguration() - self.common = hp_msa_common.HPMSACommon(self.config) - - @mock.patch.object(msa.HPMSAClient, 'vdisk_exists') - @mock.patch.object(msa.HPMSAClient, 'logout') - @mock.patch.object(msa.HPMSAClient, 'login') - def test_do_setup(self, mock_login, mock_logout, mock_vdisk_exists): - mock_login.side_effect = [msa.HPMSAConnectionError, - msa.HPMSAAuthenticationError, - None, None] - mock_vdisk_exists.side_effect = [False, True] - mock_logout.return_value = None - - self.assertRaises(exception.HPMSAConnectionError, - self.common.do_setup, None) - self.assertRaises(exception.HPMSAConnectionError, - self.common.do_setup, None) - self.assertRaises(exception.HPMSAInvalidVDisk, self.common.do_setup, - None) - mock_vdisk_exists.assert_called_with(self.config.msa_vdisk) - self.assertEqual(self.common.do_setup(None), None) - mock_vdisk_exists.assert_called_with(self.config.msa_vdisk) - mock_logout.assert_called_with() - - def test_vol_name(self): - self.assertEqual(self.common._get_vol_name(vol_id), encoded_volid) - self.assertEqual(self.common._get_snap_name(vol_id), - encoded_snapid) - - def test_check_flags(self): - class FakeOptions(): - 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(ret, None) - - options = FakeOptions({'opt1': 'val1', 'opt3': 'val3'}) - required_flags = ['opt1', 'opt2'] - self.assertEqual(ret, None) - - 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.assertIsNone(self.common._assert_connector_ok(connector)) - - @mock.patch.object(msa.HPMSAClient, 'vdisk_stats') - def test_update_volume_stats(self, mock_stats): - mock_stats.side_effect = [msa.HPMSARequestError, - stats_large_space] - - self.assertRaises(exception.Invalid, self.common._update_volume_stats) - mock_stats.assert_called_with(self.config.msa_vdisk) - ret = self.common._update_volume_stats() - self.assertEqual(ret, None) - self.assertEqual(self.common.stats, - {'storage_protocol': None, - 'vendor_name': 'Hewlett-Packard', - 'driver_version': self.common.VERSION, - 'volume_backend_name': None, - 'free_capacity_gb': 90, - 'reserved_percentage': 0, - 'total_capacity_gb': 100, - 'QoS_support': False}) - - @mock.patch.object(msa.HPMSAClient, 'create_volume') - def test_create_volume(self, mock_create): - mock_create.side_effect = [msa.HPMSARequestError, None] - - self.assertRaises(exception.Invalid, self.common.create_volume, - test_volume) - ret = self.common.create_volume(test_volume) - self.assertEqual(ret, None) - mock_create.assert_called_with(self.common.config.msa_vdisk, - encoded_volid, - "%sGB" % test_volume['size']) - - @mock.patch.object(msa.HPMSAClient, 'delete_volume') - def test_delete_volume(self, mock_delete): - not_found_e = msa.HPMSARequestError( - 'The volume was not found on this system.') - mock_delete.side_effect = [not_found_e, msa.HPMSARequestError, - None] - - self.assertEqual(self.common.delete_volume(test_volume), None) - self.assertRaises(exception.Invalid, self.common.delete_volume, - test_volume) - self.assertEqual(self.common.delete_volume(test_volume), None) - mock_delete.assert_called_with(encoded_volid) - - @mock.patch.object(msa.HPMSAClient, 'copy_volume') - @mock.patch.object(msa.HPMSAClient, 'vdisk_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.HPMSANotEnoughSpace, - self.common.create_cloned_volume, - dest_volume, detached_volume) - self.assertFalse(mock_copy.called) - - mock_copy.side_effect = [msa.HPMSARequestError, 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(ret, None) - - mock_copy.assert_called_with(encoded_volid, - 'vqqqqqqqqqqqqqqqqqqq', - self.common.config.msa_vdisk) - - @mock.patch.object(msa.HPMSAClient, 'copy_volume') - @mock.patch.object(msa.HPMSAClient, 'vdisk_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.HPMSANotEnoughSpace, - self.common.create_volume_from_snapshot, - dest_volume, test_snap) - - mock_copy.side_effect = [msa.HPMSARequestError, 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(ret, None) - mock_copy.assert_called_with('sqqqqqqqqqqqqqqqqqqq', - 'vqqqqqqqqqqqqqqqqqqq', - self.common.config.msa_vdisk) - - @mock.patch.object(msa.HPMSAClient, 'extend_volume') - def test_extend_volume(self, mock_extend): - mock_extend.side_effect = [msa.HPMSARequestError, None] - - self.assertRaises(exception.Invalid, self.common.extend_volume, - test_volume, 20) - ret = self.common.extend_volume(test_volume, 20) - self.assertEqual(ret, None) - mock_extend.assert_called_with(encoded_volid, '10GB') - - @mock.patch.object(msa.HPMSAClient, 'create_snapshot') - def test_create_snapshot(self, mock_create): - mock_create.side_effect = [msa.HPMSARequestError, None] - - self.assertRaises(exception.Invalid, self.common.create_snapshot, - test_snap) - ret = self.common.create_snapshot(test_snap) - self.assertEqual(ret, None) - mock_create.assert_called_with(encoded_volid, 'sqqqqqqqqqqqqqqqqqqq') - - @mock.patch.object(msa.HPMSAClient, 'delete_snapshot') - def test_delete_snapshot(self, mock_delete): - not_found_e = msa.HPMSARequestError( - 'The volume was not found on this system.') - mock_delete.side_effect = [not_found_e, msa.HPMSARequestError, - None] - - self.assertEqual(self.common.delete_snapshot(test_snap), None) - self.assertRaises(exception.Invalid, self.common.delete_snapshot, - test_snap) - self.assertEqual(self.common.delete_snapshot(test_snap), None) - mock_delete.assert_called_with('sqqqqqqqqqqqqqqqqqqq') - - @mock.patch.object(msa.HPMSAClient, 'map_volume') - def test_map_volume(self, mock_map): - mock_map.side_effect = [msa.HPMSARequestError, 10] - - self.assertRaises(exception.Invalid, self.common.map_volume, - test_volume, connector) - lun = self.common.map_volume(test_volume, connector) - self.assertEqual(lun, 10) - mock_map.assert_called_with(encoded_volid, connector['wwpns']) - - @mock.patch.object(msa.HPMSAClient, 'unmap_volume') - def test_unmap_volume(self, mock_unmap): - mock_unmap.side_effect = [msa.HPMSARequestError, None] - - self.assertRaises(exception.Invalid, self.common.unmap_volume, - test_volume, connector) - ret = self.common.unmap_volume(test_volume, connector) - self.assertEqual(ret, None) - mock_unmap.assert_called_with(encoded_volid, connector['wwpns']) - - -class TestHPMSAFC(test.TestCase): - @mock.patch.object(hp_msa_common.HPMSACommon, 'do_setup') - def setUp(self, mock_setup): - super(TestHPMSAFC, self).setUp() - - mock_setup.return_value = True - - def fake_init(self, *args, **kwargs): - super(hp_msa_fc.HPMSAFCDriver, self).__init__() - self.common = None - self.configuration = FakeConfiguration() - - hp_msa_fc.HPMSAFCDriver.__init__ = fake_init - self.driver = hp_msa_fc.HPMSAFCDriver() - self.driver.do_setup(None) - self.driver.common.client_login = mock.MagicMock(return_value=None) - self.driver.common.client_logout = mock.MagicMock(return_value=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(hp_msa_common.HPMSACommon, 'create_volume') - def test_create_volume(self, mock_create): - self._test_with_mock(mock_create, 'create_volume', [None], - {'metadata': None}) - - @mock.patch.object(hp_msa_common.HPMSACommon, - '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(hp_msa_common.HPMSACommon, - '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(hp_msa_common.HPMSACommon, 'delete_volume') - def test_delete_volume(self, mock_delete): - self._test_with_mock(mock_delete, 'delete_volume', [None]) - - @mock.patch.object(hp_msa_common.HPMSACommon, 'create_snapshot') - def test_create_snapshot(self, mock_create): - self._test_with_mock(mock_create, 'create_snapshot', [None]) - - @mock.patch.object(hp_msa_common.HPMSACommon, 'delete_snapshot') - def test_delete_snapshot(self, mock_delete): - self._test_with_mock(mock_delete, 'delete_snapshot', [None]) - - @mock.patch.object(hp_msa_common.HPMSACommon, 'extend_volume') - def test_extend_volume(self, mock_extend): - self._test_with_mock(mock_extend, 'extend_volume', [None, 10]) - - @mock.patch.object(hp_msa_common.HPMSACommon, 'client_logout') - @mock.patch.object(hp_msa_common.HPMSACommon, - 'get_active_fc_target_ports') - @mock.patch.object(hp_msa_common.HPMSACommon, 'map_volume') - @mock.patch.object(hp_msa_common.HPMSACommon, '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) - - ret = self.driver.initialize_connection(test_volume, connector) - self.assertEqual(ret, {'driver_volume_type': 'fibre_channel', - 'data': {'target_wwn': ['id1'], - 'target_lun': 1, - 'target_discovered': True}}) - mock_ports.assert_called_once_with() - - @mock.patch.object(hp_msa_common.HPMSACommon, 'client_logout') - @mock.patch.object(hp_msa_common.HPMSACommon, 'unmap_volume') - @mock.patch.object(hp_msa_common.HPMSACommon, 'client_login') - def test_terminate_connection(self, mock_login, mock_unmap, mock_logout): - mock_login.return_value = None - mock_logout.return_value = None - 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) - - ret = self.driver.terminate_connection(test_volume, connector) - self.assertEqual(ret, None) - - @mock.patch.object(hp_msa_common.HPMSACommon, 'client_logout') - @mock.patch.object(hp_msa_common.HPMSACommon, 'get_volume_stats') - @mock.patch.object(hp_msa_common.HPMSACommon, 'client_login') - def test_get_volume_stats(self, mock_login, mock_stats, mock_logout): - stats = {'storage_protocol': None, - 'driver_version': self.driver.VERSION, - 'volume_backend_name': None, - 'free_capacity_gb': 90, - 'reserved_percentage': 0, - 'total_capacity_gb': 100, - 'QoS_support': False} - 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(ret, {'storage_protocol': 'FC', - 'driver_version': self.driver.VERSION, - 'volume_backend_name': 'fakevalue', - 'free_capacity_gb': 90, - 'reserved_percentage': 0, - 'total_capacity_gb': 100, - 'QoS_support': False}) - - ret = self.driver.get_volume_stats(True) - self.assertEqual(ret, {'storage_protocol': 'FC', - 'driver_version': self.driver.VERSION, - 'volume_backend_name': 'fakevalue', - 'free_capacity_gb': 90, - 'reserved_percentage': 0, - 'total_capacity_gb': 100, - 'QoS_support': False}) - mock_stats.assert_called_with(True) diff --git a/cinder/volume/drivers/san/hp/hp_msa_client.py b/cinder/volume/drivers/san/hp/hp_msa_client.py deleted file mode 100644 index 09e0a8b88..000000000 --- a/cinder/volume/drivers/san/hp/hp_msa_client.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright 2014 Objectif Libre -# -# 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 hashlib -import urllib2 - -from lxml import etree - - -class HPMSAConnectionError(Exception): - pass - - -class HPMSAAuthenticationError(Exception): - pass - - -class HPMSARequestError(Exception): - pass - - -class HPMSAClient(object): - def __init__(self, host, login, password, protocol='http'): - 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 - obj = etree.XML(xml).find("OBJECT") - for prop in obj.iter("PROPERTY"): - if prop.get("name") == "response": - self._session_key = prop.text - break - - def login(self): - """Authenticates the service on the device.""" - hash = hashlib.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 HPMSAConnectionError() - - self._get_auth_token(xml) - - if self._session_key is None: - raise HPMSAAuthenticationError() - - def _assert_response_ok(self, tree): - """Parses the XML returned by the device to check the return code. - - Raises a HPMSARequestError error if the return code is not 0. - """ - - for obj in tree.iter(): - if obj.get("basetype") != "status": - continue - - ret_code = ret_str = None - for prop in obj.iter("PROPERTY"): - if prop.get("name") == "return-code": - ret_code = prop.text - elif prop.get("name") == "response": - ret_str = prop.text - - if ret_code != "0": - raise HPMSARequestError(ret_str) - else: - return - - raise HPMSARequestError("No status found") - - def _build_request_url(self, path, args=None, **kargs): - url = self._base_url + path - if kargs: - url += '/' + '/'.join(["%s/%s" % (k.replace('_', '-'), v) - for (k, v) in kargs.items()]) - if args: - if not isinstance(args, list): - args = [args] - url += '/' + '/'.join(args) - - return url - - def _request(self, path, args=None, **kargs): - """Performs an HTTP request on the device. - - Raises a HPMSARequestError 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() - except urllib2.URLError: - raise HPMSAConnectionError() - - try: - tree = etree.XML(xml) - except etree.LxmlError: - raise HPMSAConnectionError() - - self._assert_response_ok(tree) - return tree - - def logout(self): - url = self._base_url + '/exit' - try: - urllib2.urlopen(url) - return True - except HPMSARequestError: - return False - - def create_volume(self, vdisk, name, size): - # NOTE: size is in this format: [0-9]+GB - self._request("/create/volume", name, vdisk=vdisk, size=size) - 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 vdisk_exists(self, vdisk): - try: - self._request("/show/vdisks", vdisk) - return True - except HPMSARequestError: - return False - - def vdisk_stats(self, vdisk): - stats = {'free_capacity_gb': 0, - 'total_capacity_gb': 0} - tree = self._request("/show/vdisks", vdisk) - - for obj in tree.iter(): - if obj.get("basetype") != "virtual-disks": - continue - - for prop in obj.iter("PROPERTY"): - # the sizes are given in number of blocks of 512 octets - if prop.get("name") == "size-numeric": - stats['total_capacity_gb'] = \ - int(prop.text) * 512 / (10 ** 9) - elif prop.get("name") == "freespace-numeric": - stats['free_capacity_gb'] = \ - int(prop.text) * 512 / (10 ** 9) - - return stats - - def _get_first_available_lun_for_host(self, host): - luns = [] - tree = self._request("/show/host-maps", host) - - for obj in tree.iter(): - if obj.get("basetype") != "host-view-mappings": - continue - - for prop in obj.iter("PROPERTY"): - if prop.get("name") == "lun": - luns.append(int(prop.text)) - - lun = 1 - while True: - if lun not in luns: - return lun - lun += 1 - - def map_volume(self, volume_name, wwpns): - # NOTE(gpocentek): we assume that luns will be the same for all hosts - lun = self._get_first_available_lun_for_host(wwpns[0]) - hosts = ",".join(wwpns) - self._request("/map/volume", volume_name, - lun=str(lun), host=hosts, access="rw") - return lun - - def unmap_volume(self, volume_name, wwpns): - hosts = ",".join(wwpns) - self._request("/unmap/volume", volume_name, host=hosts) - - def get_active_target_ports(self): - ports = [] - tree = self._request("/show/ports") - - for obj in tree.iter(): - if obj.get("basetype") != "port": - continue - - port = {} - for prop in obj.iter("PROPERTY"): - prop_name = prop.get("name") - if prop_name in ["port-type", "target-id", "status"]: - port[prop_name] = prop.text - if port['status'] != 'Up': - continue - ports.append(port) - - return ports - - def get_active_fc_target_ports(self): - ports = [] - for port in self.get_active_target_ports(): - if port['port-type'] == "FC": - ports.append(port['target-id']) - - return ports - - def copy_volume(self, source_name, target_name, vdisk): - self._request("/volumecopy", target_name, - dest_vdisk=vdisk, - source_volume=source_name, - prompt='yes') diff --git a/cinder/volume/drivers/san/hp/hp_msa_common.py b/cinder/volume/drivers/san/hp/hp_msa_common.py deleted file mode 100644 index bf4b6f0aa..000000000 --- a/cinder/volume/drivers/san/hp/hp_msa_common.py +++ /dev/null @@ -1,323 +0,0 @@ -# Copyright 2014 Objectif Libre -# -# 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 HP MSA Storage array -""" - -import base64 -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.san.hp import hp_msa_client as msa - -LOG = logging.getLogger(__name__) - -hpmsa_opt = [ - cfg.StrOpt('msa_vdisk', - default='OpenStack', - help="The VDisk to use for volume creation."), -] - -CONF = cfg.CONF -CONF.register_opts(hpmsa_opt) - - -class HPMSACommon(object): - """Class that contains common code for MSA drivers. - - Version history: - 0.1 - First release - 0.2 - Added Logging Markers - - """ - VERSION = "0.2" - - stats = {} - - def __init__(self, config): - self.config = config - self.client = msa.HPMSAClient(self.config.san_ip, - self.config.san_login, - self.config.san_password) - - self.vdisk = self.config.msa_vdisk - - def get_version(self): - return self.VERSION - - def do_setup(self, context): - self.client_login() - self._validate_vdisks() - self.client_logout() - - def client_login(self): - LOG.debug("Connecting to MSA") - try: - self.client.login() - except msa.HPMSAConnectionError as ex: - msg = (_LE("Failed to connect to MSA Array (%(host)s): %(err)s") % - {'host': self.config.san_ip, 'err': ex}) - LOG.error(msg) - raise exception.HPMSAConnectionError(reason=msg) - except msa.HPMSAAuthenticationError: - msg = _LE("Failed to log on MSA Array (invalid login?)") - LOG.error(msg) - raise exception.HPMSAConnectionError(reason=msg) - - def _validate_vdisks(self): - if not self.client.vdisk_exists(self.vdisk): - self.client_logout() - raise exception.HPMSAInvalidVDisk(vdisk=self.vdisk) - - def client_logout(self): - self.client.logout() - LOG.debug("Disconnected from MSA Array") - - 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 MSA volume name. - - Converts the openstack volume id from - ecffc30f-98cb-4cf5-85ee-d7309cc17cd2 - to - 7P_DD5jLTPWF7tcwnMF80g - - We convert the 128 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 MSA - vol_encoded = vol_encoded.replace('+', '.') - # since we use http URLs to send paramters, '/' is not an acceptable - # parameter - vol_encoded = vol_encoded.replace('/', '_') - - # NOTE(gpocentek): 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 = _LE('%s configuration option is not set') % flag - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def create_volume(self, volume): - volume_id = self._get_vol_name(volume['id']) - LOG.debug("Create Volume (%(display_name)s: %(name)s %(id)s)" % - {'display_name': volume['display_name'], - 'name': volume['name'], 'id': volume_id}) - - # use base64 to encode the volume name (UUID is too long for MSA) - volume_name = self._get_vol_name(volume['id']) - volume_size = "%dGB" % volume['size'] - try: - metadata = self.client.create_volume(self.config.msa_vdisk, - volume_name, - volume_size) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - return metadata - - def _assert_enough_space_for_copy(self, volume_size): - """The MSA 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['free_capacity_gb']: - raise exception.HPMSANotEnoughSpace(vdisk=self.vdisk) - - def _assert_source_detached(self, volume): - """The MSA requires a volume to be detached 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": - msg = _LE("Volume must be detached to perform a clone operation.") - LOG.error(msg) - raise exception.VolumeAttached(volume_id=volume['id']) - - def create_cloned_volume(self, volume, src_vref): - 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']}) - - orig_name = self._get_vol_name(volume['source_volid']) - dest_name = self._get_vol_name(volume['id']) - try: - self.client.copy_volume(orig_name, dest_name, - self.config.msa_vdisk) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - return None - - def create_volume_from_snapshot(self, volume, snapshot): - 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']) - try: - self.client.copy_volume(orig_name, dest_name, - self.config.msa_vdisk) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - return None - - def delete_volume(self, volume): - LOG.debug("Deleting Volume (%s)" % volume['id']) - volume_name = self._get_vol_name(volume['id']) - try: - self.client.delete_volume(volume_name) - except msa.HPMSARequestError as ex: - LOG.error(ex) - # 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) - - def get_volume_stats(self, refresh=False): - if refresh: - self._update_volume_stats() - - 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, - 'free_capacity_gb': 'unknown', - 'reserved_percentage': 0, - 'storage_protocol': None, - 'total_capacity_gb': 'unknown', - 'QoS_support': False, - 'vendor_name': 'Hewlett-Packard', - 'volume_backend_name': None} - - try: - vdisk_stats = self.client.vdisk_stats(self.config.msa_vdisk) - stats.update(vdisk_stats) - except msa.HPMSARequestError: - err = (_LE("Unable to get stats for VDisk (%s)") - % self.config.msa_vdisk) - LOG.error(err) - raise exception.Invalid(reason=err) - - self.stats = stats - - def _assert_connector_ok(self, connector): - if not connector['wwpns']: - msg = _LE("Connector doesn't provide wwpns") - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def map_volume(self, volume, connector): - self._assert_connector_ok(connector) - volume_name = self._get_vol_name(volume['id']) - try: - data = self.client.map_volume(volume_name, connector['wwpns']) - return data - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - def unmap_volume(self, volume, connector): - self._assert_connector_ok(connector) - volume_name = self._get_vol_name(volume['id']) - try: - self.client.unmap_volume(volume_name, connector['wwpns']) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - def get_active_fc_target_ports(self): - return self.client.get_active_fc_target_ports() - - 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']}) - snap_name = self._get_snap_name(snapshot['id']) - vol_name = self._get_vol_name(snapshot['volume_id']) - try: - self.client.create_snapshot(vol_name, snap_name) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) - - def delete_snapshot(self, snapshot): - snap_name = self._get_snap_name(snapshot['id']) - LOG.debug("Deleting Snapshot (%s)" % snapshot['id']) - - try: - self.client.delete_snapshot(snap_name) - except msa.HPMSARequestError as ex: - LOG.error(ex) - # 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) - - def extend_volume(self, volume, new_size): - 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}) - try: - self.client.extend_volume(volume_name, "%dGB" % growth_size) - except msa.HPMSARequestError as ex: - LOG.error(ex) - raise exception.Invalid(ex) diff --git a/cinder/volume/drivers/san/hp/hp_msa_fc.py b/cinder/volume/drivers/san/hp/hp_msa_fc.py deleted file mode 100644 index 347c35d66..000000000 --- a/cinder/volume/drivers/san/hp/hp_msa_fc.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2014 Objectif Libre -# -# 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 utils -import cinder.volume.driver -from cinder.volume.drivers.san.hp import hp_msa_common as hpcommon -from cinder.volume.drivers.san import san -from cinder.zonemanager import utils as fczm_utils - -LOG = logging.getLogger(__name__) - - -class HPMSAFCDriver(cinder.volume.driver.FibreChannelDriver): - VERSION = "0.1" - - def __init__(self, *args, **kwargs): - super(HPMSAFCDriver, self).__init__(*args, **kwargs) - self.common = None - self.configuration.append_config_values(hpcommon.hpmsa_opt) - self.configuration.append_config_values(san.san_opts) - - def _init_common(self): - return hpcommon.HPMSACommon(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() - - @utils.synchronized('msa', external=True) - def create_volume(self, volume): - self.common.client_login() - try: - metadata = self.common.create_volume(volume) - return {'metadata': metadata} - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def create_volume_from_snapshot(self, volume, src_vref): - self.common.client_login() - try: - self.common.create_volume_from_snapshot(volume, src_vref) - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def create_cloned_volume(self, volume, src_vref): - self.common.client_login() - try: - new_vol = self.common.create_cloned_volume(volume, src_vref) - return {'metadata': new_vol} - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def delete_volume(self, volume): - self.common.client_login() - try: - self.common.delete_volume(volume) - finally: - self.common.client_logout() - - @fczm_utils.AddFCZone - @utils.synchronized('msa', external=True) - def initialize_connection(self, volume, connector): - self.common.client_login() - try: - data = {} - data['target_lun'] = self.common.map_volume(volume, connector) - - ports = self.common.get_active_fc_target_ports() - data['target_discovered'] = True - data['target_wwn'] = ports - - info = {'driver_volume_type': 'fibre_channel', - 'data': data} - return info - finally: - self.common.client_logout() - - @fczm_utils.RemoveFCZone - @utils.synchronized('msa', external=True) - def terminate_connection(self, volume, connector, **kwargs): - self.common.client_login() - try: - self.common.unmap_volume(volume, connector) - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def get_volume_stats(self, refresh=False): - if refresh: - self.common.client_login() - try: - 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 - finally: - if refresh: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def create_export(self, context, volume): - pass - - @utils.synchronized('msa', external=True) - def ensure_export(self, context, volume): - pass - - @utils.synchronized('msa', external=True) - def remove_export(self, context, volume): - pass - - @utils.synchronized('msa', external=True) - def create_snapshot(self, snapshot): - self.common.client_login() - try: - self.common.create_snapshot(snapshot) - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def delete_snapshot(self, snapshot): - self.common.client_login() - try: - self.common.delete_snapshot(snapshot) - finally: - self.common.client_logout() - - @utils.synchronized('msa', external=True) - def extend_volume(self, volume, new_size): - self.common.client_login() - try: - self.common.extend_volume(volume, new_size) - finally: - self.common.client_logout()