From: zhangchao010 Date: Sat, 31 Aug 2013 02:23:35 +0000 (+0800) Subject: Refactor huawei Dorado array iSCSI driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=1a2a692b79cefd1fc7e743e8e616292107afacdf;p=openstack-build%2Fcinder-build.git Refactor huawei Dorado array iSCSI driver This is the second patch, changes as follows: 1.Add ISCSIDriver for Dorado arrays. The ISCSIDriver inherit from T. 1.Add a common class DoradoCommon for both FC and iSCSI drivers. The common class inherit from T common for they have many common functions. 2.Add unit test for Dorado drivers. Change-Id: I7ff2cc1e0d058b7a3d9e55644769ec74075f962f --- diff --git a/cinder/tests/test_huawei_t_dorado.py b/cinder/tests/test_huawei_t_dorado.py index fdd35289d..6903b615b 100644 --- a/cinder/tests/test_huawei_t_dorado.py +++ b/cinder/tests/test_huawei_t_dorado.py @@ -161,6 +161,10 @@ class FakeChannel(): def __init__(self): if Curr_test[0] == 'T': self.simu = HuaweiTCLIResSimulator() + elif Curr_test[0] == 'Dorado5100': + self.simu = HuaweiDorado5100CLIResSimulator() + else: + self.simu = HuaweiDorado2100G2CLIResSimulator() def resize_pty(self, width=80, height=24): pass @@ -830,6 +834,196 @@ Multipath Type return out +class HuaweiDorado5100CLIResSimulator(HuaweiTCLIResSimulator): + def cli_showsys(self, params): + out = """/>showsys +============================================================= + System Information +------------------------------------------------------------- + System Name | SN_Dorado5100 + Device Type | Oceanstor Dorado5100 + Current System Mode | Double Controllers Normal + Mirroring Link Status | Link Up + Location | + Time | 2013-01-01 01:01:01 + Product Version | V100R001C00 +============================================================= +""" + return out + + def cli_showlun(self, params): + if '-lun' not in params: + if LUN_INFO['ID'] is None: + out = 'command operates successfully, but no information.' + elif CLONED_LUN_INFO['ID'] is None: + out = """/>showlun +=========================================================================== + LUN Information +--------------------------------------------------------------------------- + ID RAIDgroup ID Status Controller Visible Capacity(MB) LUN Name..\ + Strip Unit Size(KB) Lun Type +--------------------------------------------------------------------------- + %s %s Normal %s %s %s 64 THICK +=========================================================================== +""" % (LUN_INFO['ID'], LUN_INFO['RAID Group ID'], + LUN_INFO['Owner Controller'], str(int(LUN_INFO['Size']) * 1024), + LUN_INFO['Name']) + else: + out = """/>showlun +=========================================================================== + LUN Information +--------------------------------------------------------------------------- + ID RAIDgroup ID Status Controller Visible Capacity(MB) LUN Name \ + Strip Unit Size(KB) Lun Type +--------------------------------------------------------------------------- + %s %s Normal %s %s %s 64 THICK + %s %s Norma %s %s %s 64 THICK +=========================================================================== +""" % (LUN_INFO['ID'], LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'], + str(int(LUN_INFO['Size']) * 1024), LUN_INFO['Name'], + CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['RAID Group ID'], + CLONED_LUN_INFO['Owner Controller'], + str(int(CLONED_LUN_INFO['Size']) * 1024), + CLONED_LUN_INFO['Name']) + elif params[params.index('-lun') + 1] in VOLUME_SNAP_ID.values(): + out = """/>showlun +================================================ + LUN Information +------------------------------------------------ + ID | %s + Name | %s + LUN WWN | -- + Visible Capacity | %s + RAID GROUP ID | %s + Owning Controller | %s + Workong Controller | %s + Lun Type | %s + SnapShot ID | %s + LunCopy ID | %s +================================================ +""" % ((LUN_INFO['ID'], LUN_INFO['Name'], LUN_INFO['Visible Capacity'], + LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'], + LUN_INFO['Worker Controller'], LUN_INFO['Lun Type'], + LUN_INFO['SnapShot ID'], LUN_INFO['LunCopy ID']) + if params[params.index('-lun')] == VOLUME_SNAP_ID['vol'] else + (CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['Name'], + CLONED_LUN_INFO['Visible Capacity'], CLONED_LUN_INFO['RAID Group ID'], + CLONED_LUN_INFO['Owner Controller'], + CLONED_LUN_INFO['Worker Controller'], + CLONED_LUN_INFO['Lun Type'], CLONED_LUN_INFO['SnapShot ID'], + CLONED_LUN_INFO['LunCopy ID'])) + else: + out = 'ERROR: The object does not exist.' + return out + + +class HuaweiDorado2100G2CLIResSimulator(HuaweiTCLIResSimulator): + def cli_showsys(self, params): + out = """/>showsys +========================================================================== + System Information +-------------------------------------------------------------------------- + System Name | SN_Dorado2100_G2 + Device Type | Oceanstor Dorado2100 G2 + Current System Mode | Double Controllers Normal + Mirroring Link Status | Link Up + Location | + Time | 2013-01-01 01:01:01 + Product Version | V100R001C00 +=========================================================================== +""" + return out + + def cli_createlun(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['ID'] = VOLUME_SNAP_ID['vol'] + LUN_INFO['Size'] = FAKE_VOLUME['size'] + LUN_INFO['Lun Type'] = lun_type + LUN_INFO['Owner Controller'] = 'A' + LUN_INFO['Worker Controller'] = 'A' + 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['ID'] = VOLUME_SNAP_ID['vol_copy'] + CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size'] + CLONED_LUN_INFO['Lun Type'] = lun_type + CLONED_LUN_INFO['Owner Controller'] = 'A' + CLONED_LUN_INFO['Worker Controller'] = 'A' + CLONED_LUN_INFO['RAID Group ID'] = POOL_SETTING['ID'] + CLONED_LUN_INFO['provider_location'] = CLONED_LUN_INFO['ID'] + FAKE_CLONED_VOLUME['provider_location'] = CLONED_LUN_INFO['ID'] + out = 'command operates successfully' + return out + + def cli_showlun(self, params): + if '-lun' not in params: + if LUN_INFO['ID'] is None: + out = 'command operates successfully, but no information.' + elif CLONED_LUN_INFO['ID'] is None: + out = """/>showlun +=========================================================================== + LUN Information +--------------------------------------------------------------------------- + ID Status Controller Visible Capacity(MB) LUN Name Lun Type +--------------------------------------------------------------------------- + %s Normal %s %s %s THICK +=========================================================================== +""" % (LUN_INFO['ID'], LUN_INFO['Owner Controller'], + str(int(LUN_INFO['Size']) * 1024), LUN_INFO['Name']) + else: + out = """/>showlun +=========================================================================== + LUN Information +--------------------------------------------------------------------------- + ID Status Controller Visible Capacity(MB) LUN Name Lun Type +--------------------------------------------------------------------------- + %s Normal %s %s %s THICK + %s Normal %s %s %s THICK +=========================================================================== +""" % (LUN_INFO['ID'], LUN_INFO['Owner Controller'], + str(int(LUN_INFO['Size']) * 1024), LUN_INFO['Name'], + CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['Owner Controller'], + str(int(CLONED_LUN_INFO['Size']) * 1024), CLONED_LUN_INFO['Name']) + + elif params[params.index('-lun') + 1] in VOLUME_SNAP_ID.values(): + out = """/>showlun +================================================ + LUN Information +------------------------------------------------ + ID | %s + Name | %s + LUN WWN | -- + Visible Capacity | %s + RAID GROUP ID | %s + Owning Controller | %s + Workong Controller | %s + Lun Type | %s + SnapShot ID | %s + LunCopy ID | %s +================================================ +""" % ((LUN_INFO['ID'], LUN_INFO['Name'], LUN_INFO['Visible Capacity'], + LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'], + LUN_INFO['Worker Controller'], LUN_INFO['Lun Type'], + LUN_INFO['SnapShot ID'], LUN_INFO['LunCopy ID']) + if params[params.index('-lun')] == VOLUME_SNAP_ID['vol'] else + (CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['Name'], + CLONED_LUN_INFO['Visible Capacity'], CLONED_LUN_INFO['RAID Group ID'], + CLONED_LUN_INFO['Owner Controller'], + CLONED_LUN_INFO['Worker Controller'], + CLONED_LUN_INFO['Lun Type'], CLONED_LUN_INFO['SnapShot ID'], + CLONED_LUN_INFO['LunCopy ID'])) + + else: + out = 'ERROR: The object does not exist.' + + return out + + class HuaweiTISCSIDriverTestCase(test.TestCase): def __init__(self, *args, **kwargs): super(HuaweiTISCSIDriverTestCase, self).__init__(*args, **kwargs) @@ -1199,6 +1393,81 @@ class HuaweiTISCSIDriverTestCase(test.TestCase): self.assertEqual(stats['storage_protocol'], 'iSCSI') +class HuaweiDorado5100ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase): + def __init__(self, *args, **kwargs): + super(HuaweiDorado5100ISCSIDriverTestCase, self).__init__(*args, + **kwargs) + + def setUp(self): + super(HuaweiDorado5100ISCSIDriverTestCase, self).setUp() + + def _init_driver(self): + Curr_test[0] = 'Dorado5100' + modify_conf(self.fake_conf_file, 'Storage/Product', 'Dorado') + self.driver = HuaweiVolumeDriver(configuration=self.configuration) + self.driver.do_setup(None) + + def test_create_delete_cloned_volume(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cloned_volume, + FAKE_CLONED_VOLUME, FAKE_VOLUME) + + def test_create_delete_snapshot_volume(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + FAKE_CLONED_VOLUME, FAKE_SNAPSHOT) + + def test_volume_type(self): + pass + + +class HuaweiDorado2100G2ISCSIDriverTestCase(HuaweiTISCSIDriverTestCase): + def __init__(self, *args, **kwargs): + super(HuaweiDorado2100G2ISCSIDriverTestCase, self).__init__(*args, + **kwargs) + + def setUp(self): + super(HuaweiDorado2100G2ISCSIDriverTestCase, self).setUp() + + def _init_driver(self): + Curr_test[0] = 'Dorado2100G2' + modify_conf(self.fake_conf_file, 'Storage/Product', 'Dorado') + self.driver = HuaweiVolumeDriver(configuration=self.configuration) + self.driver.do_setup(None) + + def test_conf_invalid(self): + pass + + def test_create_delete_cloned_volume(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_cloned_volume, + FAKE_CLONED_VOLUME, FAKE_VOLUME) + + def test_create_delete_snapshot(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_snapshot, FAKE_SNAPSHOT) + + def test_create_delete_snapshot_volume(self): + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + FAKE_CLONED_VOLUME, FAKE_SNAPSHOT) + + def test_initialize_connection(self): + self.driver.create_volume(FAKE_VOLUME) + ret = self.driver.initialize_connection(FAKE_VOLUME, FAKE_CONNECTOR) + iscsi_propers = ret['data'] + self.assertEquals(iscsi_propers['target_iqn'], + INITIATOR_SETTING['TargetIQN-form']) + self.assertEquals(iscsi_propers['target_portal'], + INITIATOR_SETTING['Initiator TargetIP'] + ':3260') + self.assertEqual(MAP_INFO["DEV LUN ID"], LUN_INFO['ID']) + self.assertEqual(MAP_INFO["INI Port Info"], + FAKE_CONNECTOR['initiator']) + self.driver.terminate_connection(FAKE_VOLUME, FAKE_CONNECTOR) + self.driver.delete_volume(FAKE_VOLUME) + self.assertEqual(LUN_INFO['ID'], None) + + class SSHMethodTestCase(test.TestCase): def __init__(self, *args, **kwargs): super(SSHMethodTestCase, self).__init__(*args, **kwargs) diff --git a/cinder/volume/drivers/huawei/__init__.py b/cinder/volume/drivers/huawei/__init__.py index d1673f737..732db1d75 100644 --- a/cinder/volume/drivers/huawei/__init__.py +++ b/cinder/volume/drivers/huawei/__init__.py @@ -27,6 +27,7 @@ from cinder import exception from cinder.openstack.common import log as logging from cinder.volume.configuration import Configuration from cinder.volume import driver +from cinder.volume.drivers.huawei import huawei_dorado from cinder.volume.drivers.huawei import huawei_t from cinder.volume.drivers.huawei import ssh_common @@ -46,7 +47,7 @@ class HuaweiVolumeDriver(object): def __init__(self, *args, **kwargs): super(HuaweiVolumeDriver, self).__init__() - self._product = {'T': huawei_t} + self._product = {'T': huawei_t, 'Dorado': huawei_dorado} self._protocol = {'iSCSI': 'ISCSIDriver'} self.driver = self._instantiate_driver(*args, **kwargs) @@ -83,7 +84,7 @@ class HuaweiVolumeDriver(object): return (product, protocol) else: msg = (_('"Product" or "Protocol" is illegal. "Product" should ' - 'be set to T. "Protocol" should be set ' + 'be set to either T or Dorado. "Protocol" should be set ' 'to iSCSI. Product: %(product)s ' 'Protocol: %(protocol)s') % {'product': str(product), diff --git a/cinder/volume/drivers/huawei/huawei_dorado.py b/cinder/volume/drivers/huawei/huawei_dorado.py new file mode 100644 index 000000000..2b1393ef2 --- /dev/null +++ b/cinder/volume/drivers/huawei/huawei_dorado.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2013 Huawei Technologies Co., Ltd. +# Copyright (c) 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Volume Drivers for Huawei OceanStor Dorado series storage arrays. +""" + +from cinder.volume.drivers.huawei import huawei_t +from cinder.volume.drivers.huawei import ssh_common + + +class HuaweiDoradoISCSIDriver(huawei_t.HuaweiTISCSIDriver): + """ISCSI driver class for Huawei OceanStor Dorado storage arrays.""" + + def __init__(self, *args, **kwargs): + super(HuaweiDoradoISCSIDriver, self).__init__(*args, **kwargs) + + def do_setup(self, context): + """Instantiate common class.""" + self.common = ssh_common.DoradoCommon(configuration=self.configuration) + + self.common.do_setup(context) + self._assert_cli_out = self.common._assert_cli_out + self._assert_cli_operate_out = self.common._assert_cli_operate_out diff --git a/cinder/volume/drivers/huawei/ssh_common.py b/cinder/volume/drivers/huawei/ssh_common.py index ef507d70e..3de97b2ee 100644 --- a/cinder/volume/drivers/huawei/ssh_common.py +++ b/cinder/volume/drivers/huawei/ssh_common.py @@ -1127,3 +1127,235 @@ class TseriesCommon(): pool = out.split('\r\n')[6:-2] return [line.split() for line in pool] + + +class DoradoCommon(TseriesCommon): + """Common class for Huawei Dorado2100 G2 and Dorado5100 storage arrays. + + Dorados share a lot of common codes with T series storage systems, + so this class inherited from class TseriesCommon and just rewrite some + methods. + + """ + + def __init__(self, configuration=None): + TseriesCommon.__init__(self, configuration) + self.device_type = None + + def do_setup(self, context): + """Check config file.""" + LOG.debug(_('do_setup.')) + + self._check_conf_file() + self.lun_distribution = self._get_lun_ctr_info() + + def _check_conf_file(self): + """Check the config file, make sure the key elements are set.""" + root = parse_xml_file(self.xml_conf) + # Check login infomation + IP1 = root.findtext('Storage/ControllerIP0') + IP2 = root.findtext('Storage/ControllerIP1') + username = root.findtext('Storage/UserName') + pwd = root.findtext('Storage/UserPassword') + if (not IP1 and not IP2) or (not username) or (not pwd): + err_msg = (_('Config file invalid. Controler IP, UserName, ' + 'UserPassword must be specified.')) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + # Check storage pool + # No need for Dorado2100 G2 + self.login_info = self._get_login_info() + self.device_type = self._get_device_type() + if self.device_type == 'Dorado5100': + pool_node = root.findall('LUN/StoragePool') + if not pool_node: + err_msg = (_('_check_conf_file: Config file invalid. ' + 'StoragePool must be specified.')) + LOG.error(err_msg) + raise exception.InvalidInput(reason=err_msg) + + def _get_device_type(self): + """Run CLI command to get system type.""" + cli_cmd = 'showsys' + out = self._execute_cli(cli_cmd) + + self._assert_cli_out(re.search('System Information', out), + '_get_device_type', + 'Failed to get system information', + cli_cmd, out) + + for line in out.split('\r\n')[4:-2]: + if re.search('Device Type', line): + if re.search('Dorado2100 G2$', line): + return 'Dorado2100 G2' + elif re.search('Dorado5100$', line): + return 'Dorado5100' + else: + LOG.error(_('_get_device_type: The drivers only support' + 'Dorado5100 and Dorado 2100 G2 now.')) + raise exception.InvalidResults() + + def _get_lun_ctr_info(self): + luns = self._get_all_luns_info() + ctr_info = [0, 0] + (c, n) = ((2, 4) if self.device_type == 'Dorado2100 G2' else (3, 5)) + for lun in luns: + if lun[n].startswith('OpenStack'): + if lun[c] == 'A': + ctr_info[0] += 1 + else: + ctr_info[1] += 1 + return ctr_info + + 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 ' + '-wrtype %(wrtype)s ' + % {'name': name, + 'size': size, + 'wrtype': params['WriteType']}) + + # If write type is "write through", no need to set mirror switch. + if params['WriteType'] != '2': + cli_cmd = cli_cmd + ('-mirrorsw %(mirrorsw)s ' + % {'mirrorsw': params['MirrorSwitch']}) + + ctr = self._calculate_lun_ctr() + # Dorado5100 does not support thin LUN. + if self.device_type == 'Dorado5100': + cli_cmd = cli_cmd + ('-rg %(raidgroup)s -susize %(susize)s ' + '-c %(ctr)s' + % {'raidgroup': params['StoragePool'], + 'susize': params['StripUnitSize'], + 'ctr': ctr}) + else: + if params['LUNType'] == 'Thin': + # Not allowed to specify ctr for thin LUN. + ctr_str = '' + luntype_str = '-type 2' + else: + ctr_str = ' -c %s' % ctr + luntype_str = '-type 3' + + cli_cmd = cli_cmd + luntype_str + ctr_str + + out = self._execute_cli(cli_cmd) + + self._assert_cli_operate_out('_create_volume', + 'Failed to create volume %s' % name, + cli_cmd, out) + + self._update_lun_distribution(ctr) + + return self._get_lun_id(name) + + def _get_lun_id(self, name): + luns = self._get_all_luns_info() + if luns: + n_index = (4 if 'Dorado2100 G2' == self.device_type else 5) + for lun in luns: + if lun[n_index] == name: + return lun[0] + return None + + def create_volume_from_snapshot(self, volume, snapshot): + err_msg = (_('create_volume_from_snapshot: %(device)s does ' + 'not support create volume from snapshot.') + % {'device': self.device_type}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + def create_cloned_volume(self, volume, src_vref): + err_msg = (_('create_cloned_volume: %(device)s does ' + 'not support clone volume.') + % {'device': self.device_type}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + + def create_snapshot(self, snapshot): + if self.device_type == 'Dorado2100 G2': + err_msg = (_('create_snapshot: %(device)s does not support ' + 'snapshot.') % {'device': self.device_type}) + LOG.error(err_msg) + raise exception.VolumeBackendAPIException(data=err_msg) + else: + return TseriesCommon.create_snapshot(self, snapshot) + + def delete_snapshot(self, snapshot): + if self.device_type == 'Dorado2100 G2': + return + else: + TseriesCommon.delete_snapshot(self, snapshot) + + def _get_lun_params(self): + params_conf = self._parse_conf_lun_params() + # Select a pool with maximum capacity. + if self.device_type == 'Dorado5100': + pools_dev = self._get_dev_pool_info('Thick') + params_conf['StoragePool'] = \ + self._get_maximum_capacity_pool_id(params_conf['StoragePool'], + pools_dev, 'Thick') + return params_conf + + def _parse_conf_lun_params(self): + """Get parameters from config file for creating LUN.""" + # Default LUN parameters. + conf_params = {'LUNType': 'Thin', + 'StripUnitSize': '64', + 'WriteType': '1', + 'MirrorSwitch': '1'} + + root = parse_xml_file(self.xml_conf) + + luntype = root.findtext('LUN/LUNType') + if luntype: + if luntype.strip() in ['Thick', 'Thin']: + conf_params['LUNType'] = luntype.strip() + else: + err_msg = (_('LUNType must be "Thin" or "Thick". ' + 'LUNType:%(type)s') % {'type': luntype}) + raise exception.InvalidInput(reason=err_msg) + + # Here we do not judge whether the parameters are set correct. + # CLI will return error responses if the parameters are invalid. + stripunitsize = root.findtext('LUN/StripUnitSize') + if stripunitsize: + conf_params['StripUnitSize'] = stripunitsize.strip() + writetype = root.findtext('LUN/WriteType') + if writetype: + conf_params['WriteType'] = writetype.strip() + mirrorswitch = root.findtext('LUN/MirrorSwitch') + if mirrorswitch: + conf_params['MirrorSwitch'] = mirrorswitch.strip() + + # No need to set StoragePool for Dorado2100 G2. + if self.device_type == 'Dorado2100 G2': + return conf_params + + pools_conf = root.findall('LUN/StoragePool') + conf_params['StoragePool'] = [] + for pool in pools_conf: + conf_params['StoragePool'].append(pool.attrib['Name'].strip()) + + return conf_params + + def _get_free_capacity(self): + """Get total free capacity of pools.""" + self._update_login_info() + lun_type = ('Thin' if self.device_type == 'Dorado2100 G2' else 'Thick') + pools_dev = self._get_dev_pool_info(lun_type) + total_free_capacity = 0.0 + for pool_dev in pools_dev: + if self.device_type == 'Dorado2100 G2': + total_free_capacity += float(pool_dev[2]) + continue + else: + params_conf = self._parse_conf_lun_params() + pools_conf = params_conf['StoragePool'] + for pool_conf in pools_conf: + if pool_dev[5] == pool_conf: + total_free_capacity += float(pool_dev[3]) + break + + return total_free_capacity / 1024