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='<is> True' smarttier:policy='X'(X:0,1,2,3)"(default value is 1) * capabilities:smartpartition='<is> True' smartpartition:partitionname=test_partition_name * capabilities:smartcache='<is> True' smartcache:cachename=test_cache_name * capabilities:thin_provisioning='<is> True' * capabilities:thick_provisioning='<is> True' Implements: blueprint huawei-driver-support-retype Change-Id: I6bd1d39aca5a368594ca39c75c86eaf6ab20b00c
This commit is contained in:
parent
7ea6f03740
commit
09836b3120
@ -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
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user