Merge "Implement Huawei SDSHypervisor driver"

This commit is contained in:
Jenkins 2014-12-18 04:22:17 +00:00 committed by Gerrit Code Review
commit b7647c1266
6 changed files with 1771 additions and 0 deletions

@ -0,0 +1,780 @@
# Copyright (c) 2014 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 SDS hypervisor volume drivers.
"""
import mock
import os
import re
import tempfile
from xml.dom.minidom import Document
from oslo.utils import units
from cinder.brick.initiator import connector as brick_connector
from cinder import exception
from cinder import test
from cinder.volume import driver as base_driver
from cinder.volume.drivers.huaweistorhyper import huaweistorac
from cinder.volume.drivers.huaweistorhyper import utils
from cinder.volume.drivers.huaweistorhyper import vbs_client
from cinder.volume import volume_types
test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 2,
'volume_name': 'vol1',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'provider_auth': None,
'project_id': 'project',
'display_name': 'vol1',
'display_description': 'test volume',
'volume_type_id': None,
'host': '',
'status': 'available',
'provider_location':
'volume-21ec7341-9256-497b-97d9-ef48edcf0635'}
test_volume_with_type = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0666',
'size': 2,
'volume_name': 'vol1',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0666',
'provider_auth': None,
'project_id': 'project',
'display_name': 'vol1',
'display_description': 'test volume',
'volume_type_id': 'gold',
'volume_type': {'name': 'gold',
'extra_specs': [{'key': 'raid_level',
'value': '2'},
{'key': 'iops',
'value': '1000'}],
'qos_specs': {}},
'host': '',
'status': 'available',
'provider_location':
'volume-21ec7341-9256-497b-97d9-ef48edcf0635'}
volume_type = {'name': 'gold',
'deleted': False,
'updated_at': None,
'extra_specs': {'raid_level': '2',
'iops': '1000'},
'deleted_at': None,
'id': 'gold'}
volume_type_qos = {'name': 'white',
'deleted': False,
'updated_at': None,
'extra_specs': [{'key':
'raid_level',
'value': '3'},
{'key': 'iops',
'value':
'2000'}],
'deleted_at': None,
'qos_specs': {'id': 1,
'name': 'qos_specs',
'consumer': 'Consumer',
'specs': {'Qos-high': '10'}},
'id': 'white'}
test_volume_with_type_qos = {'name':
'volume-21ec7341-9256-497b-97d9-ef48edcf8888',
'size': 2,
'volume_name': 'vol2',
'id': '21ec7341-9256-497b-97d9-ef48edcf8888',
'volume_id':
'21ec7341-9256-497b-97d9-ef48edcf8888',
'provider_auth': None,
'project_id': 'project2',
'display_name': 'vol2',
'display_description': 'test volume',
'volume_type_id': 'white',
'volume_type': {'name': 'white',
'extra_specs': [{'key':
'raid_level',
'value': '3'},
{'key': 'iops',
'value':
'2000'}],
'qos_specs': {'id': 1,
'name': 'qos_specs',
'consumer':
'Consumer',
'specs':
{'Qos-high':
'10'}}},
'host': '',
'status': 'available',
'provider_location':
'volume-21ec7341-9256-497b-97d9-ef48edcf8888'}
test_volume_tgt = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0636',
'size': 2,
'volume_name': 'vol1',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'provider_auth': None,
'project_id': 'project',
'display_name': 'vol2',
'display_description': 'test volume',
'volume_type_id': None,
'host': '',
'status': 'available',
'provider_location':
'volume-21ec7341-9256-497b-97d9-ef48edcf0636'}
test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 1,
'volume_name': 'vol1',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'provider_auth': None,
'project_id': 'project',
'display_name': 'vol1',
'display_description': 'test volume',
'volume_type_id': None}
test_volume_orders = ['CREATE_VOLUME_REQ',
'DELETE_VOLUME_REQ',
'CREATE_VOLUME_FROM_SNAPSHOT_REQ',
'CLONE_VOLUME_REQ',
'EXTEND_VOLUME_REQ',
'CREATE_SNAPSHOT_REQ',
'DELETE_SNAPSHOT_REQ',
'CREATE_FULLVOLUME_FROM_SNAPSHOT_REQ',
'CREATE_LUN_MAPPING_REQ',
'DELETE_LUN_MAPPING_REQ'
]
test_context = None
test_image_service = None
test_image_meta = None
test_connector = {'ip': '173.30.0.23',
'initiator': 'iqn.1993-08.org.debian:01:37b12ad7d46',
'host': 'openstack'}
class FakeVbsClient(vbs_client.VbsClient):
retcode = None
delete_snapshot_ret = None
def __init__(self, conf_file):
super(FakeVbsClient, self).__init__(conf_file)
self.test_normal_case = True
self.reqs = []
def send_message(self, msg):
return self.__start_send_req(msg)
def __start_send_req(self, req):
title = self._get_title(req)
self.reqs.append(title)
self._set_ret()
if self.test_normal_case:
if title:
if title in test_volume_orders:
return 'retcode=' + FakeVbsClient.retcode
elif 'QUERY_VOLUME_REQ' == title:
return '''retcode=-900079'''
elif 'QUERY_SNAPSHOT_REQ' == title:
return '''retcode=-900079'''
elif 'QUERY_SINGLE_POOL_CAPABILITY_REQ' == title:
return {'total_capacity': '100',
'usable_capacity': '90',
'tolerance_disk_failure': '20 10',
'tolerance_cache_failure': '10 5'}
elif 'QUERY_POOLS_CAPABILITY_REQ' == title:
return 'retcode=0\npool0=[stor_id=16384,'\
'total_capacity=100,usable_capacity=97,'\
'raid_level=5,iosp=15000,max_iops=15000,'\
'min_iops=0]\npool1=[stor_id=16385,'\
'total_capacity=100,usable_capacity=97,'\
'raid_level=5,iosp=25000,max_iops=25000,'\
'min_iops=0]\n'
else:
if title:
return 'retcode=' + FakeVbsClient.retcode
def _get_title(self, req):
lines = re.split('\n', req)
return lines[0][1:-1]
def _set_ret(self):
if self.test_normal_case:
FakeVbsClient.retcode = '0'
else:
FakeVbsClient.retcode = '1'
class FakeStorACStorage(huaweistorac.StorACDriver):
def __init__(self, configuration):
super(FakeStorACStorage, self).__init__(configuration=configuration)
self.configuration = configuration
def do_setup(self, conf_file):
self._vbs_client = FakeVbsClient(conf_file)
self._get_default_volume_stats()
class HuaweistoracUtilsTestCase(test.TestCase):
def setUp(self):
super(HuaweistoracUtilsTestCase, self).setUp()
self.request_info = {'vol_name':
'volume-3f'}
self.request_type = 'QUERY_VOLUME_REQ'
self.serialize_out_fake = '[QUERY_VOLUME_REQ]\nvol_name=volume-3f\n'
def test_serialize(self):
serialize_out = utils.serialize(self.request_type,
self.request_info)
self.assertEqual(self.serialize_out_fake,
serialize_out)
def test_deserialize(self):
deserialize_out = utils.deserialize('retcode=0\npool0=[stor_id=1638]',
'\n')
self.assertEqual({'retcode': '0',
'pool0': '[stor_id=1638]'},
deserialize_out)
def test_get_valid_ip_list(self):
iplist = utils.get_valid_ip_list(['', '127.0.0.1', '33', ''])
self.assertEqual(['127.0.0.1'],
iplist)
def test_generate_dict_from_result(self):
result = utils.generate_dict_from_result("[stor_id=1638,iops=25]")
self.assertEqual({'stor_id': '1638',
'iops': '25'},
result)
class StorACDriverTestCase(test.TestCase):
def setUp(self):
super(StorACDriverTestCase, self).setUp()
self.fake_conf_file = tempfile.mktemp(suffix='.xml')
self.addCleanup(os.remove, self.fake_conf_file)
self.create_fake_conf_file()
self.configuration = mock.Mock()
self.configuration.use_multipath_for_image_xfer = False
self.configuration.num_volume_device_scan_tries = 3
self.configuration.cinder_huawei_sds_conf_file = self.fake_conf_file
self.driver = FakeStorACStorage(configuration=self.configuration)
self.driver.do_setup(self.fake_conf_file)
self.driver._vbs_client.test_normal_case = True
def create_fake_conf_file(self):
doc = Document()
config = doc.createElement('config')
doc.appendChild(config)
controller = doc.createElement('controller')
config.appendChild(controller)
self._xml_append_child(doc, controller, 'vbs_url', '127.0.0.1,')
self._xml_append_child(doc, controller, 'vbs_port', '10599')
self._xml_append_child(doc, controller, 'UserName', 'aa')
self._xml_append_child(doc, controller, 'UserPassword', 'bb')
policy = doc.createElement('policy')
config.appendChild(policy)
self._xml_append_child(doc, policy, 'force_provision_size', '2')
self._xml_append_child(doc, policy, 'iops', '200')
self._xml_append_child(doc, policy, 'cache_size', '2')
self._xml_append_child(doc, policy, 'repicate_num', '2')
self._xml_append_child(doc, policy, 'repicate_tolerant_num', '0')
self._xml_append_child(doc, policy, 'encrypt_algorithm', '0')
self._xml_append_child(doc, policy, 'consistency', '1')
self._xml_append_child(doc, policy, 'compress_algorithm', '0')
self._xml_append_child(doc, policy, 'backup_cycle', '0')
self._xml_append_child(doc, policy, 'stor_space_level', '1')
self._xml_append_child(doc, policy, 'QoS_support', '1')
self._xml_append_child(doc, policy, 'tolerance_disk_failure', '0')
self._xml_append_child(doc, policy, 'tolerance_cache_failure', '1')
capability = doc.createElement('capability')
config.appendChild(capability)
self._xml_append_child(doc, capability, 'reserved_percentage', '0')
self._xml_append_child(doc, capability, 'deduplication', '0')
self._xml_append_child(doc, capability, 'snapshot', '1')
self._xml_append_child(doc, capability, 'backup', '0')
pools = doc.createElement('pools')
config.appendChild(pools)
pool1 = doc.createElement('pool')
pool2 = doc.createElement('pool')
pools.appendChild(pool1)
pools.appendChild(pool2)
self._xml_append_child(doc, pool1, 'pool_id', 'xxx1')
self._xml_append_child(doc, pool1, 'iops', '200')
newefile = open(self.fake_conf_file, 'w')
newefile.write(doc.toprettyxml(indent=''))
newefile.close()
def _xml_append_child(self, doc, parent, child_name, child_text):
child = doc.createElement(child_name)
child_node_text = doc.createTextNode(child_text)
child.appendChild(child_node_text)
parent.appendChild(child)
def test_create_volume_success(self):
retval = self.driver.create_volume(test_volume)
self.assertEqual("volume-21ec7341-9256-497b-97d9-ef48edcf0635",
retval['provider_location'])
def test_create_volume_with_volume_type(self):
retval = self.driver.create_volume(test_volume_with_type)
self.assertEqual("volume-21ec7341-9256-497b-97d9-ef48edcf0666",
retval['provider_location'])
def test_create_volume_from_snapshot_success(self):
retval = self.driver. \
create_volume_from_snapshot(test_volume, test_snap)
self.assertEqual("volume-21ec7341-9256-497b-97d9-ef48edcf0635",
retval['provider_location'])
@mock.patch.object(base_driver.VolumeDriver,
'copy_volume_data')
def test_create_cloned_volume_success(self, mock_copy_volume_data):
mock_copy_volume_data.return_value = None
retval = self.driver.\
create_cloned_volume(test_volume_tgt, test_volume)
self.assertEqual("volume-21ec7341-9256-497b-97d9-ef48edcf0636",
retval['provider_location'])
def test_delete_volume_success(self):
self.driver.delete_volume(test_volume)
self.assertEqual('0', FakeVbsClient.retcode)
def test_extend_volume_success(self):
new_size = 4
self.driver.extend_volume(test_volume, new_size)
self.assertEqual('0', FakeVbsClient.retcode)
def test_migrate_volume_success(self):
pass
def test_get_volume_stats(self):
stats = self.driver.get_volume_stats(True)
self.assertEqual(0, stats['free_capacity_gb'])
self.assertEqual(0, stats['total_capacity_gb'])
self.assertEqual('0', stats['reserved_percentage'])
def test_create_snapshot_success(self):
retval = self.driver.create_snapshot(test_snap)
self.assertEqual('volume-21ec7341-9256-497b-97d9-ef48edcf0635',
retval['provider_location'])
@mock.patch.object(brick_connector.HuaweiStorHyperConnector,
'is_volume_connected')
@mock.patch.object(brick_connector.HuaweiStorHyperConnector,
'connect_volume')
@mock.patch.object(brick_connector.HuaweiStorHyperConnector,
'disconnect_volume')
@mock.patch.object(base_driver.VolumeDriver,
'_attach_volume')
@mock.patch.object(base_driver.VolumeDriver,
'_detach_volume')
def test_delete_snapshot_success(self, mock_disconnect_volume,
mock_connect_volume,
mock_is_volume_connected,
mock__attach_volume,
mock__detach_volume):
mock_disconnect_volume.return_value = None
mock_connect_volume.return_value = {'type': 'block',
'path': '/dev/null'}
mock_is_volume_connected.return_value = True
mock__attach_volume.return_value = None
mock__detach_volume.return_value = None
self.driver.delete_snapshot(test_snap)
self.assertEqual('0', FakeVbsClient.retcode)
mock_is_volume_connected.return_value = False
self.driver.delete_snapshot(test_snap)
self.assertEqual('0', FakeVbsClient.retcode)
@mock.patch.object(base_driver.VolumeDriver,
'copy_volume_to_image')
def test_copy_volume_to_image_success(self,
mock_copy_volume_to_image):
mock_copy_volume_to_image.return_value = None
self.driver.copy_volume_to_image(test_context,
test_volume,
test_image_service,
test_image_meta)
expected_reqs = ['CREATE_SNAPSHOT_REQ',
'CREATE_VOLUME_FROM_SNAPSHOT_REQ',
'DELETE_VOLUME_REQ',
'QUERY_VOLUME_REQ']
self.assertEqual(expected_reqs, self.driver._vbs_client.reqs)
@mock.patch.object(base_driver.VolumeDriver,
'copy_volume_data')
def test_copy_volume_data_success(self,
mock_copy_volume_data):
mock_copy_volume_data.return_value = None
self.driver.copy_volume_data(test_context,
test_volume,
test_volume_tgt,
remote=None)
expected_reqs = ['CREATE_SNAPSHOT_REQ',
'CREATE_VOLUME_FROM_SNAPSHOT_REQ',
'DELETE_VOLUME_REQ',
'QUERY_VOLUME_REQ']
self.assertEqual(expected_reqs, self.driver._vbs_client.reqs)
def test_initialize_connection_success(self):
retval = self.driver.initialize_connection(test_volume, test_connector)
self.assertEqual('HUAWEISDSHYPERVISOR', retval['driver_volume_type'])
def test_terminate_connection_success(self):
pass
def test_create_volume_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.create_volume, test_volume)
def test_create_volume_from_snapshot_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.create_volume_from_snapshot,
test_volume, test_snap)
def test_create_cloned_volume_fail(self):
self.driver._vbs_client.test_normal_case = False
test_volume_tmp = dict(test_volume)
test_volume_tmp.pop('provider_location')
self.assertRaises(exception.CinderException,
self.driver.create_cloned_volume,
test_volume_tgt, test_volume_tmp)
def test_delete_volume_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.delete_volume, test_volume)
def test_extend_volume_fail(self):
new_size = 4
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.extend_volume, test_volume, new_size)
def create_snapshot_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.create_snapshot, test_snap)
def delete_snapshot_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.delete_snapshot, test_snap)
def test_copy_volume_to_image_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.copy_volume_to_image,
test_context,
test_volume,
test_image_service,
test_image_meta)
def test_copy_volume_data_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver.copy_volume_data,
test_context,
test_volume,
test_volume_tgt,
remote=None)
def test_terminate_connection_fail(self):
pass
@mock.patch.object(brick_connector.HuaweiStorHyperConnector,
'is_volume_connected')
def test__is_volume_attached(self, mock_is_volume_connected):
mock_is_volume_connected.return_value = True
ret = self.driver._is_volume_attached('21ec7341')
self.assertEqual(True, ret)
mock_is_volume_connected.return_value = False
ret = self.driver._is_volume_attached('21ec7341')
self.assertEqual(False, ret)
def test__create_target_volume_success(self):
test_volume_name_tgt = test_volume_tgt['name']
retval = self.driver._create_target_volume(test_volume,
test_volume_name_tgt,
test_volume_tgt)
self.assertEqual("volume-21ec7341-9256-497b-97d9-ef48edcf0636",
retval['vol_name'])
def test__create_linked_volume_from_snap_success(self):
tgt_vol_name = test_volume['name']
src_snapshot_name = test_snap['name']
self.driver._create_linked_volume_from_snap(src_snapshot_name,
tgt_vol_name,
test_volume['size'])
expected_reqs = ['CREATE_VOLUME_FROM_SNAPSHOT_REQ']
self.assertEqual(expected_reqs, self.driver._vbs_client.reqs)
def test__get_all_pool_capacity_success(self):
retval = self.driver._get_all_pool_capacity()
stats = retval['16384']
self.assertEqual(97, stats['free_capacity_gb'])
self.assertEqual(100, stats['total_capacity_gb'])
self.assertEqual(0, stats['reserved_percentage'])
def test__delete_snapshot_success(self):
self.driver._delete_snapshot(test_snap)
expected_reqs = ['DELETE_SNAPSHOT_REQ',
'QUERY_SNAPSHOT_REQ']
self.assertEqual(expected_reqs, self.driver._vbs_client.reqs)
def test__create_default_volume_stats_success(self):
retval = self.driver._create_default_volume_stats()
self.assertEqual('Huawei', retval['vendor_name'])
def test__get_default_volume_stats_success(self):
retval = self.driver._get_default_volume_stats()
self.assertEqual('0', retval['reserved_percentage'])
self.assertEqual('0', retval['deduplication'])
self.assertEqual('1', retval['snapshot'])
self.assertEqual('0', retval['backup'])
def test__query_volume_success(self):
retval = self.driver._query_volume(test_volume['name'])
self.assertEqual('-900079', retval['retcode'])
def test__is_volume_exist_success(self):
volume_name = test_volume['name']
retval = self.driver._is_volume_exist(volume_name)
self.assertEqual(False, retval)
def test__create_target_volume_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver._create_target_volume,
test_volume,
test_volume_tgt['volume_name'],
test_volume_tgt)
def test__create_linked_volume_from_snap_fail(self):
tgt_vol_name = test_volume['name']
src_snapshot_name = test_snap['name']
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver._create_linked_volume_from_snap,
src_snapshot_name,
tgt_vol_name,
test_volume['size'])
def test__get_volume_stats_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.VolumeBackendAPIException,
self.driver._get_volume_stats)
def test__get_all_pool_capacity_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver._get_all_pool_capacity)
def test__delete_snapshot_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.CinderException,
self.driver._delete_snapshot,
test_snap)
def test__query_volume_fail(self):
self.driver._vbs_client.test_normal_case = False
retval = self.driver._query_volume(test_volume['name'])
self.assertEqual('1', retval['retcode'])
def test__is_volume_exist_fail(self):
volume_name = test_volume['name']
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.VolumeBackendAPIException,
self.driver._is_volume_exist,
volume_name)
def test_size_translate_success(self):
exp_size = '%s' % (2 * units.Ki)
vol_size = self.driver._size_translate(2)
self.assertEqual(exp_size, vol_size)
def test_update_volume_info_from_volume_extra_specs_success(self):
volume_info = self.driver._create_storage_info('volume_info')
extra_specs = volume_type_qos.get('extra_specs')
self.driver._update_volume_info_from_volume_extra_specs(volume_info,
extra_specs)
self.assertEqual('2000', volume_info['iops'])
def test_update_volume_info_from_volume_success(self):
volume_info = self.driver._create_storage_info('volume_info')
self.driver._update_volume_info_from_volume(volume_info,
test_volume_with_type_qos)
self.assertEqual('2000', volume_info['iops'])
self.assertEqual("3", volume_info['IOPRIORITY'])
def test_update_volume_info_from_qos_specs_success(self):
volume_info = self.driver._create_storage_info('volume_info')
self.driver._update_volume_info_from_qos_specs(volume_info,
volume_type_qos)
self.assertEqual("3", volume_info['IOPRIORITY'])
@mock.patch.object(volume_types, 'get_volume_type')
@mock.patch.object(volume_types, 'get_volume_type_qos_specs')
def test_update_volinfo_from_type_success(self,
_mock_get_volume_types,
_mock_get_volume_type_qos_specs):
volume_info = self.driver._create_storage_info('volume_info')
_mock_get_volume_types.return_value = volume_type_qos
_mock_get_volume_type_qos_specs.return_value = {'qos_specs':
{'id': 1,
'name': 'qos_specs',
'consumer':
'Consumer',
'specs': {'Qos-high':
'10'}}}
self.driver._update_volume_info_from_volume_type(volume_info, 'white')
self.assertEqual('100', volume_info['iops'])
self.assertEqual("3", volume_info['IOPRIORITY'])
def test_create_storage_info_success(self):
volume_info = self.driver._create_storage_info('volume_info')
self.assertEqual('', volume_info['vol_name'])
self.assertEqual('', volume_info['vol_size'])
self.assertEqual('0', volume_info['pool_id'])
self.assertEqual('0', volume_info['thin_flag'])
self.assertEqual('0', volume_info['reserved'])
self.assertEqual('0', volume_info['volume_space_reserved'])
self.assertEqual('0', volume_info['force_provision_size'])
self.assertEqual('100', volume_info['iops'])
self.assertEqual('100', volume_info['max_iops'])
self.assertEqual('0', volume_info['min_iops'])
self.assertEqual('0', volume_info['cache_size'])
self.assertEqual('1', volume_info['repicate_num'])
self.assertEqual('1', volume_info['repicate_tolerant_num'])
self.assertEqual('0', volume_info['encrypt_algorithm'])
self.assertEqual('0', volume_info['consistency'])
self.assertEqual('1', volume_info['stor_space_level'])
self.assertEqual('0', volume_info['compress_algorithm'])
self.assertEqual('0', volume_info['deduplication'])
self.assertEqual('0', volume_info['snapshot'])
self.assertEqual('0', volume_info['backup_cycle'])
self.assertEqual('0', volume_info['tolerance_disk_failure'])
self.assertEqual('1', volume_info['tolerance_cache_failure'])
def test_is_snapshot_exist_success(self):
result = self.driver._is_snapshot_exist('snap-21ec7341')
self.assertEqual(False, result)
def test_get_volume_pool_id_success(self):
result = self.driver._get_volume_pool_id('host#cloud')
self.assertEqual('cloud', result)
def test_send_request_success(self):
volume_info = self.driver._create_storage_info('volume_info')
volume_info['vol_name'] = 'test_vol'
volume_info['vol_size'] = 2
result = self.driver._send_request('CREATE_VOLUME_REQ',
volume_info,
'create volume error.')
self.assertEqual('0', result['retcode'])
def test_update_default_volume_stats_from_config_success(self):
default_stats = {'pools_id': []}
self.driver.\
_update_default_volume_stats_from_config(default_stats,
self.fake_conf_file)
self.assertEqual(True, default_stats['QoS_support'])
self.assertEqual('200', default_stats['iops'])
self.assertEqual('2', default_stats['cache_size'])
self.assertEqual('2', default_stats['repicate_num'])
self.assertEqual('0', default_stats['repicate_tolerant_num'])
self.assertEqual('0', default_stats['encrypt_algorithm'])
self.assertEqual('1', default_stats['consistency'])
self.assertEqual('0', default_stats['compress_algorithm'])
self.assertEqual('0', default_stats['backup_cycle'])
self.assertEqual('1', default_stats['stor_space_level'])
self.assertEqual('0', default_stats['tolerance_disk_failure'])
self.assertEqual('1', default_stats['tolerance_cache_failure'])
self.assertEqual('0', default_stats['reserved_percentage'])
self.assertEqual('0', default_stats['deduplication'])
self.assertEqual('1', default_stats['snapshot'])
self.assertEqual('0', default_stats['backup'])
def test_update_all_pool_capacity_from_policy_success(self):
all_pool_policy = {'xxx1': {'total_capacity_gb': 100,
'free_capacity_gb': 80,
'iops': 2000}}
all_pool_capacity = {'xxx1': {'total_capacity_gb': 80,
'free_capacity_gb': 60,
'iops': 1000}}
self.driver._update_all_pool_capacity_from_policy(all_pool_capacity,
all_pool_policy)
self.assertEqual(100, all_pool_capacity['xxx1']['total_capacity_gb'])
self.assertEqual(80, all_pool_capacity['xxx1']['free_capacity_gb'])
self.assertEqual(2000, all_pool_capacity['xxx1']['iops'])
def test_extract_pool_policy_mapping_from_config_success(self):
pools = self.driver.\
_extract_pool_policy_mapping_from_config(self.fake_conf_file)
self.assertEqual('200', pools['xxx1']['iops'])
def test_is_snapshot_exist_fail(self):
self.driver._vbs_client.test_normal_case = False
self.assertRaises(exception.VolumeBackendAPIException,
self.driver._is_snapshot_exist,
'test_snap_not_exist')
def test_get_volume_pool_id_default(self):
pool_info = self.driver._get_volume_pool_id('host_test')
self.assertEqual('xxx1', pool_info)
def test_create_storage_info_fail1(self):
volume_info = self.driver._create_storage_info('')
self.assertEqual(None, volume_info)
def test_create_storage_info_fail2(self):
volume_info = self.driver._create_storage_info('volume')
self.assertEqual(None, volume_info)
def test__query_volume_notexist(self):
retval = self.driver._query_volume('volume-2b73118c')
self.assertEqual(retval['retcode'], '-900079')
def test__is_volume_exist_notexist(self):
volume_name = 'volume-2b73118c-2c6d-4f2c-a00a-9c27791ee814'
retval = self.driver._is_volume_exist(volume_name)
self.assertEqual(False, retval)

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8" ?>
<config>
<controller>
<vbs_url>,127.0.0.1,</vbs_url>
<vbs_port>10599</vbs_port>
<UserName>aa</UserName>
<UserPassword>bb</UserPassword>
</controller>
<pools>
<!--<pool>
<pool_id>xxx1</pool_id>
<iops>200</iops>
</pool>
<pool>
<pool_id>xxx2</pool_id>
<iops>200</iops>
</pool>-->
</pools>
<policy>
<iops>200</iops>
<cache_size>2</cache_size>
<repicate_num>2</repicate_num>
<repicate_tolerant_num>0</repicate_tolerant_num>
<encrypt_algorithm>0</encrypt_algorithm>
<consistency>1</consistency>
<compress_algorithm>0</compress_algorithm>
<backup_cycle>0</backup_cycle>
<stor_space_level>1</stor_space_level>
<QoS_support>1</QoS_support>
<tolerance_disk_failure>0</tolerance_disk_failure>
<tolerance_cache_failure>1</tolerance_cache_failure>
</policy>
<capability>
<reserved_percentage>0</reserved_percentage>
<deduplication>0</deduplication>
<snapshot>1</snapshot>
<backup>0</backup>
</capability>
</config>

@ -0,0 +1,750 @@
# Copyright (c) 2014 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 Huawei SDSHypervisor systems.
"""
import uuid
from oslo.config import cfg
from oslo.utils import units
import six
from cinder import context
from cinder import exception
from cinder.i18n import _, _LE
from cinder.openstack.common import log as logging
from cinder.openstack.common import loopingcall
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.huaweistorhyper import utils as storhyper_utils
from cinder.volume.drivers.huaweistorhyper import vbs_client
from cinder.volume import volume_types
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
QOS_KEY = ["Qos-high", "Qos-normal", "Qos-low"]
LINKED_CLONE_TYPE = 'linked'
FULL_CLONE_TYPE = 'full'
CHECK_VOLUME_DATA_FINISHED_INTERVAL = 10
CHECK_VOLUME_DELETE_FINISHED_INTERVAL = 2
CHECK_SNAPSHOT_DELETE_FINISHED_INTERVAL = 2
huawei_storhyper_opts = [
cfg.StrOpt('cinder_huawei_sds_conf_file',
default='/etc/cinder/cinder_huawei_storac_conf.xml',
help='huawei storagehyper driver config file path'),
]
CONF.register_opts(huawei_storhyper_opts)
class StorACDriver(driver.VolumeDriver):
VERSION = '1.0.0'
del_complete_code = '-900079'
def __init__(self, *args, **kwargs):
super(StorACDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(huawei_storhyper_opts)
self._conf_file = self.configuration.cinder_huawei_sds_conf_file
LOG.debug('Conf_file is: ' + self._conf_file)
self._vbs_client = vbs_client.VbsClient(self._conf_file)
self._volume_stats = self._get_default_volume_stats()
def check_for_setup_error(self):
pass
def initialize_connection(self, volume, connector):
LOG.debug('Initialize connection.')
properties = {}
properties['volume_id'] = volume['name']
return {'driver_volume_type': 'HUAWEISDSHYPERVISOR',
'data': properties}
def terminate_connection(self, volume, connector, **kwargs):
"""Terminate the map."""
pass
def create_volume(self, volume):
"""Create a new volume."""
volume_name = volume['name']
LOG.debug('Create volume, volume name: %s.' % volume_name)
volume_size = self._size_translate(volume['size'])
volume_info = self._create_storage_info('volume_info')
volume_info['vol_name'] = volume_name
volume_info['vol_size'] = volume_size
volume_info['pool_id'] = self._get_volume_pool_id(volume['host'])
self._update_volume_info_from_volume(volume_info, volume)
self._send_request('CREATE_VOLUME_REQ',
volume_info,
'create volume error.')
return {'provider_location': volume['name']}
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot."""
tgt_vol_name = volume['name']
src_snapshot_name = snapshot['name']
LOG.debug('Create volume from snapshot: '
'tgt_vol_name: %(tgt_vol_name)s, '
'src_snapshot_name: %(src_snapshot_name)s, '
'vol_size: %(vol_size)s.'
% {'tgt_vol_name': tgt_vol_name,
'src_snapshot_name': src_snapshot_name,
'vol_size': volume['size']})
self._create_linked_volume_from_snap(src_snapshot_name,
tgt_vol_name,
volume['size'])
return {'provider_location': volume['name']}
def create_cloned_volume(self, tgt_volume, src_volume):
"""Create a clone volume."""
src_vol_name = src_volume['name']
tgt_vol_name = tgt_volume['name']
LOG.debug('Create cloned volume: src volume: %(src)s, '
'tgt volume: %(tgt)s.' % {'src': src_vol_name,
'tgt': tgt_vol_name})
src_vol_id = src_volume.get('provider_location')
if not src_vol_id:
err_msg = (_LE('Source volume %(name)s does not exist.')
% {'name': src_vol_name})
LOG.error(err_msg)
raise exception.VolumeNotFound(volume_id=src_vol_name)
volume_info = self._create_target_volume(src_volume,
tgt_vol_name,
tgt_volume)
tgt_vol_id = volume_info['vol_name']
self.copy_volume_data(context.get_admin_context(), src_volume,
tgt_volume, remote=None)
return {'provider_location': tgt_vol_id}
def delete_volume(self, volume):
"""Delete a volume."""
req_paras = {}
req_paras['vol_name'] = volume['name']
self._send_request('DELETE_VOLUME_REQ',
req_paras,
'Delete volume failed.')
self._wait_for_volume_delete(volume['name'])
def extend_volume(self, volume, new_size):
"""Extend the size of an existing volume."""
LOG.debug('Extend volume: %s.' % volume['name'])
volume_name = volume['name']
new_volume_size = self._size_translate(new_size)
volume_info = {"vol_name": volume_name,
"vol_size": new_volume_size}
self._send_request('EXTEND_VOLUME_REQ',
volume_info,
'extend volume failed.')
def get_volume_stats(self, refresh=False):
"""Get volume stats."""
if refresh:
try:
self._get_volume_stats()
except Exception as ex:
self._volume_stats = self._get_default_volume_stats()
msg = (_LE('Error from get volume stats: '
'%s, using default stats.') % ex)
LOG.error(msg)
return self._volume_stats
def create_snapshot(self, snapshot):
create_snapshot_req = {}
create_snapshot_req['snap_name'] = snapshot['name']
create_snapshot_req['vol_name'] = snapshot['volume_name']
create_snapshot_req['smartflag'] = '1'
self._send_request('CREATE_SNAPSHOT_REQ',
create_snapshot_req,
'create snapshot failed.')
return {'provider_location': snapshot['name']}
def delete_snapshot(self, snapshot):
"""Delete a snapshot."""
"""Delete SDS snapshot,ensure source volume is attached """
source_volume_id = snapshot['volume_id']
if not source_volume_id:
self._delete_snapshot(snapshot)
return
is_volume_attached = self._is_volume_attached(source_volume_id)
if is_volume_attached:
LOG.debug('Volume is attached')
self._delete_snapshot(snapshot)
else:
LOG.debug('Volume is not attached')
source_volume = {'name': 'volume-' + source_volume_id,
'id': source_volume_id}
properties = utils.brick_get_connector_properties()
source_volume_attach_info = self._attach_volume(
None, source_volume, properties, False)
try:
self._delete_snapshot(snapshot)
except Exception as ex:
err_msg = (_LE('Delete snapshot failed: '
'%s.') % ex)
LOG.error(err_msg)
self._detach_volume(
None, source_volume_attach_info, source_volume,
properties, False, False)
def create_export(self, context, volume):
"""Export the volume."""
pass
def ensure_export(self, context, volume):
"""Synchronously recreate an export for a volume."""
pass
def remove_export(self, context, volume):
"""Remove an export for a volume."""
pass
def copy_volume_to_image(self, context, volume, image_service, image_meta):
err_msg = ''
temp_snapshot, temp_volume = self._create_temp_snap_and_volume(volume)
try:
self.create_snapshot(temp_snapshot)
self._create_linked_volume_from_snap(temp_snapshot['name'],
temp_volume['name'],
temp_volume['size'])
temp_volume['status'] = volume['status']
super(StorACDriver, self).copy_volume_to_image(context,
temp_volume,
image_service,
image_meta)
except Exception as ex:
err_msg = (_LE('Copy volume to image failed: %s.') % ex)
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
finally:
self._clean_copy_volume_data(temp_volume,
temp_snapshot,
'copy_volume_to_image')
def copy_volume_data(self, context, src_vol, dest_vol, remote=None):
err_msg = ''
temp_snapshot, temp_volume = self._create_temp_snap_and_volume(src_vol)
try:
self.create_snapshot(temp_snapshot)
self._create_linked_volume_from_snap(temp_snapshot['name'],
temp_volume['name'],
temp_volume['size'])
temp_volume['status'] = src_vol['status']
super(StorACDriver, self).copy_volume_data(context,
temp_volume,
dest_vol,
remote)
except Exception as ex:
err_msg = (_LE('Copy volume data failed: %s.') % ex)
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
finally:
self._clean_copy_volume_data(temp_volume,
temp_snapshot,
'copy_volume_data')
def _create_temp_snap_and_volume(self, src_vol):
temp_snapshot = {'name': 'snapshot-' + six.text_type(uuid.uuid1()),
'volume_name': src_vol['name'],
'smartflag': '1',
'volume_id': src_vol['id']}
temp_volume_id = six.text_type(uuid.uuid1())
temp_volume = {'id': temp_volume_id,
'name': 'volume-' + temp_volume_id,
'size': src_vol['size']}
return temp_snapshot, temp_volume
def _clean_copy_volume_data(self, temp_volume, temp_snapshot, method):
try:
self.delete_volume(temp_volume)
except Exception as ex:
err_msg = (_LE('Delete temp volume failed '
'after %(method)s: %(ex)s.')
% {'ex': ex, 'method': method})
LOG.error(err_msg)
try:
self.delete_snapshot(temp_snapshot)
except Exception as ex:
err_msg = (_LE('Delete temp snapshot failed '
'after %(method)s: %(ex)s.')
% {'ex': ex, 'method': method})
LOG.error(err_msg)
def _is_volume_attached(self, volume_id):
if not volume_id:
return False
conn = {'driver_volume_type': 'HUAWEISDSHYPERVISOR',
'data': {'volume_id': 'volume-' + volume_id}}
use_multipath = self.configuration.use_multipath_for_image_xfer
device_scan_attempts = self.configuration.num_volume_device_scan_tries
protocol = conn['driver_volume_type']
connector = utils.brick_get_connector(protocol,
use_multipath=use_multipath,
device_scan_attempts=
device_scan_attempts,
conn=conn)
is_volume_attached = connector.is_volume_connected(
conn['data']['volume_id'])
return is_volume_attached
def _create_target_volume(self, src_volume, tgt_vol_name, tgt_volume):
if int(tgt_volume['size']) == 0:
tgt_vol_size = self._size_translate(src_volume['size'])
else:
tgt_vol_size = self._size_translate(tgt_volume['size'])
volume_info = self._create_storage_info('volume_info')
volume_info['vol_name'] = tgt_vol_name
volume_info['vol_size'] = tgt_vol_size
volume_info['pool_id'] = self._get_volume_pool_id(tgt_volume['host'])
self._update_volume_info_from_volume_type(volume_info,
tgt_volume['volume_type_id'])
self._send_request('CREATE_VOLUME_REQ',
volume_info,
'create volume failed.')
return volume_info
def _create_linked_volume_from_snap(self, src_snapshot_name,
tgt_vol_name, volume_size):
vol_size = self._size_translate(volume_size)
req_paras = {'vol_name': tgt_vol_name,
'vol_size': vol_size,
'snap_name_src': src_snapshot_name,
'vol_num': '1'}
self._send_request('CREATE_VOLUME_FROM_SNAPSHOT_REQ',
req_paras,
'Create volume from snapshot failed.')
def _get_volume_stats(self):
"""Retrieve stats info from volume group."""
capacity = self._get_capacity()
self._volume_stats['pools'] = capacity
if len(capacity) == 1:
for key, value in capacity[0].items():
self._volume_stats[key] = value
def _get_all_pool_capacity(self):
pool_info = {}
poolnum = len(self._volume_stats['pools_id'])
pool_info['pool_num'] = six.text_type(poolnum)
pool_info['pool_id'] = self._volume_stats['pools_id']
result = self._send_request('QUERY_POOLS_CAPABILITY_REQ',
pool_info,
'Get storage capacity failed')
return self._extract_pool_capacity_mapping_from_result(result)
def _get_capacity(self):
storage_capacity = []
try:
all_pool_policy = self._extract_pool_policy_mapping_from_config(
self._conf_file)
all_pool_capacity = self._get_all_pool_capacity()
self._update_all_pool_capacity_from_policy(all_pool_capacity,
all_pool_policy)
storage_capacity = all_pool_capacity.values()
except exception.VolumeBackendAPIException as ex:
msg = (_LE('Error from get block storage capacity: '
'%s.') % ex)
LOG.error(msg)
raise exception.VolumeBackendAPIException(msg)
return storage_capacity
def _delete_snapshot(self, snapshot):
req_paras = {}
req_paras['snap_name'] = snapshot['name']
self._send_request('DELETE_SNAPSHOT_REQ',
req_paras,
'Delete snapshot error.')
self._wait_for_snapshot_delete(snapshot['name'])
def _create_default_volume_stats(self):
default_volume_stats = {'tolerance_disk_failure': ['1', '2', '3'],
'tolerance_cache_failure': ['0', '1'],
'free_capacity_gb': 0,
'total_capacity_gb': 0,
'reserved_percentage': 0,
'vendor_name': 'Huawei',
'driver_version': self.VERSION,
'storage_protocol': 'StorageHypervisor',
'pools_id': []}
backend_name = self.configuration.safe_get('volume_backend_name')
default_volume_stats['volume_backend_name'] = (
backend_name or self.__class__.__name__)
return default_volume_stats
def _get_default_volume_stats(self):
default_volume_stats = self._create_default_volume_stats()
self._update_default_volume_stats_from_config(default_volume_stats,
self._conf_file)
return default_volume_stats
def _wait_for_volume_delete(self, volume_name):
"""Wait for volume delete to complete."""
timer = loopingcall.FixedIntervalLoopingCall(
self._check_volume_delete_finished, volume_name)
LOG.debug('Calling _wait_for_volume_delete: volume_name %s.'
% volume_name)
ret = timer.start(
interval=CHECK_VOLUME_DELETE_FINISHED_INTERVAL).wait()
timer.stop()
if not ret:
msg = (_LE('Delete volume failed,volume_name: %s.')
% volume_name)
LOG.error(msg)
raise exception.VolumeBackendAPIException(message=msg)
LOG.debug('Finish _wait_for_volume_delete: volume_name %s.'
% volume_name)
def _wait_for_snapshot_delete(self, snapshot_name):
"""Wait for snapshot delete to complete."""
timer = loopingcall.FixedIntervalLoopingCall(
self._check_snapshot_delete_finished, snapshot_name)
LOG.debug('Calling _wait_for_snapshot_delete: snapshot_name %s.'
% snapshot_name)
ret = timer.start(
interval=CHECK_SNAPSHOT_DELETE_FINISHED_INTERVAL).wait()
timer.stop()
if not ret:
msg = (_LE('Delete snapshot failed,snapshot_name: %s.')
% snapshot_name)
LOG.error(msg)
raise exception.VolumeBackendAPIException(message=msg)
LOG.debug('Finish _wait_for_snapshot_delete: snapshot_name %s.'
% snapshot_name)
def _check_volume_delete_finished(self, volume_name):
try:
is_volume_exist = self._is_volume_exist(volume_name)
except Exception as ex:
msg = (_LE('Check volume_name delete finished failed: '
'%s.') % ex)
LOG.error(msg)
raise loopingcall.LoopingCallDone(retvalue=False)
if not is_volume_exist:
raise loopingcall.LoopingCallDone(retvalue=True)
def _check_snapshot_delete_finished(self, snapshot_name):
try:
is_snapshot_exist = self._is_snapshot_exist(snapshot_name)
except Exception as ex:
msg = (_LE('Check snapshot delete finished failed: '
'%s.') % ex)
LOG.error(msg)
raise loopingcall.LoopingCallDone(retvalue=False)
if not is_snapshot_exist:
raise loopingcall.LoopingCallDone(retvalue=True)
def _query_volume(self, volume_name):
request_info = {'vol_name': volume_name}
request_type = 'QUERY_VOLUME_REQ'
rsp_str = self._vbs_client.send_message(
storhyper_utils.serialize(request_type,
request_info)
)
LOG.debug('%s received:%s.' % (request_type, repr(rsp_str)))
result = storhyper_utils.deserialize(six.text_type(rsp_str),
delimiter='\n')
storhyper_utils.log_dict(result)
return result
def _is_volume_exist(self, volume_name):
query_volume_result = self._query_volume(volume_name)
if ((not query_volume_result) or
('retcode' not in query_volume_result) or
(query_volume_result['retcode']
not in ('0', self.del_complete_code))):
msg = _('%(err)s\n') % {'err': 'Query volume failed!'
' Invalid result code'}
raise exception.VolumeBackendAPIException(data=msg)
if query_volume_result['retcode'] == self.del_complete_code:
return False
if query_volume_result['retcode'] == '0':
if 'volume0' not in query_volume_result:
msg = _('%(err)s\n') % {'err': 'Query volume failed! '
'Volume0 not exist!'}
raise exception.VolumeBackendAPIException(data=msg)
query_volume_result['volume0'] = \
storhyper_utils.generate_dict_from_result(
query_volume_result['volume0'])
if (('status' not in query_volume_result['volume0']) or
(query_volume_result['volume0']['status'] not in
('1', '2', '10'))):
msg = _('%(err)s\n') % {'err': 'Query volume failed!'
' Invalid volume status'}
raise exception.VolumeBackendAPIException(data=msg)
return True
def _query_snapshot(self, snapshot_name):
request_info = {'snap_name': snapshot_name}
request_type = 'QUERY_SNAPSHOT_REQ'
rsp_str = self._vbs_client.send_message(
storhyper_utils.serialize(request_type,
request_info)
)
LOG.debug('%s received:%s.' % (request_type, repr(rsp_str)))
result = storhyper_utils.deserialize(six.text_type(rsp_str),
delimiter='\n')
storhyper_utils.log_dict(result)
return result
def _is_snapshot_exist(self, snapshot_name):
query_snapshot_result = self._query_snapshot(snapshot_name)
if ((not query_snapshot_result) or
('retcode' not in query_snapshot_result) or
(query_snapshot_result['retcode']
not in ('0', self.del_complete_code))):
msg = _('%(err)s\n') % {'err': 'Query snapshot failed!'}
raise exception.VolumeBackendAPIException(data=msg)
if query_snapshot_result['retcode'] == self.del_complete_code:
return False
if query_snapshot_result['retcode'] == '0':
if 'snapshot0' not in query_snapshot_result:
msg = _('%(err)s\n') % {'err': 'Query snapshot failed!'}
raise exception.VolumeBackendAPIException(data=msg)
query_snapshot_result['snapshot0'] =\
storhyper_utils.generate_dict_from_result(
query_snapshot_result['snapshot0'])
if (('status' not in query_snapshot_result['snapshot0']) or
(query_snapshot_result['snapshot0']['status'] not in
('1', '2'))):
msg = _('%(err)s\n') % {'err': 'Query snapshot failed!'}
raise exception.VolumeBackendAPIException(data=msg)
return True
def _get_volume_pool_id(self, volume_host):
if volume_host:
if len(volume_host.split('#', 1)) == 2:
return volume_host.split('#')[1]
if len(self._volume_stats['pools_id']) == 1:
return self._volume_stats['pools_id'][0]
else:
msg = (_LE("Get pool id failed, invalid pool id."))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _send_request(self, request_type, request_info, error_message):
rsp_str = self._vbs_client.send_message(
storhyper_utils.serialize(request_type, request_info))
LOG.debug('%s received:%s.' % (request_type, repr(rsp_str)))
result = storhyper_utils.deserialize(six.text_type(rsp_str),
delimiter='\n')
storhyper_utils.log_dict(result)
if (len(result) < 0 or 'retcode' not in result
or result['retcode'] != '0'):
msg = _('%(err)s\n') % {'err': error_message}
raise exception.VolumeBackendAPIException(data=msg)
return result
def _update_default_volume_stats_from_config(self,
default_volume_stats,
config_file):
root = storhyper_utils.parse_xml_file(config_file)
for child in root.find('policy').findall('*'):
if child.tag == 'QoS_support':
if child.text.strip() == '0':
default_volume_stats[child.tag] = False
else:
default_volume_stats[child.tag] = True
else:
default_volume_stats[child.tag] = child.text.strip()
for child in root.find('capability').findall('*'):
default_volume_stats[child.tag] = child.text.strip()
pools = root.find('pools').findall('*')
for pool in pools:
for child in pool.findall('*'):
childtext = child.text.strip()
if child.tag == 'pool_id' and len(childtext) > 0:
default_volume_stats['pools_id'].append(childtext)
def _update_all_pool_capacity_from_policy(self,
all_pool_capacity,
all_pool_policy):
for pool_name in all_pool_capacity.keys():
if pool_name in all_pool_policy:
for pool_key, pool_value in all_pool_policy[pool_name].items():
all_pool_capacity[pool_name][pool_key] = pool_value
def _extract_pool_policy_mapping_from_config(self, conf_file):
pools_policy_mapping = {}
root = storhyper_utils.parse_xml_file(conf_file)
pools = root.find('pools').findall('*')
for pool in pools:
policy = {}
pool_id = ''
for child in pool.findall('*'):
if child.tag == 'pool_id':
pool_id = child.text.strip()
else:
policy[child.tag] = child.text.strip()
pools_policy_mapping[pool_id] = policy
return pools_policy_mapping
def _extract_pool_capacity_mapping_from_result(self, result):
pool_capacity_mapping = {}
for key, value in result.items():
if 'pool' in key and value:
pool_capacity = {}
pool_name = ''
pool_str = value.replace('[', '').replace(']', '')
paras = pool_str.split(',')
for para in paras:
key = para.split('=')[0]
value = para.split('=')[1]
if key == 'stor_id':
pool_capacity['pool_name'] = six.text_type(value)
pool_name = six.text_type(value)
elif key == 'total_capacity':
pool_capacity['total_capacity_gb'] = int(value)
elif key == 'usable_capacity':
pool_capacity['free_capacity_gb'] = int(value)
elif key == 'raid_level':
pool_capacity['raid_level'] = int(value)
elif key == 'iops':
pool_capacity['iops'] = int(value)
pool_capacity['allocated_capacity_gb'] = \
pool_capacity['total_capacity_gb'] \
- pool_capacity['free_capacity_gb']
pool_capacity['reserved_percentage'] = 0
pool_capacity_mapping[pool_name] = pool_capacity
return pool_capacity_mapping
def _size_translate(self, size):
volume_size = '%s' % (size * units.Ki)
return volume_size
def _update_volume_info_from_volume_extra_specs(self, volume_info,
extra_specs):
if not extra_specs:
return
for x in extra_specs:
key = x['key']
value = x['value']
LOG.debug('Volume type: key=%(key)s value=%(value)s.'
% {'key': key, 'value': value})
if key in volume_info.keys():
words = value.strip().split()
volume_info[key] = words.pop()
def _update_volume_info_from_volume(self, volume_info, volume):
if not volume['volume_type_id']:
return
else:
spec = volume['volume_type']['extra_specs']
self._update_volume_info_from_volume_extra_specs(volume_info,
spec)
self._update_volume_info_from_qos_specs(volume_info,
volume['volume_type'])
def _update_volume_info_from_extra_specs(self,
volume_info,
extra_specs):
if not extra_specs:
return
for key, value in extra_specs.items():
LOG.debug('key=%(key)s value=%(value)s.'
% {'key': key, 'value': value})
if key in volume_info.keys():
words = value.strip().split()
volume_info[key] = words.pop()
def _update_volume_info_from_qos_specs(self,
volume_info,
qos_specs):
if not qos_specs:
return
if qos_specs.get('qos_specs'):
if qos_specs['qos_specs'].get('specs'):
qos_spec = qos_specs['qos_specs'].get('specs')
for key, value in qos_spec.items():
LOG.debug('key=%(key)s value=%(value)s.'
% {'key': key, 'value': value})
if key in QOS_KEY:
volume_info['IOClASSID'] = value.strip()
qos_level = key
if qos_level == 'Qos-high':
volume_info['IOPRIORITY'] = "3"
elif qos_level == 'Qos-normal':
volume_info['IOPRIORITY'] = "2"
elif qos_level == 'Qos-low':
volume_info['IOPRIORITY'] = "1"
else:
volume_info['IOPRIORITY'] = "2"
def _update_volume_info_from_volume_type(self,
volume_info,
volume_type_id):
if not volume_type_id:
return
else:
volume_type = volume_types.get_volume_type(
context.get_admin_context(), volume_type_id)
extra_specs = volume_type.get('extra_specs')
self._update_volume_info_from_extra_specs(volume_info, extra_specs)
qos_specs = volume_types.get_volume_type_qos_specs(volume_type_id)
self._update_volume_info_from_qos_specs(volume_info, qos_specs)
def _create_storage_info(self, info_type):
if info_type == 'volume_info':
volume_info = {'vol_name': '',
'vol_size': '',
'pool_id': '0',
'thin_flag': '0',
'reserved': '0',
'volume_space_reserved': '0',
'force_provision_size': '0',
'iops': '100',
'max_iops': '100',
'min_iops': '0',
'cache_size': '0',
'repicate_num': '1',
'repicate_tolerant_num': '1',
'encrypt_algorithm': '0',
'consistency': '0',
'stor_space_level': '1',
'compress_algorithm': '0',
'deduplication': '0',
'snapshot': '0',
'backup_cycle': '0',
'tolerance_disk_failure': '0',
'tolerance_cache_failure': '1'}
return volume_info
else:
LOG.error(_LE('Invalid info type.'))
return None

@ -0,0 +1,120 @@
# Copyright (c) 2014 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.
"""
Utils for Huawei SDSHypervisor systems.
"""
import socket
from xml.etree import ElementTree as ETree
import six
from cinder.i18n import _LW, _LE
from cinder.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def serialize(title, para):
para_list = ['[' + title + ']\n']
if len(para):
for key, value in para.items():
if isinstance(value, list):
for item in value:
para_list.append(key + "=" + six.text_type(item) + "\n")
else:
para_list.append(key + "=" + six.text_type(value) + "\n")
LOG.debug('key=%(key)s value=%(value)s.'
% {'key': key, 'value': value})
return ''.join(para_list)
def deserialize(rsp_str, delimiter):
LOG.debug('Calling deserialize: %s.' % rsp_str)
rsp = {}
if len(rsp_str) > 0:
lines = rsp_str.split(delimiter)
for line in lines:
LOG.debug('line = %s.' % line)
if line.find('=') != -1:
paras = six.text_type(line).split('=', 1)
key = paras[0].replace('=', '')
value = paras[1].replace('\n', '').replace('\x00', '')
rsp[key] = value.strip()
return rsp
def parse_xml_file(file_path):
"""Get root of xml file."""
try:
tree = ETree.parse(file_path)
root = tree.getroot()
return root
except IOError as err:
LOG.error(_LE('Parse_xml_file: %s.'), exc_info=True)
raise err
def check_ipv4(ip_string):
"""Check if ip(v4) valid."""
if ip_string.find('.') == -1:
return False
try:
socket.inet_aton(ip_string)
return True
except Exception:
return False
def get_valid_ip_list(ip_list):
valid_ip_list = []
for ip in ip_list:
ip = ip.strip()
LOG.debug('IP=%s.' % ip)
if not check_ipv4(ip):
LOG.warn(_LW('Invalid ip, ip address is: %s.') % ip)
else:
valid_ip_list.append(ip)
return valid_ip_list
def get_ip_and_port(config_file):
root = parse_xml_file(config_file)
vbs_url = root.findtext('controller/vbs_url').strip()
LOG.debug('VbsClient vbs_url=%s.' % vbs_url)
vbs_port = root.findtext('controller/vbs_port').strip()
LOG.debug('VbsClient vbs_port=%s.' % vbs_port)
valid_ip_list = get_valid_ip_list(vbs_url.split(','))
port = int(vbs_port)
return valid_ip_list, port
def log_dict(result):
if result:
for key, value in result.items():
LOG.debug('key=%(key)s value=%(value)s.'
% {'key': key, 'value': value})
def generate_dict_from_result(result):
LOG.debug('Result from response=%s.' % result)
result = result.replace('[', '').replace(']', '')
result = deserialize(result, delimiter=',')
log_dict(result)
return result

@ -0,0 +1,82 @@
# Copyright (c) 2014 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.
"""
Vbs Client for Huawei SDSHypervisor systems internal communication.
"""
import socket
from oslo.utils import units
from cinder.i18n import _, _LE
from cinder.openstack.common import log as logging
from cinder.volume.drivers.huaweistorhyper import utils as storhyper_utils
LOG = logging.getLogger(__name__)
BUFFER_SIZE = 1024
class VbsClient(object):
def __init__(self, config_file):
LOG.debug('Vbs client init.')
self.config_file = config_file
(self.ip_list, self.port) = \
storhyper_utils.get_ip_and_port(config_file)
def send_message(self, msg):
return self._send_message_to_first_valid_host(msg)
def _send_message_to_first_valid_host(self, msg):
LOG.debug('Send message to first valid host.')
if not self.ip_list:
msg = _LE('No valid ip in vbs ip list.')
LOG.error(msg)
raise AssertionError(msg)
exec_result = ''
for ip in self.ip_list:
exec_result = VbsClient.send_and_receive(
ip, self.port, msg
)
if exec_result:
return exec_result
return exec_result
@staticmethod
def send_and_receive(ip, port, request):
rsp = None
socket_instance = None
try:
socket_instance = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
socket_instance.connect((ip, port))
LOG.debug('Start sending requests.')
socket_instance.send(request.encode('utf-8', 'strict'))
LOG.debug('Waiting for response.')
rsp = socket_instance.recv(units.Ki).decode(
'utf-8', 'strict')
LOG.debug('Response received: %s.' % repr(rsp))
return rsp
except OSError as ose:
LOG.exception(_('Send message failed,OSError. %s.'), ose)
except Exception as e:
LOG.exception(_('Send message failed. %s.'), e)
finally:
if socket_instance:
socket_instance.close()
return rsp