Merge "Add multiple pools support for Huawei driver"

This commit is contained in:
Jenkins 2015-07-27 17:44:28 +00:00 committed by Gerrit Code Review
commit 8168cd0a2c
5 changed files with 155 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

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