From 09836b31204723fc7753f3e45d6ec7e9c8fc925e Mon Sep 17 00:00:00 2001 From: zhongjun Date: Tue, 14 Jul 2015 18:06:51 +0800 Subject: [PATCH] Add volume retype support for Huawei driver Add retype volume function for Huawei driver, including SmartQos, SmartTier, SmartPartition and SmartCache, SmartThin/Thick. This commit adds the following qualified extra-specs: * capabilities:smarttier=' True' smarttier:policy='X'(X:0,1,2,3)"(default value is 1) * capabilities:smartpartition=' True' smartpartition:partitionname=test_partition_name * capabilities:smartcache=' True' smartcache:cachename=test_cache_name * capabilities:thin_provisioning=' True' * capabilities:thick_provisioning=' True' Implements: blueprint huawei-driver-support-retype Change-Id: I6bd1d39aca5a368594ca39c75c86eaf6ab20b00c --- cinder/tests/unit/test_huawei_drivers.py | 102 +++++++- cinder/volume/drivers/huawei/constants.py | 4 + cinder/volume/drivers/huawei/huawei_driver.py | 218 +++++++++++++++++- cinder/volume/drivers/huawei/huawei_utils.py | 17 +- cinder/volume/drivers/huawei/rest_client.py | 40 ++++ 5 files changed, 369 insertions(+), 12 deletions(-) diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index c327a2854..5c1496c99 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -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': ' true', + 'smartcache': ' true', + 'smartpartition': ' true', + 'thin_provisioning': ' true', + 'thick_provisioning': ' 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 diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py index 3def14a58..5bf2a74c4 100644 --- a/cinder/volume/drivers/huawei/constants.py +++ b/cinder/volume/drivers/huawei/constants.py @@ -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'] diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index 3e1f94580..0b63dcd31 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -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" diff --git a/cinder/volume/drivers/huawei/huawei_utils.py b/cinder/volume/drivers/huawei/huawei_utils.py index e2de141d1..f601c1d3a 100644 --- a/cinder/volume/drivers/huawei/huawei_utils.py +++ b/cinder/volume/drivers/huawei/huawei_utils.py @@ -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] == ''): - LOG.error(_LE("Capabilities value must be specified as " - "' True' or ' true'.")) + LOG.error(_LE("Extra specs must be specified as " + "capabilities:%s=' True' or " + "' true'."), key) else: - del words[0] - value = words[0] - opts[key] = value.lower() + # Get the second value(true/True) of the Extra specs + # value( true/ 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 diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index ff7bd2dcd..812a4d579 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -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] -- 2.45.2