diff --git a/cinder/opts.py b/cinder/opts.py index 0d10e4fcc99..48f4e825747 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -89,8 +89,6 @@ from cinder.volume.drivers.dell_emc import xtremio as \ cinder_volume_drivers_dell_emc_xtremio from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common as \ cinder_volume_drivers_fujitsu_eternus_dx_eternusdxcommon -from cinder.volume.drivers.fusionstorage import dsware as \ - cinder_volume_drivers_fusionstorage_dsware from cinder.volume.drivers.hpe import hpe_3par_common as \ cinder_volume_drivers_hpe_hpe3parcommon from cinder.volume.drivers.hpe import hpe_lefthand_iscsi as \ @@ -254,7 +252,6 @@ def list_opts(): cinder_volume_driver.scst_opts, cinder_volume_driver.backup_opts, cinder_volume_driver.image_opts, - cinder_volume_drivers_fusionstorage_dsware.volume_opts, cinder_volume_drivers_infortrend_raidcmd_cli_commoncli. infortrend_opts, cinder_volume_drivers_inspur_as13000_as13000driver. diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/__init__.py b/cinder/tests/unit/volume/drivers/fusionstorage/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py deleted file mode 100644 index bf562e2a9ba..00000000000 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py +++ /dev/null @@ -1,478 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import ddt -import json - -import mock -import uuid - -from cinder import exception -from cinder import objects -from cinder import test -from cinder.volume import configuration as config -from cinder.volume.drivers.fusionstorage import dsware -from cinder.volume.drivers.fusionstorage import fs_client -from cinder.volume.drivers.fusionstorage import fs_conf -from cinder.volume import volume_utils - - -class FakeDSWAREDriver(dsware.DSWAREDriver): - def __init__(self): - self.configuration = config.Configuration(None) - self.conf = fs_conf.FusionStorageConf(self.configuration, "cinder@fs") - self.client = None - - -@ddt.ddt -class TestDSWAREDriver(test.TestCase): - - def setUp(self): - super(TestDSWAREDriver, self).setUp() - self.fake_driver = FakeDSWAREDriver() - self.client = fs_client.RestCommon(None, None, None) - - def tearDown(self): - super(TestDSWAREDriver, self).tearDown() - - @mock.patch.object(fs_client.RestCommon, 'login') - def test_do_setup(self, mock_login): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - update_mocker = self.mock_object( - self.fake_driver.conf, 'update_config_value') - self.fake_driver.configuration.san_address = 'https://fake_rest_site' - self.fake_driver.configuration.san_user = 'fake_san_user' - self.fake_driver.configuration.san_password = 'fake_san_password' - - self.fake_driver.do_setup('context') - update_mocker.assert_called_once_with() - mock_login.assert_called_once_with() - - @mock.patch.object(fs_client.RestCommon, 'query_pool_info') - def test_check_for_setup_error(self, mock_query_pool_info): - self.fake_driver.configuration.pools_name = ['fake_pool_name'] - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - result1 = [{'poolName': 'fake_pool_name'}, - {'poolName': 'fake_pool_name1'}] - result2 = [{'poolName': 'fake_pool_name1'}, - {'poolName': 'fake_pool_name2'}] - - mock_query_pool_info.return_value = result1 - retval = self.fake_driver.check_for_setup_error() - self.assertIsNone(retval) - - mock_query_pool_info.return_value = result2 - try: - self.fake_driver.check_for_setup_error() - except Exception as e: - self.assertEqual(exception.InvalidInput, type(e)) - - @mock.patch.object(fs_client.RestCommon, 'query_pool_info') - def test__update_pool_stats(self, mock_query_pool_info): - self.fake_driver.configuration.pools_name = ['fake_pool_name'] - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - result = [{'poolName': 'fake_pool_name', - 'totalCapacity': 2048, 'usedCapacity': 1024}, - {'poolName': 'fake_pool_name1', - 'totalCapacity': 2048, 'usedCapacity': 1024}] - - mock_query_pool_info.return_value = result - retval = self.fake_driver._update_pool_stats() - self.assertDictEqual( - {"volume_backend_name": 'FakeDSWAREDriver', - "driver_version": "2.0.9", - "QoS_support": False, - "thin_provisioning_support": False, - "vendor_name": "Huawei", - "pools": - [{"pool_name": 'fake_pool_name', "total_capacity_gb": 2, - "free_capacity_gb": 1}]}, retval) - mock_query_pool_info.assert_called_once_with() - - @mock.patch.object(fs_client.RestCommon, 'keep_alive') - @mock.patch.object(dsware.DSWAREDriver, '_update_pool_stats') - def test_get_volume_stats(self, mock__update_pool_stats, mock_keep_alive): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - result = {"success"} - mock__update_pool_stats.return_value = result - retval = self.fake_driver.get_volume_stats() - self.assertEqual(result, retval) - mock_keep_alive.assert_called_once_with() - - @mock.patch.object(fs_client.RestCommon, 'query_volume_by_name') - def test__check_volume_exist(self, mock_query_volume_by_name): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - result1 = {'volName': 'fake_name'} - result2 = None - - mock_query_volume_by_name.return_value = result1 - retval = self.fake_driver._check_volume_exist(volume) - self.assertEqual(retval, result1) - - mock_query_volume_by_name.return_value = result2 - retval = self.fake_driver._check_volume_exist(volume) - self.assertIsNone(retval) - - @mock.patch.object(volume_utils, 'extract_host') - @mock.patch.object(fs_client.RestCommon, 'query_pool_info') - def test__get_pool_id(self, mock_query_pool_info, mock_extract_host): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(host='host') - pool_name1 = 'fake_pool_name1' - pool_name2 = 'fake_pool_name2' - pool_info = [{'poolName': 'fake_pool_name', 'poolId': 'fake_id'}, - {'poolName': 'fake_pool_name1', 'poolId': 'fake_id1'}] - - mock_query_pool_info.return_value = pool_info - mock_extract_host.return_value = pool_name1 - retval = self.fake_driver._get_pool_id(volume) - self.assertEqual('fake_id1', retval) - - mock_extract_host.return_value = pool_name2 - try: - self.fake_driver._get_pool_id(volume) - except Exception as e: - self.assertEqual(exception.InvalidInput, type(e)) - - def test__get_vol_name(self): - volume1 = objects.Volume(_name_id=uuid.uuid4()) - volume1.update( - {"provider_location": json.dumps({"name": "fake_name"})}) - volume2 = objects.Volume(_name_id=uuid.uuid4()) - - retval = self.fake_driver._get_vol_name(volume1) - self.assertEqual("fake_name", retval) - - retval = self.fake_driver._get_vol_name(volume2) - self.assertEqual(volume2.name, retval) - - @mock.patch.object(fs_client.RestCommon, 'create_volume') - @mock.patch.object(dsware.DSWAREDriver, '_get_pool_id') - def test_create_volume(self, mock__get_pool_id, mock_create_volume): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4(), size=1) - mock__get_pool_id.return_value = 'fake_poolID' - mock_create_volume.return_value = {'result': 0} - - retval = self.fake_driver.create_volume(volume) - self.assertIsNone(retval) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(fs_client.RestCommon, 'delete_volume') - def test_delete_volume(self, mock_delete_volume, mock__check_volume_exist): - result = True - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - mock_delete_volume.return_value = {'result': 0} - - mock__check_volume_exist.return_value = result - retval = self.fake_driver.delete_volume(volume) - self.assertIsNone(retval) - - mock__check_volume_exist.return_value = False - retval = self.fake_driver.delete_volume(volume) - self.assertIsNone(retval) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(fs_client.RestCommon, 'expand_volume') - def test_extend_volume(self, mock_expand_volume, mock__check_volume_exist): - result1 = True - result2 = False - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4(), size=2) - mock_expand_volume.return_value = { - 'volName': 'fake_name', 'size': 'new_size'} - - mock__check_volume_exist.return_value = result1 - retval = self.fake_driver.extend_volume(volume=volume, new_size=3) - self.assertIsNone(retval) - - mock__check_volume_exist.return_value = result2 - try: - self.fake_driver.extend_volume(volume=volume, new_size=3) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(dsware.DSWAREDriver, '_check_snapshot_exist') - @mock.patch.object(fs_client.RestCommon, 'create_volume_from_snapshot') - def test_create_volume_from_snapshot( - self, mock_create_volume_from_snapshot, - mock_check_snapshot_exist, mock_check_volume_exist): - result1 = True - result2 = False - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - snapshot = objects.Snapshot( - id=uuid.uuid4(), volume_size=2, volume=volume) - - volume1 = objects.Volume(_name_id=uuid.uuid4(), size=2) - volume2 = objects.Volume(_name_id=uuid.uuid4(), size=1) - mock_create_volume_from_snapshot.return_value = {'result': 0} - - mock_check_volume_exist.return_value = result2 - mock_check_snapshot_exist.return_value = result1 - retval = self.fake_driver.create_volume_from_snapshot( - volume1, snapshot) - self.assertIsNone(retval) - - mock_check_volume_exist.return_value = result1 - try: - self.fake_driver.create_volume_from_snapshot(volume1, snapshot) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - mock_check_volume_exist.return_value = result2 - mock_check_snapshot_exist.return_value = result2 - try: - self.fake_driver.create_volume_from_snapshot(volume1, snapshot) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - mock_check_volume_exist.return_value = result2 - mock_check_snapshot_exist.return_value = result1 - try: - self.fake_driver.create_volume_from_snapshot(volume2, snapshot) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(fs_client.RestCommon, 'create_volume_from_volume') - def test_cloned_volume( - self, mock_create_volume_from_volume, mock__check_volume_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4(), size=1) - src_volume = objects.Volume(_name_id=uuid.uuid4()) - result1 = True - result2 = False - - mock__check_volume_exist.return_value = result1 - retval = self.fake_driver.create_cloned_volume(volume, src_volume) - self.assertIsNone(retval) - mock_create_volume_from_volume.assert_called_once_with( - vol_name=volume.name, vol_size=volume.size * 1024, - src_vol_name=src_volume.name) - - mock__check_volume_exist.return_value = result2 - try: - self.fake_driver.create_cloned_volume(volume, src_volume) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - def test__get_snapshot_name(self): - snapshot1 = objects.Snapshot(id=uuid.uuid4()) - snapshot1.update( - {"provider_location": json.dumps({"name": "fake_name"})}) - snapshot2 = objects.Snapshot(id=uuid.uuid4()) - - retval = self.fake_driver._get_snapshot_name(snapshot1) - self.assertEqual("fake_name", retval) - - retval = self.fake_driver._get_snapshot_name(snapshot2) - self.assertEqual(snapshot2.name, retval) - - @mock.patch.object(fs_client.RestCommon, 'query_snapshot_by_name') - @mock.patch.object(dsware.DSWAREDriver, '_get_pool_id') - def test__check_snapshot_exist( - self, mock_get_pool_id, mock_query_snapshot_by_name): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - snapshot = objects.Snapshot(id=uuid.uuid4()) - result1 = {'name': 'fake_name', 'totalNum': 1} - result2 = {'name': 'fake_name', 'totalNum': 0} - mock_get_pool_id.return_value = "fake_pool_id" - - mock_query_snapshot_by_name.return_value = result1 - retval = self.fake_driver._check_snapshot_exist(volume, snapshot) - self.assertEqual({'name': 'fake_name', 'totalNum': 1}, retval) - - mock_query_snapshot_by_name.return_value = result2 - retval = self.fake_driver._check_snapshot_exist(volume, snapshot) - self.assertIsNone(retval) - - @mock.patch.object(fs_client.RestCommon, 'create_snapshot') - def test_create_snapshot(self, mock_create_snapshot): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - snapshot = objects.Snapshot(id=uuid.uuid4(), - volume_id=uuid.uuid4(), volume=volume) - - retval = self.fake_driver.create_snapshot(snapshot) - self.assertIsNone(retval) - mock_create_snapshot.assert_called_once_with( - snapshot_name=snapshot.name, vol_name=volume.name) - - @mock.patch.object(dsware.DSWAREDriver, '_check_snapshot_exist') - @mock.patch.object(fs_client.RestCommon, 'delete_snapshot') - def test_delete_snapshot(self, mock_delete_snapshot, - mock_check_snapshot_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(id=uuid.uuid4()) - snapshot = objects.Snapshot(id=uuid.uuid4(), volume=volume) - result = True - mock_delete_snapshot.return_valume = {'result': 0} - - mock_check_snapshot_exist.return_value = result - retval = self.fake_driver.delete_snapshot(snapshot) - self.assertIsNone(retval) - - mock_check_snapshot_exist.return_value = False - retval = self.fake_driver.delete_snapshot(snapshot) - self.assertIsNone(retval) - - def test__get_manager_ip(self): - context = {'host': 'host1'} - host1 = {'host1': '1.1.1.1'} - host2 = {'host2': '1.1.1.1'} - - self.fake_driver.configuration.manager_ips = host1 - retval = self.fake_driver._get_manager_ip(context) - self.assertEqual('1.1.1.1', retval) - - self.fake_driver.configuration.manager_ips = host2 - try: - self.fake_driver._get_manager_ip(context) - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip') - @mock.patch.object(fs_client.RestCommon, 'attach_volume') - def test__attach_volume(self, mock_attach_volume, - mock__get_manager_ip, mock__check_volume_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - attach_result1 = {volume.name: [{'devName': 'fake_path'}]} - attach_result2 = {volume.name: [{'devName': ''}]} - result1 = True - result2 = False - mock__get_manager_ip.return_value = 'fake_ip' - - mock__check_volume_exist.return_value = result1 - mock_attach_volume.return_value = attach_result1 - retval, vol = self.fake_driver._attach_volume( - "context", volume, "properties") - self.assertEqual( - ({'device': {'path': b'fake_path'}}, volume), (retval, vol)) - mock__get_manager_ip.assert_called_once_with("properties") - mock__check_volume_exist.assert_called_once_with(volume) - mock_attach_volume.assert_called_once_with(volume.name, 'fake_ip') - - mock__check_volume_exist.return_value = result2 - try: - self.fake_driver._attach_volume("context", volume, "properties") - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - mock__check_volume_exist.return_value = result1 - mock_attach_volume.return_value = attach_result2 - try: - self.fake_driver._attach_volume("context", volume, "properties") - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip') - @mock.patch.object(fs_client.RestCommon, 'detach_volume') - def test__detach_volume(self, mock_detach_volume, - mock__get_manager_ip, mock__check_volume_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - result1 = True - result2 = False - - mock__get_manager_ip.return_value = 'fake_ip' - mock_detach_volume.return_value = {'result': 0} - - mock__check_volume_exist.return_value = result1 - retval = self.fake_driver._detach_volume( - 'context', 'attach_info', volume, 'properties') - self.assertIsNone(retval) - - mock__check_volume_exist.return_value = result2 - retval = self.fake_driver._detach_volume( - 'context', 'attach_info', volume, 'properties') - self.assertIsNone(retval) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip') - @mock.patch.object(fs_client.RestCommon, 'attach_volume') - @mock.patch.object(fs_client.RestCommon, 'query_volume_by_name') - def test_initialize_connection(self, mock_query_volume_by_name, - mock_attach_volume, - mock__get_manager_ip, - mock__check_volume_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - attach_result = {volume.name: [{'devName': 'fake_path'}]} - - result1 = True - result2 = False - mock__get_manager_ip.return_value = 'fake_ip' - mock_query_volume_by_name.return_value = {'wwn': 'fake_wwn', - 'volName': 'fake_name'} - mock_attach_volume.return_value = attach_result - - mock__check_volume_exist.return_value = result1 - retval = self.fake_driver.initialize_connection(volume, 'connector') - self.assertDictEqual( - {'driver_volume_type': 'local', - 'data': {'device_path': '/dev/disk/by-id/wwn-0xfake_wwn'}}, - retval) - - mock__check_volume_exist.return_value = result2 - try: - self.fake_driver.initialize_connection(volume, 'connector') - except Exception as e: - self.assertEqual(exception.VolumeBackendAPIException, type(e)) - - @mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist') - @mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip') - @mock.patch.object(fs_client.RestCommon, 'detach_volume') - def test_terminate_connection(self, mock_detach_volume, - mock__get_manager_ip, - mock__check_volume_exist): - self.fake_driver.client = fs_client.RestCommon( - 'https://fake_rest_site', 'user', 'password') - volume = objects.Volume(_name_id=uuid.uuid4()) - result1 = True - result2 = False - mock__get_manager_ip.return_value = 'fake_ip' - - mock__check_volume_exist.return_value = result1 - retval = self.fake_driver.terminate_connection(volume, 'connector') - self.assertIsNone(retval) - mock_detach_volume.assert_called_once_with(volume.name, 'fake_ip') - - mock__check_volume_exist.return_value = result2 - retval = self.fake_driver.terminate_connection('volume', 'connector') - self.assertIsNone(retval) diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py deleted file mode 100644 index e5d620ba8e4..00000000000 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json -import mock -import requests - -from cinder import test -from cinder.tests.unit.volume.drivers.fusionstorage import test_utils -from cinder.volume.drivers.fusionstorage import fs_client - - -class FakeSession(test_utils.FakeBaseSession): - method_map = { - 'get': { - 'rest/version': - {'currentVersion': 'fake_version'}, - '/storagePool$': - {'storagePools': [{'poolName': 'fake_pool_name', - 'poolId': 'fake_pool_id'}]}, - r'/storagePool\?poolId=0': - {'storagePools': [{'poolName': 'fake_pool_name1', - 'poolId': 0}]}, - r'/volume/queryByName\?volName=fake_name': - {'errorCode': 0, 'lunDetailInfo': - [{'volume_id': 'fake_id', - 'volume_name': 'fake_name'}]}, - r'/volume/queryById\?volId=fake_id': - {'errorCode': 0, 'lunDetailInfo': - [{'volume_id': 'fake_id', - 'volume_name': 'fake_name'}]}, - r'/lun/wwn/list\?wwn=fake_wwn': - {'errorCode': 0, 'lunDetailInfo': - [{'volume_id': 'fake_id', - 'volume_wwn': 'fake_wwn'}]}, - }, - 'post': { - '/sec/login': {}, - '/sec/logout': {'res': 'fake_logout'}, - '/sec/keepAlive': {'res': 'fake_keepAlive'}, - '/volume/list': {'errorCode': 0, 'volumeList': [ - {'volName': 'fake_name1', 'volId': 'fake_id1'}, - {'volName': 'fake_name2', 'volId': 'fake_id2'}]}, - '/volume/create': {'ID': 'fake_volume_create_id'}, - '/volume/delete': {'ID': 'fake_volume_delete_id'}, - '/volume/attach': - {'fake_name': [{'errorCode': '0', 'ip': 'fake_ip'}]}, - '/volume/detach/': {'ID': 'fake_volume_detach_id'}, - '/volume/expand': {'ID': 'fake_volume_expend_id'}, - '/volume/snapshot/list': - {"snapshotList": [{"snapshot": "fake_name", - "size": "fake_size"}]}, - '/snapshot/list': {'totalNum': 'fake_snapshot_num', - 'snapshotList': - [{'snapName': 'fake_snapName'}]}, - '/snapshot/create/': {'ID': 'fake_snapshot_create_id'}, - '/snapshot/delete/': {'ID': 'fake_snapshot_delete_id'}, - '/snapshot/rollback': {'ID': 'fake_snapshot_delete_id'}, - '/snapshot/volume/create/': {'ID': 'fake_vol_from_snap_id'}, - } - } - - -class TestFsclient(test.TestCase): - def setUp(self): - super(TestFsclient, self).setUp() - self.mock_object(requests, 'Session', FakeSession) - self.client = fs_client.RestCommon('https://fake_rest_site', - 'fake_user', - 'fake_password') - self.client.login() - - def tearDown(self): - super(TestFsclient, self).tearDown() - - def test_login(self): - self.assertEqual('fake_version', - self.client.version) - self.assertEqual('fake_token', - self.client.session.headers['X-Auth-Token']) - - def test_keep_alive(self): - retval = self.client.keep_alive() - self.assertIsNone(retval) - - def test_logout(self): - self.assertIsNone(self.client.logout()) - - def test_query_all_pool_info(self): - with mock.patch.object(self.client.session, 'get', - wraps=self.client.session.get) as mocker: - retval = self.client.query_pool_info() - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/' - 'fake_version/storagePool', timeout=50) - self.assertListEqual( - [{'poolName': 'fake_pool_name', - 'poolId': 'fake_pool_id'}], retval) - - def test_query_pool_info(self): - with mock.patch.object(self.client.session, 'get', - wraps=self.client.session.get) as mocker: - retval = self.client.query_pool_info(pool_id=0) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/' - 'fake_version/storagePool?poolId=0', timeout=50) - self.assertListEqual( - [{'poolName': 'fake_pool_name1', 'poolId': 0}], retval) - - def test_query_volume_by_name(self): - with mock.patch.object(self.client.session, 'get', - wraps=self.client.session.get) as mocker: - retval = self.client.query_volume_by_name(vol_name='fake_name') - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/queryByName?volName=fake_name', timeout=50) - self.assertListEqual( - [{'volume_id': 'fake_id', 'volume_name': 'fake_name'}], retval) - - def test_query_volume_by_id(self): - with mock.patch.object(self.client.session, 'get', - wraps=self.client.session.get) as mocker: - retval = self.client.query_volume_by_id(vol_id='fake_id') - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/queryById?volId=fake_id', timeout=50) - self.assertListEqual( - [{'volume_id': 'fake_id', 'volume_name': 'fake_name'}], retval) - - def test_create_volume(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.create_volume( - vol_name='fake_name', vol_size=1, pool_id='fake_id') - except_data = json.dumps( - {"volName": "fake_name", "volSize": 1, "poolId": "fake_id"}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/create', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_delete_volume(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.delete_volume(vol_name='fake_name') - except_data = json.dumps({"volNames": ['fake_name']}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/delete', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_attach_volume(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.attach_volume( - vol_name='fake_name', manage_ip='fake_ip') - except_data = json.dumps( - {"volName": ['fake_name'], "ipList": ['fake_ip']}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/attach', data=except_data, timeout=50) - self.assertDictEqual( - {'result': 0, - 'fake_name': [{'errorCode': '0', 'ip': 'fake_ip'}]}, - retval) - - def test_detach_volume(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.detach_volume( - vol_name='fake_name', manage_ip='fake_ip') - except_data = json.dumps( - {"volName": ['fake_name'], "ipList": ['fake_ip']}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/detach/', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_expand_volume(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.expand_volume( - vol_name='fake_name', new_vol_size=2) - except_data = json.dumps({"volName": 'fake_name', "newVolSize": 2}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'volume/expand', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_query_snapshot_by_name(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.query_snapshot_by_name( - pool_id='fake_id', snapshot_name='fake_name') - except_data = json.dumps( - {"poolId": 'fake_id', "pageNum": 1, - "pageSize": 1000, "filters": {"volumeName": 'fake_name'}}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'snapshot/list', data=except_data, timeout=50) - self.assertDictEqual( - {'result': 0, 'totalNum': 'fake_snapshot_num', - 'snapshotList': [{'snapName': 'fake_snapName'}]}, retval) - - def test_create_snapshot(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.create_snapshot( - snapshot_name='fake_snap', vol_name='fake_name') - except_data = json.dumps( - {"volName": "fake_name", "snapshotName": "fake_snap"}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'snapshot/create/', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_delete_snapshot(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.delete_snapshot(snapshot_name='fake_snap') - except_data = json.dumps({"snapshotName": "fake_snap"}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'snapshot/delete/', data=except_data, timeout=50) - self.assertIsNone(retval) - - def test_create_volume_from_snapshot(self): - with mock.patch.object(self.client.session, 'post', - wraps=self.client.session.post) as mocker: - retval = self.client.create_volume_from_snapshot( - snapshot_name='fake_snap', vol_name='fake_name', vol_size=2) - except_data = json.dumps({"src": 'fake_snap', - "volName": 'fake_name', - "volSize": 2}) - mocker.assert_called_once_with( - 'https://fake_rest_site/dsware/service/fake_version/' - 'snapshot/volume/create/', data=except_data, timeout=50) - self.assertIsNone(retval) - - @mock.patch.object(fs_client.RestCommon, 'create_snapshot') - @mock.patch.object(fs_client.RestCommon, 'create_volume_from_snapshot') - @mock.patch.object(fs_client.RestCommon, 'delete_snapshot') - def test_create_volume_from_volume( - self, mock_delete_snapshot, mock_volume_from_snapshot, - mock_create_snapshot): - vol_name = 'fake_name' - vol_size = 3 - src_vol_name = 'src_fake_name' - temp_snapshot_name = "temp" + src_vol_name + "clone" + vol_name - - retval = self.client.create_volume_from_volume( - vol_name, vol_size, src_vol_name) - mock_create_snapshot.assert_called_once_with( - vol_name=src_vol_name, snapshot_name=temp_snapshot_name) - mock_volume_from_snapshot.assert_called_once_with( - snapshot_name=temp_snapshot_name, - vol_name=vol_name, vol_size=vol_size) - mock_delete_snapshot.assert_called_once_with( - snapshot_name=temp_snapshot_name) - self.assertIsNone(retval) diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_conf.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_conf.py deleted file mode 100644 index ce70c967ea4..00000000000 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_conf.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import ddt - -import mock -import os -import shutil -import tempfile - -from six.moves import configparser - -from cinder import test -from cinder.volume.drivers.fusionstorage import fs_conf - - -@ddt.ddt -class FusionStorageConfTestCase(test.TestCase): - def setUp(self): - super(FusionStorageConfTestCase, self).setUp() - self.tmp_dir = tempfile.mkdtemp() - self.conf = mock.Mock() - self._create_fake_conf_file() - self.fusionstorage_conf = fs_conf.FusionStorageConf( - self.conf, "cinder@fs") - - def tearDown(self): - shutil.rmtree(self.tmp_dir) - super(FusionStorageConfTestCase, self).tearDown() - - def _create_fake_conf_file(self): - self.conf.cinder_fusionstorage_conf_file = ( - self.tmp_dir + '/cinder.conf') - - config = configparser.ConfigParser() - config.add_section('backend_name') - config.set('backend_name', 'dsware_rest_url', 'https://fake_rest_site') - config.set('backend_name', 'san_login', 'fake_user') - config.set('backend_name', 'san_password', 'fake_passwd') - config.set('backend_name', 'dsware_storage_pools', 'fake_pool') - - config.add_section('manager_ip') - config.set('manager_ip', 'fake_host', 'fake_ip') - config.write(open(self.conf.cinder_fusionstorage_conf_file, 'w')) - - @mock.patch.object(fs_conf.FusionStorageConf, '_encode_authentication') - @mock.patch.object(fs_conf.FusionStorageConf, '_pools_name') - @mock.patch.object(fs_conf.FusionStorageConf, '_san_address') - @mock.patch.object(fs_conf.FusionStorageConf, '_san_user') - @mock.patch.object(fs_conf.FusionStorageConf, '_san_password') - def test_update_config_value(self, mock_san_password, mock_san_user, - mock_san_address, mock_pools_name, - mock_encode_authentication): - self.fusionstorage_conf.update_config_value() - mock_encode_authentication.assert_called_once_with() - mock_pools_name.assert_called_once_with() - mock_san_address.assert_called_once_with() - mock_san_user.assert_called_once_with() - mock_san_password.assert_called_once_with() - - @mock.patch.object(os.path, 'exists') - def test__encode_authentication(self, mock_exists): - config = configparser.ConfigParser() - config.read(self.conf.cinder_fusionstorage_conf_file) - mock_exists.return_value = False - - user_name = 'fake_user' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=user_name) - self.fusionstorage_conf._encode_authentication() - - password = 'fake_passwd' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=password) - self.fusionstorage_conf._encode_authentication() - - @mock.patch.object(os.path, 'exists') - @mock.patch.object(configparser.ConfigParser, 'set') - def test__rewrite_conf(self, mock_set, mock_exists): - mock_exists.return_value = False - mock_set.return_value = "success" - self.fusionstorage_conf._rewrite_conf('fake_name', 'fake_pwd') - - def test__san_address(self): - address = 'https://fake_rest_site' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=address) - self.fusionstorage_conf._san_address() - self.assertEqual('https://fake_rest_site', - self.fusionstorage_conf.configuration.san_address) - - def test__san_user(self): - user = '!&&&ZmFrZV91c2Vy' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=user) - self.fusionstorage_conf._san_user() - self.assertEqual( - 'fake_user', self.fusionstorage_conf.configuration.san_user) - - user = 'fake_user_2' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=user) - self.fusionstorage_conf._san_user() - self.assertEqual( - 'fake_user_2', self.fusionstorage_conf.configuration.san_user) - - def test__san_password(self): - password = '!&&&ZmFrZV9wYXNzd2Q=' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=password) - self.fusionstorage_conf._san_password() - self.assertEqual( - 'fake_passwd', self.fusionstorage_conf.configuration.san_password) - - password = 'fake_passwd_2' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=password) - self.fusionstorage_conf._san_password() - self.assertEqual('fake_passwd_2', - self.fusionstorage_conf.configuration.san_password) - - def test__pools_name(self): - pools_name = 'fake_pool' - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=pools_name) - self.fusionstorage_conf._pools_name() - self.assertListEqual( - ['fake_pool'], self.fusionstorage_conf.configuration.pools_name) - - def test__manager_ip(self): - manager_ips = {'fake_host': 'fake_ip'} - self.mock_object( - self.fusionstorage_conf.configuration, 'safe_get', - return_value=manager_ips) - self.fusionstorage_conf._manager_ip() - self.assertDictEqual({'fake_host': 'fake_ip'}, - self.fusionstorage_conf.configuration.manager_ips) diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py deleted file mode 100644 index 211663e0831..00000000000 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import json - -import re -import requests - - -class FakeBaseSession(requests.Session): - method_map = {} - - def _get_response(self, method, url): - url_map = self.method_map.get(method, {}) - tmp = None - data = {} - for k in url_map: - if re.search(k, url): - if not tmp or len(tmp) < len(k): - data = url_map[k] - tmp = k - - resp_content = {'result': 0} - resp_content.update(data) - resp = requests.Response() - resp.headers['X-Auth-Token'] = 'fake_token' - resp.status_code = 0 - resp.encoding = 'utf-8' - resp._content = json.dumps(resp_content).encode('utf-8') - - return resp - - def get(self, url, **kwargs): - return self._get_response('get', url) - - def post(self, url, **kwargs): - return self._get_response('post', url) diff --git a/cinder/volume/drivers/fusionstorage/__init__.py b/cinder/volume/drivers/fusionstorage/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/cinder/volume/drivers/fusionstorage/constants.py b/cinder/volume/drivers/fusionstorage/constants.py deleted file mode 100644 index 874670b53b3..00000000000 --- a/cinder/volume/drivers/fusionstorage/constants.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2016 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -DEFAULT_TIMEOUT = 50 -LOGIN_SOCKET_TIMEOUT = 32 - -CONNECT_ERROR = 403 -ERROR_UNAUTHORIZED = 10000003 -VOLUME_NOT_EXIST = 31000000 - -BASIC_URI = '/dsware/service/' -CONF_PATH = "/etc/cinder/cinder.conf" - -CONF_ADDRESS = "dsware_rest_url" -CONF_MANAGER_IP = "manager_ips" -CONF_POOLS = "dsware_storage_pools" -CONF_PWD = "san_password" -CONF_USER = "san_login" diff --git a/cinder/volume/drivers/fusionstorage/dsware.py b/cinder/volume/drivers/fusionstorage/dsware.py deleted file mode 100644 index cc55fc7c8d5..00000000000 --- a/cinder/volume/drivers/fusionstorage/dsware.py +++ /dev/null @@ -1,383 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import units - -from cinder import exception -from cinder.i18n import _ -from cinder import interface -from cinder.volume import driver -from cinder.volume.drivers.fusionstorage import fs_client -from cinder.volume.drivers.fusionstorage import fs_conf -from cinder.volume.drivers.san import san -from cinder.volume import volume_utils - -LOG = logging.getLogger(__name__) - -volume_opts = [ - cfg.BoolOpt("dsware_isthin", - default=False, - help='The flag of thin storage allocation.', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.StrOpt("dsware_manager", - default='', - help='Fusionstorage manager ip addr for cinder-volume.', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.StrOpt('fusionstorageagent', - default='', - help='Fusionstorage agent ip addr range', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.StrOpt('pool_type', - default='default', - help='Pool type, like sata-2copy', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.ListOpt('pool_id_filter', - default=[], - help='Pool id permit to use', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.IntOpt('clone_volume_timeout', - default=680, - help='Create clone volume timeout', - deprecated_for_removal=True, - deprecated_since='14.0.0', - deprecated_reason='FusionStorage cinder driver refactored the ' - 'code with Restful method and the old CLI ' - 'mode has been abandon. So those ' - 'configuration items are no longer used.'), - cfg.DictOpt('manager_ips', - default={}, - help='This option is to support the FSA to mount across the ' - 'different nodes. The parameters takes the standard dict ' - 'config form, manager_ips = host1:ip1, host2:ip2...'), - cfg.StrOpt('dsware_rest_url', - default='', - help='The address of FusionStorage array. For example, ' - '"dsware_rest_url=xxx"'), - cfg.StrOpt('dsware_storage_pools', - default="", - help='The list of pools on the FusionStorage array, the ' - 'semicolon(;) was used to split the storage pools, ' - '"dsware_storage_pools = xxx1; xxx2; xxx3"') -] - -CONF = cfg.CONF -CONF.register_opts(volume_opts) - - -@interface.volumedriver -class DSWAREDriver(driver.VolumeDriver): - VERSION = '2.0' - CI_WIKI_NAME = 'Huawei_FusionStorage_CI' - - # TODO(jsbryant) Remove driver in the 'U' release due to no py37 support. - SUPPORTED = False - - def __init__(self, *args, **kwargs): - super(DSWAREDriver, self).__init__(*args, **kwargs) - - if not self.configuration: - msg = _('Configuration is not found.') - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - self.configuration.append_config_values(volume_opts) - self.configuration.append_config_values(san.san_opts) - self.conf = fs_conf.FusionStorageConf(self.configuration, self.host) - self.client = None - - @staticmethod - def get_driver_options(): - return volume_opts - - def do_setup(self, context): - self.conf.update_config_value() - url_str = self.configuration.san_address - url_user = self.configuration.san_user - url_password = self.configuration.san_password - - self.client = fs_client.RestCommon( - fs_address=url_str, fs_user=url_user, - fs_password=url_password) - self.client.login() - - def check_for_setup_error(self): - all_pools = self.client.query_pool_info() - all_pools_name = [p['poolName'] for p in all_pools - if p.get('poolName')] - - for pool in self.configuration.pools_name: - if pool not in all_pools_name: - msg = _('Storage pool %(pool)s does not exist ' - 'in the FusionStorage.') % {'pool': pool} - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def _update_pool_stats(self): - backend_name = self.configuration.safe_get( - 'volume_backend_name') or self.__class__.__name__ - data = {"volume_backend_name": backend_name, - "driver_version": "2.0.9", - "QoS_support": False, - "thin_provisioning_support": False, - "pools": [], - "vendor_name": "Huawei" - } - all_pools = self.client.query_pool_info() - - for pool in all_pools: - if pool['poolName'] in self.configuration.pools_name: - single_pool_info = self._update_single_pool_info_status(pool) - data['pools'].append(single_pool_info) - return data - - def _get_capacity(self, pool_info): - pool_capacity = {} - - total = float(pool_info['totalCapacity']) / units.Ki - free = (float(pool_info['totalCapacity']) - - float(pool_info['usedCapacity'])) / units.Ki - pool_capacity['total_capacity_gb'] = total - pool_capacity['free_capacity_gb'] = free - - return pool_capacity - - def _update_single_pool_info_status(self, pool_info): - status = {} - capacity = self._get_capacity(pool_info=pool_info) - status.update({ - "pool_name": pool_info['poolName'], - "total_capacity_gb": capacity['total_capacity_gb'], - "free_capacity_gb": capacity['free_capacity_gb'], - }) - return status - - def get_volume_stats(self, refresh=False): - self.client.keep_alive() - stats = self._update_pool_stats() - return stats - - def _check_volume_exist(self, volume): - vol_name = self._get_vol_name(volume) - result = self.client.query_volume_by_name(vol_name=vol_name) - if result: - return result - - def _raise_exception(self, msg): - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - def _get_pool_id(self, volume): - pool_id = None - pool_name = volume_utils.extract_host(volume.host, level='pool') - all_pools = self.client.query_pool_info() - for pool in all_pools: - if pool_name == pool['poolName']: - pool_id = pool['poolId'] - - if pool_id is None: - msg = _('Storage pool %(pool)s does not exist on the array. ' - 'Please check.') % {"pool": pool_id} - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - return pool_id - - def _get_vol_name(self, volume): - provider_location = volume.get("provider_location", None) - if provider_location: - vol_name = json.loads(provider_location).get("name") - else: - vol_name = volume.name - return vol_name - - def create_volume(self, volume): - pool_id = self._get_pool_id(volume) - vol_name = volume.name - vol_size = volume.size - vol_size *= units.Ki - self.client.create_volume( - pool_id=pool_id, vol_name=vol_name, vol_size=vol_size) - - def delete_volume(self, volume): - vol_name = self._get_vol_name(volume) - if self._check_volume_exist(volume): - self.client.delete_volume(vol_name=vol_name) - - def extend_volume(self, volume, new_size): - vol_name = self._get_vol_name(volume) - if not self._check_volume_exist(volume): - msg = _("Volume: %(vol_name)s does not exist!" - ) % {"vol_name": vol_name} - self._raise_exception(msg) - else: - new_size *= units.Ki - self.client.expand_volume(vol_name, new_size) - - def _check_snapshot_exist(self, volume, snapshot): - pool_id = self._get_pool_id(volume) - snapshot_name = self._get_snapshot_name(snapshot) - result = self.client.query_snapshot_by_name( - pool_id=pool_id, snapshot_name=snapshot_name) - if result.get('totalNum'): - return result - - def _get_snapshot_name(self, snapshot): - provider_location = snapshot.get("provider_location", None) - if provider_location: - snapshot_name = json.loads(provider_location).get("name") - else: - snapshot_name = snapshot.name - return snapshot_name - - def create_volume_from_snapshot(self, volume, snapshot): - vol_name = self._get_vol_name(volume) - snapshot_name = self._get_snapshot_name(snapshot) - vol_size = volume.size - - if not self._check_snapshot_exist(snapshot.volume, snapshot): - msg = _("Snapshot: %(name)s does not exist!" - ) % {"name": snapshot_name} - self._raise_exception(msg) - elif self._check_volume_exist(volume): - msg = _("Volume: %(vol_name)s already exists!" - ) % {'vol_name': vol_name} - self._raise_exception(msg) - else: - vol_size *= units.Ki - self.client.create_volume_from_snapshot( - snapshot_name=snapshot_name, vol_name=vol_name, - vol_size=vol_size) - - def create_cloned_volume(self, volume, src_volume): - vol_name = self._get_vol_name(volume) - src_vol_name = self._get_vol_name(src_volume) - - vol_size = volume.size - vol_size *= units.Ki - - if not self._check_volume_exist(src_volume): - msg = _("Volume: %(vol_name)s does not exist!" - ) % {"vol_name": src_vol_name} - self._raise_exception(msg) - else: - self.client.create_volume_from_volume( - vol_name=vol_name, vol_size=vol_size, - src_vol_name=src_vol_name) - - def create_snapshot(self, snapshot): - snapshot_name = self._get_snapshot_name(snapshot) - vol_name = self._get_vol_name(snapshot.volume) - - self.client.create_snapshot( - snapshot_name=snapshot_name, vol_name=vol_name) - - def delete_snapshot(self, snapshot): - snapshot_name = self._get_snapshot_name(snapshot) - - if self._check_snapshot_exist(snapshot.volume, snapshot): - self.client.delete_snapshot(snapshot_name=snapshot_name) - - def _get_manager_ip(self, context): - if self.configuration.manager_ips.get(context['host']): - return self.configuration.manager_ips.get(context['host']) - else: - msg = _("The required host: %(host)s and its manager ip are not " - "included in the configuration file." - ) % {"host": context['host']} - LOG.error(msg) - raise exception.VolumeBackendAPIException(msg) - - def _attach_volume(self, context, volume, properties, remote=False): - vol_name = self._get_vol_name(volume) - if not self._check_volume_exist(volume): - msg = _("Volume: %(vol_name)s does not exist!" - ) % {"vol_name": vol_name} - self._raise_exception(msg) - manager_ip = self._get_manager_ip(properties) - result = self.client.attach_volume(vol_name, manager_ip) - attach_path = result[vol_name][0]['devName'].encode('unicode-escape') - attach_info = dict() - attach_info['device'] = dict() - attach_info['device']['path'] = attach_path - if attach_path == '': - msg = _("Host attach volume failed!") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - return attach_info, volume - - def _detach_volume(self, context, attach_info, volume, properties, - force=False, remote=False, ignore_errors=False): - vol_name = self._get_vol_name(volume) - if self._check_volume_exist(volume): - manager_ip = self._get_manager_ip(properties) - self.client.detach_volume(vol_name, manager_ip) - - def initialize_connection(self, volume, connector): - vol_name = self._get_vol_name(volume) - manager_ip = self._get_manager_ip(connector) - if not self._check_volume_exist(volume): - msg = _("Volume: %(vol_name)s does not exist!" - ) % {"vol_name": vol_name} - self._raise_exception(msg) - self.client.attach_volume(vol_name, manager_ip) - volume_info = self.client.query_volume_by_name(vol_name=vol_name) - vol_wwn = volume_info.get('wwn') - by_id_path = "/dev/disk/by-id/" + "wwn-0x%s" % vol_wwn - properties = {'device_path': by_id_path} - return {'driver_volume_type': 'local', - 'data': properties} - - def terminate_connection(self, volume, connector, **kwargs): - if self._check_volume_exist(volume): - manager_ip = self._get_manager_ip(connector) - vol_name = self._get_vol_name(volume) - self.client.detach_volume(vol_name, manager_ip) - - def create_export(self, context, volume, connector): - pass - - def ensure_export(self, context, volume): - pass - - def remove_export(self, context, volume): - pass diff --git a/cinder/volume/drivers/fusionstorage/fs_client.py b/cinder/volume/drivers/fusionstorage/fs_client.py deleted file mode 100644 index 40e98ab6929..00000000000 --- a/cinder/volume/drivers/fusionstorage/fs_client.py +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json - -from oslo_log import log as logging -import requests -import six - -from cinder import exception -from cinder.i18n import _ -from cinder.volume.drivers.fusionstorage import constants - -LOG = logging.getLogger(__name__) - - -class RestCommon(object): - def __init__(self, fs_address, fs_user, fs_password): - self.address = fs_address - self.user = fs_user - self.password = fs_password - - self.session = None - self.token = None - self.version = None - - self.init_http_head() - - LOG.warning("Suppressing requests library SSL Warnings") - requests.packages.urllib3.disable_warnings( - requests.packages.urllib3.exceptions.InsecureRequestWarning) - requests.packages.urllib3.disable_warnings( - requests.packages.urllib3.exceptions.InsecurePlatformWarning) - - def init_http_head(self): - self.session = requests.Session() - self.session.headers.update({ - "Content-Type": "application/json;charset=UTF-8", - }) - self.session.verify = False - - def call(self, url, method, data=None, - call_timeout=constants.DEFAULT_TIMEOUT, - get_version=False, filter_flag=False, json_flag=False): - kwargs = {'timeout': call_timeout} - if data: - kwargs['data'] = json.dumps(data) - - if not get_version: - call_url = self.address + constants.BASIC_URI + self.version + url - else: - call_url = self.address + constants.BASIC_URI + url - - func = getattr(self.session, method.lower()) - - try: - result = func(call_url, **kwargs) - except Exception as err: - LOG.error('Bad response from server: %(url)s. ' - 'Error: %(err)s'), {'url': url, 'err': err} - return {"error": { - "code": constants.CONNECT_ERROR, - "description": "Connect to server error."}} - - try: - result.raise_for_status() - except requests.HTTPError as exc: - return {"error": {"code": exc.response.status_code, - "description": six.text_type(exc)}} - - if not filter_flag: - LOG.info(''' - Request URL: %(url)s, - Call Method: %(method)s, - Request Data: %(data)s, - Response Data: %(res)s, - Result Data: %(res_json)s''', {'url': url, 'method': method, - 'data': data, 'res': result, - 'res_json': result.json()}) - - if json_flag: - return result - else: - return result.json() - - def _assert_rest_result(self, result, err_str): - if result.get('result') != 0: - msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str, - 'res': result}) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - def get_version(self): - url = 'rest/version' - self.session.headers.update({ - "Referer": self.address + constants.BASIC_URI - }) - result = self.call(url=url, method='GET', get_version=True) - self._assert_rest_result(result, _('Get version session error.')) - if result.get("currentVersion"): - self.version = result["currentVersion"] - - def login(self): - self.get_version() - url = '/sec/login' - data = {"userName": self.user, "password": self.password} - result = self.call(url, 'POST', data=data, - call_timeout=constants.LOGIN_SOCKET_TIMEOUT, - filter_flag=True, json_flag=True) - self._assert_rest_result(result.json(), _('Login session error.')) - self.token = result.headers['X-Auth-Token'] - - self.session.headers.update({ - "x-auth-token": self.token - }) - - def logout(self): - url = '/sec/logout' - if self.address: - result = self.call(url, 'POST') - self._assert_rest_result(result, _('Logout session error.')) - - def keep_alive(self): - url = '/sec/keepAlive' - result = self.call(url, 'POST', filter_flag=True) - - if result.get('result') == constants.ERROR_UNAUTHORIZED: - try: - self.login() - except Exception: - LOG.error('The FusionStorage may have been powered off. ' - 'Power on the FusionStorage and then log in.') - raise - else: - self._assert_rest_result(result, _('Keep alive session error.')) - - def query_pool_info(self, pool_id=None): - pool_id = str(pool_id) - if pool_id != 'None': - url = '/storagePool' + '?poolId=' + pool_id - else: - url = '/storagePool' - result = self.call(url, 'GET', filter_flag=True) - self._assert_rest_result(result, _("Query pool session error.")) - return result['storagePools'] - - def query_volume_by_name(self, vol_name): - url = '/volume/queryByName?volName=' + vol_name - result = self.call(url, 'GET') - if result.get('errorCode') == constants.VOLUME_NOT_EXIST: - return None - self._assert_rest_result( - result, _("Query volume by name session error")) - return result.get('lunDetailInfo') - - def query_volume_by_id(self, vol_id): - url = '/volume/queryById?volId=' + vol_id - result = self.call(url, 'GET') - if result.get('errorCode') == constants.VOLUME_NOT_EXIST: - return None - self._assert_rest_result( - result, _("Query volume by ID session error")) - return result.get('lunDetailInfo') - - def create_volume(self, vol_name, vol_size, pool_id): - url = '/volume/create' - params = {"volName": vol_name, "volSize": vol_size, "poolId": pool_id} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Create volume session error.')) - - def delete_volume(self, vol_name): - url = '/volume/delete' - params = {"volNames": [vol_name]} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Delete volume session error.')) - - def attach_volume(self, vol_name, manage_ip): - url = '/volume/attach' - params = {"volName": [vol_name], "ipList": [manage_ip]} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Attach volume session error.')) - - if int(result[vol_name][0]['errorCode']) != 0: - msg = _("Host attach volume failed!") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - return result - - def detach_volume(self, vol_name, manage_ip): - url = '/volume/detach/' - params = {"volName": [vol_name], "ipList": [manage_ip]} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Detach volume session error.')) - - def expand_volume(self, vol_name, new_vol_size): - url = '/volume/expand' - params = {"volName": vol_name, "newVolSize": new_vol_size} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Expand volume session error.')) - - def query_snapshot_by_name(self, pool_id, snapshot_name, page_num=1, - page_size=1000): - # Filter the snapshot according to the name, while the "page_num" and - # "page_size" must be set while using the interface. - url = '/snapshot/list' - params = {"poolId": pool_id, "pageNum": page_num, - "pageSize": page_size, - "filters": {"volumeName": snapshot_name}} - - result = self.call(url, "POST", params) - self._assert_rest_result( - result, _('query snapshot list session error.')) - return result - - def create_snapshot(self, snapshot_name, vol_name): - url = '/snapshot/create/' - params = {"volName": vol_name, "snapshotName": snapshot_name} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Create snapshot error.')) - - def delete_snapshot(self, snapshot_name): - url = '/snapshot/delete/' - params = {"snapshotName": snapshot_name} - result = self.call(url, "POST", params) - self._assert_rest_result(result, _('Delete snapshot session error.')) - - def create_volume_from_snapshot(self, snapshot_name, vol_name, vol_size): - url = '/snapshot/volume/create/' - params = {"src": snapshot_name, "volName": vol_name, - "volSize": vol_size} - result = self.call(url, "POST", params) - self._assert_rest_result( - result, _('create volume from snapshot session error.')) - - def create_volume_from_volume(self, vol_name, vol_size, src_vol_name): - temp_snapshot_name = "temp" + src_vol_name + "clone" + vol_name - - self.create_snapshot(vol_name=src_vol_name, - snapshot_name=temp_snapshot_name) - - self.create_volume_from_snapshot(snapshot_name=temp_snapshot_name, - vol_name=vol_name, vol_size=vol_size) - - self.delete_snapshot(snapshot_name=temp_snapshot_name) diff --git a/cinder/volume/drivers/fusionstorage/fs_conf.py b/cinder/volume/drivers/fusionstorage/fs_conf.py deleted file mode 100644 index ea72444cd84..00000000000 --- a/cinder/volume/drivers/fusionstorage/fs_conf.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2018 Huawei Technologies Co., Ltd. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import base64 -import os -import six - -from oslo_log import log as logging -from six.moves import configparser - -from cinder import exception -from cinder.i18n import _ -from cinder import utils -from cinder.volume.drivers.fusionstorage import constants - - -LOG = logging.getLogger(__name__) - - -class FusionStorageConf(object): - def __init__(self, configuration, host): - self.configuration = configuration - self._check_host(host) - - def _check_host(self, host): - if host and len(host.split('@')) > 1: - self.host = host.split('@')[1] - else: - msg = _("The host %s is not reliable. Please check cinder-volume " - "backend.") % host - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def update_config_value(self): - self._encode_authentication() - self._pools_name() - self._san_address() - self._san_user() - self._san_password() - - def _encode_authentication(self): - name_node = self.configuration.safe_get(constants.CONF_USER) - pwd_node = self.configuration.safe_get(constants.CONF_PWD) - - need_encode = False - if name_node is not None and not name_node.startswith('!&&&'): - encoded = base64.b64encode(six.b(name_node)).decode() - name_node = '!&&&' + encoded - need_encode = True - - if pwd_node is not None and not pwd_node.startswith('!&&&'): - encoded = base64.b64encode(six.b(pwd_node)).decode() - pwd_node = '!&&&' + encoded - need_encode = True - - if need_encode: - self._rewrite_conf(name_node, pwd_node) - - def _rewrite_conf(self, name_node, pwd_node): - if os.path.exists(constants.CONF_PATH): - utils.execute("chmod", "666", constants.CONF_PATH, - run_as_root=True) - conf = configparser.ConfigParser() - conf.read(constants.CONF_PATH) - if name_node: - conf.set(self.host, constants.CONF_USER, name_node) - if pwd_node: - conf.set(self.host, constants.CONF_PWD, pwd_node) - fh = open(constants.CONF_PATH, 'w') - conf.write(fh) - fh.close() - utils.execute("chmod", "644", constants.CONF_PATH, - run_as_root=True) - - def _assert_text_result(self, text, mess): - if not text: - msg = _("%s is not configured.") % mess - LOG.error(msg) - raise exception.InvalidInput(reason=msg) - - def _san_address(self): - address = self.configuration.safe_get(constants.CONF_ADDRESS) - self._assert_text_result(address, mess=constants.CONF_ADDRESS) - setattr(self.configuration, 'san_address', address) - - def _decode_text(self, text): - return (base64.b64decode(six.b(text[4:])).decode() if - text.startswith('!&&&') else text) - - def _san_user(self): - user_text = self.configuration.safe_get(constants.CONF_USER) - self._assert_text_result(user_text, mess=constants.CONF_USER) - user = self._decode_text(user_text) - setattr(self.configuration, 'san_user', user) - - def _san_password(self): - pwd_text = self.configuration.safe_get(constants.CONF_PWD) - self._assert_text_result(pwd_text, mess=constants.CONF_PWD) - pwd = self._decode_text(pwd_text) - setattr(self.configuration, 'san_password', pwd) - - def _pools_name(self): - pools_name = self.configuration.safe_get(constants.CONF_POOLS) - self._assert_text_result(pools_name, mess=constants.CONF_POOLS) - pools = set(x.strip() for x in pools_name.split(';') if x.strip()) - if not pools: - msg = _('No valid storage pool configured.') - LOG.error(msg) - raise exception.InvalidInput(msg) - setattr(self.configuration, 'pools_name', list(pools)) - - def _manager_ip(self): - manager_ips = self.configuration.safe_get(constants.CONF_MANAGER_IP) - self._assert_text_result(manager_ips, mess=constants.CONF_MANAGER_IP) - setattr(self.configuration, 'manager_ips', manager_ips) diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index eb0acf62d77..84ab6195d36 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -81,9 +81,6 @@ title=Huawei 18000 Series Driver (iSCSI, FC) [driver.huawei_dorado] title=Huawei Dorado V3 Series Driver (iSCSI, FC) -[driver.huawei_fusionstorage] -title=Huawei FusionStorage Driver (dsware) - [driver.ibm_ds8k] title=IBM DS8k Storage Driver (FC) @@ -224,7 +221,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=complete driver.ibm_ds8k=complete driver.ibm_flashsystem=missing @@ -289,7 +285,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=complete driver.infinidat=complete driver.ibm_ds8k=missing driver.ibm_flashsystem=complete @@ -354,7 +349,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=complete driver.infinidat=missing driver.ibm_ds8k=missing driver.ibm_flashsystem=missing @@ -422,7 +416,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=complete driver.ibm_ds8k=missing driver.ibm_flashsystem=missing @@ -489,7 +482,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=missing driver.ibm_ds8k=complete driver.ibm_flashsystem=missing @@ -557,7 +549,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=missing driver.ibm_ds8k=complete driver.ibm_flashsystem=missing @@ -624,7 +615,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=complete driver.ibm_ds8k=missing driver.ibm_flashsystem=missing @@ -692,7 +682,6 @@ driver.huawei_v5=complete driver.huawei_f_v5=complete driver.huawei_18000=complete driver.huawei_dorado=complete -driver.huawei_fusionstorage=missing driver.infinidat=missing driver.ibm_ds8k=missing driver.ibm_flashsystem=missing @@ -760,7 +749,6 @@ driver.huawei_v5=missing driver.huawei_f_v5=missing driver.huawei_18000=missing driver.huawei_dorado=missing -driver.huawei_fusionstorage=missing driver.infinidat=complete driver.ibm_ds8k=complete driver.ibm_flashsystem=missing @@ -825,7 +813,6 @@ driver.huawei_f_v5=missing driver.huawei_v5=missing driver.huawei_18000=missing driver.huawei_dorado=missing -driver.huawei_fusionstorage=missing driver.infinidat=missing driver.ibm_ds8k=missing driver.ibm_flashsystem=missing @@ -894,7 +881,6 @@ driver.huawei_f_v5=missing driver.huawei_v5=missing driver.huawei_18000=missing driver.huawei_dorado=missing -driver.huawei_fusionstorage=missing driver.infinidat=missing driver.ibm_ds8k=missing driver.ibm_flashsystem=missing diff --git a/doc/source/reference/support-matrix.rst b/doc/source/reference/support-matrix.rst index e1be7c02725..fce6b753d5f 100644 --- a/doc/source/reference/support-matrix.rst +++ b/doc/source/reference/support-matrix.rst @@ -85,3 +85,6 @@ release. * Tintri Storage Driver * Veritas HyperScale Storage Driver * Nexenta Edge Storage Driver + +* Ussuri + * Huawei FusionStorage Driver diff --git a/releasenotes/notes/huawei-fusionstorage-driver-removal-dedb8ea4d30df009.yaml b/releasenotes/notes/huawei-fusionstorage-driver-removal-dedb8ea4d30df009.yaml new file mode 100644 index 00000000000..a034bd19fcf --- /dev/null +++ b/releasenotes/notes/huawei-fusionstorage-driver-removal-dedb8ea4d30df009.yaml @@ -0,0 +1,6 @@ +upgrade: + - | + The Huawei FusionStorage driver was marked unsupported in the + Train release and has now been removed. All data on + FusionStorage backends should be migrated to a supported + storage backend before upgrading your Cinder installation.