From 20474c8583ed2bc58bc11304c6bbcc7538195a21 Mon Sep 17 00:00:00 2001 From: zhangchao010 Date: Fri, 18 Oct 2013 16:32:56 +0800 Subject: [PATCH] Add extend_volume for Huawei drivers 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 --- cinder/tests/test_huawei_hvs.py | 16 +++ cinder/tests/test_huawei_t_dorado.py | 59 ++++++++-- cinder/volume/drivers/huawei/huawei_hvs.py | 8 ++ cinder/volume/drivers/huawei/huawei_t.py | 8 ++ cinder/volume/drivers/huawei/rest_common.py | 14 +++ cinder/volume/drivers/huawei/ssh_common.py | 121 ++++++++++++++++++-- 6 files changed, 205 insertions(+), 21 deletions(-) diff --git a/cinder/tests/test_huawei_hvs.py b/cinder/tests/test_huawei_hvs.py index c05b91f95..55d2b4a90 100644 --- a/cinder/tests/test_huawei_hvs.py +++ b/cinder/tests/test_huawei_hvs.py @@ -64,6 +64,8 @@ FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3', 'host': 'fakehost', 'ip': '10.10.0.1'} +volume_size = 3 + def Fake_sleep(time): pass @@ -466,6 +468,10 @@ class FakeHVSCommon(rest_common.HVSCommon): 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}}""" @@ -528,6 +534,11 @@ class HVSRESTiSCSIDriverTestCase(test.TestCase): 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) @@ -713,6 +724,11 @@ class HVSRESTFCDriverTestCase(test.TestCase): 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) diff --git a/cinder/tests/test_huawei_t_dorado.py b/cinder/tests/test_huawei_t_dorado.py index 4657f0375..31caa97e3 100644 --- a/cinder/tests/test_huawei_t_dorado.py +++ b/cinder/tests/test_huawei_t_dorado.py @@ -332,8 +332,9 @@ def reset_error_flg(cmd): 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 @@ -341,7 +342,7 @@ class HuaweiTCLIResSimulator(): 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 @@ -350,8 +351,7 @@ class HuaweiTCLIResSimulator(): 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 @@ -525,8 +525,7 @@ class HuaweiTCLIResSimulator(): 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 @@ -823,6 +822,16 @@ Multipath Type 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): @@ -928,7 +937,7 @@ class HuaweiDorado2100G2CLIResSimulator(HuaweiTCLIResSimulator): 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 @@ -937,8 +946,7 @@ class HuaweiDorado2100G2CLIResSimulator(HuaweiTCLIResSimulator): 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 @@ -1208,6 +1216,25 @@ class HuaweiTISCSIDriverTestCase(test.TestCase): 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' @@ -1538,6 +1565,12 @@ class HuaweiDorado2100G2FCDriverTestCase(HuaweiTFCDriverTestCase): 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): @@ -1613,6 +1646,12 @@ class HuaweiDorado2100G2ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase): 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): diff --git a/cinder/volume/drivers/huawei/huawei_hvs.py b/cinder/volume/drivers/huawei/huawei_hvs.py index 513f733c9..0d70948dd 100644 --- a/cinder/volume/drivers/huawei/huawei_hvs.py +++ b/cinder/volume/drivers/huawei/huawei_hvs.py @@ -52,6 +52,10 @@ class HuaweiHVSISCSIDriver(driver.ISCSIDriver): """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) @@ -123,6 +127,10 @@ class HuaweiHVSFCDriver(driver.FibreChannelDriver): """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) diff --git a/cinder/volume/drivers/huawei/huawei_t.py b/cinder/volume/drivers/huawei/huawei_t.py index 478b9a114..68551752d 100644 --- a/cinder/volume/drivers/huawei/huawei_t.py +++ b/cinder/volume/drivers/huawei/huawei_t.py @@ -69,6 +69,10 @@ class HuaweiTISCSIDriver(driver.ISCSIDriver): 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) @@ -399,6 +403,10 @@ class HuaweiTFCDriver(driver.FibreChannelDriver): 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) diff --git a/cinder/volume/drivers/huawei/rest_common.py b/cinder/volume/drivers/huawei/rest_common.py index daeb054c0..34f684e8b 100644 --- a/cinder/volume/drivers/huawei/rest_common.py +++ b/cinder/volume/drivers/huawei/rest_common.py @@ -1286,3 +1286,17 @@ class HVSCommon(): 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')) diff --git a/cinder/volume/drivers/huawei/ssh_common.py b/cinder/volume/drivers/huawei/ssh_common.py index ec30edd36..660980980 100644 --- a/cinder/volume/drivers/huawei/ssh_common.py +++ b/cinder/volume/drivers/huawei/ssh_common.py @@ -65,7 +65,7 @@ def ssh_read(user, channel, cmd, timeout): 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): @@ -96,6 +96,7 @@ class TseriesCommon(): 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.""" @@ -103,9 +104,11 @@ class TseriesCommon(): 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.""" @@ -172,7 +175,7 @@ class TseriesCommon(): 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 @@ -182,7 +185,6 @@ class TseriesCommon(): """ - luns = self._get_all_luns_info() ctr_info = [0, 0] for lun in luns: if (lun[6].startswith(VOL_AND_SNAP_NAME_PREFIX) and @@ -207,6 +209,16 @@ class TseriesCommon(): 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']) @@ -474,7 +486,7 @@ class TseriesCommon(): 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. @@ -502,18 +514,40 @@ class TseriesCommon(): 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 @@ -696,6 +730,50 @@ class TseriesCommon(): 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']) @@ -1173,8 +1251,10 @@ class DoradoCommon(TseriesCommon): 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.""" @@ -1234,8 +1314,7 @@ class DoradoCommon(TseriesCommon): '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: @@ -1246,6 +1325,17 @@ class DoradoCommon(TseriesCommon): 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 ' @@ -1311,6 +1401,15 @@ class DoradoCommon(TseriesCommon): 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 ' -- 2.45.2