Merge "Add volume retype support for Huawei driver"

This commit is contained in:
Jenkins 2015-08-25 08:31:41 +00:00 committed by Gerrit Code Review
commit 8819da0ce0
5 changed files with 369 additions and 12 deletions

View File

@ -97,6 +97,28 @@ test_host = {'host': 'ubuntu001@backend001#OpenStack_Pool',
}
}
test_new_type = {
'name': u'new_type',
'qos_specs_id': None,
'deleted': False,
'created_at': None,
'updated_at': None,
'extra_specs': {
'smarttier': '<is> true',
'smartcache': '<is> true',
'smartpartition': '<is> true',
'thin_provisioning': '<is> true',
'thick_provisioning': '<is> False',
'policy': '2',
'smartcache:cachename': 'cache-test',
'smartpartition:partitionname': 'partition-test',
},
'is_public': True,
'deleted_at': None,
'id': u'530a56e1-a1a4-49f3-ab6c-779a6e5d999f',
'description': None,
}
FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'wwpns': ['10000090fa0d6754'],
'wwnns': ['10000090fa0d6755'],
@ -696,6 +718,45 @@ 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_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 = """
{
@ -969,6 +1030,9 @@ MAP_COMMAND_TO_FAKE_RESPONSE['portgroup?range=[0-8191]&TYPE=257/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['system/'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['lun/associate/cachepartition/POST'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
def Fake_sleep(time):
pass
@ -984,6 +1048,8 @@ class Fake18000Client(rest_client.RestClient):
self.test_fail = False
self.checkFlag = False
self.remove_chap_flag = False
self.cache_not_exist = False
self.partition_not_exist = False
def _change_file_mode(self, filepath):
pass
@ -1015,12 +1081,13 @@ class Fake18000Client(rest_client.RestClient):
return True
def get_partition_id_by_name(self, name):
if self.partition_not_exist:
return None
return "11"
def add_lun_to_partition(self, lunid, partition_id):
pass
def get_cache_id_by_name(self, name):
if self.cache_not_exist:
return None
return "11"
def add_lun_to_cache(self, lunid, cache_id):
@ -1719,6 +1786,35 @@ class Huawei18000FCDriverTestCase(test.TestCase):
self.assertEqual({'_name_id': '21ec7341-9256-497b-97d9-ef48edcf0637'},
model_update)
def test_retype_volume_success(self):
self.driver.restclient.login()
retype = self.driver.retype(None, test_volume,
test_new_type, None, test_host)
self.assertTrue(retype)
def test_retype_volume_cache_fail(self):
self.driver.restclient.cache_not_exist = True
self.driver.restclient.login()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.retype, None,
test_volume, test_new_type, None, test_host)
def test_retype_volume_partition_fail(self):
self.driver.restclient.partition_not_exist = True
self.driver.restclient.login()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.retype, None,
test_volume, test_new_type, None, test_host)
@mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
def test_retype_volume_fail(self, mock_add_lun_to_partition):
self.driver.restclient.login()
mock_add_lun_to_partition.side_effect = (
exception.VolumeBackendAPIException(data='Error occurred.'))
retype = self.driver.retype(None, test_volume,
test_new_type, None, test_host)
self.assertFalse(retype)
def create_fake_conf_file(self):
"""Create a fake Config file

View File

