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
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
'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',
}
"""
+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":{
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
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):
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')
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.
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
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):
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')
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_'
DEFAULT_WAIT_INTERVAL = 5
ERROR_CONNECT_TO_SERVER = -403
ERROR_UNAUTHORIZED_TO_SERVER = -401
+SOCKET_TIME_OUT = 720
MAX_HOSTNAME_LENTH = 31
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__)
@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)
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
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
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
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'
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',
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')
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:
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
# under the License.
import json
+import socket
import time
from oslo_log import log as logging
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'
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)
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)
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']
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)
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']
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
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,
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
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
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
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}
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']
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']
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):
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']
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