From: chenzongliang Date: Thu, 4 Jun 2015 03:47:17 +0000 (+0800) Subject: Add multiple pools support for Huawei driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=12b7e0f654d8f09cf3eb842c5b7824f1a9660fc9;p=openstack-build%2Fcinder-build.git Add multiple pools support for Huawei driver Pool aware scheduler support was introduced in Juno. This patch proposes to add multiple pools support for Huawei driver. Co-Authored-By: Bob-OpenStack liuxinguo@huawei.com Change-Id: Ibe3b2a123ea5c700d2c2f66d262a8161c5d3e129 Implements: blueprint huawei-storage-multiple-pools-support --- diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index 8991f77b8..c037710cb 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -26,6 +26,7 @@ from oslo_log import log as logging from cinder import exception from cinder import test from cinder.volume import configuration as conf +from cinder.volume.drivers.huawei import constants from cinder.volume.drivers.huawei import huawei_driver from cinder.volume.drivers.huawei import huawei_utils from cinder.volume.drivers.huawei import rest_client @@ -46,6 +47,20 @@ test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', 'provider_location': '11', } +error_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0637', + 'size': 2, + 'volume_name': 'vol2', + 'id': '21ec7341-9256-497b-97d9-ef48edcf0637', + 'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0637', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol2', + 'display_description': 'test error_volume', + 'volume_type_id': None, + 'host': 'ubuntu@huawei#OpenStack_Pool_error', + 'provider_location': '12', + } + test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635', 'size': 1, 'volume_name': 'vol1', @@ -593,6 +608,26 @@ FAKE_PORT_GROUP_RESPONSE = """ } """ +FAKE_ISCSI_INITIATOR_RESPONSE = """ +{ + "error":{ + "code":0 + }, + "data":[{ + "CHAPNAME":"mm-user", + "HEALTHSTATUS":"1", + "ID":"iqn.1993-08.org.debian:01:9073aba6c6f", + "ISFREE":"true", + "MULTIPATHTYPE":"1", + "NAME":"", + "OPERATIONSYSTEM":"255", + "RUNNINGSTATUS":"28", + "TYPE":222, + "USECHAP":"true" + }] +} +""" + FAKE_ERROR_INFO_RESPONSE = """ { "error":{ @@ -975,6 +1010,9 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, test_volume) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume, error_volume) + def test_delete_volume_fail(self): self.driver.restclient.login() self.driver.restclient.test_fail = True @@ -995,7 +1033,7 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): self.assertEqual(1, iscsi_properties['data']['target_lun']) def test_get_default_timeout(self): - result = huawei_utils._get_default_timeout(self.xml_file_path) + result = huawei_utils.get_default_timeout(self.xml_file_path) self.assertEqual('43200', result) def test_get_wait_interval(self): @@ -1005,14 +1043,14 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): def test_lun_is_associated_to_lungroup(self): self.driver.restclient.login() - self.driver.restclient._associate_lun_to_lungroup('11', '11') + self.driver.restclient.associate_lun_to_lungroup('11', '11') result = self.driver.restclient._is_lun_associated_to_lungroup('11', '11') self.assertTrue(result) def test_lun_is_not_associated_to_lun_group(self): self.driver.restclient.login() - self.driver.restclient._associate_lun_to_lungroup('12', '12') + self.driver.restclient.associate_lun_to_lungroup('12', '12') self.driver.restclient.remove_lun_from_lungroup('12', '12') result = self.driver.restclient._is_lun_associated_to_lungroup('12', '12') @@ -1080,6 +1118,36 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase): initiator_name) self.assertEqual('1', type) + def test_find_pool_info(self): + self.driver.restclient.login() + pools = { + "error": {"code": 0}, + "data": [{ + "NAME": "test001", + "ID": "0", + "USERFREECAPACITY": "36", + "USERTOTALCAPACITY": "48", + "USAGETYPE": constants.BLOCK_STORAGE_POOL_TYPE}, + {"NAME": "test002", + "ID": "1", + "USERFREECAPACITY": "37", + "USERTOTALCAPACITY": "49", + "USAGETYPE": constants.FILE_SYSTEM_POOL_TYPE}]} + pool_name = 'test001' + test_info = {'CAPACITY': '36', 'ID': '0', 'TOTALCAPACITY': '48'} + pool_info = self.driver.restclient.find_pool_info(pool_name, pools) + self.assertEqual(test_info, pool_info) + + pool_name = 'test002' + test_info = {} + pool_info = self.driver.restclient.find_pool_info(pool_name, pools) + self.assertEqual(test_info, pool_info) + + pool_name = 'test000' + test_info = {} + pool_info = self.driver.restclient.find_pool_info(pool_name, pools) + self.assertEqual(test_info, pool_info) + def create_fake_conf_file(self): """Create a fake Config file. @@ -1252,6 +1320,9 @@ class Huawei18000FCDriverTestCase(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, test_volume) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume, error_volume) + def test_delete_volume_fail(self): self.driver.restclient.login() self.driver.restclient.test_fail = True @@ -1272,7 +1343,7 @@ class Huawei18000FCDriverTestCase(test.TestCase): test_volume, FakeConnector) def test_get_default_timeout(self): - result = huawei_utils._get_default_timeout(self.xml_file_path) + result = huawei_utils.get_default_timeout(self.xml_file_path) self.assertEqual('43200', result) def test_get_wait_interval(self): @@ -1282,14 +1353,14 @@ class Huawei18000FCDriverTestCase(test.TestCase): def test_lun_is_associated_to_lungroup(self): self.driver.restclient.login() - self.driver.restclient._associate_lun_to_lungroup('11', '11') + self.driver.restclient.associate_lun_to_lungroup('11', '11') result = self.driver.restclient._is_lun_associated_to_lungroup('11', '11') self.assertTrue(result) def test_lun_is_not_associated_to_lun_group(self): self.driver.restclient.login() - self.driver.restclient._associate_lun_to_lungroup('12', '12') + self.driver.restclient.associate_lun_to_lungroup('12', '12') self.driver.restclient.remove_lun_from_lungroup('12', '12') result = self.driver.restclient._is_lun_associated_to_lungroup('12', '12') diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py index 20f754ac4..4afd4b172 100644 --- a/cinder/volume/drivers/huawei/constants.py +++ b/cinder/volume/drivers/huawei/constants.py @@ -15,6 +15,10 @@ STATUS_HEALTH = '1' STATUS_RUNNING = '10' +BLOCK_STORAGE_POOL_TYPE = '1' +FILE_SYSTEM_POOL_TYPE = '2' +STATUS_VOLUME_READY = '27' +STATUS_LUNCOPY_READY = '40' HOSTGROUP_PREFIX = 'OpenStack_HostGroup_' LUNGROUP_PREFIX = 'OpenStack_LunGroup_' @@ -25,6 +29,7 @@ DEFAULT_WAIT_TIMEOUT = 3600 * 24 * 30 DEFAULT_WAIT_INTERVAL = 5 ERROR_CONNECT_TO_SERVER = -403 ERROR_UNAUTHORIZED_TO_SERVER = -401 +SOCKET_TIME_OUT = 720 MAX_HOSTNAME_LENTH = 31 diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index 8b4bbab49..403230765 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -27,6 +27,7 @@ from cinder.volume import driver from cinder.volume.drivers.huawei import constants from cinder.volume.drivers.huawei import huawei_utils from cinder.volume.drivers.huawei import rest_client +from cinder.volume import utils as volume_utils from cinder.zonemanager import utils as fczm_utils LOG = logging.getLogger(__name__) @@ -69,7 +70,15 @@ class HuaweiBaseDriver(driver.VolumeDriver): @utils.synchronized('huawei', external=True) def create_volume(self, volume): """Create a volume.""" - pool_info = self.restclient.find_pool_info() + pool_name = volume_utils.extract_host(volume['host'], + level='pool') + pools = self.restclient.find_all_pools() + pool_info = self.restclient.find_pool_info(pool_name, pools) + if not pool_info: + msg = (_('Error in getting pool information for the pool: %s.') + % pool_name) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) volume_name = huawei_utils.encode_name(volume['id']) volume_description = volume['name'] volume_size = huawei_utils.get_volume_size(volume) @@ -156,8 +165,8 @@ class HuaweiBaseDriver(driver.VolumeDriver): def _volume_ready(): result = self.restclient.get_lun_info(tgt_lun_id) - if result['HEALTHSTATUS'] == "1": - if result['RUNNINGSTATUS'] == "27": + if result['HEALTHSTATUS'] == constants.STATUS_HEALTH: + if result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY: return True return False @@ -545,12 +554,12 @@ class HuaweiBaseDriver(driver.VolumeDriver): def _luncopy_complete(): luncopy_info = self.restclient.get_luncopy_info(luncopy_id) - if luncopy_info['status'] == '40': + if luncopy_info['status'] == constants.STATUS_LUNCOPY_READY: # luncopy_info['status'] means for the running status of # the luncopy. If luncopy_info['status'] is equal to '40', # this luncopy is completely ready. return True - elif luncopy_info['state'] != '1': + elif luncopy_info['state'] != constants.STATUS_HEALTH: # luncopy_info['state'] means for the healthy status of the # luncopy. If luncopy_info['state'] is not equal to '1', # this means that an error occurred during the LUNcopy diff --git a/cinder/volume/drivers/huawei/huawei_utils.py b/cinder/volume/drivers/huawei/huawei_utils.py index ae13da953..8bb7267d7 100644 --- a/cinder/volume/drivers/huawei/huawei_utils.py +++ b/cinder/volume/drivers/huawei/huawei_utils.py @@ -89,7 +89,7 @@ def _get_opts_from_specs(opts_capability, opts_value, specs): opts.update(opts_capability) opts.update(opts_value) - for key, value in specs.iteritems(): + for key, value in specs.items(): # Get the scope, if is using scope format. scope = None @@ -206,7 +206,7 @@ def get_qos_by_volume_type(volume_type): return qos LOG.info(_LI('The QoS sepcs is: %s.'), kvs) - for key, value in kvs.iteritems(): + for key, value in kvs.items(): if key in constants.HUAWEI_VALID_KEYS: if (key.upper() != 'IOTYPE') and (int(value) <= 0): err_msg = (_('Qos config is wrong. %(key)s' @@ -243,7 +243,7 @@ def _get_volume_type(type_id): def get_lun_conf_params(xml_file_path): """Get parameters from config file for creating lun.""" lunsetinfo = { - 'LUNType': 'Thick', + 'LUNType': 0, 'StripUnitSize': '64', 'WriteType': '1', 'MirrorSwitch': '1', @@ -361,7 +361,7 @@ def get_wait_interval(xml_file_path, event_type): return int(wait_interval) -def _get_default_timeout(xml_file_path): +def get_default_timeout(xml_file_path): """Get timeout from huawei conf file.""" root = parse_xml_file(xml_file_path) timeout = root.findtext('LUN/Timeout') @@ -378,7 +378,7 @@ def _get_default_timeout(xml_file_path): def wait_for_condition(xml_file_path, func, interval, timeout=None): start_time = time.time() if timeout is None: - timeout = _get_default_timeout(xml_file_path) + timeout = get_default_timeout(xml_file_path) def _inner(): try: @@ -440,7 +440,7 @@ def get_iscsi_conf(xml_file_path): def check_qos_high_priority(qos): """Check QoS priority.""" - for key, value in qos.iteritems(): + for key, value in qos.items(): if (key.find('MIN') == 0) or (key.find('LATENCY') == 0): return True diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 5e1af6214..adff4b1ae 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -14,6 +14,7 @@ # under the License. import json +import socket import time from oslo_log import log as logging @@ -47,16 +48,18 @@ class RestClient(object): Send HTTPS call, get response in JSON. Convert response into Python Object and return it. """ - opener = urllib.build_opener(urllib.HTTPCookieProcessor(self.cookie)) - urllib.install_opener(opener) + + handler = urllib.request.HTTPCookieProcessor(self.cookie) + opener = urllib.request.build_opener(handler) + urllib.request.install_opener(opener) res_json = None try: - urllib.socket.setdefaulttimeout(720) - req = urllib.Request(url, data, self.headers) + socket.setdefaulttimeout(constants.SOCKET_TIME_OUT) + req = urllib.request.Request(url, data, self.headers) if method: req.get_method = lambda: method - res = urllib.urlopen(req).read().decode("utf-8") + res = urllib.request.urlopen(req).read().decode("utf-8") if "xx/sessions" not in url: LOG.info(_LI('\n\n\n\nRequest URL: %(url)s\n\n' @@ -98,15 +101,15 @@ class RestClient(object): if result['error']['code'] == constants.ERROR_CONNECT_TO_SERVER: continue - if (result['error']['code'] != 0) or ("data" not in result): + if (result['error']['code'] != 0) or ('data' not in result): msg = (_("Login error, reason is: %s.") % result) LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - deviceid = result['data']['deviceid'] - self.url = item_url + deviceid + device_id = result['data']['deviceid'] + self.url = item_url + device_id self.headers['iBaseToken'] = result['data']['iBaseToken'] - return deviceid + return device_id msg = _("Login error: Can't connect to server.") LOG.error(msg) @@ -121,7 +124,7 @@ class RestClient(object): raise exception.VolumeBackendAPIException(data=msg) def _assert_data_in_result(self, result, msg): - if "data" not in result: + if 'data' not in result: err_msg = (_('%s "data" was not in result.') % msg) LOG.error(err_msg) raise exception.VolumeBackendAPIException(data=err_msg) @@ -159,34 +162,33 @@ class RestClient(object): result = self.call(url, data, "DELETE") self._assert_rest_result(result, 'Delete lun error.') - def find_pool_info(self): - root = huawei_utils.parse_xml_file(self.xml_file_path) - pool_name = root.findtext('Storage/StoragePool') - if not pool_name: - err_msg = (_("Invalid resource pool: %s.") % pool_name) - LOG.error(err_msg) - raise exception.InvalidInput(err_msg) - + def find_all_pools(self): url = self.url + "/storagepool" result = self.call(url, None) self._assert_rest_result(result, 'Query resource pool error.') + self._assert_data_in_result(result, 'Query resource pool error.') + return result + def find_pool_info(self, pool_name, result): poolinfo = {} - if "data" in result: + if not pool_name: + return poolinfo + + if 'data' in result: for item in result['data']: if pool_name.strip() == item['NAME']: + # USAGETYPE means pool type. + if ('USAGETYPE' in item and + item['USAGETYPE'] == constants.FILE_SYSTEM_POOL_TYPE): + break poolinfo['ID'] = item['ID'] poolinfo['CAPACITY'] = item['USERFREECAPACITY'] poolinfo['TOTALCAPACITY'] = item['USERTOTALCAPACITY'] break - if not poolinfo: - msg = (_('Get pool info error, pool name is: %s.') % pool_name) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) return poolinfo def _get_id_from_result(self, result, name, key): - if "data" in result: + if 'data' in result: for item in result['data']: if name == item[key]: return item['ID'] @@ -358,7 +360,7 @@ class RestClient(object): is_associated = self._is_lun_associated_to_lungroup(lungroup_id, lun_id) if not is_associated: - self._associate_lun_to_lungroup(lungroup_id, lun_id) + self.associate_lun_to_lungroup(lungroup_id, lun_id) if view_id is None: view_id = self._add_mapping_view(mapping_view_name) @@ -519,7 +521,7 @@ class RestClient(object): self._assert_rest_result(result, 'Find host lun id error.') host_lun_id = 1 - if "data" in result: + if 'data' in result: for item in result['data']: if lun_id == item['ID']: associate_data = item['ASSOCIATEMETADATA'] @@ -587,7 +589,7 @@ class RestClient(object): result = self.call(url, data) self._assert_rest_result(result, 'Add new host error.') - if "data" in result: + if 'data' in result: return result['data']['ID'] else: return @@ -629,7 +631,7 @@ class RestClient(object): result = self.call(url, data) self._assert_rest_result(result, 'Associate host to hostgroup error.') - def _associate_lun_to_lungroup(self, lungroup_id, lun_id): + def associate_lun_to_lungroup(self, lungroup_id, lun_id): """Associate lun to lungroup.""" url = self.url + "/lungroup/associate" data = json.dumps({"ID": lungroup_id, @@ -655,7 +657,7 @@ class RestClient(object): self._assert_rest_result(result, 'Check initiator added to array error.') - if "data" in result: + if 'data' in result: for item in result['data']: if item["ID"] == ininame: return True @@ -668,7 +670,7 @@ class RestClient(object): self._assert_rest_result(result, 'Check initiator associated to host error.') - if "data" in result: + if 'data' in result: for item in result['data']: if item['ID'] == ininame and item['ISFREE'] == "true": return False @@ -870,7 +872,7 @@ class RestClient(object): result = self.call(url, None, "GET") self._assert_rest_result(result, 'Find lun number error.') lunnum = -1 - if "data" in result: + if 'data' in result: lunnum = result['data']['COUNT'] return lunnum @@ -905,9 +907,9 @@ class RestClient(object): result = self.call(url, data, "PUT") self._assert_rest_result(result, 'Start LUNcopy error.') - def _get_capacity(self): + def _get_capacity(self, pool_name, result): """Get free capacity and total capacity of the pool.""" - poolinfo = self.find_pool_info() + poolinfo = self.find_pool_info(pool_name, result) pool_capacity = {'total_capacity': 0.0, 'free_capacity': 0.0} @@ -927,7 +929,7 @@ class RestClient(object): self._assert_rest_result(result, 'Get LUNcopy information error.') luncopyinfo = {} - if "data" in result: + if 'data' in result: for item in result['data']: if luncopyid == item['ID']: luncopyinfo['name'] = item['NAME'] @@ -1031,7 +1033,7 @@ class RestClient(object): self._assert_rest_result(result, msg) fc_wwpns = None - if "data" in result: + if 'data' in result: for item in result['data']: if wwn == item['INITIATOR_PORT_WWN']: fc_wwpns = item['TARGET_PORT_WWN'] @@ -1041,25 +1043,27 @@ class RestClient(object): def update_volume_stats(self): root = huawei_utils.parse_xml_file(self.xml_file_path) - pool_name = root.findtext('Storage/StoragePool') - if not pool_name: + pool_names = root.findtext('Storage/StoragePool') + if not pool_names: msg = (_( 'Invalid resource pool name. ' 'Please check the config file.')) LOG.error(msg) raise exception.InvalidInput(msg) data = {} - capacity = self._get_capacity() data["pools"] = [] - pool = {} - pool.update(dict( - pool_name=pool_name, - total_capacity_gb=capacity['total_capacity'], - free_capacity_gb=capacity['free_capacity'], - reserved_percentage=0, - QoS_support=True, - )) - data["pools"].append(pool) + result = self.find_all_pools() + for pool_name in pool_names.split(";"): + pool_name = pool_name.strip(' \t\n\r') + capacity = self._get_capacity(pool_name, result) + pool = {'pool_name': pool_name, + 'total_capacity_gb': capacity['total_capacity'], + 'free_capacity_gb': capacity['free_capacity'], + 'reserved_percentage': 0, + 'QoS_support': True, + } + + data["pools"].append(pool) return data def _find_qos_policy_info(self, policy_name): @@ -1070,7 +1074,7 @@ class RestClient(object): self._assert_rest_result(result, msg) qos_info = {} - if "data" in result: + if 'data' in result: for item in result['data']: if policy_name == item['NAME']: qos_info['ID'] = item['ID'] @@ -1099,7 +1103,7 @@ class RestClient(object): self._assert_rest_result(result, msg) self._assert_data_in_result(result, msg) - if "data" in result: + if 'data' in result: for item in result['data']: if (item['IPV4ADDR'] and item['HEALTHSTATUS'] == constants.STATUS_HEALTH