@ -39,6 +39,8 @@ ERROR_CONNECT_TO_SERVER = -403
ERROR_UNAUTHORIZED_TO_SERVER = -401
SOCKET_TIME_OUT = 720
THICK_LUNTYPE = 0
THIN_LUNTYPE = 1
MAX_HOSTNAME_LENGTH = 31
OS_TYPE = {'Linux': '0',
@ -52,3 +54,5 @@ OS_TYPE = {'Linux': '0',
HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth',
'maxBandWidth', 'latency', 'IOType']
QOS_KEYS = ['MAXIOPS', 'MINIOPS', 'MINBANDWidth',
'MAXBANDWidth', 'LATENCY', 'IOTYPE']

View File

@ -45,7 +45,7 @@ CONF = cfg.CONF
CONF.register_opts(huawei_opt)
class HuaweiBaseDriver(driver.MigrateVD, driver.BaseVD):
class HuaweiBaseDriver(driver.VolumeDriver):
def __init__(self, *args, **kwargs):
super(HuaweiBaseDriver, self).__init__(*args, **kwargs)
@ -747,6 +747,220 @@ class HuaweiBaseDriver(driver.MigrateVD, driver.BaseVD):
self.restclient.remove_host(host_id)
self.restclient.delete_mapping_view(view_id)
def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type."""
LOG.debug("Enter retype: id=%(id)s, new_type=%(new_type)s, "
"diff=%(diff)s, host=%(host)s.", {'id': volume['id'],
'new_type': new_type,
'diff': diff,
'host': host})
# Check what changes are needed
migration, change_opts, lun_id = self.determine_changes_when_retype(
volume, new_type, host)
try:
if migration:
LOG.debug("Begin to migrate LUN(id: %(lun_id)s) with "
"change %(change_opts)s.",
{"lun_id": lun_id, "change_opts": change_opts})
if self._migrate_volume(volume, host, new_type):
return True
else:
LOG.warning(_LW("Storage-assisted migration failed during "
"retype."))
return False
else:
# Modify lun to change policy
self.modify_lun(lun_id, change_opts)
return True
except exception.VolumeBackendAPIException:
LOG.exception(_LE('Retype volume error.'))
return False
def modify_lun(self, lun_id, change_opts):
if change_opts.get('partitionid', None):
old, new = change_opts['partitionid']
old_id = old[0]
old_name = old[1]
new_id = new[0]
new_name = new[1]
if old_id:
self.restclient.remove_lun_from_partition(lun_id, old_id)
if new_id:
self.restclient.add_lun_to_partition(lun_id, new_id)
LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartpartition from "
"(name: %(old_name)s, id: %(old_id)s) to "
"(name: %(new_name)s, id: %(new_id)s) success."),
{"lun_id": lun_id,
"old_id": old_id, "old_name": old_name,
"new_id": new_id, "new_name": new_name})
if change_opts.get('cacheid', None):
old, new = change_opts['cacheid']
old_id = old[0]
old_name = old[1]
new_id = new[0]
new_name = new[1]
if old_id:
self.restclient.remove_lun_from_cache(lun_id, old_id)
if new_id:
self.restclient.add_lun_to_cache(lun_id, new_id)
LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartcache from "
"(name: %(old_name)s, id: %(old_id)s) to "
"(name: %(new_name)s, id: %(new_id)s) successfully."),
{'lun_id': lun_id,
'old_id': old_id, "old_name": old_name,
'new_id': new_id, "new_name": new_name})
if change_opts.get('policy', None):
old_policy, new_policy = change_opts['policy']
self.restclient.change_lun_smarttier(lun_id, new_policy)
LOG.info(_LI("Retype LUN(id: %(lun_id)s) smarttier policy from "
"%(old_policy)s to %(new_policy)s success."),
{'lun_id': lun_id,
'old_policy': old_policy,
'new_policy': new_policy})
if change_opts.get('qos', None):
old_qos, new_qos = change_opts['qos']
old_qos_id = old_qos[0]
old_qos_value = old_qos[1]
if old_qos_id:
self.remove_qos_lun(lun_id, old_qos_id)
if new_qos:
smart_qos = smartx.SmartQos(self.restclient)
smart_qos.create_qos(new_qos, lun_id)
LOG.info(_LI("Retype LUN(id: %(lun_id)s) smartqos from "
"%(old_qos_value)s to %(new_qos)s success."),
{'lun_id': lun_id,
'old_qos_value': old_qos_value,
'new_qos': new_qos})
def get_lun_specs(self, lun_id):
lun_opts = {
'policy': None,
'partitionid': None,
'cacheid': None,
'LUNType': None,
}
lun_info = self.restclient.get_lun_info(lun_id)
lun_opts['LUNType'] = int(lun_info['ALLOCTYPE'])
if lun_info['DATATRANSFERPOLICY']:
lun_opts['policy'] = lun_info['DATATRANSFERPOLICY']
if lun_info['SMARTCACHEPARTITIONID']:
lun_opts['cacheid'] = lun_info['SMARTCACHEPARTITIONID']
if lun_info['CACHEPARTITIONID']:
lun_opts['partitionid'] = lun_info['CACHEPARTITIONID']
return lun_opts
def determine_changes_when_retype(self, volume, new_type, host):
migration = False
change_opts = {
'policy': None,
'partitionid': None,
'cacheid': None,
'qos': None,
'host': None,
'LUNType': None,
}
lun_id = volume.get('provider_location', None)
old_opts = self.get_lun_specs(lun_id)
new_specs = new_type['extra_specs']
new_opts = huawei_utils._get_extra_spec_value(new_specs)
new_opts = smartx.SmartX().get_smartx_specs_opts(new_opts)
if 'LUNType' not in new_opts:
new_opts['LUNType'] = huawei_utils.find_luntype_in_xml(
self.xml_file_path)
if volume['host'] != host['host']:
migration = True
change_opts['host'] = (volume['host'], host['host'])
if old_opts['LUNType'] != new_opts['LUNType']:
migration = True
change_opts['LUNType'] = (old_opts['LUNType'], new_opts['LUNType'])
new_cache_id = None
new_cache_name = new_opts['cachename']
if new_cache_name:
new_cache_id = self.restclient.get_cache_id_by_name(new_cache_name)
if new_cache_id is None:
msg = (_(
"Can't find cache name on the array, cache name is: "
"%(name)s.") % {'name': new_cache_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
new_partition_id = None
new_partition_name = new_opts['partitionname']
if new_partition_name:
new_partition_id = self.restclient.get_partition_id_by_name(
new_partition_name)
if new_partition_id is None:
msg = (_(
"Can't find partition name on the array, partition name "
"is: %(name)s.") % {'name': new_partition_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# smarttier
if old_opts['policy'] != new_opts['policy']:
change_opts['policy'] = (old_opts['policy'], new_opts['policy'])
# smartcache
old_cache_id = old_opts['cacheid']
if old_cache_id != new_cache_id:
old_cache_name = None
if old_cache_id:
cache_info = self.restclient.get_cache_info_by_id(old_cache_id)
old_cache_name = cache_info['NAME']
change_opts['cacheid'] = ([old_cache_id, old_cache_name],
[new_cache_id, new_cache_name])
# smartpartition
old_partition_id = old_opts['partitionid']
if old_partition_id != new_partition_id:
old_partition_name = None
if old_partition_id:
partition_info = self.restclient.get_partition_info_by_id(
old_partition_id)
old_partition_name = partition_info['NAME']
change_opts['partitionid'] = ([old_partition_id,
old_partition_name],
[new_partition_id,
new_partition_name])
# smartqos
new_qos = huawei_utils.get_qos_by_volume_type(new_type)
old_qos_id = self.restclient.get_qosid_by_lunid(lun_id)
old_qos = self._get_qos_specs_from_array(old_qos_id)
if old_qos != new_qos:
change_opts['qos'] = ([old_qos_id, old_qos], new_qos)
LOG.debug("Determine changes when retype. Migration: "
"%(migration)s, change_opts: %(change_opts)s.",
{'migration': migration, 'change_opts': change_opts})
return migration, change_opts, lun_id
def _get_qos_specs_from_array(self, qos_id):
qos = {}
qos_info = {}
if qos_id:
qos_info = self.restclient.get_qos_info(qos_id)
for key, value in qos_info.items():
if key.upper() in constants.QOS_KEYS:
if key.upper() == 'LATENCY' and value == '0':
continue
else:
qos[key.upper()] = value
return qos
def terminate_connection_fc(self, volume, connector):
"""Delete map between a volume and a host."""
wwns = connector['wwpns']
@ -867,6 +1081,7 @@ class Huawei18000ISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
ISCSI multipath support
SmartX support
Volume migration support
Volume retype support
"""
VERSION = "1.1.1"
@ -905,6 +1120,7 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
Multiple pools support
SmartX support
Volume migration support
Volume retype support
"""
VERSION = "1.1.1"

View File

@ -113,12 +113,13 @@ def _get_opts_from_specs(opts_capability, opts_value, specs):
words = value.split()
if not (words and len(words) == 2 and words[0] == '<is>'):
LOG.error(_LE("Capabilities value must be specified as "
"'<is> True' or '<is> true'."))
LOG.error(_LE("Extra specs must be specified as "
"capabilities:%s='<is> True' or "
"'<is> true'."), key)
else:
del words[0]
value = words[0]
opts[key] = value.lower()
# Get the second value(true/True) of the Extra specs
# value(<is> true/<is> True)
opts[key] = words[1].lower()
if (scope in opts_capability) and (key in opts_value):
if (scope in opts_associate) and (opts_associate[scope] == key):
@ -329,9 +330,9 @@ def find_luntype_in_xml(xml_file_path):
if luntype:
if luntype.strip() in ['Thick', 'Thin']:
if luntype.strip() == 'Thick':
luntype = 0
luntype = constants.THICK_LUNTYPE
elif luntype.strip() == 'Thin':
luntype = 1
luntype = constants.THIN_LUNTYPE
else:
err_msg = (_(
"LUNType config is wrong. LUNType must be 'Thin'"
@ -340,7 +341,7 @@ def find_luntype_in_xml(xml_file_path):
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
else:
luntype = 0
luntype = constants.THICK_LUNTYPE
return luntype

View File

@ -1287,6 +1287,17 @@ class RestClient(object):
result = self.call(url, data, "PUT")
self._assert_rest_result(result, _('Change lun priority error.'))
def change_lun_smarttier(self, lunid, smarttier_policy):
"""Change lun smarttier policy."""
url = self.url + "/lun/" + lunid
data = json.dumps({"TYPE": "11",
"ID": lunid,
"DATATRANSFERPOLICY": smarttier_policy})
result = self.call(url, data, "PUT")
self._assert_rest_result(
result, _('Change lun smarttier policy error.'))
def get_qosid_by_lunid(self, lun_id):
"""Get QoS id by lun id."""
url = self.url + "/lun/" + lun_id
@ -1396,6 +1407,13 @@ class RestClient(object):
result = self.call(url, data, "POST")
self._assert_rest_result(result, _('Add lun to partition error.'))
def remove_lun_from_partition(self, lun_id, partition_id):
url = (self.url + '/lun/associate/cachepartition?ID=' + partition_id
+ '&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=' + lun_id)
result = self.call(url, None, "DELETE")
self._assert_rest_result(result, _('Remove lun from partition error.'))
def get_cache_id_by_name(self, name):
url = self.url + "/SMARTCACHEPARTITION"
result = self.call(url, None, "GET")
@ -1406,6 +1424,27 @@ class RestClient(object):
if name == item['NAME']:
return item['ID']
def get_cache_info_by_id(self, cacheid):
url = self.url + "/SMARTCACHEPARTITION/" + cacheid
data = json.dumps({"TYPE": "273",
"ID": cacheid})
result = self.call(url, data, "GET")
self._assert_rest_result(
result, _('Get smartcache by cache id error.'))
return result['data']
def remove_lun_from_cache(self, lun_id, cache_id):
url = self.url + "/SMARTCACHEPARTITION/REMOVE_ASSOCIATE"
data = json.dumps({"ID": cache_id,
"ASSOCIATEOBJTYPE": 11,
"ASSOCIATEOBJID": lun_id,
"TYPE": 273})
result = self.call(url, data, "PUT")
self._assert_rest_result(result, _('Remove lun from cache error.'))
def find_available_qos(self, qos):
""""Find available QoS on the array."""
qos_id = None
@ -1431,6 +1470,7 @@ class RestClient(object):
return (qos_id, lun_list)
def add_lun_to_qos(self, qos_id, lun_id, lun_list):
"""Add lun to QoS."""
url = self.url + "/ioclass/" + qos_id
lun_list = []
lun_string = lun_list[1:-1]