Refactor Huawei iSCSI driver

We plan to refactor Huawei iSCSI drivers and add Huawei FC
drivers. For that's a huge change, we break the codes into
three patches:
1.Refactor T iSCSI driver
2.Refactor Dorado iSCSI driver
3.Add FC drivers for both T and Dorado arrays.

This is the first patch, changes as follows:
1.Define a common class for both FC and iSCSI drivers, and
also provide a unified class HuaweiVolumeDriver for users.
The unified driver will call HuaweiTISCSIDriver according to
users' configuration. The HuaweiTISCSIDriver is a subclass of
driver.ISCSIDriver, so it could get good inheritance.
2.Support volume type.
3.Refactor unit test to make it more logic clear and add more
test cases to get higher coverage rate.

Change-Id: I79b7bac7f38f2dcbb22c5db6207d8b55906fdad1
This commit is contained in:
zhangchao010
2013-08-29 10:33:52 +08:00
parent 034db09b9c
commit 17c4fd7103
9 changed files with 2827 additions and 2452 deletions

View File

@@ -1,862 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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.
"""
Tests for HUAWEI volume driver.
"""
import mox
import os
import shutil
import tempfile
from xml.dom.minidom import Document
from xml.etree import ElementTree as ET
from cinder import exception
from cinder.openstack.common import log as logging
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.huawei import huawei_iscsi
LOG = logging.getLogger(__name__)
LUNInfo = {'ID': None,
'Name': None,
'Size': None,
'LUN WWN': None,
'Status': None,
'Visible Capacity': None,
'Stripe Unit Size': None,
'Disk Pool ID': None,
'Format Progress': None,
'Cache Prefetch Strategy': None,
'LUNType': None,
'Cache Write Strategy': None,
'Running Cache Write Strategy': None,
'Consumed Capacity': None,
'Pool ID': None,
'SnapShot ID': None,
'LunCopy ID': None,
'Whether Private LUN': None,
'Remote Replication ID': None,
'Split mirror ID': None,
'Owner Controller': None,
'Worker Controller': None,
'RAID Group ID': None}
LUNInfoCopy = {'ID': None,
'Name': None,
'Size': None,
'LUN WWN': None,
'Status': None,
'Visible Capacity': None,
'Stripe Unit Size': None,
'Disk Pool ID': None,
'Format Progress': None,
'Cache Prefetch Strategy': None,
'LUNType': None,
'Cache Write Strategy': None,
'Running Cache Write Strategy': None,
'Consumed Capacity': None,
'Pool ID': None,
'SnapShot ID': None,
'LunCopy ID': None,
'Whether Private LUN': None,
'Remote Replication ID': None,
'Split mirror ID': None,
'Owner Controller': None,
'Worker Controller': None,
'RAID Group ID': None}
SnapshotInfo = {'Source LUN ID': None,
'Source LUN Name': None,
'ID': None,
'Name': None,
'Type': 'Public',
'Status': None,
'Time Stamp': '2013-01-15 14:00:00',
'Rollback Start Time': '--',
'Rollback End Time': '--',
'Rollback Speed': '--',
'Rollback Progress': '--'}
MapInfo = {'Host Group ID': None,
'Host Group Name': None,
'File Engine Cluster': None,
'Host ID': None,
'Host Name': None,
'Os Type': None,
'INI Port ID': None,
'INI Port Name': None,
'INI Port Info': None,
'Port Type': None,
'Link Status': None,
'LUN WWN': None,
'DEV LUN ID': None,
'Host LUN ID': None}
HostPort = {'ID': None,
'Name': None,
'Info': None}
LUNCopy = {'Name': None,
'ID': None,
'Type': None,
'State': None,
'Status': 'Disable'}
FakeVolume = {'name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
'size': '2',
'id': '0',
'wwn': '630303710030303701094b2b00000031',
'provider_auth': None}
FakeVolumeCopy = {'name': 'Volume-jeje34fe-223f-dd33-4423-asdfghjklqwg',
'size': '3',
'ID': '1',
'wwn': '630303710030303701094b2b0000003'}
FakeLUNCopy = {'ID': '1',
'Type': 'FULL',
'State': 'Created',
'Status': 'Normal'}
FakeSnapshot = {'name': 'keke34fe-223f-dd33-4423-asdfghjklqwf',
'volume_name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
'id': '3'}
FakePoolInfo = {'ID': '2',
'Level': 'RAID6',
'Status': 'Normal',
'Free Capacity': '10240',
'Disk List': '0,1;0,2;0,3;0,4;0,5;0,6',
'Name': 'RAID_001',
'Type': 'Thick'}
FakeConfInfo = {'HostGroup': 'HostGroup_OpenStack',
'HostnamePrefix': 'Host_',
'DefaultTargetIP': '192.168.100.1',
'TargetIQN': 'iqn.2006-08.com.huawei:oceanspace:2103037:',
'TargetIQN-T': 'iqn.2006-08.com.huawei:oceanspace:2103037::'
'20001:192.168.100.2',
'TargetIQN-Dorado5100': 'iqn.2006-08.com.huawei:oceanspace:'
'2103037::192.168.100.2',
'TargetIQN-Dorado2100G2': 'iqn.2006-08.com.huawei:oceanspace:'
'2103037::192.168.100.2-20001',
'Initiator Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'Initiator TargetIP': '192.168.100.2'}
FakeConnector = {'initiator': "iqn.1993-08.debian:01:ec2bff7ac3a3"}
class HuaweiVolumeTestCase(test.TestCase):
def __init__(self, *args, **kwargs):
super(HuaweiVolumeTestCase, self).__init__(*args, **kwargs)
def setUp(self):
super(HuaweiVolumeTestCase, self).setUp()
self.tmp_dir = tempfile.mkdtemp()
self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
self._create_fake_conf_file()
configuration = mox.MockObject(conf.Configuration)
configuration.cinder_huawei_conf_file = self.fake_conf_file
configuration.append_config_values(mox.IgnoreArg())
self.driver = FakeHuaweiStorage(configuration=configuration)
self.driver.do_setup({})
self.driver._test_flg = 'check_for_fail'
self._test_check_for_setup_errors()
def tearDown(self):
shutil.rmtree(self.tmp_dir)
super(HuaweiVolumeTestCase, self).tearDown()
def test_create_export_failed(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_export,
{}, FakeVolume)
def test_delete_volume_failed(self):
self._test_delete_volume()
def test_create_snapshot_failed(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot,
FakeSnapshot)
def test_delete_snapshot_failed(self):
self._test_delete_snapshot()
def test_create_luncopy_failed(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
FakeVolumeCopy, FakeSnapshot)
def test_initialize_failed(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
FakeVolume, FakeConnector)
def test_terminate_connection_failed(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.terminate_connection,
FakeVolume, FakeConnector)
def test_normal(self):
# test for T Series
self.driver._test_flg = 'check_for_T'
self._test_check_for_setup_errors()
self._test_create_volume()
self._test_create_export()
self._test_create_snapshot()
self._test_create_volume_from_snapshot()
self._test_initialize_connection_for_T()
self._test_terminate_connection()
self._test_delete_snapshot()
self._test_delete_volume()
self._test_get_get_volume_stats()
# test for Dorado2100 G2
self.driver._test_flg = 'check_for_Dorado2100G2'
self._test_check_for_setup_errors()
self._test_create_volume()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot,
FakeSnapshot)
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
FakeVolumeCopy, FakeSnapshot)
self._test_initialize_connection_for_Dorado2100G2()
self._test_terminate_connection()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_snapshot,
FakeSnapshot)
self._test_delete_volume()
# test for Dorado5100
self.driver._test_flg = 'check_for_Dorado5100'
self._test_check_for_setup_errors()
self._test_create_volume()
self._test_create_snapshot()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
FakeVolumeCopy, FakeSnapshot)
self._test_initialize_connection_for_Dorado5100()
self._test_terminate_connection()
self._test_delete_snapshot()
self._test_delete_volume()
def cleanup(self):
if os.path.exists(self.fake_conf_file):
os.remove(self.fake_conf_file)
shutil.rmtree(self.tmp_dir)
def _create_fake_conf_file(self):
doc = Document()
config = doc.createElement('config')
doc.appendChild(config)
storage = doc.createElement('Storage')
config.appendChild(storage)
controllerip0 = doc.createElement('ControllerIP0')
controllerip0_text = doc.createTextNode('10.10.10.1')
controllerip0.appendChild(controllerip0_text)
storage.appendChild(controllerip0)
controllerip1 = doc.createElement('ControllerIP1')
controllerip1_text = doc.createTextNode('10.10.10.2')
controllerip1.appendChild(controllerip1_text)
storage.appendChild(controllerip1)
username = doc.createElement('UserName')
username_text = doc.createTextNode('admin')
username.appendChild(username_text)
storage.appendChild(username)
userpassword = doc.createElement('UserPassword')
userpassword_text = doc.createTextNode('123456')
userpassword.appendChild(userpassword_text)
storage.appendChild(userpassword)
lun = doc.createElement('LUN')
config.appendChild(lun)
storagepool = doc.createElement('StoragePool')
storagepool.setAttribute('Name', 'RAID_001')
lun.appendChild(storagepool)
storagepool = doc.createElement('StoragePool')
storagepool.setAttribute('Name', 'RAID_002')
lun.appendChild(storagepool)
iscsi = doc.createElement('iSCSI')
config.appendChild(iscsi)
defaulttargetip = doc.createElement('DefaultTargetIP')
defaulttargetip_text = doc.createTextNode('192.168.100.1')
defaulttargetip.appendChild(defaulttargetip_text)
iscsi.appendChild(defaulttargetip)
initiator = doc.createElement('Initiator')
initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3')
initiator.setAttribute('TargetIP', '192.168.100.2')
iscsi.appendChild(initiator)
file = open(self.fake_conf_file, 'w')
file.write(doc.toprettyxml(indent=''))
file.close()
def _test_check_for_setup_errors(self):
self.driver.check_for_setup_error()
def _test_create_volume(self):
self.driver.create_volume(FakeVolume)
self.assertNotEqual(LUNInfo["ID"], None)
self.assertEqual(LUNInfo["RAID Group ID"], FakePoolInfo['ID'])
def _test_delete_volume(self):
self.driver.delete_volume(FakeVolume)
self.assertEqual(LUNInfo["ID"], None)
def _test_create_snapshot(self):
self.driver.create_snapshot(FakeSnapshot)
self.assertNotEqual(SnapshotInfo["ID"], None)
self.assertNotEqual(LUNInfo["ID"], None)
self.assertEqual(SnapshotInfo["Status"], 'Active')
self.assertEqual(SnapshotInfo["Source LUN ID"], LUNInfo["ID"])
def _test_delete_snapshot(self):
self.driver.delete_snapshot(FakeSnapshot)
self.assertEqual(SnapshotInfo["ID"], None)
def _test_create_volume_from_snapshot(self):
self.driver.create_volume_from_snapshot(FakeVolumeCopy, FakeSnapshot)
self.assertNotEqual(LUNInfoCopy["ID"], None)
def _test_create_export(self):
retval = self.driver.create_export({}, FakeVolume)
self.assertNotEqual(retval, FakeVolume["id"])
def _test_initialize_connection_for_T(self):
connection_data = self.driver.initialize_connection(FakeVolume,
FakeConnector)
iscsi_properties = connection_data['data']
self.assertEquals(iscsi_properties['target_iqn'],
FakeConfInfo['TargetIQN-T'])
self.assertEquals(iscsi_properties['target_portal'],
FakeConfInfo['Initiator TargetIP'] + ':3260')
self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
self.assertEqual(MapInfo["INI Port Info"],
FakeConnector['initiator'])
def _test_initialize_connection_for_Dorado2100G2(self):
connection_data = self.driver.initialize_connection(FakeVolume,
FakeConnector)
iscsi_properties = connection_data['data']
self.assertEquals(iscsi_properties['target_iqn'],
FakeConfInfo['TargetIQN-Dorado2100G2'])
self.assertEquals(iscsi_properties['target_portal'],
FakeConfInfo['Initiator TargetIP'] + ':3260')
self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
self.assertEqual(MapInfo["INI Port Info"],
FakeConnector['initiator'])
def _test_initialize_connection_for_Dorado5100(self):
connection_data = self.driver.initialize_connection(FakeVolume,
FakeConnector)
iscsi_properties = connection_data['data']
self.assertEquals(iscsi_properties['target_iqn'],
FakeConfInfo['TargetIQN-Dorado5100'])
self.assertEquals(iscsi_properties['target_portal'],
FakeConfInfo['Initiator TargetIP'] + ':3260')
self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
self.assertEqual(MapInfo["INI Port Info"],
FakeConnector['initiator'])
def _test_terminate_connection(self):
self.driver.terminate_connection(FakeVolume, FakeConnector)
self.assertEqual(MapInfo["DEV LUN ID"], None)
self.assertEqual(MapInfo["Host LUN ID"], None)
self.assertEqual(MapInfo["INI Port Info"], None)
def _test_get_get_volume_stats(self):
stats = self.driver.get_volume_stats(True)
fakecapacity = float(FakePoolInfo['Free Capacity']) / 1024
self.assertEqual(stats['free_capacity_gb'], fakecapacity)
class FakeHuaweiStorage(huawei_iscsi.HuaweiISCSIDriver):
"""Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver."""
def __init__(self, *args, **kwargs):
super(FakeHuaweiStorage, self).__init__(*args, **kwargs)
self._test_flg = None
def _execute_cli(self, cmdIn):
cmd = cmdIn.split(' ')[0].lower()
if cmd == 'showsys':
if ((self._test_flg == 'check_for_fail') or
(self._test_flg == 'check_for_T')):
out = """/>showsys
==========================================================================
System Information
--------------------------------------------------------------------------
System Name | SN_S5500T-xu-0123456789
Device Type | Oceanstor S5500T
Current System Mode | Double Controllers Normal
Mirroring Link Status | Link Up
Location |
Time | 2013-01-01 01:01:01
Product Version | V100R005C00
===========================================================================
"""
elif self._test_flg == 'check_for_Dorado2100G2':
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
===========================================================================
"""
elif self._test_flg == 'check_for_Dorado5100':
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
===========================================================================
"""
elif cmd == 'addhostmap':
MapInfo['DEV LUN ID'] = LUNInfo['ID']
MapInfo['LUN WWN'] = LUNInfo['LUN WWN']
MapInfo['Host LUN ID'] = '0'
out = 'command operates successfully'
elif cmd == 'showhostmap':
if MapInfo['DEV LUN ID'] is None:
out = 'command operates successfully, but no information.'
else:
out = """/>showhostmap
==========================================================================
Map Information
--------------------------------------------------------------------------
Map ID Working Controller Dev LUN ID LUN WWN Host LUN ID Mapped to \
RAID ID Dev LUN Cap(MB) Map Type Whether Command LUN Pool ID
---------------------------------------------------------------------------
2147483649 %s %s %s %s Host: %s %s %s HOST No --
===========================================================================
""" % (LUNInfo['Worker Controller'], LUNInfo['ID'], LUNInfo['LUN WWN'],
MapInfo['Host ID'], MapInfo['Host ID'], LUNInfo['RAID Group ID'],
str(int(LUNInfo['Size']) * 1024))
elif cmd == 'delhostmap':
MapInfo['DEV LUN ID'] = None
MapInfo['LUN WWN'] = None
MapInfo['Host LUN ID'] = None
out = 'command operates successfully'
elif cmd == 'createsnapshot':
SnapshotInfo['Source LUN ID'] = LUNInfo['ID']
SnapshotInfo['Source LUN Name'] = LUNInfo['Name']
SnapshotInfo['ID'] = FakeSnapshot['id']
SnapshotInfo['Name'] = self._name_translate(FakeSnapshot['name'])
SnapshotInfo['Status'] = 'Disable'
out = 'command operates successfully'
elif cmd == 'actvsnapshot':
SnapshotInfo['Status'] = 'Active'
out = 'command operates successfully'
elif cmd == 'disablesnapshot':
SnapshotInfo['Status'] = 'Disable'
out = 'command operates successfully'
elif cmd == 'delsnapshot':
SnapshotInfo['Source LUN ID'] = None
SnapshotInfo['Source LUN Name'] = None
SnapshotInfo['ID'] = None
SnapshotInfo['Name'] = None
SnapshotInfo['Status'] = None
out = 'command operates successfully'
elif cmd == 'showsnapshot':
if SnapshotInfo['ID'] is None:
out = 'command operates successfully, but no information.'
else:
out = """/>showsnapshot
==========================================================================
Snapshot Information
--------------------------------------------------------------------------
Name ID Type Status Time Stamp
--------------------------------------------------------------------------
%s %s Public %s 2013-01-15 14:21:13
==========================================================================
""" % (SnapshotInfo['Name'], SnapshotInfo['ID'], SnapshotInfo['Status'])
elif cmd == 'showlunsnapshot':
if SnapshotInfo['ID'] is None:
out = """Current LUN is not a source LUN"""
else:
out = """/>showlunsnapshot -lun 2
==========================================================================
Snapshot of LUN
--------------------------------------------------------------------------
Name ID Type Status Time Stamp
--------------------------------------------------------------------------
%s %s Public %s 2013-01-15 14:17:19
==========================================================================
""" % (SnapshotInfo['Name'], SnapshotInfo['ID'], SnapshotInfo['Status'])
elif cmd == 'createlun':
if LUNInfo['ID'] is None:
LUNInfo['Name'] = self._name_translate(FakeVolume['name'])
LUNInfo['ID'] = FakeVolume['id']
LUNInfo['Size'] = FakeVolume['size']
LUNInfo['LUN WWN'] = FakeVolume['wwn']
LUNInfo['Owner Controller'] = 'A'
LUNInfo['Worker Controller'] = 'A'
LUNInfo['RAID Group ID'] = FakePoolInfo['ID']
else:
LUNInfoCopy['Name'] = \
self._name_translate(FakeVolumeCopy['name'])
LUNInfoCopy['ID'] = FakeVolumeCopy['ID']
LUNInfoCopy['Size'] = FakeVolumeCopy['size']
LUNInfoCopy['LUN WWN'] = FakeVolumeCopy['wwn']
LUNInfoCopy['Owner Controller'] = 'A'
LUNInfoCopy['Worker Controller'] = 'A'
LUNInfoCopy['RAID Group ID'] = FakePoolInfo['ID']
out = 'command operates successfully'
elif cmd == 'dellun':
LUNInfo['Name'] = None
LUNInfo['ID'] = None
LUNInfo['Size'] = None
LUNInfo['LUN WWN'] = None
LUNInfo['Owner Controller'] = None
LUNInfo['Worker Controller'] = None
LUNInfo['RAID Group ID'] = None
out = 'command operates successfully'
elif cmd == 'showlun':
if LUNInfo['ID'] is None:
out = 'command operates successfully, but no information.'
elif LUNInfoCopy['ID'] is None:
if ((self._test_flg == 'check_for_fail') or
(self._test_flg == 'check_for_T')):
out = """/>showlun
===========================================================================
LUN Information
---------------------------------------------------------------------------
ID RAID Group ID Disk Pool ID Status Controller Visible Capacity(MB) \
LUN Name Stripe Unit Size(KB) Lun Type
---------------------------------------------------------------------------
%s %s -- Normal %s %s %s 64 THICK
===========================================================================
""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'])
elif self._test_flg == 'check_for_Dorado2100G2':
out = """/>showlun
===========================================================================
LUN Information
---------------------------------------------------------------------------
ID Status Controller Visible Capacity(MB) LUN Name Lun Type
---------------------------------------------------------------------------
%s Normal %s %s %s THICK
===========================================================================
""" % (LUNInfo['ID'], LUNInfo['Owner Controller'],
str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'])
elif self._test_flg == 'check_for_Dorado5100':
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
===========================================================================
""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'],
LUNInfo['Owner Controller'], str(int(LUNInfo['Size']) * 1024),
LUNInfo['Name'])
else:
if ((self._test_flg == 'check_for_fail') or
(self._test_flg == 'check_for_T')):
out = """/>showlun
============================================================================
LUN Information
----------------------------------------------------------------------------
ID RAID Group ID Disk Pool ID Status Controller Visible Capacity(MB)\
LUN Name Stripe Unit Size(KB) Lun Type
----------------------------------------------------------------------------
%s %s -- Normal %s %s %s 64 THICK
%s %s -- Normal %s %s %s 64 THICK
============================================================================
""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'], LUNInfoCopy['ID'],
LUNInfoCopy['RAID Group ID'], LUNInfoCopy['Owner Controller'],
str(int(LUNInfoCopy['Size']) * 1024), LUNInfoCopy['Name'])
elif self._test_flg == 'check_for_Dorado2100G2':
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
===========================================================================
""" % (LUNInfo['ID'], LUNInfo['Owner Controller'],
str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'],
LUNInfoCopy['ID'], LUNInfoCopy['Owner Controller'],
str(int(LUNInfoCopy['Size']) * 1024), LUNInfoCopy['Name'])
elif self._test_flg == 'check_for_Dorado5100':
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
===========================================================================
""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'],
LUNInfoCopy['ID'], LUNInfoCopy['RAID Group ID'],
LUNInfoCopy['Owner Controller'], str(int(LUNInfoCopy['Size']) * 1024),
LUNInfoCopy['Name'])
elif cmd == 'createhostgroup':
MapInfo['Host Group ID'] = '1'
MapInfo['Host Group Name'] = FakeConfInfo['HostGroup']
out = 'command operates successfully'
elif cmd == 'showhostgroup':
if MapInfo['Host Group ID'] is None:
out = """/>showhostgroup
============================================================
Host Group Information
------------------------------------------------------------
Host Group ID Name File Engine Cluster
------------------------------------------------------------
0 Default Group NO
============================================================
"""
else:
out = """/>showhostgroup
============================================================
Host Group Information
------------------------------------------------------------
Host Group ID Name File Engine Cluster
------------------------------------------------------------
0 Default Group NO
%s %s NO
============================================================
""" % (MapInfo['Host Group ID'], MapInfo['Host Group Name'])
elif cmd == 'addhost':
MapInfo['Host ID'] = '1'
MapInfo['Host Name'] = FakeConfInfo['HostnamePrefix'] + \
str(hash(FakeConnector['initiator']))
MapInfo['Os Type'] = 'Linux'
out = 'command operates successfully'
elif cmd == 'delhost':
MapInfo['Host ID'] = None
MapInfo['Host Name'] = None
MapInfo['Os Type'] = None
out = 'command operates successfully'
elif cmd == 'showhost':
if MapInfo['Host ID'] is None:
out = 'command operates successfully, but no information.'
else:
out = """/>showhost
=======================================================
Host Information
-------------------------------------------------------
Host ID Host Name Host Group ID Os Type
-------------------------------------------------------
%s %s %s Linux
=======================================================
""" % (MapInfo['Host ID'], MapInfo['Host Name'], MapInfo['Host Group ID'])
elif cmd == 'createluncopy':
LUNCopy['Name'] = LUNInfoCopy['Name']
LUNCopy['ID'] = FakeLUNCopy['ID']
LUNCopy['Type'] = FakeLUNCopy['Type']
LUNCopy['State'] = FakeLUNCopy['State']
LUNCopy['Status'] = FakeLUNCopy['Status']
out = 'command operates successfully'
elif cmd == 'delluncopy':
LUNCopy['Name'] = None
LUNCopy['ID'] = None
LUNCopy['Type'] = None
LUNCopy['State'] = None
LUNCopy['Status'] = None
out = 'command operates successfully'
elif cmd == 'chgluncopystatus':
LUNCopy['State'] = 'Complete'
out = 'command operates successfully'
elif cmd == 'showluncopy':
if LUNCopy['ID'] is None:
out = 'command operates successfully, but no information.'
else:
out = """/>showluncopy
============================================================================
LUN Copy Information
----------------------------------------------------------------------------
LUN Copy Name LUN Copy ID Type LUN Copy State LUN Copy Status
----------------------------------------------------------------------------
%s %s %s %s %s
============================================================================
""" % (LUNCopy['Name'], LUNCopy['ID'], LUNCopy['Type'],
LUNCopy['State'], LUNCopy['Status'])
elif cmd == 'showiscsitgtname':
if ((self._test_flg == 'check_for_fail') or
(self._test_flg == 'check_for_T')):
out = """/>showiscsitgtname
============================================================================
ISCSI Name
----------------------------------------------------------------------------
Iscsi Name | %s
============================================================================
""" % FakeConfInfo['TargetIQN']
elif (self._test_flg == 'check_for_Dorado2100G2' or
self._test_flg == 'check_for_Dorado5100'):
out = """/>showiscsitgtname
============================================================================
ISCSI Name
----------------------------------------------------------------------------
Iscsi Name | %s
============================================================================
""" % FakeConfInfo['TargetIQN']
elif cmd == 'showiscsiip':
out = """/>showiscsiip
============================================================================
iSCSI IP Information
----------------------------------------------------------------------------
Controller ID Interface Module ID Port ID IP Address Mask
----------------------------------------------------------------------------
A 0 P1 %s 255.255.255.0
============================================================================
""" % FakeConfInfo['Initiator TargetIP']
elif cmd == 'addhostport':
MapInfo['INI Port ID'] = HostPort['ID']
MapInfo['INI Port Name'] = HostPort['Name']
MapInfo['INI Port Info'] = HostPort['Info']
out = 'command operates successfully'
elif cmd == 'delhostport':
MapInfo['INI Port ID'] = None
MapInfo['INI Port Name'] = None
MapInfo['INI Port Info'] = None
out = 'command operates successfully'
elif cmd == 'showhostport':
if MapInfo['INI Port ID'] is None:
out = 'command operates successfully, but no information.'
else:
out = """/>showhostport -host 3
==============================================================================
Host Port Information
------------------------------------------------------------------------------
Port ID Port Name Port Information Port Type Host ID \
Link Status Multipath Type
------------------------------------------------------------------------------
%s %s %s ISCSITGT %s Unconnected Default
==============================================================================
""" % (MapInfo['INI Port ID'], MapInfo['INI Port Name'],
MapInfo['INI Port Info'], MapInfo['Host ID'])
elif cmd == 'addiscsiini':
HostPort['ID'] = '1'
HostPort['Name'] = 'iSCSIInitiator001'
HostPort['Info'] = FakeConfInfo['Initiator Name']
out = 'command operates successfully'
elif cmd == 'deliscsiini':
HostPort['ID'] = None
HostPort['Name'] = None
HostPort['Info'] = None
out = 'command operates successfully'
elif cmd == 'showiscsiini':
if HostPort['ID'] is None:
out = 'Error: The parameter is wrong.'
else:
out = """/>showiscsiini -ini iqn.1993-08.org\
.debian:01:503629a9d3f
========================================================
Initiator Information
--------------------------------------------------------
Initiator Name Chap Status
--------------------------------------------------------
%s Disable
========================================================
""" % (HostPort['Info'])
elif cmd == 'showrg':
out = """/>showrg
=====================================================================
RAID Group Information
---------------------------------------------------------------------
ID Level Status Free Capacity(MB) Disk List Name
---------------------------------------------------------------------
0 RAID6 Normal 1024 0,0;0,2;0,4;0,5;0,6;0,7; RAID003
%s %s %s %s %s %s
=====================================================================
""" % (FakePoolInfo['ID'], FakePoolInfo['Level'],
FakePoolInfo['Status'], FakePoolInfo['Free Capacity'],
FakePoolInfo['Disk List'], FakePoolInfo['Name'])
elif cmd == 'showrespool':
out = """/>showrespool
============================================================================
Resource Pool Information
----------------------------------------------------------------------------
Pool ID Size(MB) Usage(MB) Valid Size(MB) Alarm Threshold(%)
----------------------------------------------------------------------------
A 5130.0 0.0 5130.0 80
B 3082.0 0.0 3082.0 80
============================================================================
"""
elif cmd == 'chglun':
out = 'command operates successfully'
out = out.replace('\n', '\r\n')
return out
def _get_lun_controller(self, lunid):
pass

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
# Copyright (c) 2012 Huawei Technologies Co., Ltd.
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 Huawei Technologies Co., Ltd.
# Copyright (c) 2012 OpenStack LLC.
# All Rights Reserved.
#
@@ -13,3 +15,89 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Provide a unified driver class for users.
The product type and the protocol should be specified in confige file before.
"""
from oslo.config import cfg
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_t
from cinder.volume.drivers.huawei import ssh_common
LOG = logging.getLogger(__name__)
huawei_opt = [
cfg.StrOpt('cinder_huawei_conf_file',
default='/etc/cinder/cinder_huawei_conf.xml',
help='config data for cinder huawei plugin')]
CONF = cfg.CONF
CONF.register_opts(huawei_opt)
class HuaweiVolumeDriver(object):
"""Define an unified driver for Huawei drivers."""
def __init__(self, *args, **kwargs):
super(HuaweiVolumeDriver, self).__init__()
self._product = {'T': huawei_t}
self._protocol = {'iSCSI': 'ISCSIDriver'}
self.driver = self._instantiate_driver(*args, **kwargs)
def _instantiate_driver(self, *args, **kwargs):
"""Instantiate the specified driver."""
self.configuration = kwargs.get('configuration', None)
if not self.configuration:
msg = (_('_instantiate_driver: configuration not found.'))
raise exception.InvalidInput(reason=msg)
self.configuration.append_config_values(huawei_opt)
conf_file = self.configuration.cinder_huawei_conf_file
(product, protocol) = self._get_conf_info(conf_file)
LOG.debug(_('_instantiate_driver: Loading %(protocol)s driver for '
'Huawei OceanStor %(product)s series storage arrays.')
% {'protocol': protocol,
'product': product})
driver_module = self._product[product]
driver_class = 'Huawei' + product + self._protocol[protocol]
driver_class = getattr(driver_module, driver_class)
return driver_class(*args, **kwargs)
def _get_conf_info(self, filename):
"""Get product type and connection protocol from config file."""
root = ssh_common.parse_xml_file(filename)
product = root.findtext('Storage/Product').strip()
protocol = root.findtext('Storage/Protocol').strip()
if (product in self._product.keys() and
protocol in self._protocol.keys()):
return (product, protocol)
else:
msg = (_('"Product" or "Protocol" is illegal. "Product" should '
'be set to T. "Protocol" should be set '
'to iSCSI. Product: %(product)s '
'Protocol: %(protocol)s')
% {'product': str(product),
'protocol': str(protocol)})
raise exception.InvalidInput(reason=msg)
def __setattr__(self, name, value):
"""Set the attribute."""
if getattr(self, 'driver', None):
self.driver.__setattr__(name, value)
return
object.__setattr__(self, name, value)
def __getattr__(self, name):
""""Get the attribute."""
drver = object.__getattribute__(self, 'driver')
return getattr(drver, name)

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<Storage>
<ControllerIP0>x.x.x.x</ControllerIP0>
<ControllerIP1>x.x.x.x</ControllerIP1>
<UserName>xxxxxx</UserName>
<UserPassword>xxxxxx</UserPassword>
</Storage>
<LUN>
<!--LUN Type: Thick, or Thin. Default: Thick-->
<LUNType>Thick</LUNType>
<!--The stripe size can be 4, 8, 16, 32, 64, 128, 256, and 512 in the unit of KB.Default: 64-->
<StripUnitSize>64</StripUnitSize>
<!--The write cache policy of the LUN:-->
<!--1 specifies write back, 2 specifies write through, 3 specifies write back mandatorily.Default: 1-->
<WriteType>1</WriteType>
<!--Enables or disbles cahce mirroring: 0 Disable, or 1 Enable. Default: Enable-->
<MirrorSwitch>1</MirrorSwitch>
<!--The prefetch policy of the reading cache:-->
<!--prefetch type 0 specifies non-preftch and prefetch value is 0,-->
<!--prefetch type 1 specifies constant prefetch and prefetch value ranges from 0 to 1024 in the unit of KB,-->
<!--prefetch type 2 specifies variable prefetch and value specifies cache prefetch multiple ranges from 0 to 65535,-->
<!--prefetch type 3 specifies intelligent prefetch Intelligent and Vaule is 0,-->
<!--Default: prefetch type 0 and prefetch value 0-->
<Prefetch Type="0" Value="0"/>
<StoragePool Name="xxxxxx"/>
<StoragePool Name="xxxxxx"/>
</LUN>
<iSCSI>
<DefaultTargetIP>x.x.x.x</DefaultTargetIP>
<Initiator Name="xxxxxx" TargetIP="x.x.x.x"/>
<Initiator Name="xxxxxx" TargetIP="x.x.x.x"/>
</iSCSI>
</config>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,361 @@
# 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 T series storage arrays.
"""
import re
import time
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.volume import driver
from cinder.volume.drivers.huawei import ssh_common
LOG = logging.getLogger(__name__)
HOST_PORT_PREFIX = 'HostPort_'
class HuaweiTISCSIDriver(driver.ISCSIDriver):
"""ISCSI driver for Huawei OceanStor T series storage arrays."""
VERSION = '1.1.0'
def __init__(self, *args, **kwargs):
super(HuaweiTISCSIDriver, self).__init__(*args, **kwargs)
def do_setup(self, context):
"""Instantiate common class."""
self.common = ssh_common.TseriesCommon(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
def check_for_setup_error(self):
"""Check something while starting."""
self.common.check_for_setup_error()
def create_volume(self, volume):
"""Create a new volume."""
volume_id = self.common.create_volume(volume)
return {'provider_location': volume_id}
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot."""
volume_id = self.common.create_volume_from_snapshot(volume, snapshot)
return {'provider_location': volume_id}
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the specified volume."""
volume_id = self.common.create_cloned_volume(volume, src_vref)
return {'provider_location': volume_id}
def delete_volume(self, volume):
"""Delete a volume."""
self.common.delete_volume(volume)
def create_export(self, context, volume):
"""Export the volume."""
pass
def ensure_export(self, context, volume):
"""Synchronously recreate an export for a volume."""
pass
def remove_export(self, context, volume):
"""Remove an export for a volume."""
pass
def create_snapshot(self, snapshot):
"""Create a snapshot."""
snapshot_id = self.common.create_snapshot(snapshot)
return {'provider_location': snapshot_id}
def delete_snapshot(self, snapshot):
"""Delete a snapshot."""
self.common.delete_snapshot(snapshot)
def initialize_connection(self, volume, connector):
"""Map a volume to a host and return target iSCSI information."""
LOG.debug(_('initialize_connection: volume name: %(vol)s '
'host: %(host)s initiator: %(ini)s')
% {'vol': volume['name'],
'host': connector['host'],
'ini': connector['initiator']})
self.common._update_login_info()
(iscsi_iqn, target_ip, port_ctr) =\
self._get_iscsi_params(connector['initiator'])
# First, add a host if not added before.
host_id = self.common.add_host(connector['host'])
# Then, add the iSCSI port to the host.
self._add_iscsi_port_to_host(host_id, connector)
# Finally, map the volume to the host.
volume_id = volume['provider_location']
hostlun_id = self.common.map_volume(host_id, volume_id)
# Change LUN ctr for better performance, just for single path.
lun_details = self.common.get_lun_details(volume_id)
if (lun_details['LunType'] == 'THICK' and
lun_details['OwningController'] != port_ctr):
self.common.change_lun_ctr(volume_id, port_ctr)
properties = {}
properties['target_discovered'] = False
properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
properties['target_iqn'] = iscsi_iqn
properties['target_lun'] = int(hostlun_id)
properties['volume_id'] = volume['id']
auth = volume['provider_auth']
if auth:
(auth_method, auth_username, auth_secret) = auth.split()
properties['auth_method'] = auth_method
properties['auth_username'] = auth_username
properties['auth_password'] = auth_secret
return {'driver_volume_type': 'iscsi', 'data': properties}
def _get_iscsi_params(self, initiator):
"""Get target iSCSI params, including iqn and IP."""
conf_file = self.common.configuration.cinder_huawei_conf_file
iscsi_conf = self._get_iscsi_conf(conf_file)
target_ip = None
for ini in iscsi_conf['Initiator']:
if ini['Name'] == initiator:
target_ip = ini['TargetIP']
break
# If didn't specify target IP for some initiator, use default IP.
if not target_ip:
if iscsi_conf['DefaultTargetIP']:
target_ip = iscsi_conf['DefaultTargetIP']
else:
msg = (_('_get_iscsi_params: Failed to get target IP '
'for initiator %(ini)s, please check config file.')
% {'ini': initiator})
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
(target_iqn, port_ctr) = self._get_tgt_iqn(target_ip)
return (target_iqn, target_ip, port_ctr)
def _get_iscsi_conf(self, filename):
"""Get iSCSI info from config file.
This function returns a dict:
{'DefaultTargetIP': '11.11.11.11',
'Initiator': [{'Name': 'iqn.xxxxxx.1', 'TargetIP': '11.11.11.12'},
{'Name': 'iqn.xxxxxx.2', 'TargetIP': '11.11.11.13'}
]
}
"""
iscsiinfo = {}
root = ssh_common.parse_xml_file(filename)
default_ip = root.findtext('iSCSI/DefaultTargetIP')
if default_ip:
iscsiinfo['DefaultTargetIP'] = default_ip.strip()
else:
iscsiinfo['DefaultTargetIP'] = None
initiator_list = []
tmp_dic = {}
for dic in root.findall('iSCSI/Initiator'):
# Strip the values of dict.
for k, v in dic.items():
tmp_dic[k] = v.strip()
initiator_list.append(tmp_dic)
iscsiinfo['Initiator'] = initiator_list
return iscsiinfo
def _get_tgt_iqn(self, port_ip):
"""Run CLI command to get target iSCSI iqn.
The iqn is formed with three parts:
iSCSI target name + iSCSI port info + iSCSI IP
"""
LOG.debug(_('_get_tgt_iqn: iSCSI IP is %s.') % port_ip)
cli_cmd = 'showiscsitgtname'
out = self.common._execute_cli(cli_cmd)
self._assert_cli_out(re.search('ISCSI Name', out),
'_get_tgt_iqn',
'Failed to get iSCSI target %s iqn.' % port_ip,
cli_cmd, out)
lines = out.split('\r\n')
index = lines[4].index('iqn')
iqn_prefix = lines[4][index:].strip()
# Here we make sure port_info won't be None.
port_info = self._get_iscsi_tgt_port_info(port_ip)
ctr = ('0' if port_info[0] == 'A' else '1')
interface = '0' + port_info[1]
port = '0' + port_info[2][1:]
iqn_suffix = ctr + '02' + interface + port
# iqn_suffix should not start with 0
while(True):
if iqn_suffix.startswith('0'):
iqn_suffix = iqn_suffix[1:]
else:
break
iqn = iqn_prefix + ':' + iqn_suffix + ':' + port_info[3]
LOG.debug(_('_get_tgt_iqn: iSCSI target iqn is %s') % iqn)
return (iqn, port_info[0])
def _get_iscsi_tgt_port_info(self, port_ip):
"""Get iSCSI Port information of storage device."""
cli_cmd = 'showiscsiip'
out = self.common._execute_cli(cli_cmd)
if re.search('iSCSI IP Information', out):
for line in out.split('\r\n')[6:-2]:
tmp_line = line.split()
if tmp_line[3] == port_ip:
return tmp_line
err_msg = _('_get_iscsi_tgt_port_info: Failed to get iSCSI port '
'info. Please make sure the iSCSI port IP %s is '
'configured in array.') % port_ip
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
def _add_iscsi_port_to_host(self, hostid, connector, multipathtype=0):
"""Add an iSCSI port to the given host.
First, add an initiator if needed, the initiator is equivalent to
an iSCSI port. Then, add the initiator to host if not added before.
"""
initiator = connector['initiator']
# Add an iSCSI initiator.
if not self._initiator_added(initiator):
self._add_initiator(initiator)
# Add the initiator to host if not added before.
port_name = HOST_PORT_PREFIX + str(hash(initiator))
portadded = False
hostport_info = self.common._get_host_port_info(hostid)
if hostport_info:
for hostport in hostport_info:
if hostport[2] == initiator:
portadded = True
break
if not portadded:
cli_cmd = ('addhostport -host %(id)s -type 5 '
'-info %(info)s -n %(name)s -mtype %(multype)s'
% {'id': hostid,
'info': initiator,
'name': port_name,
'multype': multipathtype})
out = self.common._execute_cli(cli_cmd)
msg = ('Failed to add iSCSI port %(port)s to host %(host)s'
% {'port': port_name,
'host': hostid})
self._assert_cli_operate_out('_add_iscsi_port_to_host',
msg, cli_cmd, out)
def _initiator_added(self, ininame):
"""Check whether the initiator is already added."""
cli_cmd = 'showiscsiini -ini %(name)s' % {'name': ininame}
out = self.common._execute_cli(cli_cmd)
return (True if re.search('Initiator Information', out) else False)
def _add_initiator(self, ininame):
"""Add a new initiator to storage device."""
cli_cmd = 'addiscsiini -n %(name)s' % {'name': ininame}
out = self.common._execute_cli(cli_cmd)
self._assert_cli_operate_out('_add_iscsi_host_port',
'Failed to add initiator %s' % ininame,
cli_cmd, out)
def _delete_initiator(self, ininame, attempts=2):
"""Delete an initiator."""
cli_cmd = 'deliscsiini -n %(name)s' % {'name': ininame}
while(attempts > 0):
out = self.common._execute_cli(cli_cmd)
if re.search('the port is in use', out):
attempts -= 1
time.sleep(2)
else:
break
self._assert_cli_operate_out('_map_lun',
'Failed to delete initiator %s.'
% ininame,
cli_cmd, out)
def terminate_connection(self, volume, connector, **kwargs):
"""Terminate the map."""
LOG.debug(_('terminate_connection: volume: %(vol)s host: %(host)s '
'connector: %(initiator)s')
% {'vol': volume['name'],
'host': connector['host'],
'initiator': connector['initiator']})
self.common._update_login_info()
host_id = self.common.remove_map(volume['provider_location'],
connector['host'])
if not self.common._get_host_map_info(host_id):
self._remove_iscsi_port(host_id, connector)
def _remove_iscsi_port(self, hostid, connector):
"""Remove iSCSI ports and delete host."""
initiator = connector['initiator']
# Delete the host initiator if no LUN mapped to it.
port_num = 0
port_info = self.common._get_host_port_info(hostid)
if port_info:
port_num = len(port_info)
for port in port_info:
if port[2] == initiator:
self.common._delete_hostport(port[0])
self._delete_initiator(initiator)
port_num -= 1
break
else:
LOG.warn(_('_remove_iscsi_port: iSCSI port was not found '
'on host %(hostid)s') % {'hostid': hostid})
# Delete host if no initiator added to it.
if port_num == 0:
self.common._delete_host(hostid)
def get_volume_stats(self, refresh=False):
"""Get volume stats."""
self._stats = self.common.get_volume_stats(refresh)
self._stats['storage_protocol'] = 'iSCSI'
self._stats['driver_version'] = self.VERSION
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats['volume_backend_name'] = (backend_name or
self.__class__.__name__)
return self._stats

File diff suppressed because it is too large Load Diff

View File

@@ -124,7 +124,9 @@ MAPPING = {
'cinder.volume.drivers.netapp.nfs.NetAppNFSDriver':
'cinder.volume.drivers.netapp.common.Deprecated',
'cinder.volume.drivers.netapp.nfs.NetAppCmodeNfsDriver':
'cinder.volume.drivers.netapp.common.Deprecated'}
'cinder.volume.drivers.netapp.common.Deprecated',
'cinder.volume.drivers.huawei.HuaweiISCSIDriver':
'cinder.volume.drivers.huawei.HuaweiVolumeDriver'}
class VolumeManager(manager.SchedulerDependentManager):

View File

@@ -1162,7 +1162,7 @@
#
# Options defined in cinder.volume.drivers.huawei.huawei_iscsi
# Options defined in cinder.volume.drivers.huawei
#
# config data for cinder huawei plugin (string value)