From 73372c279e4b2d389b410c92885e65758ec7c2fd Mon Sep 17 00:00:00 2001 From: doubletao Date: Mon, 26 Nov 2018 10:52:12 +0800 Subject: [PATCH] Update FusionStorage Cinder Driver by using REST API The FusionStorage OpenStack Cinder Driver no longer used the CLI method. Instead, it uses the RESTful method to reconstruct the origin code so that the storage can be accessed quickly, convenient and professionally. Change-Id: I2c5da50d5465b6d34ac7e9b8023a1b12eaa7d1c5 --- cinder/opts.py | 2 +- .../drivers/fusionstorage/test_dsware.py | 1211 ++++++----------- .../drivers/fusionstorage/test_fs_client.py | 272 ++++ .../drivers/fusionstorage/test_fs_conf.py | 99 ++ .../drivers/fusionstorage/test_fspythonapi.py | 447 ------ .../drivers/fusionstorage/test_utils.py | 48 + .../volume/drivers/fusionstorage/constants.py | 31 + cinder/volume/drivers/fusionstorage/dsware.py | 841 ++++-------- .../volume/drivers/fusionstorage/fs_client.py | 256 ++++ .../volume/drivers/fusionstorage/fs_conf.py | 135 ++ .../drivers/fusionstorage/fspythonapi.py | 495 ------- .../configuration/tables/cinder-fusionio.inc | 78 +- 12 files changed, 1635 insertions(+), 2280 deletions(-) create mode 100644 cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py create mode 100644 cinder/tests/unit/volume/drivers/fusionstorage/test_fs_conf.py delete mode 100644 cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py create mode 100644 cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py create mode 100644 cinder/volume/drivers/fusionstorage/constants.py create mode 100644 cinder/volume/drivers/fusionstorage/fs_client.py create mode 100644 cinder/volume/drivers/fusionstorage/fs_conf.py delete mode 100644 cinder/volume/drivers/fusionstorage/fspythonapi.py diff --git a/cinder/opts.py b/cinder/opts.py index dd79dc861f6..38359e94cfd 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -252,6 +252,7 @@ def list_opts(): cinder_volume_driver.nvmet_opts, cinder_volume_drivers_datacore_driver.datacore_opts, cinder_volume_drivers_datacore_iscsi.datacore_iscsi_opts, + cinder_volume_drivers_fusionstorage_dsware.volume_opts, cinder_volume_drivers_inspur_as13000_as13000driver. inspur_as13000_opts, cinder_volume_drivers_inspur_instorage_instoragecommon. @@ -295,7 +296,6 @@ def list_opts(): cinder_volume_drivers_drbdmanagedrv.drbd_opts, cinder_volume_drivers_fujitsu_eternusdxcommon. FJ_ETERNUS_DX_OPT_opts, - cinder_volume_drivers_fusionstorage_dsware.volume_opts, cinder_volume_drivers_hpe_hpe3parcommon.hpe3par_opts, cinder_volume_drivers_hpe_hpelefthandiscsi.hpelefthand_opts, cinder_volume_drivers_huawei_common.huawei_opts, diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py index b9cc0e8f520..272e9086209 100644 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013 - 2016 Huawei Technologies Co., Ltd. +# Copyright (c) 2018 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,774 +12,467 @@ # 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 Huawei FusionStorage drivers. -""" +import ddt +import json import mock -from oslo_config import cfg -from oslo_service import loopingcall +import uuid -from cinder import context from cinder import exception -from cinder.image import image_utils +from cinder import objects from cinder import test -from cinder.tests.unit import fake_constants -from cinder.tests.unit import fake_snapshot -from cinder.tests.unit import fake_volume -from cinder.volume import configuration as conf +from cinder.volume import configuration as config from cinder.volume.drivers.fusionstorage import dsware -from cinder.volume.drivers.fusionstorage import fspythonapi - - -test_volume = {'name': 'test_vol1', - 'size': 4, - 'volume_metadata': '', - 'host': 'host01@dsware', - 'instance_uuid': None, - 'provider_id': '127.0.0.1', - 'id': fake_constants.VOLUME_ID} - -test_src_volume = {'name': 'test_vol2', - 'size': 4, - 'status': 'available'} - -test_snapshot = { - 'name': 'test_snapshot1', - 'volume_id': fake_constants.VOLUME_ID, - 'volume_size': '4'} +from cinder.volume.drivers.fusionstorage import fs_client +from cinder.volume.drivers.fusionstorage import fs_conf +from cinder.volume import utils as volume_utils class FakeDSWAREDriver(dsware.DSWAREDriver): def __init__(self): - configuration = conf.Configuration( - [ - cfg.StrOpt('fake'), - ], - None - ) - super(FakeDSWAREDriver, self).__init__(configuration=configuration) - self.dsware_client = fspythonapi.FSPythonApi() - self.manage_ip = '127.0.0.1' - self.pool_type = '1' + self.configuration = config.Configuration(None) + self.conf = fs_conf.FusionStorageConf(self.configuration, "cinder@fs") + self.client = None -class DSwareDriverTestCase(test.TestCase): +@ddt.ddt +class TestDSWAREDriver(test.TestCase): + def setUp(self): - super(DSwareDriverTestCase, self).setUp() - self.driver = FakeDSWAREDriver() - self.context = context.get_admin_context() - self.volume = fake_volume.fake_volume_obj(context=self.context, - **test_volume) - self.scr_volume = fake_volume.fake_volume_obj(context=self.context, - **test_src_volume) - self.snapshot = fake_snapshot.fake_snapshot_obj(context=self.context, - **test_snapshot) - - def test_private_get_dsware_manage_ip(self): - retval = self.driver._get_dsware_manage_ip(self.volume) - self.assertEqual('127.0.0.1', retval) - - test_volume_fail_dict = {'name': 'test_vol', - 'size': 4, - 'volume_metadata': '', - 'host': 'host01@dsware', - 'provider_id': None} - test_volume_fail = fake_volume.fake_volume_obj(context=self.context, - **test_volume_fail_dict) - self.assertRaises(exception.CinderException, - self.driver._get_dsware_manage_ip, - test_volume_fail) - - def test_private_get_poolid_from_host(self): - retval = self.driver._get_poolid_from_host( - 'abc@fusionstorage_sas2copy#0') - self.assertEqual('0', retval) - - retval = self.driver._get_poolid_from_host( - 'abc@fusionstorage_sas2copy@0') - self.assertEqual(self.driver.pool_type, retval) - - retval = self.driver._get_poolid_from_host(None) - self.assertEqual(self.driver.pool_type, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_old_version(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 1, old version - mock_query_dsware.return_value = 1 - mock_create_volume.return_value = 0 - self.driver._create_volume(self.volume.name, - self.volume.size, - True, - 'abc@fusionstorage_sas2copy') - mock_create_volume.assert_called_with(self.volume.name, 0, - self.volume.size, 1) - - self.driver._create_volume(self.volume.name, - self.volume.size, - False, - 'abc@fusionstorage_sas2copy') - mock_create_volume.assert_called_with(self.volume.name, 0, - self.volume.size, 0) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_new_version(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 0, new version - mock_query_dsware.return_value = 0 - mock_get_poolid.return_value = 0 - mock_create_volume.return_value = 0 - self.driver._create_volume(self.volume.name, - self.volume.size, - True, - 'abcE@fusionstorage_sas2copy#0') - mock_create_volume.assert_called_with(self.volume.name, 0, - self.volume.size, 1) - - self.driver._create_volume(self.volume.name, - self.volume.size, - False, - 'abc@fusionstorage_sas2copy#0') - mock_create_volume.assert_called_with(self.volume.name, 0, - self.volume.size, 0) - - mock_query_dsware.return_value = 0 - mock_get_poolid.return_value = 1 - mock_create_volume.return_value = 0 - self.driver._create_volume(self.volume.name, - self.volume.size, - True, - 'abc@fusionstorage_sas2copy#1') - mock_create_volume.assert_called_with(self.volume.name, 1, - self.volume.size, 1) - - self.driver._create_volume(self.volume.name, - self.volume.size, - False, - 'abc@fusionstorage_sas2copy#1') - mock_create_volume.assert_called_with(self.volume.name, 1, - self.volume.size, 0) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_query_version_fail(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 500015, query dsware version failed! - mock_query_dsware.return_value = 500015 - self.assertRaises(exception.CinderException, - self.driver._create_volume, - self.volume.name, - self.volume.size, - True, - 'abc@fusionstorage_sas2copy#0') - self.assertRaises(exception.CinderException, - self.driver._create_volume, - self.volume.name, - self.volume.size, - False, - 'abc@fusionstorage_sas2copy#0') - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_fail(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - mock_query_dsware.return_value = 1 - # create_volume return 1, create volume failed - mock_create_volume.return_value = 1 - self.assertRaises(exception.CinderException, - self.driver._create_volume, - self.volume.name, - self.volume.size, - True, - 'abc@fusionstorage_sas2copy#0') - self.assertRaises(exception.CinderException, - self.driver._create_volume, - self.volume.name, - self.volume.size, - False, - 'abc@fusionstorage_sas2copy#0') - - @mock.patch.object(dsware.DSWAREDriver, '_create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - def test_create_volume(self, mock_get_manage_ip, mock_create_volume): - # success - mock_get_manage_ip.return_value = self.driver.manage_ip - retval = self.driver.create_volume(self.volume) - self.assertEqual({"provider_id": self.driver.manage_ip}, - retval) - - # failure - mock_create_volume.side_effect = exception.CinderException( - 'DSWARE Create Volume failed!') - - self.assertRaises(exception.CinderException, - self.driver.create_volume, - self.volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') - def test_private_create_volume_from_snap(self, mock_create_volume): - mock_create_volume.side_effect = [0, 1] - self.driver._create_volume_from_snap(self.volume.name, - self.volume.size, - self.snapshot.name) - # failure - self.assertRaises(exception.CinderException, - self.driver._create_volume_from_snap, - self.volume.name, self.volume.size, - self.snapshot.name) - - @mock.patch.object(fspythonapi.FSPythonApi, 'extend_volume') - def test_extend_volume(self, mock_extend_volume): - mock_extend_volume.return_value = 0 - self.driver.extend_volume(self.volume, 5) - - mock_extend_volume.return_value = 0 - self.assertRaises(exception.CinderException, - self.driver.extend_volume, - self.volume, - 3) - - mock_extend_volume.return_value = 1 - self.assertRaises(exception.CinderException, - self.driver.extend_volume, - self.volume, - 5) - - @mock.patch.object(dsware.DSWAREDriver, '_create_volume_from_snap') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - def test_create_volume_from_snap(self, mock_manage_ip, mock_create_vol): - # success - mock_manage_ip.return_value = self.driver.manage_ip - retval = self.driver.create_volume_from_snapshot(self.volume, - self.snapshot) - self.assertEqual({"provider_id": self.driver.manage_ip}, - retval) - - # failure - mock_create_vol.side_effect = exception.CinderException( - 'DSWARE:create volume from snap failed') - self.assertRaises(exception.CinderException, - self.driver.create_volume_from_snapshot, - self.volume, self.snapshot) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, - '_wait_for_create_cloned_volume_finish_timer') - def test_create_cloned_volume(self, mock_wait_finish, - mock_get_manage_ip, mock_create_volume): - # success - mock_create_volume.return_value = None - mock_get_manage_ip.return_value = self.driver.manage_ip - mock_wait_finish.return_value = True - retval = self.driver.create_cloned_volume(self.volume, self.scr_volume) - self.assertEqual({"provider_id": "127.0.0.1"}, retval) - - # failure:create exception - mock_create_volume.return_value = 500015 - self.assertRaises(exception.CinderException, - self.driver.create_cloned_volume, - self.volume, self.scr_volume) - # failure:wait exception - mock_create_volume.return_value = None - mock_wait_finish.return_value = False - self.assertRaises(exception.CinderException, - self.driver.create_cloned_volume, - self.volume, self.scr_volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') - def test_private_check_create_cloned_volume_finish(self, - mock_query_volume): - query_result_done = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '0', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - - query_result_doing = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '6', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - - mock_query_volume.side_effect = [ - query_result_done, query_result_doing, query_result_doing] - - # success - self.assertRaises(loopingcall.LoopingCallDone, - self.driver._check_create_cloned_volume_finish, - self.volume.name) - - # in the process of creating volume - self.driver.count = self.driver.configuration.clone_volume_timeout - 1 - self.driver._check_create_cloned_volume_finish(self.volume.name) - self.assertEqual(self.driver.configuration.clone_volume_timeout, - self.driver.count) - - # timeout - self.driver.count = self.driver.configuration.clone_volume_timeout - self.assertRaises(loopingcall.LoopingCallDone, - self.driver._check_create_cloned_volume_finish, - self.volume.name) - - @mock.patch.object(dsware.DSWAREDriver, - '_check_create_cloned_volume_finish') - def test_private_wait_for_create_cloned_volume_finish_timer(self, - mock_check): - mock_check.side_effect = [loopingcall.LoopingCallDone(retvalue=True), - loopingcall.LoopingCallDone(retvalue=False)] - retval = self.driver._wait_for_create_cloned_volume_finish_timer( - self.volume.name) - self.assertTrue(retval) - - retval = self.driver._wait_for_create_cloned_volume_finish_timer( - self.volume.name) - self.assertFalse(retval) - - def test_private_analyse_output(self): - out = 'ret_code=10\nret_desc=test\ndev_addr=/sda\n' - retval = self.driver._analyse_output(out) - self.assertEqual({'dev_addr': '/sda', - 'ret_desc': 'test', 'ret_code': '10'}, - retval) - - out = 'abcdefg' - retval = self.driver._analyse_output(out) - self.assertEqual({}, retval) - - def test_private_attach_volume(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # attached successful - retval = self.driver._attach_volume(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', 'ret_code': '0'}, - retval) - # attached failure - retval = self.driver._attach_volume(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', 'ret_code': '50510011'}, - retval) - - def test_private_detach_volume(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # detached successful - retval = self.driver._detach_volume(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', 'ret_code': '0'}, - retval) - # detached failure - retval = self.driver._detach_volume(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', - 'ret_code': '50510011'}, - retval) - - def test_private_query_volume_attach(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # query successful - retval = self.driver._query_volume_attach(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', - 'ret_code': '0'}, - retval) - # query failure - retval = self.driver._query_volume_attach(self.volume.name, - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', - 'ret_code': '50510011'}, - retval) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(image_utils, 'fetch_to_raw') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_image_to_volume(self, mock_detach, mock_fetch, - mock_attach, mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_id = '' - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.side_effect = [success, failure, success] - mock_detach.side_effect = [success, failure, failure] - - # success - self.driver.copy_image_to_volume(context, self.volume, image_service, - image_id) - - # failure - attach failure - self.assertRaises(exception.CinderException, - self.driver.copy_image_to_volume, - context, self.volume, image_service, image_id) - - # failure - detach failure - self.assertRaises(exception.CinderException, - self.driver.copy_image_to_volume, - context, self.volume, image_service, image_id) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_success(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = success - mock_detach.return_value = success - self.driver.copy_volume_to_image(context, self.volume, image_service, - image_meta) - mock_upload.assert_called_with('', '', '', '/dev/sdb') - - mock_attach.return_value = already_attached - mock_query.return_value = success - mock_detach.return_value = success - self.driver.copy_volume_to_image(context, self.volume, image_service, - image_meta) - mock_upload.assert_called_with('', '', '', '/dev/sdb') - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_attach_fail(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = failure - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, self.volume, image_service, image_meta) - mock_attach.return_value = None - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, self.volume, image_service, image_meta) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_query_attach_fail(self, mock_detach, - mock_upload, mock_query, - mock_attach, - mock_get_manage_ip): - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = already_attached - mock_query.return_value = failure - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, self.volume, image_service, image_meta) - - mock_query.return_value = None - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, self.volume, image_service, image_meta) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_upload_fail(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = already_attached - mock_query.return_value = success - mock_upload.side_effect = exception.CinderException( - 'upload_volume error') - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, self.volume, image_service, image_meta) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') - def test_private_get_volume(self, mock_query): - result_success = {'result': 0} - result_not_exist = {'result': "50150005\n"} - result_exception = {'result': "50510006\n"} - - mock_query.side_effect = [ - result_success, result_not_exist, result_exception] - - retval = self.driver._get_volume(self.volume.name) - self.assertTrue(retval) - - retval = self.driver._get_volume(self.volume.name) - self.assertFalse(retval) - - self.assertRaises(exception.CinderException, - self.driver._get_volume, - self.volume.name) - - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') - def test_private_delete_volume(self, mock_delete): - result_success = 0 - result_not_exist = '50150005\n' - result_being_deleted = '50151002\n' - result_exception = '51050006\n' - - mock_delete.side_effect = [result_success, result_not_exist, - result_being_deleted, result_exception] - - retval = self.driver._delete_volume(self.volume.name) - self.assertTrue(retval) - - retval = self.driver._delete_volume(self.volume.name) - self.assertTrue(retval) - - retval = self.driver._delete_volume(self.volume.name) - self.assertTrue(retval) - - self.assertRaises(exception.CinderException, - self.driver._delete_volume, self.volume.name) - - @mock.patch.object(dsware.DSWAREDriver, '_get_volume') - @mock.patch.object(dsware.DSWAREDriver, '_delete_volume') - def test_delete_volume(self, mock_delete, mock_get): - mock_get.return_value = False - retval = self.driver.delete_volume(self.volume) - self.assertTrue(retval) - - mock_get.return_value = True - mock_delete.return_value = True - retval = self.driver.delete_volume(self.volume) - self.assertTrue(retval) - - mock_get.return_value = True - mock_delete.side_effect = exception.CinderException( - 'delete volume exception') - self.assertRaises(exception.CinderException, - self.driver.delete_volume, - self.volume) - - mock_get.side_effect = exception.CinderException( - 'get volume exception') - self.assertRaises(exception.CinderException, - self.driver.delete_volume, - self.volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_snap') - def test_private_get_snapshot(self, mock_query): - result_success = {'result': 0} - result_not_found = {'result': "50150006\n"} - result_exception = {'result': "51050007\n"} - mock_query.side_effect = [result_success, result_not_found, - result_exception] - - retval = self.driver._get_snapshot(self.snapshot.name) - self.assertTrue(retval) - - retval = self.driver._get_snapshot(self.snapshot.name) - self.assertFalse(retval) - - self.assertRaises(exception.CinderException, - self.driver._get_snapshot, - self.snapshot.name) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - def test_private_create_snapshot(self, mock_create): - mock_create.side_effect = [0, 1] - - self.driver._create_snapshot(self.snapshot.name, - self.volume.name) - - self.assertRaises(exception.CinderException, - self.driver._create_snapshot, - self.snapshot.name, self.volume.name) - - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - def test_private_delete_snapshot(self, mock_delete): - mock_delete.side_effect = [0, 1] - - self.driver._delete_snapshot(self.snapshot.name) - - self.assertRaises(exception.CinderException, - self.driver._delete_snapshot, self.snapshot.name) - - @mock.patch.object(dsware.DSWAREDriver, '_get_volume') - @mock.patch.object(dsware.DSWAREDriver, '_create_snapshot') - def test_create_snapshot(self, mock_create, mock_get): - mock_get.return_value = True - self.driver.create_snapshot(self.snapshot) - - mock_create.side_effect = exception.CinderException( - 'create snapshot failed') - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, self.snapshot) - - mock_get.side_effect = [ - False, exception.CinderException('get volume failed')] - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, - self.snapshot) - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, - self.snapshot) - - @mock.patch.object(dsware.DSWAREDriver, '_get_snapshot') - @mock.patch.object(dsware.DSWAREDriver, '_delete_snapshot') - def test_delete_snapshot(self, mock_delete, mock_get): - mock_get.side_effect = [True, False, exception.CinderException, True] - self.driver.delete_snapshot(self.snapshot) - self.driver.delete_snapshot(self.snapshot) - - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, - self.snapshot) - mock_delete.side_effect = exception.CinderException( - 'delete snapshot exception') - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, - self.snapshot) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_info') - def test_private_update_single_pool_info_status(self, mock_query): - pool_info = {'result': 0, - 'pool_id': 10, - 'total_capacity': 10240, - 'used_capacity': 5120, - 'alloc_capacity': 7168} - pool_info_none = {'result': 1} - - mock_query.side_effect = [pool_info, pool_info_none] - - self.driver._update_single_pool_info_status() - self.assertEqual({'total_capacity_gb': 10.0, - 'free_capacity_gb': 5.0, - 'volume_backend_name': None, - 'vendor_name': 'Open Source', - 'driver_version': '1.0', - 'storage_protocol': 'dsware', - 'reserved_percentage': 0, - 'QoS_support': False}, - self.driver._stats) - - self.driver._update_single_pool_info_status() - self.assertIsNone(self.driver._stats) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_type') - def test_private_update_multi_pool_of_same_type_status(self, mock_query): - query_result = (0, [{'result': 0, - 'pool_id': '0', - 'total_capacity': '10240', - 'used_capacity': '5120', - 'alloc_capacity': '7168'}]) - query_result_none = (0, []) - - mock_query.side_effect = [query_result, query_result_none] - - self.driver._update_multi_pool_of_same_type_status() - self.assertEqual({'volume_backend_name': None, - 'vendor_name': 'Open Source', - 'driver_version': '1.0', - 'storage_protocol': 'dsware', - 'pools': [{'pool_name': '0', - 'total_capacity_gb': 10.0, - 'allocated_capacity_gb': 5.0, - 'free_capacity_gb': 5.0, - 'QoS_support': False, - 'reserved_percentage': 0}]}, - self.driver._stats) - - self.driver._update_multi_pool_of_same_type_status() - self.assertIsNone(self.driver._stats) - - def test_private_calculate_pool_info(self): - pool_sets = [{'pool_id': 0, - 'total_capacity': 10240, - 'used_capacity': 5120, - 'QoS_support': False, - 'reserved_percentage': 0}] - retval = self.driver._calculate_pool_info(pool_sets) - self.assertEqual([{'pool_name': 0, - 'total_capacity_gb': 10.0, - 'allocated_capacity_gb': 5.0, - 'free_capacity_gb': 5.0, - 'QoS_support': False, - 'reserved_percentage': 0}], - retval) - - @mock.patch.object(dsware.DSWAREDriver, '_update_single_pool_info_status') - @mock.patch.object(dsware.DSWAREDriver, - '_update_multi_pool_of_same_type_status') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - def test_get_volume_stats(self, mock_query, mock_type, mock_info): - mock_query.return_value = 1 - - self.driver.get_volume_stats(False) - mock_query.assert_not_called() - - self.driver.get_volume_stats(True) - mock_query.assert_called_once_with() + 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 new file mode 100644 index 00000000000..842e00d4ba3 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py @@ -0,0 +1,272 @@ +# 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'}]}, + '/storagePool\?poolId=0': + {'storagePools': [{'poolName': 'fake_pool_name1', + 'poolId': 0}]}, + '/volume/queryByName\?volName=fake_name': + {'errorCode': 0, 'lunDetailInfo': + [{'volume_id': 'fake_id', + 'volume_name': 'fake_name'}]}, + '/volume/queryById\?volId=fake_id': + {'errorCode': 0, 'lunDetailInfo': + [{'volume_id': 'fake_id', + 'volume_name': 'fake_name'}]}, + '/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 new file mode 100644 index 00000000000..cbed803b704 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_fs_conf.py @@ -0,0 +1,99 @@ +# 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 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('storage') + config.set('storage', 'RestURL', 'https://fake_rest_site') + config.set('storage', 'UserName', 'fake_user') + config.set('storage', 'Password', 'fake_passwd') + config.set('storage', 'StoragePool', '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')) + + def test_update_config_value(self): + config = configparser.ConfigParser() + config.read(self.conf.cinder_fusionstorage_conf_file) + storage_info = {'RestURL': config.get('storage', 'RestURL'), + 'UserName': config.get('storage', 'UserName'), + 'Password': config.get('storage', 'Password'), + 'StoragePool': config.get('storage', 'StoragePool')} + + self.mock_object( + self.fusionstorage_conf.configuration, 'safe_get', + return_value=storage_info) + + self.fusionstorage_conf.update_config_value() + + self.assertEqual('https://fake_rest_site', + self.fusionstorage_conf.configuration.san_address) + self.assertEqual( + 'fake_user', self.fusionstorage_conf.configuration.san_user) + self.assertEqual( + 'fake_passwd', self.fusionstorage_conf.configuration.san_password) + self.assertListEqual( + ['fake_pool'], self.fusionstorage_conf.configuration.pools_name) + + def test__encode_authentication(self): + config = configparser.ConfigParser() + config.read(self.conf.cinder_fusionstorage_conf_file) + + storage_info = {'RestURL': config.get('storage', 'RestURL'), + 'UserName': config.get('storage', 'UserName'), + 'Password': config.get('storage', 'Password'), + 'StoragePool': config.get('storage', 'StoragePool')} + self.fusionstorage_conf._encode_authentication(storage_info) + name_node = storage_info.get('UserName') + pwd_node = storage_info.get('Password') + self.assertEqual('!&&&ZmFrZV91c2Vy', name_node) + self.assertEqual('!&&&ZmFrZV9wYXNzd2Q=', pwd_node) + + 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_fspythonapi.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py deleted file mode 100644 index 785334e5e15..00000000000 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py +++ /dev/null @@ -1,447 +0,0 @@ -# Copyright (c) 2013 - 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. -""" -Unit Tests for Huawei FusionStorage drivers. -""" - -import mock - -from cinder import test -from cinder import utils -from cinder.volume.drivers.fusionstorage import fspythonapi - - -class FSPythonApiTestCase(test.TestCase): - - def setUp(self): - super(FSPythonApiTestCase, self).setUp() - self.api = fspythonapi.FSPythonApi() - - @mock.patch.object(fspythonapi.FSPythonApi, 'get_ip_port') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - @mock.patch.object(utils, 'execute') - def test_start_execute_cmd(self, mock_execute, - mock_get_manage_ip, mock_get_ip_port): - result1 = ['result=0\ndesc=success\n', ''] - result2 = ['result=50150007\ndesc=volume does not exist\n', ''] - result3 = ['result=50150008\ndesc=volume is being deleted\n', ''] - result4 = ['result=50\ndesc=exception\n', ''] - cmd = 'abcdef' - - mock_get_ip_port.return_value = ['127.0.0.1', '128.0.0.1'] - mock_get_manage_ip.return_value = '127.0.0.1' - - mock_execute.return_value = result1 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result2 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result3 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result4 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=50', retval) - - mock_execute.return_value = result1 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual(['result=0', 'desc=success', ''], retval) - - mock_execute.return_value = result2 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result3 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result4 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual(['result=50', 'desc=exception', ''], retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_volume(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual(0, retval) - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual('50150007\n', retval) - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_extend_volume(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual(0, retval) - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual('50150007\n', retval) - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_volume_from_snap(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual(0, retval) - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_fullvol_from_snap(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual(0, retval) - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_fullvol_from_snap') - def test_create_volume_from_volume(self, mock_create_fullvol, - mock_delete_volume, mock_delete_snap, - mock_create_volume, mock_create_snap): - mock_create_snap.return_value = 0 - mock_create_volume.return_value = 0 - mock_create_fullvol.return_value = 0 - - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(0, retval) - - mock_create_snap.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - mock_create_snap.return_value = 0 - mock_create_volume.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - mock_create_volume.return_value = 0 - self.api.create_fullvol_from_snap.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') - def test_create_clone_volume_from_volume(self, mock_volume, mock_snap): - mock_snap.side_effect = [0, 1] - mock_volume.side_effect = [0, 1] - retval = self.api.create_clone_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(0, retval) - retval = self.api.create_clone_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - def test_volume_info_analyze_success(self): - vol_info = ('vol_name=vol1,father_name=vol1_father,' - 'status=available,vol_size=1024,real_size=1024,' - 'pool_id=pool1,create_time=01/01/2015') - vol_info_res = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', - 'status': 'available', 'vol_size': '1024', - 'real_size': '1024', 'pool_id': 'pool1', - 'create_time': '01/01/2015'} - - retval = self.api.volume_info_analyze(vol_info) - self.assertEqual(vol_info_res, retval) - - def test_volume_info_analyze_fail(self): - vol_info = '' - vol_info_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - retval = self.api.volume_info_analyze(vol_info) - self.assertEqual(vol_info_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - @mock.patch.object(fspythonapi.FSPythonApi, 'volume_info_analyze') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - def test_query_volume(self, mock_delete, mock_analyze, mock_execute): - exec_result = ['result=0\n', - 'vol_name=vol1,father_name=vol1_father,status=0,' + - 'vol_size=1024,real_size=1024,pool_id=pool1,' + - 'create_time=01/01/2015'] - query_result = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '0', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - mock_delete.return_value = 0 - mock_execute.return_value = exec_result - mock_analyze.return_value = query_result - retval = self.api.query_volume('vol1') - self.assertEqual(query_result, retval) - - exec_result = ['result=0\n', - 'vol_name=vol1,father_name=vol1_father,status=1,' + - 'vol_size=1024,real_size=1024,pool_id=pool1,' + - 'create_time=01/01/2015'] - query_result = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '1', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - mock_delete.return_value = 0 - mock_execute.return_value = exec_result - mock_analyze.return_value = query_result - retval = self.api.query_volume('vol1') - self.assertEqual(query_result, retval) - - vol_info_failure = 'result=32500000\n' - failure_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - mock_execute.return_value = vol_info_failure - retval = self.api.query_volume('vol1') - self.assertEqual(failure_res, retval) - - vol_info_failure = None - failure_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - - mock_execute.return_value = vol_info_failure - retval = self.api.query_volume('vol1') - self.assertEqual(failure_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_delete_volume(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.delete_volume('volume_name') - self.assertEqual(0, retval) - - retval = self.api.delete_volume('volume_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.delete_volume('volume_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_snapshot(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual(0, retval) - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual('50150007\n', retval) - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual(1, retval) - - def test_snap_info_analyze_success(self): - snap_info = ('snap_name=snap1,father_name=snap1_father,status=0,' - 'snap_size=1024,real_size=1024,pool_id=pool1,' - 'delete_priority=1,create_time=01/01/2015') - snap_info_res = {'result': 0, 'snap_name': 'snap1', - 'father_name': 'snap1_father', 'status': '0', - 'snap_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'delete_priority': '1', - 'create_time': '01/01/2015'} - - retval = self.api.snap_info_analyze(snap_info) - self.assertEqual(snap_info_res, retval) - - def test_snap_info_analyze_fail(self): - snap_info = '' - snap_info_res = {'result': 1, 'snap_name': '', 'father_name': '', - 'status': '', 'snap_size': '', 'real_size': '', - 'pool_id': '', 'delete_priority': '', - 'create_time': ''} - retval = self.api.snap_info_analyze(snap_info) - self.assertEqual(snap_info_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_snap(self, mock_execute): - exec_result = ['result=0\n', - 'snap_name=snap1,father_name=snap1_father,status=0,' + - 'snap_size=1024,real_size=1024,pool_id=pool1,' + - 'delete_priority=1,create_time=01/01/2015'] - query_result = {'result': 0, 'snap_name': 'snap1', - 'father_name': 'snap1_father', 'status': '0', - 'snap_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'delete_priority': '1', - 'create_time': '01/01/2015'} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(query_result, retval) - - exec_result = ['result=50150007\n'] - qurey_result = {'result': '50150007\n', 'snap_name': '', - 'father_name': '', 'status': '', 'snap_size': '', - 'real_size': '', 'pool_id': '', - 'delete_priority': '', 'create_time': ''} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(qurey_result, retval) - - exec_result = '' - query_result = {'result': 1, 'snap_name': '', 'father_name': '', - 'status': '', 'snap_size': '', 'real_size': '', - 'pool_id': '', 'delete_priority': '', - 'create_time': ''} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_delete_snapshot(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual(0, retval) - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual(1, retval) - - def test_pool_info_analyze(self): - pool_info = 'pool_id=pool100,total_capacity=1024,' + \ - 'used_capacity=500,alloc_capacity=500' - analyze_res = {'result': 0, 'pool_id': 'pool100', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'} - - retval = self.api.pool_info_analyze(pool_info) - self.assertEqual(analyze_res, retval) - - pool_info = '' - analyze_res = {'result': 1, 'pool_id': '', 'total_capacity': '', - 'used_capacity': '', 'alloc_capacity': ''} - retval = self.api.pool_info_analyze(pool_info) - self.assertEqual(analyze_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_pool_info(self, mock_execute): - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = {'result': 0, 'pool_id': '0', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - exec_result = ['result=51050008\n'] - query_result = {'result': '51050008\n', 'pool_id': '', - 'total_capacity': '', 'used_capacity': '', - 'alloc_capacity': ''} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - exec_result = '' - query_result = {'result': 1, 'pool_id': '', 'total_capacity': '', - 'used_capacity': '', 'alloc_capacity': ''} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_pool_type(self, mock_execute): - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = (0, [{'result': 0, - 'pool_id': '0', 'total_capacity': '1024', - 'used_capacity': '500', 'alloc_capacity': '500'}]) - - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n', - 'pool_id=1,total_capacity=2048,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = (0, [{'result': 0, 'pool_id': '0', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'}, - {'result': 0, 'pool_id': '1', - 'total_capacity': '2048', 'used_capacity': '500', - 'alloc_capacity': '500'}]) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = ['result=51010015\n'] - query_result = (51010015, []) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = '' - query_result = (0, []) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_dsware_version(self, mock_execute): - mock_execute.side_effect = ['result=0\n', 'result=50500001\n', - 'result=50150007\n', None] - - retval = self.api.query_dsware_version() - self.assertEqual(0, retval) - - retval = self.api.query_dsware_version() - self.assertEqual(1, retval) - - retval = self.api.query_dsware_version() - self.assertEqual('50150007\n', retval) - - retval = self.api.query_dsware_version() - self.assertEqual(2, retval) diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py new file mode 100644 index 00000000000..211663e0831 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py @@ -0,0 +1,48 @@ +# 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/constants.py b/cinder/volume/drivers/fusionstorage/constants.py new file mode 100644 index 00000000000..d262b0ec89b --- /dev/null +++ b/cinder/volume/drivers/fusionstorage/constants.py @@ -0,0 +1,31 @@ +# 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 = "RestURL" +CONF_MANAGER_IP = "manager_ips" +CONF_POOLS = "StoragePool" +CONF_PWD = "Password" +CONF_STORAGE = "storage" +CONF_USER = "UserName" diff --git a/cinder/volume/drivers/fusionstorage/dsware.py b/cinder/volume/drivers/fusionstorage/dsware.py index aecc9cc6d2a..7cf1a3db1ed 100644 --- a/cinder/volume/drivers/fusionstorage/dsware.py +++ b/cinder/volume/drivers/fusionstorage/dsware.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013 - 2016 Huawei Technologies Co., Ltd. +# Copyright (c) 2018 Huawei Technologies Co., Ltd. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,610 +12,355 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Driver for Huawei FusionStorage. -""" -import os -import re +import json from oslo_config import cfg from oslo_log import log as logging -from oslo_service import loopingcall +from oslo_utils import units from cinder import exception from cinder.i18n import _ -from cinder.image import image_utils from cinder import interface -from cinder.volume import configuration from cinder.volume import driver -from cinder.volume.drivers.fusionstorage import fspythonapi +from cinder.volume.drivers.fusionstorage import fs_client +from cinder.volume.drivers.fusionstorage import fs_conf +from cinder.volume import utils as volume_utils LOG = logging.getLogger(__name__) volume_opts = [ - cfg.BoolOpt('dsware_isthin', + cfg.BoolOpt("dsware_isthin", default=False, - help='The flag of thin storage allocation.'), - cfg.StrOpt('dsware_manager', + 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.'), + 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.'), + 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.'), + 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.'), + 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.'), + 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.DictOpt('storage', + default={}, + secret=True, + help='This field is configured with the information of array ' + 'and user info. The parameters takes the standard dict ' + 'config form, Storage = UserName:xxx, Password:xxx, ' + 'RestURL:xxx') ] CONF = cfg.CONF -CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP) - -OLD_VERSION = 1 -NEW_VERSION = 0 -VOLUME_ALREADY_ATTACHED = 50151401 -VOLUME_NOT_EXIST = '50150005\n' -VOLUME_BEING_DELETED = '50151002\n' -SNAP_NOT_EXIST = '50150006\n' +CONF.register_opts(volume_opts) @interface.volumedriver class DSWAREDriver(driver.VolumeDriver): - """Huawei FusionStorage Driver.""" - VERSION = '1.0' - - # ThirdPartySystems wiki page - CI_WIKI_NAME = "Huawei_FusionStorage_CI" - - DSWARE_VOLUME_CREATE_SUCCESS_STATUS = 0 - DSWARE_VOLUME_DUPLICATE_VOLUME = 6 - DSWARE_VOLUME_CREATING_STATUS = 7 + VERSION = '2.0' + CI_WIKI_NAME = 'Huawei_FusionStorage_CI' def __init__(self, *args, **kwargs): super(DSWAREDriver, self).__init__(*args, **kwargs) - self.dsware_client = fspythonapi.FSPythonApi() - self.check_cloned_interval = 2 - self.configuration.append_config_values(volume_opts) - def check_for_setup_error(self): - # lrk: check config file here. - if not os.path.exists(fspythonapi.fsc_conf_file): - msg = _("Dsware config file not exists!") - LOG.error("Dsware config file: %s not exists!", - fspythonapi.fsc_conf_file) - raise exception.VolumeBackendAPIException(data=msg) + 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.conf = fs_conf.FusionStorageConf(self.configuration, self.host) + self.client = None def do_setup(self, context): - # lrk: create fsc_conf_file here. - conf_info = ["manage_ip=%s" % self.configuration.dsware_manager, - "\n", - "vbs_url=%s" % self.configuration.fusionstorageagent] + self.conf.update_config_value() + url_str = self.configuration.san_address + url_user = self.configuration.san_user + url_password = self.configuration.san_password - fsc_dir = os.path.dirname(fspythonapi.fsc_conf_file) - if not os.path.exists(fsc_dir): - os.makedirs(fsc_dir) + self.client = fs_client.RestCommon( + fs_address=url_str, fs_user=url_user, + fs_password=url_password) + self.client.login() - with open(fspythonapi.fsc_conf_file, 'w') as f: - f.writelines(conf_info) + 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')] - # Get pool type. - self.pool_type = self.configuration.pool_type - LOG.debug("Dsware Driver do_setup finish.") - - def _get_dsware_manage_ip(self, volume): - dsw_manager_ip = volume.provider_id - if dsw_manager_ip is not None: - return dsw_manager_ip - else: - msg = _("Dsware get manager ip failed, " - "volume provider_id is None!") - raise exception.VolumeBackendAPIException(data=msg) - - def _get_poolid_from_host(self, host): - # Host format: 'hostid@backend#poolid'. - # Other formats: return 'default', and the pool id would be zero. - if host: - if len(host.split('#', 1)) == 2: - return host.split('#')[1] - return self.pool_type - - def _create_volume(self, volume_id, volume_size, is_thin, volume_host): - pool_id = 0 - result = 1 - - # Query Dsware version. - retcode = self.dsware_client.query_dsware_version() - # Old version. - if retcode == OLD_VERSION: - pool_id = 0 - # New version. - elif retcode == NEW_VERSION: - pool_info = self._get_poolid_from_host(volume_host) - if pool_info != self.pool_type: - pool_id = int(pool_info) - # Query Dsware version failed! - else: - LOG.error("Query Dsware version fail!") - msg = (_("Query Dsware version failed! Retcode is %s.") % - retcode) - raise exception.VolumeBackendAPIException(data=msg) - - try: - result = self.dsware_client.create_volume( - volume_id, pool_id, volume_size, int(is_thin)) - except Exception as e: - LOG.exception("Create volume error, details is: %s.", e) - raise - - if result != 0: - msg = _("Dsware create volume failed! Result is: %s.") % result - raise exception.VolumeBackendAPIException(data=msg) - - def create_volume(self, volume): - # Creates a volume in Dsware. - LOG.debug("Begin to create volume %s in Dsware.", volume.name) - volume_id = volume.name - volume_size = volume.size - volume_host = volume.host - is_thin = self.configuration.dsware_isthin - # Change GB to MB. - volume_size *= 1024 - self._create_volume(volume_id, volume_size, is_thin, volume_host) - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def _create_volume_from_snap(self, volume_id, volume_size, snapshot_name): - result = self.dsware_client.create_volume_from_snap( - volume_id, volume_size, snapshot_name) - if result != 0: - msg = (_("Dsware: create volume from snap failed. Result: %s.") % - result) - raise exception.VolumeBackendAPIException(data=msg) - - def create_volume_from_snapshot(self, volume, snapshot): - # Creates a volume from snapshot. - volume_id = volume.name - volume_size = volume.size - snapshot_name = snapshot.name - if volume_size < int(snapshot.volume_size): - msg = _("Dsware: volume size can not be less than snapshot size.") - raise exception.VolumeBackendAPIException(data=msg) - # Change GB to MB. - volume_size *= 1024 - self._create_volume_from_snap(volume_id, volume_size, snapshot_name) - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def create_cloned_volume(self, volume, src_volume): - """Dispatcher to Dsware client to create volume from volume. - - Wait volume create finished. - """ - volume_name = volume.name - volume_size = volume.size - src_volume_name = src_volume.name - # Change GB to MB. - volume_size *= 1024 - result = self.dsware_client.create_volume_from_volume( - volume_name, volume_size, src_volume_name) - if result: - msg = _('Dsware fails to start cloning volume %s.') % volume_name - raise exception.VolumeBackendAPIException(data=msg) - - LOG.debug('Dsware create volume %(volume_name)s of size ' - '%(volume_size)s from src volume %(src_volume_name)s start.', - {"volume_name": volume_name, - "volume_size": volume_size, - "src_volume_name": src_volume_name}) - - ret = self._wait_for_create_cloned_volume_finish_timer(volume_name) - if not ret: - msg = (_('Clone volume %s failed while waiting for success.') % - volume_name) - raise exception.VolumeBackendAPIException(data=msg) - - LOG.debug('Dsware create volume from volume ends.') - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def _check_create_cloned_volume_finish(self, new_volume_name): - LOG.debug('Loopcall: _check_create_cloned_volume_finish(), ' - 'volume-name: %s.', new_volume_name) - current_volume = self.dsware_client.query_volume(new_volume_name) - - if current_volume: - status = current_volume['status'] - LOG.debug('Wait clone volume %(volume_name)s, status: %(status)s.', - {"volume_name": new_volume_name, - "status": status}) - if int(status) == self.DSWARE_VOLUME_CREATING_STATUS or int( - status) == self.DSWARE_VOLUME_DUPLICATE_VOLUME: - self.count += 1 - elif int(status) == self.DSWARE_VOLUME_CREATE_SUCCESS_STATUS: - raise loopingcall.LoopingCallDone(retvalue=True) - else: - msg = _('Clone volume %(new_volume_name)s failed, ' - 'volume status is: %(status)s.') - LOG.error(msg, {'new_volume_name': new_volume_name, - 'status': status}) - raise loopingcall.LoopingCallDone(retvalue=False) - if self.count > self.configuration.clone_volume_timeout: - msg = _('Dsware clone volume time out. ' - 'Volume: %(new_volume_name)s, status: %(status)s') - LOG.error(msg, {'new_volume_name': new_volume_name, - 'status': current_volume['status']}) - raise loopingcall.LoopingCallDone(retvalue=False) - else: - LOG.warning('Can not find volume %s from Dsware.', - new_volume_name) - self.count += 1 - if self.count > 10: - msg = _("Dsware clone volume failed: volume " - "can not be found from Dsware.") + 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 loopingcall.LoopingCallDone(retvalue=False) + raise exception.InvalidInput(reason=msg) - def _wait_for_create_cloned_volume_finish_timer(self, new_volume_name): - timer = loopingcall.FixedIntervalLoopingCall( - self._check_create_cloned_volume_finish, new_volume_name) - LOG.debug('Call _check_create_cloned_volume_finish: volume-name %s.', - new_volume_name) - self.count = 0 - ret = timer.start(interval=self.check_cloned_interval).wait() - timer.stop() - return ret + 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() - def _analyse_output(self, out): - if out is not None: - analyse_result = {} - out_temp = out.split('\n') - for line in out_temp: - if re.search('^ret_code=', line): - analyse_result['ret_code'] = line[9:] - elif re.search('^ret_desc=', line): - analyse_result['ret_desc'] = line[9:] - elif re.search('^dev_addr=', line): - analyse_result['dev_addr'] = line[9:] - return analyse_result - else: - return None + 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 _attach_volume(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'attachwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Attach volume result is %s.", analyse_result) - return analyse_result + def _get_capacity(self, pool_info): + pool_capacity = {} - def _detach_volume(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'detachwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Detach volume result is %s.", analyse_result) - return analyse_result + 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 - def _query_volume_attach(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'querydevwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Query volume attach result is %s.", analyse_result) - return analyse_result + return pool_capacity - def copy_image_to_volume(self, context, volume, image_service, image_id): - # Copy image to volume. - # Step1: attach volume to host. - LOG.debug("Begin to copy image to volume.") - dsw_manager_ip = self._get_dsware_manage_ip(volume) - volume_attach_result = self._attach_volume(volume.name, - dsw_manager_ip) - volume_attach_path = '' - if volume_attach_result is not None and int( - volume_attach_result['ret_code']) == 0: - volume_attach_path = volume_attach_result['dev_addr'] - LOG.debug("Volume attach path is %s.", volume_attach_path) - if volume_attach_path == '': - msg = _("Host attach volume failed!") - raise exception.VolumeBackendAPIException(data=msg) - # Step2: fetch the image from image_service and write it to the - # volume. - try: - image_utils.fetch_to_raw(context, - image_service, - image_id, - volume_attach_path, - self.configuration.volume_dd_blocksize) - finally: - # Step3: detach volume from host. - dsw_manager_ip = self._get_dsware_manage_ip(volume) - volume_detach_result = self._detach_volume(volume.name, - dsw_manager_ip) - if volume_detach_result is not None and int( - volume_detach_result['ret_code']) != 0: - msg = (_("Dsware detach volume from host failed: %s!") % - volume_detach_result) - raise exception.VolumeBackendAPIException(data=msg) - - def copy_volume_to_image(self, context, volume, image_service, image_meta): - # Copy volume to image. - # If volume was not attached, then attach it. - - dsw_manager_ip = self._get_dsware_manage_ip(volume) - - already_attached = False - _attach_result = self._attach_volume(volume.name, dsw_manager_ip) - if _attach_result: - retcode = _attach_result['ret_code'] - if int(retcode) == VOLUME_ALREADY_ATTACHED: - already_attached = True - result = self._query_volume_attach(volume.name, - dsw_manager_ip) - if not result or int(result['ret_code']) != 0: - msg = (_("Query volume attach failed, result=%s.") % - result) - raise exception.VolumeBackendAPIException(data=msg) - - elif int(retcode) == 0: - result = _attach_result - else: - msg = (_("Attach volume to host failed " - "in copy volume to image, retcode: %s.") % - retcode) - raise exception.VolumeBackendAPIException(data=msg) - - volume_attach_path = result['dev_addr'] - - else: - msg = _("Attach_volume failed.") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - try: - image_utils.upload_volume(context, - image_service, - image_meta, - volume_attach_path) - except Exception as e: - LOG.error("Upload volume error, details: %s.", e) - raise - finally: - if not already_attached: - self._detach_volume(volume.name, dsw_manager_ip) - - def _get_volume(self, volume_name): - result = self.dsware_client.query_volume(volume_name) - LOG.debug("Dsware query volume result is %s.", result['result']) - if result['result'] == VOLUME_NOT_EXIST: - LOG.debug("Dsware volume %s does not exist.", volume_name) - return False - elif result['result'] == 0: - return True - else: - msg = _("Dsware query volume %s failed!") % volume_name - raise exception.VolumeBackendAPIException(data=msg) - - def _delete_volume(self, volume_name): - # Delete volume in Dsware. - result = self.dsware_client.delete_volume(volume_name) - LOG.debug("Dsware delete volume, result is %s.", result) - if result == VOLUME_NOT_EXIST: - LOG.debug("Dsware delete volume, volume does not exist.") - return True - elif result == VOLUME_BEING_DELETED: - LOG.debug("Dsware delete volume, volume is being deleted.") - return True - elif result == 0: - return True - else: - msg = _("Dsware delete volume failed: %s!") % result - raise exception.VolumeBackendAPIException(data=msg) - - def delete_volume(self, volume): - # Delete volume. - # If volume does not exist, then return. - LOG.debug("Begin to delete volume in Dsware: %s.", volume.name) - if not self._get_volume(volume.name): - return True - - return self._delete_volume(volume.name) - - def _get_snapshot(self, snapshot_name): - snapshot_info = self.dsware_client.query_snap(snapshot_name) - LOG.debug("Get snapshot, snapshot_info is : %s.", snapshot_info) - if snapshot_info['result'] == SNAP_NOT_EXIST: - LOG.error('Snapshot: %s not found!', snapshot_name) - return False - elif snapshot_info['result'] == 0: - return True - else: - msg = _("Dsware get snapshot failed!") - raise exception.VolumeBackendAPIException(data=msg) - - def _create_snapshot(self, snapshot_id, volume_id): - LOG.debug("Create snapshot %s to Dsware.", snapshot_id) - smart_flag = 0 - res = self.dsware_client.create_snapshot(snapshot_id, - volume_id, - smart_flag) - if res != 0: - msg = _("Dsware Create Snapshot failed! Result: %s.") % res - raise exception.VolumeBackendAPIException(data=msg) - - def _delete_snapshot(self, snapshot_id): - LOG.debug("Delete snapshot %s to Dsware.", snapshot_id) - res = self.dsware_client.delete_snapshot(snapshot_id) - LOG.debug("Ddelete snapshot result is: %s.", res) - if res != 0: - raise exception.SnapshotIsBusy(snapshot_name=snapshot_id) - - def create_snapshot(self, snapshot): - vol_id = 'volume-%s' % snapshot.volume_id - snapshot_id = snapshot.name - if not self._get_volume(vol_id): - LOG.error('Create Snapshot, but volume: %s not found!', vol_id) - raise exception.VolumeNotFound(volume_id=vol_id) - else: - self._create_snapshot(snapshot_id, vol_id) - - def delete_snapshot(self, snapshot): - LOG.debug("Delete snapshot %s.", snapshot.name) - snapshot_id = snapshot.name - if self._get_snapshot(snapshot_id): - self._delete_snapshot(snapshot_id) - - def _calculate_pool_info(self, pool_sets): - filter = False - pools_status = [] - reserved_percentage = self.configuration.reserved_percentage - pool_id_filter = self.configuration.pool_id_filter - LOG.debug("Filtered pool id is %s.", pool_id_filter) - if pool_id_filter == []: - for pool_info in pool_sets: - pool = {} - pool['pool_name'] = pool_info['pool_id'] - pool['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - pool['allocated_capacity_gb'] = float( - pool_info['used_capacity']) / 1024 - pool['free_capacity_gb'] = pool['total_capacity_gb'] - pool[ - 'allocated_capacity_gb'] - pool['QoS_support'] = False - pool['reserved_percentage'] = reserved_percentage - pools_status.append(pool) - else: - for pool_info in pool_sets: - for pool_id in pool_id_filter: - if pool_id == pool_info['pool_id']: - filter = True - break - - if filter: - pool = {} - pool['pool_name'] = pool_info['pool_id'] - pool['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - pool['allocated_capacity_gb'] = float( - pool_info['used_capacity']) / 1024 - pool['free_capacity_gb'] = float( - pool['total_capacity_gb'] - pool[ - 'allocated_capacity_gb']) - pool['QoS_support'] = False - pool['reserved_percentage'] = reserved_percentage - pools_status.append(pool) - - filter = False - - return pools_status - - def _update_single_pool_info_status(self): - """Query pool info when Dsware is single-pool version.""" + def _update_single_pool_info_status(self, pool_info): status = {} - status['volume_backend_name'] = self.configuration.volume_backend_name - status['vendor_name'] = 'Open Source' - status['driver_version'] = self.VERSION - status['storage_protocol'] = 'dsware' - - status['total_capacity_gb'] = 0 - status['free_capacity_gb'] = 0 - status['reserved_percentage'] = self.configuration.reserved_percentage - status['QoS_support'] = False - pool_id = 0 - pool_info = self.dsware_client.query_pool_info(pool_id) - result = pool_info['result'] - if result == 0: - status['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - status['free_capacity_gb'] = (float( - pool_info['total_capacity']) - float( - pool_info['used_capacity'])) / 1024 - LOG.debug("total_capacity_gb is %s, free_capacity_gb is %s.", - status['total_capacity_gb'], - status['free_capacity_gb']) - self._stats = status - else: - self._stats = None - - def _update_multi_pool_of_same_type_status(self): - """Query info of multiple pools when Dsware is multi-pool version. - - These pools have the same pool type. - """ - status = {} - status['volume_backend_name'] = self.configuration.volume_backend_name - status['vendor_name'] = 'Open Source' - status['driver_version'] = self.VERSION - status['storage_protocol'] = 'dsware' - - (result, pool_sets) = self.dsware_client.query_pool_type( - self.pool_type) - if pool_sets == []: - self._stats = None - else: - pools_status = self._calculate_pool_info(pool_sets) - status['pools'] = pools_status - self._stats = 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): - if refresh: - dsware_version = self.dsware_client.query_dsware_version() - # Old version. - if dsware_version == OLD_VERSION: - self._update_single_pool_info_status() - # New version. - elif dsware_version == NEW_VERSION: - self._update_multi_pool_of_same_type_status() - else: - msg = _("Dsware query Dsware version failed!") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) + self.client.keep_alive() + stats = self._update_pool_stats() + return stats - return self._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): - # Extend volume in Dsware. - LOG.debug("Begin to extend volume in Dsware: %s.", volume.name) - volume_id = volume.name - if volume.size > new_size: - msg = (_("Dsware extend Volume failed! " - "New size %(new_size)s should be greater than " - "old size %(old_size)s!") - % {'new_size': new_size, - 'old_size': volume.size}) - raise exception.VolumeBackendAPIException(data=msg) - # Change GB to MB. - volume_size = new_size * 1024 - result = self.dsware_client.extend_volume(volume_id, volume_size) - if result != 0: - msg = _("Dsware extend Volume failed! Result:%s.") % result + 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): - """Initializes the connection and returns connection info.""" - LOG.debug("Begin initialize connection.") - - properties = {} - properties['volume_name'] = volume.name - properties['volume'] = volume - properties['dsw_manager_ip'] = self._get_dsware_manage_ip(volume) - - LOG.debug("End initialize connection with properties:%s.", properties) - - return {'driver_volume_type': 'dsware', + 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, force=False, **kwargs): - pass + 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 diff --git a/cinder/volume/drivers/fusionstorage/fs_client.py b/cinder/volume/drivers/fusionstorage/fs_client.py new file mode 100644 index 00000000000..40e98ab6929 --- /dev/null +++ b/cinder/volume/drivers/fusionstorage/fs_client.py @@ -0,0 +1,256 @@ +# 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 new file mode 100644 index 00000000000..3374c56ac9e --- /dev/null +++ b/cinder/volume/drivers/fusionstorage/fs_conf.py @@ -0,0 +1,135 @@ +# 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): + storage_info = self.configuration.safe_get(constants.CONF_STORAGE) + self._pools_name(storage_info) + self._san_address(storage_info) + self._encode_authentication(storage_info) + self._san_user(storage_info) + self._san_password(storage_info) + + def _encode_authentication(self, storage_info): + name_node = storage_info.get(constants.CONF_USER) + pwd_node = storage_info.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(storage_info, name_node, pwd_node) + + def _rewrite_conf(self, storage_info, name_node, pwd_node): + storage_info.update({constants.CONF_USER: name_node, + constants.CONF_PWD: pwd_node}) + storage_info = ("\n %(conf_name)s: %(name)s," + "\n %(conf_pwd)s: %(pwd)s," + "\n %(conf_url)s: %(url)s," + "\n %(conf_pool)s: %(pool)s" + % {"conf_name": constants.CONF_USER, + "conf_pwd": constants.CONF_PWD, + "conf_url": constants.CONF_ADDRESS, + "conf_pool": constants.CONF_POOLS, + "name": name_node, + "pwd": pwd_node, + "url": storage_info.get(constants.CONF_ADDRESS), + "pool": storage_info.get(constants.CONF_POOLS)}) + 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) + conf.set(self.host, constants.CONF_STORAGE, storage_info) + 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, storage_info): + address = storage_info.get(constants.CONF_ADDRESS) + self._assert_text_result(address, mess=constants.CONF_ADDRESS) + setattr(self.configuration, 'san_address', address) + + def _san_user(self, storage_info): + user_text = storage_info.get(constants.CONF_USER) + self._assert_text_result(user_text, mess=constants.CONF_USER) + user = base64.b64decode(six.b(user_text[4:])).decode() + setattr(self.configuration, 'san_user', user) + + def _san_password(self, storage_info): + pwd_text = storage_info.get(constants.CONF_PWD) + self._assert_text_result(pwd_text, mess=constants.CONF_PWD) + pwd = base64.b64decode(six.b(pwd_text[4:])).decode() + setattr(self.configuration, 'san_password', pwd) + + def _pools_name(self, storage_info): + pools_name = storage_info.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/cinder/volume/drivers/fusionstorage/fspythonapi.py b/cinder/volume/drivers/fusionstorage/fspythonapi.py deleted file mode 100644 index a88687028da..00000000000 --- a/cinder/volume/drivers/fusionstorage/fspythonapi.py +++ /dev/null @@ -1,495 +0,0 @@ -# Copyright (c) 2013 - 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. -""" -Volume api for FusionStorage systems. -""" - -import os -import re -import six - -from oslo_log import log as logging - -from cinder import utils - -LOG = logging.getLogger(__name__) -fsc_conf_file = "/etc/cinder/volumes/fsc_conf" -fsc_cli = "fsc_cli" -fsc_ip = [] -fsc_port = '10519' -manage_ip = "127.0.0.1" -CMD_BIN = fsc_cli - -volume_info = { - 'result': '', - 'vol_name': '', - 'father_name': '', - 'status': '', - 'vol_size': '', - 'real_size': '', - 'pool_id': '', - 'create_time': ''} - - -snap_info = { - 'result': '', - 'snap_name': '', - 'father_name': '', - 'status': '', - 'snap_size': '', - 'real_size': '', - 'pool_id': '', - 'delete_priority': '', - 'create_time': ''} - - -pool_info = { - 'result': '', - 'pool_id': '', - 'total_capacity': '', - 'used_capacity': '', - 'alloc_capacity': ''} - - -class FSPythonApi(object): - - def __init__(self): - LOG.debug("FSPythonApi init.") - self.get_ip_port() - self.res_idx = len('result=') - - def get_ip_port(self): - LOG.debug("File fsc_conf_file is %s.", fsc_conf_file) - if os.path.exists(fsc_conf_file): - try: - fsc_file = open(fsc_conf_file, 'r') - full_txt = fsc_file.readlines() - LOG.debug("Full_txt is %s.", full_txt) - for line in full_txt: - if re.search('^vbs_url=', line): - tmp_vbs_url = line[8:] - return re.split(',', tmp_vbs_url) - except Exception as e: - LOG.debug("Get fsc ip failed, error=%s.", e) - finally: - fsc_file.close() - else: - LOG.debug("Fsc conf file not exist, file_name=%s.", fsc_conf_file) - - def get_manage_ip(self): - LOG.debug("File fsc_conf_file is %s.", fsc_conf_file) - if os.path.exists(fsc_conf_file): - try: - fsc_file = open(fsc_conf_file, 'r') - full_txt = fsc_file.readlines() - for line in full_txt: - if re.search('^manage_ip=', line): - manage_ip = line[len('manage_ip='):] - manage_ip = manage_ip.strip('\n') - return manage_ip - except Exception as e: - LOG.debug("Get manage ip failed, error=%s.", e) - finally: - fsc_file.close() - else: - LOG.debug("Fsc conf file not exist, file_name=%s.", fsc_conf_file) - - def get_dsw_manage_ip(self): - return manage_ip - - def start_execute_cmd(self, cmd, full_result_flag): - fsc_ip = self.get_ip_port() - manage_ip = self.get_manage_ip() - ip_num = len(fsc_ip) - - LOG.debug("fsc_ip is %s", fsc_ip) - - if ip_num <= 0: - return None - - if ip_num > 3: - ip_num = 3 - - exec_result = '' - result = '' - if full_result_flag: - for ip in fsc_ip: - cmd_args = [CMD_BIN, '--manage_ip', manage_ip.replace( - '\n', ''), '--ip', ip.replace('\n', '')] + cmd.split() - LOG.debug("Dsware cmd_args is %s.", cmd_args) - - exec_result, err = utils.execute(*cmd_args, run_as_root=True) - exec_result = exec_result.split('\n') - LOG.debug("Result is %s.", exec_result) - if exec_result: - for line in exec_result: - if re.search('^result=0', line): - return exec_result - elif re.search('^result=50150007', line): - return 'result=0' - elif re.search('^result=50150008', line): - return 'result=0' - elif re.search('^result=50', line): - return exec_result - return exec_result - else: - for ip in fsc_ip: - cmd_args = [CMD_BIN, '--manage_ip', manage_ip.replace( - '\n', ''), '--ip', ip.replace('\n', '')] + cmd.split() - LOG.debug("Dsware cmd_args is %s.", cmd_args) - - exec_result, err = utils.execute(*cmd_args, run_as_root=True) - LOG.debug("Result is %s.", exec_result) - exec_result = exec_result.split('\n') - if exec_result: - for line in exec_result: - if re.search('^result=', line): - result = line - if re.search('^result=0', line): - return line - elif re.search('^result=50150007', line): - return 'result=0' - elif re.search('^result=50150008', line): - return 'result=0' - elif re.search('^result=50', line): - return line - return result - - def create_volume(self, vol_name, pool_id, vol_size, thin_flag): - cmd = '--op createVolume' + ' ' + '--volName' + ' ' + six.text_type( - vol_name) + ' ' + '--poolId' + ' ' + six.text_type( - pool_id) + ' ' + '--volSize' + ' ' + six.text_type( - vol_size) + ' ' + '--thinFlag' + ' ' + six.text_type(thin_flag) - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def extend_volume(self, vol_name, new_vol_size): - cmd = '' - cmd = '--op expandVolume' + ' ' + '--volName' + ' ' + six.text_type( - vol_name) + ' ' + '--volSize' + ' ' + six.text_type(new_vol_size) - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def create_volume_from_snap(self, vol_name, vol_size, snap_name): - cmd = ('--op createVolumeFromSnap' + ' ') + ( - '--volName' + ' ') + six.text_type( - vol_name) + ' ' + '--snapNameSrc' + ' ' + six.text_type( - snap_name) + ' ' + '--volSize' + ' ' + six.text_type(vol_size) - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def create_fullvol_from_snap(self, vol_name, snap_name): - cmd = ('--op createFullVolumeFromSnap' + ' ') + ( - '--volName' + ' ') + six.text_type( - vol_name) + ' ' + '--snapName' + ' ' + six.text_type(snap_name) - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def create_volume_from_volume(self, vol_name, vol_size, src_vol_name): - retcode = 1 - tmp_snap_name = six.text_type(vol_name) + '_tmp_snap' - - retcode = self.create_snapshot(tmp_snap_name, src_vol_name, 0) - if 0 != retcode: - return retcode - - retcode = self.create_volume(vol_name, 0, vol_size, 0) - if 0 != retcode: - self.delete_snapshot(tmp_snap_name) - return retcode - - retcode = self.create_fullvol_from_snap(vol_name, tmp_snap_name) - if 0 != retcode: - self.delete_snapshot(tmp_snap_name) - self.delete_volume(vol_name) - return retcode - - return 0 - - def create_clone_volume_from_volume(self, vol_name, - vol_size, src_vol_name): - retcode = 1 - tmp_snap_name = six.text_type(src_vol_name) + '_DT_clnoe_snap' - - retcode = self.create_snapshot(tmp_snap_name, src_vol_name, 0) - if 0 != retcode: - return retcode - - retcode = self.create_volume_from_snap( - vol_name, vol_size, tmp_snap_name) - if 0 != retcode: - return retcode - - return 0 - - def volume_info_analyze(self, vol_info): - local_volume_info = volume_info - - if not vol_info: - local_volume_info['result'] = 1 - return local_volume_info - - local_volume_info['result'] = 0 - - vol_info_list = [] - vol_info_list = re.split(',', vol_info) - for line in vol_info_list: - line = line.replace('\n', '') - if re.search('^vol_name=', line): - local_volume_info['vol_name'] = line[len('vol_name='):] - elif re.search('^father_name=', line): - local_volume_info['father_name'] = line[len('father_name='):] - elif re.search('^status=', line): - local_volume_info['status'] = line[len('status='):] - elif re.search('^vol_size=', line): - local_volume_info['vol_size'] = line[len('vol_size='):] - elif re.search('^real_size=', line): - local_volume_info['real_size'] = line[len('real_size='):] - elif re.search('^pool_id=', line): - local_volume_info['pool_id'] = line[len('pool_id='):] - elif re.search('^create_time=', line): - local_volume_info['create_time'] = line[len('create_time='):] - else: - LOG.error("Analyze key not exist, key=%s.", line) - return local_volume_info - - def query_volume(self, vol_name): - tmp_volume_info = volume_info - cmd = '--op queryVolume' + ' ' + '--volName' + ' ' + vol_name - - exec_result = self.start_execute_cmd(cmd, 1) - if exec_result: - for line in exec_result: - if re.search('^result=', line): - if not re.search('^result=0', line): - tmp_volume_info['result'] = line[self.res_idx:] - return tmp_volume_info - for line in exec_result: - if re.search('^vol_name=' + vol_name, line): - tmp_volume_info = self.volume_info_analyze(line) - if six.text_type(0) == tmp_volume_info['status']: - tmp_snap_name = six.text_type( - vol_name) + '_tmp_snap' - self.delete_snapshot(tmp_snap_name) - return tmp_volume_info - - tmp_volume_info['result'] = 1 - return tmp_volume_info - - def delete_volume(self, vol_name): - cmd = '--op deleteVolume' + ' ' + '--volName' + ' ' + vol_name - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def create_snapshot(self, snap_name, vol_name, smart_flag): - cmd = '--op createSnapshot' + ' ' + '--volName' + ' ' + six.text_type( - vol_name) + ' ' + '--snapName' + ' ' + six.text_type( - snap_name) + ' ' + '--smartFlag' + ' ' + six.text_type(smart_flag) - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def snap_info_analyze(self, info): - local_snap_info = snap_info.copy() - - if not info: - local_snap_info['result'] = 1 - return local_snap_info - - local_snap_info['result'] = 0 - - snap_info_list = [] - snap_info_list = re.split(',', info) - for line in snap_info_list: - line = line.replace('\n', '') - if re.search('^snap_name=', line): - local_snap_info['snap_name'] = line[len('snap_name='):] - elif re.search('^father_name=', line): - local_snap_info['father_name'] = line[len('father_name='):] - elif re.search('^status=', line): - local_snap_info['status'] = line[len('status='):] - elif re.search('^snap_size=', line): - local_snap_info['snap_size'] = line[len('snap_size='):] - elif re.search('^real_size=', line): - local_snap_info['real_size'] = line[len('real_size='):] - elif re.search('^pool_id=', line): - local_snap_info['pool_id'] = line[len('pool_id='):] - elif re.search('^delete_priority=', line): - local_snap_info['delete_priority'] = line[ - len('delete_priority='):] - elif re.search('^create_time=', line): - local_snap_info['create_time'] = line[len('create_time='):] - else: - LOG.error("Analyze key not exist, key=%s.", line) - - return local_snap_info - - def query_snap(self, snap_name): - tmp_snap_info = snap_info.copy() - cmd = '--op querySnapshot' + ' ' + '--snapName' + ' ' + snap_name - - exec_result = self.start_execute_cmd(cmd, 1) - if exec_result: - for line in exec_result: - if re.search('^result=', line): - if not re.search('^result=0', line): - tmp_snap_info['result'] = line[self.res_idx:] - return tmp_snap_info - for line in exec_result: - if re.search('^snap_name=' + snap_name, line): - tmp_snap_info = self.snap_info_analyze(line) - return tmp_snap_info - - tmp_snap_info['result'] = 1 - return tmp_snap_info - - def delete_snapshot(self, snap_name): - cmd = '--op deleteSnapshot' + ' ' + '--snapName' + ' ' + snap_name - - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - if re.search('^result=0', exec_result): - return 0 - else: - return exec_result[self.res_idx:] - else: - return 1 - - def pool_info_analyze(self, info): - local_pool_info = pool_info.copy() - - if not info: - local_pool_info['result'] = 1 - return local_pool_info - - local_pool_info['result'] = 0 - - pool_info_list = [] - pool_info_list = re.split(',', info) - for line in pool_info_list: - line = line.replace('\n', '') - if re.search('^pool_id=', line): - local_pool_info['pool_id'] = line[len('pool_id='):] - elif re.search('^total_capacity=', line): - local_pool_info['total_capacity'] = line[ - len('total_capacity='):] - elif re.search('^used_capacity=', line): - local_pool_info['used_capacity'] = line[len('used_capacity='):] - elif re.search('^alloc_capacity=', line): - local_pool_info['alloc_capacity'] = line[ - len('alloc_capacity='):] - else: - LOG.error("Analyze key not exist, key=%s.", line) - return local_pool_info - - def query_pool_info(self, pool_id): - tmp_pool_info = pool_info.copy() - cmd = '--op queryPoolInfo' + ' ' + '--poolId' + ' ' + six.text_type( - pool_id) - LOG.debug("Pool id is %s.", pool_id) - exec_result = self.start_execute_cmd(cmd, 1) - if exec_result: - for line in exec_result: - if re.search('^result=', line): - if not re.search('^result=0', line): - tmp_pool_info['result'] = line[self.res_idx:] - return tmp_pool_info - for line in exec_result: - if re.search('^pool_id=' + six.text_type(pool_id), - line): - tmp_pool_info = self.pool_info_analyze(line) - return tmp_pool_info - - tmp_pool_info['result'] = 1 - return tmp_pool_info - - def query_pool_type(self, pool_type): - pool_list = [] - tmp_pool_info = {} - result = 0 - cmd = '' - cmd = '--op queryPoolType --poolType' + ' ' + pool_type - LOG.debug("Query poolType: %s.", pool_type) - exec_result = self.start_execute_cmd(cmd, 1) - if exec_result: - for line in exec_result: - line = line.replace('\n', '') - if re.search('^result=', line): - if not re.search('^result=0', line): - result = int(line[self.res_idx:]) - break - for one_line in exec_result: - if re.search('^pool_id=', one_line): - tmp_pool_info = self.pool_info_analyze(one_line) - pool_list.append(tmp_pool_info) - break - return (result, pool_list) - - def query_dsware_version(self): - retcode = 2 - cmd = '--op getDSwareIdentifier' - exec_result = self.start_execute_cmd(cmd, 0) - if exec_result: - # New version. - if re.search('^result=0', exec_result): - retcode = 0 - # Old version. - elif re.search('^result=50500001', exec_result): - retcode = 1 - # Failed! - else: - retcode = exec_result[self.res_idx:] - return retcode diff --git a/doc/source/configuration/tables/cinder-fusionio.inc b/doc/source/configuration/tables/cinder-fusionio.inc index e1e7229e13e..ce384463bbf 100644 --- a/doc/source/configuration/tables/cinder-fusionio.inc +++ b/doc/source/configuration/tables/cinder-fusionio.inc @@ -1,30 +1,48 @@ -.. - Warning: Do not edit this file. It is automatically generated from the - software project's code and your changes will be overwritten. - - The tool to generate this file lives in openstack-doc-tools repository. - - Please make any changes needed in the code, then run the - autogenerate-config-doc tool from the openstack-doc-tools repository, or - ask for help on the documentation mailing list, IRC channel or meeting. - -.. _cinder-fusionio: - -.. list-table:: Description of Fusion-io driver configuration options - :header-rows: 1 - :class: config-ref-table - - * - Configuration option = Default value - - Description - * - **[DEFAULT]** - - - * - ``dsware_isthin`` = ``False`` - - (Boolean) The flag of thin storage allocation. - * - ``dsware_manager`` = - - (String) Fusionstorage manager ip addr for cinder-volume. - * - ``fusionstorageagent`` = - - (String) Fusionstorage agent ip addr range. - * - ``pool_id_filter`` = - - (List) Pool id permit to use. - * - ``pool_type`` = ``default`` - - (String) Pool type, like sata-2copy. + +Volume driver configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section describes how to configure the FusionStorage Volume Driver. + +To configure the volume driver, follow the steps below: + +#. Configure the ``cinder.conf`` file. + + In the ``[default]`` block of ``/etc/cinder/cinder.conf``, + enable the ``VOLUME_BACKEND``: + + .. code-block:: ini + + enabled_backends = VOLUME_BACKEND + + Add a new block ``[VOLUME_BACKEND]``, and add the following contents: + + .. code-block:: ini + + [VOLUME_BACKEND] + volume_driver = cinder.volume.drivers.fusionstorage.dsware.DSWAREDriver + volume_backend_name = backend_name + manager_ips = + host1:ip1, + host2:ip2 + storage = + UserName: username, + Password: password, + RestURL: url, + StoragePool: pool0;pool1;pool2 + + * ``volume_driver`` indicates the loaded driver. + + * ``volume_backend_name`` indicates the name of the backend. + + * ``manager_ips`` indicates the management host name and its corresponding IP address; + The parameters takes the standard dict config form, such as + manager_ips = host1:ip1, host2:ip2. + + * ``storage`` indicates the FusionStorage and user info, include "UserName", + "Password", "RestURL", "StoragePool". The parameters takes the standard + dict config form. + +#. Run the :command:`service cinder-volume restart` command to restart the + Block Storage service. +