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:
@@ -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
|
||||
1244
cinder/tests/test_huawei_t_dorado.py
Normal file
1244
cinder/tests/test_huawei_t_dorado.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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
361
cinder/volume/drivers/huawei/huawei_t.py
Normal file
361
cinder/volume/drivers/huawei/huawei_t.py
Normal 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
|
||||
1129
cinder/volume/drivers/huawei/ssh_common.py
Normal file
1129
cinder/volume/drivers/huawei/ssh_common.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user