From: zhangchao010 Date: Tue, 19 Feb 2013 03:39:25 +0000 (+0800) Subject: Add HUAWEI volume driver in Cinder X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=74d1add5b7181b75ca4eb564028e8e07c0c48030;p=openstack-build%2Fcinder-build.git Add HUAWEI volume driver in Cinder This is an iSCSI driver.It supports HUAWEI OceanStor T and Dorado series storage systems.The driver communicates with storage systems based on SSH. Implements: blueprint huawei-volume-driver Change-Id: Ib3fd8ff636106e751789f6367a5b57bdaab316f4 --- diff --git a/cinder/tests/test_huawei.py b/cinder/tests/test_huawei.py new file mode 100644 index 000000000..032476170 --- /dev/null +++ b/cinder/tests/test_huawei.py @@ -0,0 +1,805 @@ +# 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 = """ + + + 10.10.10.1 + 10.10.10.2 + admin + 123456 + + + Thick + 64 + 1 + 1 + + + + + + 192.168.100.1 + + +""" + +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 diff --git a/cinder/volume/drivers/huawei/__init__.py b/cinder/volume/drivers/huawei/__init__.py new file mode 100644 index 000000000..0f4b6d394 --- /dev/null +++ b/cinder/volume/drivers/huawei/__init__.py @@ -0,0 +1,15 @@ +# 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. diff --git a/cinder/volume/drivers/huawei/cinder_huawei_conf.xml.sample b/cinder/volume/drivers/huawei/cinder_huawei_conf.xml.sample new file mode 100644 index 000000000..8d5a577bf --- /dev/null +++ b/cinder/volume/drivers/huawei/cinder_huawei_conf.xml.sample @@ -0,0 +1,34 @@ + + + + x.x.x.x + x.x.x.x + xxxxxx + xxxxxx + + + + Thick + + 64 + + + 1 + + 1 + + + + + + + + + + + + x.x.x.x + + + + diff --git a/cinder/volume/drivers/huawei/huawei_iscsi.py b/cinder/volume/drivers/huawei/huawei_iscsi.py new file mode 100644 index 000000000..4667925f3 --- /dev/null +++ b/cinder/volume/drivers/huawei/huawei_iscsi.py @@ -0,0 +1,1382 @@ +# 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))