Merge "Refactor huawei Dorado array iSCSI driver"

This commit is contained in:
Jenkins 2013-09-03 06:57:21 +00:00 committed by Gerrit Code Review
commit 2a76d0c81b
4 changed files with 542 additions and 2 deletions

View File

@ -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)

View File

@ -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),

View File

@ -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

View File

@ -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