--- /dev/null
+# 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.
+"""
+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.drivers.huawei import huawei_iscsi
+
+LOG = logging.getLogger(__name__)
+
+FakeXML = """<?xml version="1.0" encoding="UTF-8" ?>
+<config>
+ <Storage>
+ <ControllerIP0>10.10.10.1</ControllerIP0>
+ <ControllerIP1>10.10.10.2</ControllerIP1>
+ <UserName>admin</UserName>
+ <UserPassword>123456</UserPassword>
+ </Storage>
+ <LUN>
+ <LUNType>Thick</LUNType>
+ <StripUnitSize>64</StripUnitSize>
+ <WriteType>1</WriteType>
+ <MirrorSwitch>1</MirrorSwitch>
+ <Prefetch Type="3" Value="0"/>
+ <StoragePool Name="RAID_001"/>
+ <StoragePool Name="RAID_002"/>
+ </LUN>
+ <iSCSI>
+ <DefaultTargetIP>192.168.100.1</DefaultTargetIP>
+ <Initiator Name="iqn.1993-08.debian:01:ec2bff7ac3a3"
+ TargetIP="192.168.100.2"/>
+ </iSCSI>
+</config>"""
+
+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)
+ self.driver = FakeHuaweiStorage()
+ self.driver.do_setup({})
+ self.driver._test_flg = 'check_for_fail'
+ self._test_check_for_setup_errors()
+
+ def setUp(self):
+ super(HuaweiVolumeTestCase, self).setUp()
+
+ 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 _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 = str(int(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'])
+
+ out = out.replace('\n', '\r\n')
+ return out
+
+ def _read_xml(self):
+ try:
+ root = ET.fromstring(FakeXML)
+ except Exception as err:
+ LOG.debug(_('_read_xml:ERROR:%s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+ return root
--- /dev/null
+# 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.
+"""
+Volume driver for HUAWEI T series and Dorado storage systems.
+"""
+
+import re
+import socket
+import time
+from xml.etree import ElementTree as ET
+
+from cinder import exception
+from cinder import flags
+from cinder.openstack.common import cfg
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import utils
+from cinder.volume import driver
+
+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')]
+
+FLAGS = flags.FLAGS
+FLAGS.register_opts(huawei_opt)
+
+HOST_GROUP_NAME = 'HostGroup_OpenStack'
+HOST_NAME_PREFIX = 'Host_'
+HOST_PORT_PREFIX = 'HostPort_'
+VOL_AND_SNAP_NAME_FREFIX = 'OpenStack_'
+READBUFFERSIZE = 8192
+
+
+class SSHConnection(utils.SSHPool):
+ """An SSH connetion class .
+
+ For some reasons, we can not use method ssh_execute defined in utils to
+ send CLI commands. Here we define a new class inherited to SSHPool. Use
+ method create() to build a new SSH client and use invoke_shell() to start
+ an interactive shell session on the storage system.
+ """
+
+ def __init__(self, ip, port, login, password, conn_timeout,
+ privatekey=None, *args, **kwargs):
+ self.ssh = None
+ super(SSHConnection, self).__init__(ip, port, conn_timeout, login,
+ password, privatekey=None,
+ *args, **kwargs)
+
+ def connect(self):
+ """Create an SSH client and open an interactive SSH channel."""
+ self.ssh = self.create()
+ self.channel = self.ssh.invoke_shell()
+ self.channel.resize_pty(600, 800)
+
+ def close(self):
+ """Close SSH connection."""
+ self.channel.close()
+ self.ssh.close()
+
+ def read(self, timeout=None):
+ """Read data from SSH channel."""
+ result = ''
+ user_flg = self.login + ':/>$'
+ self.channel.settimeout(timeout)
+ while True:
+ try:
+ result = result + self.channel.recv(READBUFFERSIZE)
+ except socket.timeout:
+ break
+ else:
+ # If we get the complete result string, then no need to wait
+ # until time out.
+ if re.search(user_flg, result) or re.search('(y/n)', result):
+ break
+ return result
+
+ def send_cmd(self, strcmd, timeout, waitstr=None):
+ """Send SSH commands and return results."""
+ info = ''
+ self.channel.send(strcmd + '\n')
+ result = self.read(timeout)
+ info = '\r\n'.join(result.split('\r\n')[1:-1])
+ return info
+
+
+class HuaweiISCSIDriver(driver.ISCSIDriver):
+ """Huawei T series and Dorado iSCSI volume driver."""
+
+ def __init__(self, *args, **kwargs):
+ super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
+ self.device_type = {}
+ self.login_info = {}
+ self.hostgroup_id = None
+
+ # Flag to tell whether the other controller is available
+ # if the current controller can not be connected to.
+ self.controller_alterable = True
+
+ def do_setup(self, context):
+ """Check config file."""
+ LOG.debug(_('do_setup.'))
+
+ self._check_conf_file()
+ # Create hostgroup.
+ self.login_info = self._get_login_info()
+ hostgroup_name = HOST_GROUP_NAME
+ self.hostgroup_id = self._find_hostgroup(hostgroup_name)
+ if not self.hostgroup_id:
+ self._create_hostgroup(hostgroup_name)
+ self.hostgroup_id = self._find_hostgroup(hostgroup_name)
+
+ def check_for_setup_error(self):
+ """Try to connect with device and get device type."""
+ LOG.debug(_('check_for_setup_error.'))
+
+ self.login_info = self._get_login_info()
+ self.device_type = self._get_device_type()
+ if not self.device_type['type']:
+ err_msg = (_('check_for_setup_error: Can not get device type.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ LOG.debug(_('check_for_setup_error: Device type is:%(type)s, '
+ 'version is:%(version)s.')
+ % {'type': self.device_type['type'],
+ 'version': self.device_type['version']})
+
+ # Now only version V1 is supported.
+ if self.device_type['version'] != 'V100R':
+ err_msg = (_('check_for_setup_error: Product version not right. '
+ 'Please make sure the product version is V1.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def create_volume(self, volume):
+ """Create a new volume."""
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('create_volume:volume name: %s.') % volume_name)
+
+ self.login_info = self._get_login_info()
+ if int(volume['size']) == 0:
+ volume_sise = '100M'
+ else:
+ volume_sise = '%sG' % volume['size']
+
+ self._create_volume(volume_name, volume_sise)
+
+ def delete_volume(self, volume):
+ """Delete a volume."""
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('delete_volume: volume name: %s.') % volume_name)
+
+ self.login_info = self._get_login_info()
+ volume_id = self._find_lun(volume_name)
+ if volume_id:
+ self._delete_volume(volume_name, volume_id)
+ else:
+ err_msg = (_('delete_volume:No need to delete volume.'
+ 'Volume %(name)s does not exist.')
+ % {'name': volume['name']})
+ LOG.error(err_msg)
+
+ def create_export(self, context, volume):
+ """Driver entry point to get the export info for a new volume."""
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('create_export: volume name:%s') % volume['name'])
+
+ lun_id = self._find_lun(volume_name)
+ if not lun_id:
+ err_msg = (_('create_export:Volume %(name)s does not exist.')
+ % {'name': volume_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ return {'provider_location': lun_id}
+
+ def ensure_export(self, context, volume):
+ """Driver entry point to get the export info for a existing volume."""
+ pass
+
+ def remove_export(self, context, volume_id):
+ """Driver entry point to remove an export for a volume."""
+ pass
+
+ def initialize_connection(self, volume, connector):
+ """Map a volume to a host and return target iSCSI information."""
+ initiator_name = connector['initiator']
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('initialize_connection: volume name: %(volume)s. '
+ 'initiator name: %(ini)s.')
+ % {'volume': volume_name,
+ 'ini': initiator_name})
+
+ host_name = HOST_NAME_PREFIX + str(hash(initiator_name))
+ host_id = self._find_host_in_hostgroup(host_name, self.hostgroup_id)
+ if not host_id:
+ self._add_host(host_name, self.hostgroup_id)
+ host_id = self._find_host_in_hostgroup(host_name,
+ self.hostgroup_id)
+
+ # Create an initiator.
+ added = self._check_initiator(initiator_name)
+ if not added:
+ self._add_initiator(initiator_name)
+
+ # Add the initiator to host.
+ port_name = HOST_PORT_PREFIX + str(hash(initiator_name))
+ port_info = initiator_name
+ portadded = False
+ hostport_info = self._get_hostport_info(host_id)
+ if hostport_info:
+ for hostport in hostport_info:
+ if hostport['info'] == initiator_name:
+ portadded = True
+ break
+ if not portadded:
+ self._add_hostport(port_name, host_id, port_info)
+
+ LOG.debug(_('initialize_connection:host name: %(host)s,'
+ 'initiator name: %(ini)s, '
+ 'hostport name: %(port)s')
+ % {'host': host_name,
+ 'ini': initiator_name,
+ 'port': port_name})
+
+ # Get target iSCSI iqn.
+ iscsi_conf = self._get_iscsi_info()
+ target_ip = None
+ for ini in iscsi_conf['Initiator']:
+ if ini['Name'] == initiator_name:
+ target_ip = ini['TargetIP']
+ break
+ if not target_ip:
+ target_ip = iscsi_conf['DefaultTargetIP']
+
+ target_iqn = self._get_tgt_iqn(target_ip)
+ if not target_iqn:
+ err_msg = (_('initialize_connection:'
+ 'Failed to find target iSCSI iqn.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ # Map a LUN to a host if not mapped.
+ lun_id = self._find_lun(volume_name)
+ if not lun_id:
+ err_msg = (_('initialize_connection:Failed to find the '
+ 'given volume. '
+ 'volume name:%(volume)s.')
+ % {'volume': volume_name})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ hostlun_id = None
+ map_info = self._get_map_info(host_id)
+ if map_info:
+ for map in map_info:
+ if map['devlunid'] == lun_id:
+ hostlun_id = map['hostlunid']
+ break
+ # The LUN is not mapped to the host.
+ if not hostlun_id:
+ self._map_lun(lun_id, host_id)
+ hostlun_id = self._get_hostlunid(host_id, lun_id)
+
+ # Return iSCSI properties.
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
+ properties['target_iqn'] = target_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 terminate_connection(self, volume, connector, **kwargs):
+ """Delete map between a volume and a host."""
+ initiator_name = connector['initiator']
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('terminate_connection:volume name: %(volume)s, '
+ 'initiator name: %(ini)s.')
+ % {'volume': volume_name,
+ 'ini': initiator_name})
+
+ self.login_info = self._get_login_info()
+ host_name = HOST_NAME_PREFIX + str(hash(initiator_name))
+ host_id = self._find_host_in_hostgroup(host_name, self.hostgroup_id)
+ if not host_id:
+ err_msg = (_('terminate_connection:Host does not exist. '
+ 'Host name:%(host)s.')
+ % {'host': host_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ # Delete host map.
+ lun_id = self._find_lun(volume_name)
+ if not lun_id:
+ err_msg = (_('terminate_connection:volume does not exist.'
+ 'volume name:%(volume)s')
+ % {'volume': volume_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ map_id = None
+ mapnum = 0
+ map_info = self._get_map_info(host_id)
+ if map_info:
+ mapnum = len(map_info)
+ for map in map_info:
+ if map['devlunid'] == lun_id:
+ map_id = map['mapid']
+ break
+ if map_id:
+ self._delete_map(map_id)
+ mapnum = mapnum - 1
+ else:
+ LOG.error(_('terminate_connection:No map between host '
+ 'and volume. Host name:%(hostname)s,'
+ 'volume name:%(volumename)s.')
+ % {'hostname': host_name,
+ 'volumename': volume_name})
+
+ # Delete host initiator when no LUN mapped to it.
+ portnum = 0
+ hostportinfo = self._get_hostport_info(host_id)
+ if hostportinfo:
+ portnum = len(hostportinfo)
+ for hostport in hostportinfo:
+ if hostport['info'] == initiator_name and mapnum == 0:
+ self._delete_hostport(hostport['id'])
+ self._delete_initiator(initiator_name)
+ portnum = portnum - 1
+ break
+ else:
+ LOG.error(_('terminate_connection:No initiator is added '
+ 'to the host. Host name:%(hostname)s')
+ % {'hostname': host_name})
+
+ # Delete host when no initiator added to it.
+ if portnum == 0:
+ self._delete_host(host_id)
+
+ def create_snapshot(self, snapshot):
+ """Create a snapshot."""
+ snapshot_name = self._name_translate(snapshot['name'])
+ volume_name = self._name_translate(snapshot['volume_name'])
+
+ LOG.debug(_('create_snapshot:snapshot name:%(snapshot)s, '
+ 'volume name:%(volume)s.')
+ % {'snapshot': snapshot_name,
+ 'volume': volume_name})
+
+ self.login_info = self._get_login_info()
+ if self.device_type['type'] == 'Dorado2100 G2':
+ err_msg = (_('create_snapshot:Device does not support snapshot.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ lun_id = self._find_lun(volume_name)
+ if not lun_id:
+ err_msg = (_('create_snapshot:Volume does not exist.'
+ 'Volume name:%(name)s')
+ % {'name': volume_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ self._create_snapshot(snapshot_name, lun_id)
+ snapshot_id = self._find_snapshot(snapshot_name)
+ if not snapshot_id:
+ err_msg = (_('create_snapshot:Snapshot does not exist.'
+ 'Snapshot name:%(name)s')
+ % {'name': snapshot_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+ self._active_snapshot(snapshot_id)
+
+ def delete_snapshot(self, snapshot):
+ """Delete a snapshot."""
+ snapshot_name = self._name_translate(snapshot['name'])
+ volume_name = self._name_translate(snapshot['volume_name'])
+
+ LOG.debug(_('delete_snapshot:snapshot name:%(snapshot)s, '
+ 'volume name:%(volume)s.')
+ % {'snapshot': snapshot_name,
+ 'volume': volume_name})
+
+ self.login_info = self._get_login_info()
+ if self.device_type['type'] == 'Dorado2100 G2':
+ err_msg = (_('delete_snapshot:Device does not support snapshot.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ snapshot_id = self._find_snapshot(snapshot_name)
+ if snapshot_id:
+ self._disable_snapshot(snapshot_id)
+ self._delete_snapshot(snapshot_id)
+ else:
+ err_msg = (_('delete_snapshot:Snapshot does not exist. '
+ 'snapshot name:%(snap)s')
+ % {'snap': snapshot_name})
+ LOG.debug(err_msg)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Create a volume from a snapshot.
+
+ We use LUNcopy to create a new LUN from snapshot.
+ """
+ snapshot_name = self._name_translate(snapshot['name'])
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('create_volume_from_snapshot:snapshot '
+ 'name:%(snapshot)s, '
+ 'volume name:%(volume)s.')
+ % {'snapshot': snapshot_name,
+ 'volume': volume_name})
+
+ self.login_info = self._get_login_info()
+ if self.device_type['type'].find('Dorado') > -1:
+ err_msg = (_('create_volume_from_snapshot:Device does '
+ 'not support create volume from snapshot. '
+ 'Volume name:%(volume)s, '
+ 'snapshot name:%(snapshot)s.')
+ % {'volume': volume_name,
+ 'snapshot': snapshot_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ snapshot_id = self._find_snapshot(snapshot_name)
+ if not snapshot_id:
+ err_msg = (_('create_volume_from_snapshot:Snapshot does not exist.'
+ 'Snapshot name:%(name)s')
+ % {'name': snapshot_name})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ # Create a target LUN.
+ if int(volume['size']) == 0:
+ volume_size = '%sG' % snapshot['volume_size']
+ else:
+ volume_size = '%sG' % volume['size']
+
+ self._create_volume(volume_name, volume_size)
+ volume_id = self._find_lun(volume_name)
+ luncopy_name = volume_name
+ try:
+ self._create_luncopy(luncopy_name, snapshot_id, volume_id)
+ luncopy_id = self._find_luncopy(luncopy_name)
+ self._start_luncopy(luncopy_id)
+ self._wait_for_luncopy(luncopy_name)
+ # If LUNcopy failed,we should delete the target volume.
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._delete_luncopy(luncopy_id)
+ self._delete_volume(volume_name, volume_id)
+
+ self._delete_luncopy(luncopy_id)
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume status.
+
+ If 'refresh' is True, run update the stats first.
+ """
+ if refresh:
+ self._update_volume_status()
+
+ return self._stats
+
+ def _check_conf_file(self):
+ """Check the config file, make sure the key elements are set."""
+ root = self._read_xml()
+ try:
+ IP1 = root.findtext('Storage/ControllerIP0')
+ IP2 = root.findtext('Storage/ControllerIP1')
+ username = root.findtext('Storage/UserName')
+ pwd = root.findtext('Storage/UserPassword')
+ pool = root.findall('LUN/StoragePool')
+
+ isconfwrong = False
+ if ((not IP1 and not IP2) or
+ (not username) or
+ (not pwd) or
+ (not pool)):
+ isconfwrong = True
+
+ elif not pool[0].attrib['Name']:
+ isconfwrong = True
+
+ if isconfwrong is True:
+ err_msg = (_('Config file is wrong. '
+ 'Controler IP, UserName, UserPassword and '
+ 'StoragePool must be set.'))
+ LOG.error(err_msg)
+ raise exception.InvalidInput(reason=err_msg)
+
+ except Exception as err:
+ LOG.error(_('_check_conf_file: %s') % str(err))
+ raise exception.VolumeBackendAPIException(data=err)
+
+ def _read_xml(self):
+ """Open xml file."""
+ filename = FLAGS.cinder_huawei_conf_file
+ try:
+ tree = ET.parse(filename)
+ root = tree.getroot()
+
+ except Exception as err:
+ LOG.error(_('_read_xml:%s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+ return root
+
+ def _get_login_info(self):
+ """Get login IP, username and password from config file."""
+ logininfo = {}
+ root = self._read_xml()
+ try:
+ logininfo['ControllerIP0'] = root.findtext('Storage/ControllerIP0')
+ logininfo['ControllerIP1'] = root.findtext('Storage/ControllerIP1')
+ logininfo['UserName'] = root.findtext('Storage/UserName')
+ logininfo['UserPassword'] = root.findtext('Storage/UserPassword')
+
+ except Exception as err:
+ LOG.debug(_('_get_login_info error. %s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+ return logininfo
+
+ def _get_lun_set_info(self):
+ """Get parameters from config file for creating LUN."""
+ # Default LUN set information
+ lunsetinfo = {'LUNType': 'Thick',
+ 'StripUnitSize': '64',
+ 'WriteType': '1',
+ 'MirrorSwitch': '1',
+ 'PrefetchType': '3',
+ 'PrefetchValue': '0',
+ 'PrefetchTimes': '0',
+ 'StoragePool': 'RAID_001'}
+
+ root = self._read_xml()
+ try:
+ luntype = root.findtext('LUN/LUNType')
+ if luntype in ['Thick', 'Thin']:
+ lunsetinfo['LUNType'] = luntype
+ elif luntype is not '' or luntype is not None:
+ err_msg = (_('Config file is wrong. LUNType must be "Thin"'
+ ' or "Thick". LUNType:%(type)s')
+ % {'type': luntype})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ # Here we do not judge whether the parameters are right.
+ # CLI will return error responses if the parameters not right.
+ stripunitsize = root.findtext('LUN/StripUnitSize')
+ if stripunitsize is not '' and stripunitsize is not None:
+ lunsetinfo['StripUnitSize'] = stripunitsize
+ writetype = root.findtext('LUN/WriteType')
+ if writetype is not '' and writetype is not None:
+ lunsetinfo['WriteType'] = writetype
+ mirrorswitch = root.findtext('LUN/MirrorSwitch')
+ if mirrorswitch is not '' and mirrorswitch is not None:
+ lunsetinfo['MirrorSwitch'] = mirrorswitch
+
+ if self.device_type['type'] == 'Tseries':
+ pooltype = luntype
+ prefetch = root.find('LUN/Prefetch')
+ if ((prefetch is not None) and
+ (prefetch.attrib['Type'] != '')):
+ lunsetinfo['PrefetchType'] = prefetch.attrib['Type']
+ if lunsetinfo['PrefetchType'] == '1':
+ lunsetinfo['PrefetchValue'] = prefetch.attrib['Value']
+ elif lunsetinfo['PrefetchType'] == '2':
+ lunsetinfo['PrefetchTimes'] = prefetch.attrib['Value']
+ else:
+ LOG.debug(_('_get_lun_set_info:Use default prefetch type. '
+ 'Prefetch type:Intelligent.'))
+
+ # No need to set Prefetch type for Dorado.
+ elif self.device_type['type'] == 'Dorado5100':
+ pooltype = 'Thick'
+ elif self.device_type['type'] == 'Dorado2100 G2':
+ return lunsetinfo
+
+ poolsinfo = self._find_pool_info(pooltype)
+ if not poolsinfo:
+ err_msg = (_('_get_lun_set_info:No available pools! '
+ 'Please check whether storage pool is created.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ pools = root.findall('LUN/StoragePool')
+ lunsetinfo['StoragePool'] = \
+ self._get_maximum_pool(pools, poolsinfo, luntype)
+
+ except Exception as err:
+ LOG.error(_('_get_lun_set_info:%s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+
+ return lunsetinfo
+
+ def _find_pool_info(self, pooltype):
+ """Return pools information created in storage device."""
+ if pooltype == 'Thick':
+ cli_cmd = ('showrg')
+ else:
+ cli_cmd = ('showpool')
+
+ out = self._execute_cli(cli_cmd)
+
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ pools_list = []
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ pools_list.append(r)
+ return pools_list
+
+ def _get_maximum_pool(self, poolinconf, poolindev, luntype):
+ """Get the maximum pool from config file.
+
+ According to the given pools' name in config file,
+ we select the pool of maximum free capacity.
+ """
+ maxpoolid = None
+ maxpoolsize = 0
+ if luntype == 'Thin':
+ nameindex = 1
+ sizeindex = 4
+ else:
+ nameindex = 5
+ sizeindex = 3
+
+ for pool in poolinconf:
+ poolname = pool.attrib['Name']
+ for pooldetail in poolindev:
+ if pooldetail[nameindex] == poolname:
+ if int(float(pooldetail[sizeindex])) > maxpoolsize:
+ maxpoolid = pooldetail[0]
+ maxpoolsize = int(float(pooldetail[sizeindex]))
+ break
+ if maxpoolid:
+ return maxpoolid
+ else:
+ err_msg = (_('_get_maximum_pool:maxpoolid is None.'
+ 'Please check config file and make sure '
+ 'the "Name" in "StoragePool" is right.'))
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _get_iscsi_info(self):
+ """Get iSCSI info from config file."""
+ iscsiinfo = {}
+ root = self._read_xml()
+ try:
+ iscsiinfo['DefaultTargetIP'] = \
+ root.findtext('iSCSI/DefaultTargetIP')
+ initiator_list = []
+ for dic in root.findall('iSCSI/Initiator'):
+ initiator_list.append(dic.attrib)
+ iscsiinfo['Initiator'] = initiator_list
+
+ except Exception as err:
+ LOG.error(_('_get_iscsi_info:%s') % str(err))
+
+ return iscsiinfo
+
+ def _execute_cli(self, cmd):
+ """Build SSH connection to execute CLI commands.
+
+ If the connection to first controller time out,
+ try to connect to the other controller.
+ """
+ user = self.login_info['UserName']
+ pwd = self.login_info['UserPassword']
+ while True:
+ if self.controller_alterable:
+ ip = self.login_info['ControllerIP0']
+ else:
+ ip = self.login_info['ControllerIP1']
+
+ try:
+ ssh = SSHConnection(ip, 22, user, pwd, 30)
+ ssh.connect()
+ while True:
+ out = ssh.send_cmd(cmd, 30)
+ if out.find('(y/n)') > -1:
+ cmd = 'y'
+ else:
+ break
+ ssh.close()
+ except Exception as err:
+ if ((not self.controller_alterable) and
+ (str(err).find('timed out') > -1)):
+ self.controller_alterable = False
+
+ LOG.debug(_('_execute_cli:Connect to controller0 %(ctr0)s'
+ ' time out.Try to Connect to controller1 '
+ '%(ctr1)s.')
+ % {'ctr0': self.login_info['ControllerIP0'],
+ 'ctr1': self.login_info['ControllerIP1']})
+
+ continue
+ else:
+ LOG.error(_('_execute_cli:%s') % err)
+ raise exception.VolumeBackendAPIException(data=err)
+
+ index = out.find(user + ':/>')
+ if index > -1:
+ return out[index:]
+ else:
+ return out
+
+ def _name_translate(self, name):
+ """Form new names because of the 32-character limit on names."""
+ newname = VOL_AND_SNAP_NAME_FREFIX + str(hash(name))
+
+ LOG.debug(_('_name_translate:Name in cinder: %(old)s, '
+ 'new name in storage system: %(new)s')
+ % {'old': name,
+ 'new': newname})
+
+ return newname
+
+ def _find_lun(self, name):
+ """Get the ID of a LUN with the given LUN name."""
+ cli_cmd = ('showlun')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ if 'Dorado2100 G2' == self.device_type['type']:
+ d = 2
+ elif 'Dorado5100' == self.device_type['type']:
+ d = 1
+ else:
+ d = 0
+
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[6 - d] == name:
+ return r[0]
+ return None
+
+ def _create_hostgroup(self, hostgroupname):
+ """Create a host group."""
+ cli_cmd = ('createhostgroup -n %(name)s'
+ % {'name': hostgroupname})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_create_hostgroup:Failed to Create hostgroup. '
+ 'Hostgroup name: %(name)s. '
+ 'out:%(out)s.')
+ % {'name': hostgroupname,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _find_hostgroup(self, groupname):
+ """Get the given hostgroup ID."""
+ cli_cmd = ('showhostgroup')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[1] == groupname:
+ return r[0]
+ return None
+
+ def _add_host(self, hostname, hostgroupid):
+ """Add a new host."""
+ cli_cmd = ('addhost -group %(groupid)s -n %(hostname)s -t 0'
+ % {'groupid': hostgroupid,
+ 'hostname': hostname})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_add_host:Failed to add host to hostgroup.'
+ 'host name:%(host)s '
+ 'hostgroup id:%(hostgroup)s '
+ 'out:%(out)s')
+ % {'host': hostname,
+ 'hostgroup': hostgroupid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _check_initiator(self, ininame):
+ """Check whether the initiator is already added."""
+ cli_cmd = ('showiscsiini -ini %(name)s'
+ % {'name': ininame})
+ out = self._execute_cli(cli_cmd)
+ if out.find('Initiator Information') > -1:
+ return True
+ else:
+ return False
+
+ def _add_initiator(self, ininame):
+ """Add a new initiator to storage device."""
+ cli_cmd = ('addiscsiini -n %(name)s'
+ % {'name': ininame})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_add_initiator:Failed to add initiator.'
+ 'initiator name:%(name)s '
+ 'out:%(out)s')
+ % {'name': ininame,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_initiator(self, ininame):
+ """Delete an initiator."""
+ cli_cmd = ('deliscsiini -n %(name)s'
+ % {'name': ininame})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_delete_initiator:ERROE:Failed to delete initiator.'
+ 'initiator name:%(name)s '
+ 'out:%(out)s')
+ % {'name': ininame,
+ 'out': out})
+ LOG.error(err_msg)
+
+ def _find_host_in_hostgroup(self, hostname, hostgroupid):
+ """Get the given host ID."""
+ cli_cmd = ('showhost -group %(groupid)s'
+ % {'groupid': hostgroupid})
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) < 6:
+ return None
+
+ en = out.split('\r\n')
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[1] == hostname:
+ return r[0]
+ return None
+
+ def _get_hostport_info(self, hostid):
+ """Get hostports details of the given host."""
+ cli_cmd = ('showhostport -host %(hostid)s'
+ % {'hostid': hostid})
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) < 6:
+ return None
+
+ hostportinfo = []
+ list_key = ['id', 'name', 'info', 'type', 'hostid',
+ 'linkstatus', 'multioathtype']
+ en = out.split('\r\n')
+ for i in range(6, len(en) - 2):
+ list_val = en[i].split()
+ hostport_dic = dict(map(None, list_key, list_val))
+ hostportinfo.append(hostport_dic)
+ return hostportinfo
+
+ def _add_hostport(self, portname, hostid, portinfo, multipathtype=0):
+ """Add a host port."""
+ cli_cmd = ('addhostport -host %(id)s -type 5 '
+ '-info %(info)s -n %(name)s -mtype %(mtype)s'
+ % {'id': hostid,
+ 'info': portinfo,
+ 'name': portname,
+ 'mtype': multipathtype})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_add_hostport:Failed to add hostport. '
+ 'port name:%(port)s'
+ 'port information:%(info)s '
+ 'host id:%(host)s'
+ 'out:%(out)s')
+ % {'port': portname,
+ 'info': portinfo,
+ 'host': hostid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_hostport(self, portid):
+ """Delete a host port."""
+ cli_cmd = ('delhostport -force -p %(portid)s'
+ % {'portid': portid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_delete_hostport:Failed to delete host port. '
+ 'port id:%(portid)s')
+ % {'portid': portid})
+ LOG.error(err_msg)
+
+ def _get_tgt_iqn(self, iscsiip):
+ """Get target iSCSI iqn."""
+ LOG.debug(_('_get_tgt_iqn:iSCSI IP is %s.') % iscsiip)
+ cli_cmd = ('showiscsitgtname')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) < 4:
+ return None
+
+ index = en[4].find('iqn')
+ iqn_prefix = en[4][index:]
+ iqn_prefix.strip()
+ iscsiip_info = self._get_iscsi_ip_info(iscsiip)
+ if iscsiip_info:
+ if iscsiip_info['ctrid'] == 'A':
+ ctr = '0'
+ elif iscsiip_info['ctrid'] == 'B':
+ ctr = '1'
+
+ interface = '0' + iscsiip_info['interfaceid']
+ port = iscsiip_info['portid'].replace('P', '0')
+ iqn_suffix = ctr + '02' + interface + port
+ for i in range(0, len(iqn_suffix)):
+ if iqn_suffix[i] != '0':
+ iqn_suffix = iqn_suffix[i:]
+ break
+ if self.device_type['type'] == 'Tseries':
+ iqn = iqn_prefix + ':' + iqn_suffix + ':' \
+ + iscsiip_info['ipaddress']
+ elif self.device_type['type'] == "Dorado2100 G2":
+ iqn = iqn_prefix + ":" + iscsiip_info['ipaddress'] + "-" \
+ + iqn_suffix
+ else:
+ iqn = iqn_prefix + ':' + iscsiip_info['ipaddress']
+
+ LOG.debug(_('_get_tgt_iqn:iSCSI target iqn is:%s') % iqn)
+
+ return iqn
+ else:
+ return None
+
+ def _get_iscsi_ip_info(self, iscsiip):
+ """Get iSCSI IP infomation of storage device."""
+ cli_cmd = ('showiscsiip')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) < 6:
+ return None
+
+ iscsiIPinfo = {}
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[3] == iscsiip:
+ iscsiIPinfo['ctrid'] = r[0]
+ iscsiIPinfo['interfaceid'] = r[1]
+ iscsiIPinfo['portid'] = r[2]
+ iscsiIPinfo['ipaddress'] = r[3]
+ return iscsiIPinfo
+ return None
+
+ def _map_lun(self, lunid, hostid):
+ """Map a lun to a host."""
+ cli_cmd = ('addhostmap -host %(hostid)s -devlun %(lunid)s'
+ % {'hostid': hostid,
+ 'lunid': lunid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_map_lun:Failed to add hostmap.'
+ 'hostid:%(host)s'
+ 'lunid:%(lun)s.'
+ 'out:%(out)s')
+ % {'host': hostid,
+ 'lun': lunid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _get_hostlunid(self, hostid, lunid):
+ """Get the hostLUN ID of a LUN according host ID and LUN ID."""
+ mapinfo = self._get_map_info(hostid)
+ if mapinfo:
+ for map in mapinfo:
+ if map['devlunid'] == lunid:
+ return map['hostlunid']
+ return None
+
+ def _delete_map(self, mapid, attempts=1):
+ """Remove the map."""
+ cli_cmd = ('delhostmap -force -map %(mapid)s'
+ % {'mapid': mapid})
+ while attempts >= 0:
+ attempts -= 1
+ out = self._execute_cli(cli_cmd)
+
+ # We retry to delete host map 10s later if there are
+ # IOs accessing the system.
+ if re.search('command operates successfully', out):
+ break
+ else:
+ if re.search('there are IOs accessing the system', out):
+ time.sleep(10)
+ LOG.debug(_('_delete_map:There are IOs accessing '
+ 'the system. Retry to delete host map. '
+ 'map id:%(mapid)s')
+ % {'mapid': mapid})
+ continue
+ else:
+ err_msg = (_('_delete_map:Failed to delete host map.'
+ ' mapid:%(mapid)s '
+ 'out:%(out)s')
+ % {'mapid': mapid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_host(self, hostid):
+ """Delete a host."""
+ cli_cmd = ('delhost -force -host %(hostid)s'
+ % {'hostid': hostid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('Error: Failed delete host,host id: %(hostid)s.'
+ 'out:%(out)s')
+ % {'hostid': hostid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _get_map_info(self, hostid):
+ """Get map infomation of the given host."""
+ cli_cmd = ('showhostmap -host %(hostid)s'
+ % {'hostid': hostid})
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ mapinfo = []
+ list_tmp = []
+ list_key = ['mapid', 'devlunid', 'hostlunid']
+ en = out.split('\r\n')
+ for i in range(6, len(en) - 2):
+ list_tmp = en[i].split()
+ list_val = [list_tmp[0], list_tmp[2], list_tmp[4]]
+ dic = dict(map(None, list_key, list_val))
+ mapinfo.append(dic)
+ return mapinfo
+
+ def _get_device_type(self):
+ """Get the storage device type and product version."""
+ cli_cmd = ('showsys')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ for line in en:
+ if re.search('Device Type', line):
+ if re.search('T$', line):
+ device_type = 'Tseries'
+ elif re.search('Dorado2100 G2$', line):
+ device_type = 'Dorado2100 G2'
+ elif re.search('Dorado5100$', line):
+ device_type = 'Dorado5100'
+ else:
+ device_type = None
+ continue
+
+ if re.search('Product Version', line):
+ if re.search('V100R+', line):
+ product_version = 'V100R'
+ else:
+ product_version = None
+ break
+
+ r = {'type': device_type, 'version': product_version}
+ return r
+
+ def _active_snapshot(self, snapshotid):
+ """Active a snapshot."""
+ cli_cmd = ('actvsnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_active_snapshot:Failed to active snapshot. '
+ 'snapshot id:%(name)s. '
+ 'out:%(out)s')
+ % {'name': snapshotid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _disable_snapshot(self, snapshotid):
+ """Disable a snapshot."""
+ cli_cmd = ('disablesnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_disable_snapshot:Failed to disable snapshot. '
+ 'snapshot id:%(id)s. '
+ 'out:%(out)s')
+ % {'id': snapshotid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_snapshot(self, snapshotid):
+ """Delete a snapshot."""
+ cli_cmd = ('delsnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_delete_snapshot:Failed to delete snapshot. '
+ 'snapshot id:%(id)s. '
+ 'out:%(out)s')
+ % {'id': snapshotid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _create_volume(self, name, size):
+ """Create a new volume with the given name and size."""
+ lunsetinfo = self._get_lun_set_info()
+ cli_cmd = ('createlun -n %(name)s -lunsize %(size)s '
+ '-wrtype %(wrtype)s '
+ % {'name': name,
+ 'size': size,
+ 'wrtype': lunsetinfo['WriteType']})
+
+ # If write type is "write through", no need to set mirror switch.
+ if lunsetinfo['WriteType'] != '2':
+ cli_cmd = cli_cmd + ('-mirrorsw %(mirrorsw)s '
+ % {'mirrorsw': lunsetinfo['MirrorSwitch']})
+
+ # Differences exist between "Thin" and "thick" LUN for CLI commands.
+ luntype = lunsetinfo['LUNType']
+ if luntype == 'Thin':
+ dorado2100g2_luntype = '2'
+ Tseries = ('-pool %(pool)s '
+ % {'pool': lunsetinfo['StoragePool']})
+ else:
+ dorado2100g2_luntype = '3'
+ Tseries = ('-rg %(raidgroup)s -susize %(susize)s '
+ % {'raidgroup': lunsetinfo['StoragePool'],
+ 'susize': lunsetinfo['StripUnitSize']})
+
+ prefetch_value_or_times = ''
+ pretype = '-pretype %s ' % lunsetinfo['PrefetchType']
+ # If constant prefetch, we should set prefetch value.
+ if lunsetinfo['PrefetchType'] == '1':
+ prefetch_value_or_times = '-value %s' % lunsetinfo['PrefetchValue']
+ # If variable prefetch, we should set prefetch mutiple.
+ elif lunsetinfo['PrefetchType'] == '2':
+ prefetch_value_or_times = '-times %s' % lunsetinfo['PrefetchTimes']
+
+ if self.device_type['type'] == 'Tseries':
+ cli_cmd = cli_cmd + Tseries + pretype + prefetch_value_or_times
+
+ elif self.device_type['type'] == 'Dorado5100':
+ cli_cmd = cli_cmd + ('-rg %(raidgroup)s -susize %(susize)s'
+ % {'raidgroup': lunsetinfo['StoragePool'],
+ 'susize': lunsetinfo['StripUnitSize']})
+
+ elif self.device_type['type'] == 'Dorado2100 G2':
+ cli_cmd = cli_cmd + ('-type %(type)s'
+ % {'type': dorado2100g2_luntype})
+
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_create_volume:Failed to Create volume. '
+ 'volume name:%(name)s. '
+ 'out:%(out)s')
+ % {'name': name,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_volume(self, name, lunid):
+ """Delete a volume."""
+ cli_cmd = ('dellun -force -lun %s' % (lunid))
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_delete_volume:Failed to delete volume. '
+ 'Volume name:%(name)s '
+ 'out:%(out)s')
+ % {'name': name,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _create_luncopy(self, luncopyname, srclunid, tgtlunid):
+ """Create a LUNcopy."""
+ cli_cmd = ('createluncopy -n %(name)s -l 4 -slun %(srclunid)s '
+ '-tlun %(tgtlunid)s'
+ % {'name': luncopyname,
+ 'srclunid': srclunid,
+ 'tgtlunid': tgtlunid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_create_luncopy:Failed to Create LUNcopy. '
+ 'LUNcopy name:%(name)s '
+ 'out:%(out)s')
+ % {'name': luncopyname,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _start_luncopy(self, luncopyid):
+ """Starte a LUNcopy."""
+ cli_cmd = ('chgluncopystatus -luncopy %(luncopyid)s -start'
+ % {'luncopyid': luncopyid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_start_luncopy:Failed to start LUNcopy. '
+ 'LUNcopy id:%(luncopyid)s '
+ 'out:%(out)s')
+ % {'luncopyid': luncopyid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _find_luncopy(self, luncopyname):
+ """Get the given LUNcopy's ID."""
+ cli_cmd = ('showluncopy')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[0] == luncopyname:
+ luncopyid = r[1]
+ return luncopyid
+ return None
+
+ def _wait_for_luncopy(self, luncopyname):
+ """Wait for LUNcopy to complete."""
+ while True:
+ luncopy_info = self._get_luncopy_info(luncopyname)
+ if luncopy_info['state'] == 'Complete':
+ break
+ elif luncopy_info['status'] != 'Normal':
+ err_msg = (_('_wait_for_luncopy:LUNcopy status isnot normal. '
+ 'LUNcopy name:%(luncopyname)s')
+ % {'luncopyname': luncopyname})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ time.sleep(10)
+
+ def _get_luncopy_info(self, luncopyname):
+ """Get LUNcopy information."""
+ cli_cmd = ('showluncopy')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ luncopyinfo = {}
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[0] == luncopyname:
+ luncopyinfo['name'] = r[0]
+ luncopyinfo['id'] = r[1]
+ luncopyinfo['state'] = r[3]
+ luncopyinfo['status'] = r[4]
+ return luncopyinfo
+ return None
+
+ def _delete_luncopy(self, luncopyid):
+ """Delete a LUNcopy."""
+ cli_cmd = ('delluncopy -luncopy %(id)s'
+ % {'id': luncopyid})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_delete_luncopy:Failed to delete LUNcopy. '
+ 'LUNcopy id:%(luncopyid)s '
+ 'out:%(out)s')
+ % {'luncopyid': luncopyid,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _create_snapshot(self, snapshotname, srclunid):
+ """Create a snapshot with snapshot name and source LUN ID."""
+ cli_cmd = ('createsnapshot -lun %(lunid)s -n %(snapname)s'
+ % {'lunid': srclunid,
+ 'snapname': snapshotname})
+ out = self._execute_cli(cli_cmd)
+ if not re.search('command operates successfully', out):
+ err_msg = (_('_create_snapshot:Failed to Create snapshot. '
+ 'Snapshot name:%(name)s '
+ 'out:%(out)s')
+ % {'name': snapshotname,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _find_snapshot(self, snapshotname):
+ """Get the given snapshot ID."""
+ cli_cmd = ('showsnapshot')
+ out = self._execute_cli(cli_cmd)
+ en = out.split('\r\n')
+ if len(en) <= 6:
+ return None
+
+ for i in range(6, len(en) - 2):
+ r = en[i].split()
+ if r[0] == snapshotname:
+ return r[1]
+ return None
+
+ def _update_volume_status(self):
+ """Retrieve status info from volume group."""
+
+ LOG.debug(_("Updating volume status"))
+ data = {}
+ data['volume_backend_name'] = 'HuaweiISCSIDriver'
+ data['vendor_name'] = 'Huawei'
+ data['driver_version'] = '1.0'
+ data['storage_protocol'] = 'iSCSI'
+
+ data['total_capacity_gb'] = 'infinite'
+ data['free_capacity_gb'] = self._get_free_capacity()
+ data['reserved_percentage'] = 0
+
+ self._stats = data
+
+ def _get_free_capacity(self):
+ """Get total free capacity of pools."""
+ self.login_info = self._get_login_info()
+ self.device_type = self._get_device_type()
+
+ root = self._read_xml()
+ lun_type = root.findtext('LUN/LUNType')
+ if (self.device_type['type'] == 'Dorado5100' or not lun_type):
+ lun_type = 'Thick'
+ elif self.device_type['type'] == 'Dorado2100 G2':
+ lun_type = 'Thin'
+ poolinfo_dev = self._find_pool_info(lun_type)
+ pools_conf = root.findall('LUN/StoragePool')
+ total_free_capacity = 0
+ for poolinfo in poolinfo_dev:
+ if self.device_type['type'] == 'Dorado2100 G2':
+ total_free_capacity += float(poolinfo[2])
+ continue
+ for pool in pools_conf:
+ if ((self.device_type['type'] == 'Dorado5100') and
+ (poolinfo[5] == pool.attrib['Name'])):
+ total_free_capacity += float(poolinfo[3])
+ break
+ else:
+ if ((lun_type == 'Thick') and
+ (poolinfo[5] == pool.attrib['Name'])):
+ total_free_capacity += float(poolinfo[3])
+ break
+ elif poolinfo[1] == pool.attrib['Name']:
+ total_free_capacity += float(poolinfo[4])
+ break
+
+ return str(int(total_free_capacity / 1024))