Merge "Update FusionStorage Cinder Driver by using REST API"
This commit is contained in:
commit
af84029b07
@ -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
272
cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py
Normal file
272
cinder/tests/unit/volume/drivers/fusionstorage/test_fs_client.py
Normal 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)
|
@ -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)
|
@ -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)
|
48
cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py
Normal file
48
cinder/tests/unit/volume/drivers/fusionstorage/test_utils.py
Normal 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)
|
31
cinder/volume/drivers/fusionstorage/constants.py
Normal file
31
cinder/volume/drivers/fusionstorage/constants.py
Normal 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"
|
@ -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
|
||||
|
256
cinder/volume/drivers/fusionstorage/fs_client.py
Normal file
256
cinder/volume/drivers/fusionstorage/fs_client.py
Normal 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)
|
135
cinder/volume/drivers/fusionstorage/fs_conf.py
Normal file
135
cinder/volume/drivers/fusionstorage/fs_conf.py
Normal 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)
|
@ -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
|
@ -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.
|
||||
Volume driver configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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.
|
||||
This section describes how to configure the FusionStorage Volume Driver.
|
||||
|
||||
.. _cinder-fusionio:
|
||||
To configure the volume driver, follow the steps below:
|
||||
|
||||
.. list-table:: Description of Fusion-io driver configuration options
|
||||
:header-rows: 1
|
||||
:class: config-ref-table
|
||||
#. 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.
|
||||
|
||||
* - 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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user