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
This commit is contained in:
doubletao 2018-11-26 10:52:12 +08:00 committed by futaotao
parent da34b35579
commit 73372c279e
12 changed files with 1635 additions and 2280 deletions

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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.