This patch adds extend_volume function for Huawei drivers.
Huawei T driver needs to create a slave LUN for the extended LUN.
And accordingly it needs to delete all these slave LUNs before deleting
the extended LUN.
HVS driver just sends a rest command to finish extending volume.
Change-Id: Iae17dba8c4d88ec09eb52b4a14bd17bc5a2c1ccc
'host': 'fakehost',
'ip': '10.10.0.1'}
+volume_size = 3
+
def Fake_sleep(time):
pass
if url == "ioclass/0":
data = """{"error":{"code":0}}"""
+ if url == "lun/expand":
+ data = """{"error":{"code":0}}"""
+ self.lun_id = '0'
+
else:
data = """{"error":{"code":31755596}}"""
self.driver.create_volume(test_volume)
self.assertEqual(self.driver.common.lun_id, "0")
+ def test_extend_volume_success(self):
+ self.driver.common.login()
+ self.driver.extend_volume(test_volume, volume_size)
+ self.assertEqual(self.driver.common.lun_id, "0")
+
def test_create_snapshot_success(self):
self.driver.common.login()
self.driver.create_snapshot(test_volume)
self.driver.create_volume(test_volume)
self.assertEqual(self.driver.common.lun_id, "0")
+ def test_extend_volume_success(self):
+ self.driver.common.login()
+ self.driver.extend_volume(test_volume, volume_size)
+ self.assertEqual(self.driver.common.lun_id, "0")
+
def test_create_snapshot_success(self):
self.driver.common.login()
self.driver.create_snapshot(test_volume)
class HuaweiTCLIResSimulator():
- def _name_translate(self, name):
- return 'OpenStack_' + str(hash(name))
+ def _paras_name(self, params):
+ index = params.index('-n')
+ return params[index + 1]
def cli_showsys(self, params):
pass
def cli_createlun(self, params):
lun_type = ('THIN' if '-pool' in params else 'THICK')
if LUN_INFO['ID'] is None:
- LUN_INFO['Name'] = self._name_translate(FAKE_VOLUME['name'])
+ LUN_INFO['Name'] = self._paras_name(params)
LUN_INFO['ID'] = VOLUME_SNAP_ID['vol']
LUN_INFO['Size'] = FAKE_VOLUME['size']
LUN_INFO['Lun Type'] = lun_type
LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
else:
- CLONED_LUN_INFO['Name'] = \
- self._name_translate(FAKE_CLONED_VOLUME['name'])
+ CLONED_LUN_INFO['Name'] = self._paras_name(params)
CLONED_LUN_INFO['ID'] = VOLUME_SNAP_ID['vol_copy']
CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size']
CLONED_LUN_INFO['Lun Type'] = lun_type
SNAPSHOT_INFO['Source LUN ID'] = LUN_INFO['ID']
SNAPSHOT_INFO['Source LUN Name'] = LUN_INFO['Name']
SNAPSHOT_INFO['ID'] = VOLUME_SNAP_ID['snap']
- SNAPSHOT_INFO['Name'] =\
- self._name_translate(FAKE_SNAPSHOT['name'])
+ SNAPSHOT_INFO['Name'] = self._paras_name(params)
SNAPSHOT_INFO['Status'] = 'Disable'
out = 'command operates successfully'
return out
out = 'command operates successfully'
return out
+ def cli_addluntoextlun(self, params):
+ LUN_INFO['Size'] = int(LUN_INFO['Size']) + int(CLONED_LUN_INFO['Size'])
+ out = 'command operates successfully'
+ return out
+
+ def cli_rmlunfromextlun(self, patams):
+ LUN_INFO['Size'] = int(LUN_INFO['Size']) - int(CLONED_LUN_INFO['Size'])
+ out = 'command operates successfully'
+ return out
+
class HuaweiDorado5100CLIResSimulator(HuaweiTCLIResSimulator):
def cli_showsys(self, params):
lun_type = ('THIN' if params[params.index('-type') + 1] == '2' else
'THICK')
if LUN_INFO['ID'] is None:
- LUN_INFO['Name'] = self._name_translate(FAKE_VOLUME['name'])
+ LUN_INFO['Name'] = self._paras_name(params)
LUN_INFO['ID'] = VOLUME_SNAP_ID['vol']
LUN_INFO['Size'] = FAKE_VOLUME['size']
LUN_INFO['Lun Type'] = lun_type
LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
else:
- CLONED_LUN_INFO['Name'] = \
- self._name_translate(FAKE_CLONED_VOLUME['name'])
+ CLONED_LUN_INFO['Name'] = self._paras_name(params)
CLONED_LUN_INFO['ID'] = VOLUME_SNAP_ID['vol_copy']
CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size']
CLONED_LUN_INFO['Lun Type'] = lun_type
self.driver.delete_volume(FAKE_VOLUME)
self.assertIsNone(LUN_INFO['ID'])
+ def test_extend_volume(self):
+ VOLUME_SIZE = 5
+ # Test no extended volume
+ self.assertRaises(exception.VolumeNotFound,
+ self.driver.extend_volume, FAKE_VOLUME, VOLUME_SIZE)
+
+ self.driver.create_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['Size'], '2')
+ # Test extend volume cli exception
+ set_error_flg('addluntoextlun')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.extend_volume, FAKE_VOLUME, VOLUME_SIZE)
+ self.assertEqual(CLONED_LUN_INFO['Name'], None)
+
+ self.driver.extend_volume(FAKE_VOLUME, VOLUME_SIZE)
+ self.assertEqual(LUN_INFO['Size'], VOLUME_SIZE)
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['Name'], None)
+
def test_create_delete_snapshot(self):
# Test no resource pool
RESPOOL_A_SIM['Valid Size'] = '0'
self.driver.create_volume_from_snapshot,
FAKE_CLONED_VOLUME, FAKE_SNAPSHOT)
+ def test_extend_volume(self):
+ NEWSIZE = 5
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.extend_volume,
+ FAKE_VOLUME, NEWSIZE)
+
class HuaweiDorado5100ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase):
def __init__(self, *args, **kwargs):
self.driver.delete_volume(FAKE_VOLUME)
self.assertIsNone(LUN_INFO['ID'])
+ def test_extend_volume(self):
+ NEWSIZE = 5
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.extend_volume,
+ FAKE_VOLUME, NEWSIZE)
+
class SSHMethodTestCase(test.TestCase):
def __init__(self, *args, **kwargs):
"""Create a clone of the specified volume."""
self.common.create_cloned_volume(volume, src_vref)
+ def extend_volume(self, volume, new_size):
+ """Extend a volume."""
+ self.common.extend_volume(volume, new_size)
+
def delete_volume(self, volume):
"""Delete a volume."""
self.common.delete_volume(volume)
"""Create a clone of the specified volume."""
self.common.create_cloned_volume(volume, src_vref)
+ def extend_volume(self, volume, new_size):
+ """Extend a volume."""
+ self.common.extend_volume(volume, new_size)
+
def delete_volume(self, volume):
"""Delete a volume."""
self.common.delete_volume(volume)
volume_id = self.common.create_cloned_volume(volume, src_vref)
return {'provider_location': volume_id}
+ def extend_volume(self, volume, new_size):
+ """Extend a volume."""
+ self.common.extend_volume(volume, new_size)
+
def delete_volume(self, volume):
"""Delete a volume."""
self.common.delete_volume(volume)
volume_id = self.common.create_cloned_volume(volume, src_vref)
return {'provider_location': volume_id}
+ def extend_volume(self, volume, new_size):
+ """Extend a volume."""
+ self.common.extend_volume(volume, new_size)
+
def delete_volume(self, volume):
"""Delete a volume."""
self.common.delete_volume(volume)
target_iqn = self._get_tgt_iqn(target_ip)
return (target_iqn, target_ip)
+
+ def extend_volume(self, volume, new_size):
+ name = self._encode_name(volume['id'])
+ lun_id = self._get_volume_by_name(name)
+ if lun_id:
+ url = self.url + "/lun/expand"
+ capacity = int(new_size) * units.GiB / 512
+ data = json.dumps({"TYPE": "11",
+ "ID": lun_id,
+ "CAPACITY": capacity})
+ result = self.call(url, data, "PUT")
+ self._assert_rest_result(result, 'Extend lun error.')
+ else:
+ LOG.warn(_('Can not find lun in array'))
if result.startswith(cmd) and result.endswith(user + ':/>'):
break
# Some commands need to send 'y'.
- elif re.search('(y/n)', result):
+ elif re.search('(y/n)|y or n', result):
break
# Reach maximum limit of SSH connection.
elif re.search('No response message', result):
self.ssh_pool = None
self.lock_ip = threading.Lock()
self.luncopy_list = [] # to store LUNCopy name
+ self.extended_lun_dict = {}
def do_setup(self, context):
"""Check config file."""
self._check_conf_file()
self.login_info = self._get_login_info()
- self.lun_distribution = self._get_lun_distribution_info()
+ exist_luns = self._get_all_luns_info()
+ self.lun_distribution = self._get_lun_distribution_info(exist_luns)
self.luncopy_list = self._get_all_luncopy_name()
self.hostgroup_id = self._get_hostgroup_id(HOST_GROUP_NAME)
+ self.extended_lun_dict = self._get_extended_lun(exist_luns)
def _check_conf_file(self):
"""Check config file, make sure essential items are set."""
def _change_file_mode(self, filepath):
utils.execute('chmod', '777', filepath, run_as_root=True)
- def _get_lun_distribution_info(self):
+ def _get_lun_distribution_info(self, luns):
"""Get LUN distribution information.
For we have two controllers for each array, we want to make all
"""
- luns = self._get_all_luns_info()
ctr_info = [0, 0]
for lun in luns:
if (lun[6].startswith(VOL_AND_SNAP_NAME_PREFIX) and
luncopy_ids.append(tmp_line[0])
return luncopy_ids
+ def _get_extended_lun(self, luns):
+ extended_dict = {}
+ for lun in luns:
+ if lun[6].startswith('ext'):
+ vol_name = lun[6].split('_')[1]
+ add_ids = extended_dict.get(vol_name, [])
+ add_ids = add_ids.append(lun[0])
+ extended_dict[vol_name] = add_ids
+ return extended_dict
+
def create_volume(self, volume):
"""Create a new volume."""
volume_name = self._name_translate(volume['name'])
while True:
ssh_client.chan.send(cmd + '\n')
out = ssh_read(user, ssh_client.chan, cmd, 20)
- if out.find('(y/n)') > -1:
+ if out.find('(y/n)') > -1 or out.find('y or n') > -1:
cmd = 'y'
else:
# Put SSH client back into SSH pool.
self._update_login_info()
volume_id = volume.get('provider_location', None)
- if (volume_id is not None) and self._check_volume_created(volume_id):
- self._delete_volume(volume_id)
- else:
+ if volume_id is None or not self._check_volume_created(volume_id):
err_msg = (_('delete_volume: Volume %(name)s does not exist.')
% {'name': volume['name']})
LOG.warn(err_msg)
+ return
+ else:
+ name = volume_name[len(VOL_AND_SNAP_NAME_PREFIX):]
+ added_vol_ids = self.extended_lun_dict.get(name, None)
+ if added_vol_ids:
+ self._del_lun_from_extended_lun(volume_id, added_vol_ids)
+ self.extended_lun_dict.pop(name)
+ self._delete_volume(volume_id)
def _check_volume_created(self, volume_id):
cli_cmd = 'showlun -lun %s' % volume_id
out = self._execute_cli(cli_cmd)
return (True if re.search('LUN Information', out) else False)
+ def _del_lun_from_extended_lun(self, extended_id, added_ids):
+ cli_cmd = 'rmlunfromextlun -ext %s' % extended_id
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_del_lun_from_extended_lun',
+ ('Failed to remove LUN from extended '
+ 'LUN: %s' % extended_id),
+ cli_cmd, out)
+ for id in added_ids:
+ cli_cmd = 'dellun -lun %s' % id
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_del_lun_from_extended_lun',
+ 'Failed to delete LUN: %s' % id,
+ cli_cmd, out)
+
def _delete_volume(self, volumeid):
"""Run CLI command to delete volume."""
cli_cmd = 'dellun -force -lun %s' % volumeid
return lun[0]
return None
+ def extend_volume(self, volume, new_size):
+ extended_vol_name = self._name_translate(volume['name'])
+ name = extended_vol_name[len(VOL_AND_SNAP_NAME_PREFIX):]
+ added_vol_ids = self.extended_lun_dict.get(name, [])
+ added_vol_name = ('ext_' + extended_vol_name.split('_')[1] + '_' +
+ str(len(added_vol_ids)))
+ added_vol_size = str(int(new_size) - int(volume['size'])) + 'G'
+
+ LOG.debug(_('extend_volume: extended volume name: %(extended_name)s '
+ 'new added volume name: %(added_name)s '
+ 'new added volume size: %(added_size)s')
+ % {'extended_name': extended_vol_name,
+ 'added_name': added_vol_name,
+ 'added_size': added_vol_size})
+
+ if not volume['provider_location']:
+ err_msg = (_('extend_volume: volume %s does not exist.')
+ % extended_vol_name)
+ LOG.error(err_msg)
+ raise exception.VolumeNotFound(volume_id=extended_vol_name)
+
+ type_id = volume['volume_type_id']
+ parameters = self._parse_volume_type(type_id)
+ added_vol_id = self._create_volume(added_vol_name, added_vol_size,
+ parameters)
+ try:
+ self._extend_volume(volume['provider_location'], added_vol_id)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._delete_volume(added_vol_id)
+
+ added_vol_ids.append(added_vol_id)
+ self.extended_lun_dict[name] = added_vol_ids
+
+ def _extend_volume(self, extended_vol_id, added_vol_id):
+ cli_cmd = ('addluntoextlun -extlun %(extended_vol)s '
+ '-lun %(added_vol)s' % {'extended_vol': extended_vol_id,
+ 'added_vol': added_vol_id})
+ out = self._execute_cli(cli_cmd)
+ self._assert_cli_operate_out('_extend_volume',
+ ('Failed to extend volume %s'
+ % extended_vol_id),
+ cli_cmd, out)
+
def create_snapshot(self, snapshot):
snapshot_name = self._name_translate(snapshot['name'])
volume_name = self._name_translate(snapshot['volume_name'])
LOG.debug(_('do_setup'))
self._check_conf_file()
- self.lun_distribution = self._get_lun_ctr_info()
+ exist_luns = self._get_all_luns_info()
+ self.lun_distribution = self._get_lun_distribution_info(exist_luns)
self.hostgroup_id = self._get_hostgroup_id(HOST_GROUP_NAME)
+ self.extended_lun_dict = self._get_extended_lun(exist_luns)
def _check_conf_file(self):
"""Check the config file, make sure the key elements are set."""
'Dorado5100 and Dorado 2100 G2 now.'))
raise exception.InvalidResults()
- def _get_lun_ctr_info(self):
- luns = self._get_all_luns_info()
+ def _get_lun_distribution_info(self, luns):
ctr_info = [0, 0]
(c, n) = ((2, 4) if self.device_type == 'Dorado2100 G2' else (3, 5))
for lun in luns:
ctr_info[1] += 1
return ctr_info
+ def _get_extended_lun(self, luns):
+ extended_dict = {}
+ n = 4 if self.device_type == 'Dorado2100 G2' else 5
+ for lun in luns:
+ if lun[n].startswith('ext'):
+ vol_name = lun[n].split('_')[1]
+ add_ids = extended_dict.get(vol_name, [])
+ add_ids.append(lun[0])
+ extended_dict[vol_name] = add_ids
+ return extended_dict
+
def _create_volume(self, name, size, params):
"""Create a new volume with the given name and size."""
cli_cmd = ('createlun -n %(name)s -lunsize %(size)s '
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
+ def extend_volume(self, volume, new_size):
+ if self.device_type == 'Dorado2100 G2':
+ err_msg = (_('extend_volume: %(device)s does not support '
+ 'extend volume.') % {'device': self.device_type})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+ else:
+ return TseriesCommon.extend_volume(self, volume, new_size)
+
def create_snapshot(self, snapshot):
if self.device_type == 'Dorado2100 G2':
err_msg = (_('create_snapshot: %(device)s does not support '