+++ /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.
-"""
-import mox
-import os
-import shutil
-import tempfile
-from xml.dom.minidom import Document
-from xml.etree import ElementTree as ET
-
-from cinder import exception
-from cinder.openstack.common import log as logging
-from cinder import test
-from cinder.volume import configuration as conf
-from cinder.volume.drivers.huawei import huawei_iscsi
-
-LOG = logging.getLogger(__name__)
-
-LUNInfo = {'ID': None,
- 'Name': None,
- 'Size': None,
- 'LUN WWN': None,
- 'Status': None,
- 'Visible Capacity': None,
- 'Stripe Unit Size': None,
- 'Disk Pool ID': None,
- 'Format Progress': None,
- 'Cache Prefetch Strategy': None,
- 'LUNType': None,
- 'Cache Write Strategy': None,
- 'Running Cache Write Strategy': None,
- 'Consumed Capacity': None,
- 'Pool ID': None,
- 'SnapShot ID': None,
- 'LunCopy ID': None,
- 'Whether Private LUN': None,
- 'Remote Replication ID': None,
- 'Split mirror ID': None,
- 'Owner Controller': None,
- 'Worker Controller': None,
- 'RAID Group ID': None}
-
-LUNInfoCopy = {'ID': None,
- 'Name': None,
- 'Size': None,
- 'LUN WWN': None,
- 'Status': None,
- 'Visible Capacity': None,
- 'Stripe Unit Size': None,
- 'Disk Pool ID': None,
- 'Format Progress': None,
- 'Cache Prefetch Strategy': None,
- 'LUNType': None,
- 'Cache Write Strategy': None,
- 'Running Cache Write Strategy': None,
- 'Consumed Capacity': None,
- 'Pool ID': None,
- 'SnapShot ID': None,
- 'LunCopy ID': None,
- 'Whether Private LUN': None,
- 'Remote Replication ID': None,
- 'Split mirror ID': None,
- 'Owner Controller': None,
- 'Worker Controller': None,
- 'RAID Group ID': None}
-
-SnapshotInfo = {'Source LUN ID': None,
- 'Source LUN Name': None,
- 'ID': None,
- 'Name': None,
- 'Type': 'Public',
- 'Status': None,
- 'Time Stamp': '2013-01-15 14:00:00',
- 'Rollback Start Time': '--',
- 'Rollback End Time': '--',
- 'Rollback Speed': '--',
- 'Rollback Progress': '--'}
-
-MapInfo = {'Host Group ID': None,
- 'Host Group Name': None,
- 'File Engine Cluster': None,
- 'Host ID': None,
- 'Host Name': None,
- 'Os Type': None,
- 'INI Port ID': None,
- 'INI Port Name': None,
- 'INI Port Info': None,
- 'Port Type': None,
- 'Link Status': None,
- 'LUN WWN': None,
- 'DEV LUN ID': None,
- 'Host LUN ID': None}
-
-HostPort = {'ID': None,
- 'Name': None,
- 'Info': None}
-
-LUNCopy = {'Name': None,
- 'ID': None,
- 'Type': None,
- 'State': None,
- 'Status': 'Disable'}
-
-FakeVolume = {'name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
- 'size': '2',
- 'id': '0',
- 'wwn': '630303710030303701094b2b00000031',
- 'provider_auth': None}
-
-FakeVolumeCopy = {'name': 'Volume-jeje34fe-223f-dd33-4423-asdfghjklqwg',
- 'size': '3',
- 'ID': '1',
- 'wwn': '630303710030303701094b2b0000003'}
-
-FakeLUNCopy = {'ID': '1',
- 'Type': 'FULL',
- 'State': 'Created',
- 'Status': 'Normal'}
-
-FakeSnapshot = {'name': 'keke34fe-223f-dd33-4423-asdfghjklqwf',
- 'volume_name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
- 'id': '3'}
-
-FakePoolInfo = {'ID': '2',
- 'Level': 'RAID6',
- 'Status': 'Normal',
- 'Free Capacity': '10240',
- 'Disk List': '0,1;0,2;0,3;0,4;0,5;0,6',
- 'Name': 'RAID_001',
- 'Type': 'Thick'}
-
-FakeConfInfo = {'HostGroup': 'HostGroup_OpenStack',
- 'HostnamePrefix': 'Host_',
- 'DefaultTargetIP': '192.168.100.1',
- 'TargetIQN': 'iqn.2006-08.com.huawei:oceanspace:2103037:',
- 'TargetIQN-T': 'iqn.2006-08.com.huawei:oceanspace:2103037::'
- '20001:192.168.100.2',
- 'TargetIQN-Dorado5100': 'iqn.2006-08.com.huawei:oceanspace:'
- '2103037::192.168.100.2',
- 'TargetIQN-Dorado2100G2': 'iqn.2006-08.com.huawei:oceanspace:'
- '2103037::192.168.100.2-20001',
- 'Initiator Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
- 'Initiator TargetIP': '192.168.100.2'}
-
-FakeConnector = {'initiator': "iqn.1993-08.debian:01:ec2bff7ac3a3"}
-
-
-class HuaweiVolumeTestCase(test.TestCase):
-
- def __init__(self, *args, **kwargs):
- super(HuaweiVolumeTestCase, self).__init__(*args, **kwargs)
-
- def setUp(self):
- super(HuaweiVolumeTestCase, self).setUp()
- self.tmp_dir = tempfile.mkdtemp()
- self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
- self._create_fake_conf_file()
- configuration = mox.MockObject(conf.Configuration)
- configuration.cinder_huawei_conf_file = self.fake_conf_file
- configuration.append_config_values(mox.IgnoreArg())
- self.driver = FakeHuaweiStorage(configuration=configuration)
-
- self.driver.do_setup({})
- self.driver._test_flg = 'check_for_fail'
- self._test_check_for_setup_errors()
-
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- super(HuaweiVolumeTestCase, self).tearDown()
-
- def test_create_export_failed(self):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_export,
- {}, FakeVolume)
-
- def test_delete_volume_failed(self):
- self._test_delete_volume()
-
- def test_create_snapshot_failed(self):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_snapshot,
- FakeSnapshot)
-
- def test_delete_snapshot_failed(self):
- self._test_delete_snapshot()
-
- def test_create_luncopy_failed(self):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_volume_from_snapshot,
- FakeVolumeCopy, FakeSnapshot)
-
- def test_initialize_failed(self):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.initialize_connection,
- FakeVolume, FakeConnector)
-
- def test_terminate_connection_failed(self):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.terminate_connection,
- FakeVolume, FakeConnector)
-
- def test_normal(self):
- # test for T Series
- self.driver._test_flg = 'check_for_T'
- self._test_check_for_setup_errors()
- self._test_create_volume()
- self._test_create_export()
- self._test_create_snapshot()
- self._test_create_volume_from_snapshot()
- self._test_initialize_connection_for_T()
- self._test_terminate_connection()
- self._test_delete_snapshot()
- self._test_delete_volume()
- self._test_get_get_volume_stats()
-
- # test for Dorado2100 G2
- self.driver._test_flg = 'check_for_Dorado2100G2'
- self._test_check_for_setup_errors()
- self._test_create_volume()
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_snapshot,
- FakeSnapshot)
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_volume_from_snapshot,
- FakeVolumeCopy, FakeSnapshot)
- self._test_initialize_connection_for_Dorado2100G2()
- self._test_terminate_connection()
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.delete_snapshot,
- FakeSnapshot)
- self._test_delete_volume()
-
- # test for Dorado5100
- self.driver._test_flg = 'check_for_Dorado5100'
- self._test_check_for_setup_errors()
- self._test_create_volume()
- self._test_create_snapshot()
- self.assertRaises(exception.VolumeBackendAPIException,
- self.driver.create_volume_from_snapshot,
- FakeVolumeCopy, FakeSnapshot)
- self._test_initialize_connection_for_Dorado5100()
- self._test_terminate_connection()
- self._test_delete_snapshot()
- self._test_delete_volume()
-
- def cleanup(self):
- if os.path.exists(self.fake_conf_file):
- os.remove(self.fake_conf_file)
- shutil.rmtree(self.tmp_dir)
-
- def _create_fake_conf_file(self):
- doc = Document()
-
- config = doc.createElement('config')
- doc.appendChild(config)
-
- storage = doc.createElement('Storage')
- config.appendChild(storage)
- controllerip0 = doc.createElement('ControllerIP0')
- controllerip0_text = doc.createTextNode('10.10.10.1')
- controllerip0.appendChild(controllerip0_text)
- storage.appendChild(controllerip0)
- controllerip1 = doc.createElement('ControllerIP1')
- controllerip1_text = doc.createTextNode('10.10.10.2')
- controllerip1.appendChild(controllerip1_text)
- storage.appendChild(controllerip1)
- username = doc.createElement('UserName')
- username_text = doc.createTextNode('admin')
- username.appendChild(username_text)
- storage.appendChild(username)
- userpassword = doc.createElement('UserPassword')
- userpassword_text = doc.createTextNode('123456')
- userpassword.appendChild(userpassword_text)
- storage.appendChild(userpassword)
-
- lun = doc.createElement('LUN')
- config.appendChild(lun)
- storagepool = doc.createElement('StoragePool')
- storagepool.setAttribute('Name', 'RAID_001')
- lun.appendChild(storagepool)
- storagepool = doc.createElement('StoragePool')
- storagepool.setAttribute('Name', 'RAID_002')
- lun.appendChild(storagepool)
-
- iscsi = doc.createElement('iSCSI')
- config.appendChild(iscsi)
- defaulttargetip = doc.createElement('DefaultTargetIP')
- defaulttargetip_text = doc.createTextNode('192.168.100.1')
- defaulttargetip.appendChild(defaulttargetip_text)
- iscsi.appendChild(defaulttargetip)
- initiator = doc.createElement('Initiator')
- initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3')
- initiator.setAttribute('TargetIP', '192.168.100.2')
- iscsi.appendChild(initiator)
-
- file = open(self.fake_conf_file, 'w')
- file.write(doc.toprettyxml(indent=''))
- file.close()
-
- def _test_check_for_setup_errors(self):
- self.driver.check_for_setup_error()
-
- def _test_create_volume(self):
- self.driver.create_volume(FakeVolume)
- self.assertNotEqual(LUNInfo["ID"], None)
- self.assertEqual(LUNInfo["RAID Group ID"], FakePoolInfo['ID'])
-
- def _test_delete_volume(self):
- self.driver.delete_volume(FakeVolume)
- self.assertEqual(LUNInfo["ID"], None)
-
- def _test_create_snapshot(self):
- self.driver.create_snapshot(FakeSnapshot)
- self.assertNotEqual(SnapshotInfo["ID"], None)
- self.assertNotEqual(LUNInfo["ID"], None)
- self.assertEqual(SnapshotInfo["Status"], 'Active')
- self.assertEqual(SnapshotInfo["Source LUN ID"], LUNInfo["ID"])
-
- def _test_delete_snapshot(self):
- self.driver.delete_snapshot(FakeSnapshot)
- self.assertEqual(SnapshotInfo["ID"], None)
-
- def _test_create_volume_from_snapshot(self):
- self.driver.create_volume_from_snapshot(FakeVolumeCopy, FakeSnapshot)
- self.assertNotEqual(LUNInfoCopy["ID"], None)
-
- def _test_create_export(self):
- retval = self.driver.create_export({}, FakeVolume)
- self.assertNotEqual(retval, FakeVolume["id"])
-
- def _test_initialize_connection_for_T(self):
- connection_data = self.driver.initialize_connection(FakeVolume,
- FakeConnector)
- iscsi_properties = connection_data['data']
-
- self.assertEquals(iscsi_properties['target_iqn'],
- FakeConfInfo['TargetIQN-T'])
- self.assertEquals(iscsi_properties['target_portal'],
- FakeConfInfo['Initiator TargetIP'] + ':3260')
- self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
- self.assertEqual(MapInfo["INI Port Info"],
- FakeConnector['initiator'])
-
- def _test_initialize_connection_for_Dorado2100G2(self):
- connection_data = self.driver.initialize_connection(FakeVolume,
- FakeConnector)
- iscsi_properties = connection_data['data']
-
- self.assertEquals(iscsi_properties['target_iqn'],
- FakeConfInfo['TargetIQN-Dorado2100G2'])
- self.assertEquals(iscsi_properties['target_portal'],
- FakeConfInfo['Initiator TargetIP'] + ':3260')
- self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
- self.assertEqual(MapInfo["INI Port Info"],
- FakeConnector['initiator'])
-
- def _test_initialize_connection_for_Dorado5100(self):
- connection_data = self.driver.initialize_connection(FakeVolume,
- FakeConnector)
- iscsi_properties = connection_data['data']
-
- self.assertEquals(iscsi_properties['target_iqn'],
- FakeConfInfo['TargetIQN-Dorado5100'])
- self.assertEquals(iscsi_properties['target_portal'],
- FakeConfInfo['Initiator TargetIP'] + ':3260')
- self.assertEqual(MapInfo["DEV LUN ID"], FakeVolume['id'])
- self.assertEqual(MapInfo["INI Port Info"],
- FakeConnector['initiator'])
-
- def _test_terminate_connection(self):
- self.driver.terminate_connection(FakeVolume, FakeConnector)
- self.assertEqual(MapInfo["DEV LUN ID"], None)
- self.assertEqual(MapInfo["Host LUN ID"], None)
- self.assertEqual(MapInfo["INI Port Info"], None)
-
- def _test_get_get_volume_stats(self):
- stats = self.driver.get_volume_stats(True)
-
- fakecapacity = float(FakePoolInfo['Free Capacity']) / 1024
- self.assertEqual(stats['free_capacity_gb'], fakecapacity)
-
-
-class FakeHuaweiStorage(huawei_iscsi.HuaweiISCSIDriver):
- """Fake Huawei Storage, Rewrite some methods of HuaweiISCSIDriver."""
-
- def __init__(self, *args, **kwargs):
- super(FakeHuaweiStorage, self).__init__(*args, **kwargs)
- self._test_flg = None
-
- def _execute_cli(self, cmdIn):
- cmd = cmdIn.split(' ')[0].lower()
- if cmd == 'showsys':
- if ((self._test_flg == 'check_for_fail') or
- (self._test_flg == 'check_for_T')):
- out = """/>showsys
-==========================================================================
- System Information
---------------------------------------------------------------------------
- System Name | SN_S5500T-xu-0123456789
- Device Type | Oceanstor S5500T
- Current System Mode | Double Controllers Normal
- Mirroring Link Status | Link Up
- Location |
- Time | 2013-01-01 01:01:01
- Product Version | V100R005C00
-===========================================================================
-"""
- elif self._test_flg == 'check_for_Dorado2100G2':
- out = """/>showsys
-==========================================================================
- System Information
---------------------------------------------------------------------------
- System Name | SN_Dorado2100_G2
- Device Type | Oceanstor Dorado2100 G2
- Current System Mode | Double Controllers Normal
- Mirroring Link Status | Link Up
- Location |
- Time | 2013-01-01 01:01:01
- Product Version | V100R001C00
-===========================================================================
-"""
- elif self._test_flg == 'check_for_Dorado5100':
- out = """/>showsys
-==========================================================================
- System Information
---------------------------------------------------------------------------
- System Name | SN_Dorado5100
- Device Type | Oceanstor Dorado5100
- Current System Mode | Double Controllers Normal
- Mirroring Link Status | Link Up
- Location |
- Time | 2013-01-01 01:01:01
- Product Version | V100R001C00
-===========================================================================
-"""
- elif cmd == 'addhostmap':
- MapInfo['DEV LUN ID'] = LUNInfo['ID']
- MapInfo['LUN WWN'] = LUNInfo['LUN WWN']
- MapInfo['Host LUN ID'] = '0'
- out = 'command operates successfully'
-
- elif cmd == 'showhostmap':
- if MapInfo['DEV LUN ID'] is None:
- out = 'command operates successfully, but no information.'
- else:
- out = """/>showhostmap
-==========================================================================
- Map Information
---------------------------------------------------------------------------
- Map ID Working Controller Dev LUN ID LUN WWN Host LUN ID Mapped to \
- RAID ID Dev LUN Cap(MB) Map Type Whether Command LUN Pool ID
----------------------------------------------------------------------------
- 2147483649 %s %s %s %s Host: %s %s %s HOST No --
-===========================================================================
-""" % (LUNInfo['Worker Controller'], LUNInfo['ID'], LUNInfo['LUN WWN'],
- MapInfo['Host ID'], MapInfo['Host ID'], LUNInfo['RAID Group ID'],
- str(int(LUNInfo['Size']) * 1024))
-
- elif cmd == 'delhostmap':
- MapInfo['DEV LUN ID'] = None
- MapInfo['LUN WWN'] = None
- MapInfo['Host LUN ID'] = None
- out = 'command operates successfully'
-
- elif cmd == 'createsnapshot':
- SnapshotInfo['Source LUN ID'] = LUNInfo['ID']
- SnapshotInfo['Source LUN Name'] = LUNInfo['Name']
- SnapshotInfo['ID'] = FakeSnapshot['id']
- SnapshotInfo['Name'] = self._name_translate(FakeSnapshot['name'])
- SnapshotInfo['Status'] = 'Disable'
- out = 'command operates successfully'
-
- elif cmd == 'actvsnapshot':
- SnapshotInfo['Status'] = 'Active'
- out = 'command operates successfully'
-
- elif cmd == 'disablesnapshot':
- SnapshotInfo['Status'] = 'Disable'
- out = 'command operates successfully'
-
- elif cmd == 'delsnapshot':
- SnapshotInfo['Source LUN ID'] = None
- SnapshotInfo['Source LUN Name'] = None
- SnapshotInfo['ID'] = None
- SnapshotInfo['Name'] = None
- SnapshotInfo['Status'] = None
- out = 'command operates successfully'
-
- elif cmd == 'showsnapshot':
- if SnapshotInfo['ID'] is None:
- out = 'command operates successfully, but no information.'
- else:
- out = """/>showsnapshot
-==========================================================================
- Snapshot Information
---------------------------------------------------------------------------
- Name ID Type Status Time Stamp
---------------------------------------------------------------------------
- %s %s Public %s 2013-01-15 14:21:13
-==========================================================================
-""" % (SnapshotInfo['Name'], SnapshotInfo['ID'], SnapshotInfo['Status'])
-
- elif cmd == 'showlunsnapshot':
- if SnapshotInfo['ID'] is None:
- out = """Current LUN is not a source LUN"""
- else:
- out = """/>showlunsnapshot -lun 2
-==========================================================================
- Snapshot of LUN
---------------------------------------------------------------------------
- Name ID Type Status Time Stamp
---------------------------------------------------------------------------
- %s %s Public %s 2013-01-15 14:17:19
-==========================================================================
-""" % (SnapshotInfo['Name'], SnapshotInfo['ID'], SnapshotInfo['Status'])
-
- elif cmd == 'createlun':
- if LUNInfo['ID'] is None:
- LUNInfo['Name'] = self._name_translate(FakeVolume['name'])
- LUNInfo['ID'] = FakeVolume['id']
- LUNInfo['Size'] = FakeVolume['size']
- LUNInfo['LUN WWN'] = FakeVolume['wwn']
- LUNInfo['Owner Controller'] = 'A'
- LUNInfo['Worker Controller'] = 'A'
- LUNInfo['RAID Group ID'] = FakePoolInfo['ID']
- else:
- LUNInfoCopy['Name'] = \
- self._name_translate(FakeVolumeCopy['name'])
- LUNInfoCopy['ID'] = FakeVolumeCopy['ID']
- LUNInfoCopy['Size'] = FakeVolumeCopy['size']
- LUNInfoCopy['LUN WWN'] = FakeVolumeCopy['wwn']
- LUNInfoCopy['Owner Controller'] = 'A'
- LUNInfoCopy['Worker Controller'] = 'A'
- LUNInfoCopy['RAID Group ID'] = FakePoolInfo['ID']
- out = 'command operates successfully'
-
- elif cmd == 'dellun':
- LUNInfo['Name'] = None
- LUNInfo['ID'] = None
- LUNInfo['Size'] = None
- LUNInfo['LUN WWN'] = None
- LUNInfo['Owner Controller'] = None
- LUNInfo['Worker Controller'] = None
- LUNInfo['RAID Group ID'] = None
- out = 'command operates successfully'
-
- elif cmd == 'showlun':
- if LUNInfo['ID'] is None:
- out = 'command operates successfully, but no information.'
- elif LUNInfoCopy['ID'] is None:
- if ((self._test_flg == 'check_for_fail') or
- (self._test_flg == 'check_for_T')):
- out = """/>showlun
-===========================================================================
- LUN Information
----------------------------------------------------------------------------
- ID RAID Group ID Disk Pool ID Status Controller Visible Capacity(MB) \
- LUN Name Stripe Unit Size(KB) Lun Type
----------------------------------------------------------------------------
- %s %s -- Normal %s %s %s 64 THICK
-===========================================================================
-""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
- str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'])
- elif self._test_flg == 'check_for_Dorado2100G2':
- out = """/>showlun
-===========================================================================
- LUN Information
----------------------------------------------------------------------------
- ID Status Controller Visible Capacity(MB) LUN Name Lun Type
----------------------------------------------------------------------------
- %s Normal %s %s %s THICK
-===========================================================================
-""" % (LUNInfo['ID'], LUNInfo['Owner Controller'],
- str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'])
- elif self._test_flg == 'check_for_Dorado5100':
- out = """/>showlun
-===========================================================================
- LUN Information
----------------------------------------------------------------------------
- ID RAIDgroup ID Status Controller Visible Capacity(MB) LUN Name
- Strip Unit Size(KB) Lun Type
----------------------------------------------------------------------------
- %s %s Normal %s %s %s 64 THICK
-===========================================================================
-""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'],
- LUNInfo['Owner Controller'], str(int(LUNInfo['Size']) * 1024),
- LUNInfo['Name'])
- else:
- if ((self._test_flg == 'check_for_fail') or
- (self._test_flg == 'check_for_T')):
- out = """/>showlun
-============================================================================
- LUN Information
-----------------------------------------------------------------------------
- ID RAID Group ID Disk Pool ID Status Controller Visible Capacity(MB)\
- LUN Name Stripe Unit Size(KB) Lun Type
-----------------------------------------------------------------------------
- %s %s -- Normal %s %s %s 64 THICK
- %s %s -- Normal %s %s %s 64 THICK
-============================================================================
-""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
- str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'], LUNInfoCopy['ID'],
- LUNInfoCopy['RAID Group ID'], LUNInfoCopy['Owner Controller'],
- str(int(LUNInfoCopy['Size']) * 1024), LUNInfoCopy['Name'])
- elif self._test_flg == 'check_for_Dorado2100G2':
- out = """/>showlun
-===========================================================================
- LUN Information
----------------------------------------------------------------------------
- ID Status Controller Visible Capacity(MB) LUN Name Lun Type
----------------------------------------------------------------------------
- %s Normal %s %s %s THICK
- %s Normal %s %s %s THICK
-===========================================================================
-""" % (LUNInfo['ID'], LUNInfo['Owner Controller'],
- str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'],
- LUNInfoCopy['ID'], LUNInfoCopy['Owner Controller'],
- str(int(LUNInfoCopy['Size']) * 1024), LUNInfoCopy['Name'])
- elif self._test_flg == 'check_for_Dorado5100':
- out = """/>showlun
-===========================================================================
- LUN Information
----------------------------------------------------------------------------
- ID RAIDgroup ID Status Controller Visible Capacity(MB) LUN Name \
- Strip Unit Size(KB) Lun Type
----------------------------------------------------------------------------
- %s %s Normal %s %s %s 64 THICK
- %s %s Norma %s %s %s 64 THICK
-===========================================================================
-""" % (LUNInfo['ID'], LUNInfo['RAID Group ID'], LUNInfo['Owner Controller'],
- str(int(LUNInfo['Size']) * 1024), LUNInfo['Name'],
- LUNInfoCopy['ID'], LUNInfoCopy['RAID Group ID'],
- LUNInfoCopy['Owner Controller'], str(int(LUNInfoCopy['Size']) * 1024),
- LUNInfoCopy['Name'])
-
- elif cmd == 'createhostgroup':
- MapInfo['Host Group ID'] = '1'
- MapInfo['Host Group Name'] = FakeConfInfo['HostGroup']
- out = 'command operates successfully'
-
- elif cmd == 'showhostgroup':
- if MapInfo['Host Group ID'] is None:
- out = """/>showhostgroup
-============================================================
- Host Group Information
-------------------------------------------------------------
- Host Group ID Name File Engine Cluster
-------------------------------------------------------------
- 0 Default Group NO
-============================================================
-"""
- else:
- out = """/>showhostgroup
-============================================================
- Host Group Information
-------------------------------------------------------------
- Host Group ID Name File Engine Cluster
-------------------------------------------------------------
- 0 Default Group NO
- %s %s NO
-============================================================
-""" % (MapInfo['Host Group ID'], MapInfo['Host Group Name'])
-
- elif cmd == 'addhost':
- MapInfo['Host ID'] = '1'
- MapInfo['Host Name'] = FakeConfInfo['HostnamePrefix'] + \
- str(hash(FakeConnector['initiator']))
- MapInfo['Os Type'] = 'Linux'
- out = 'command operates successfully'
-
- elif cmd == 'delhost':
- MapInfo['Host ID'] = None
- MapInfo['Host Name'] = None
- MapInfo['Os Type'] = None
- out = 'command operates successfully'
-
- elif cmd == 'showhost':
- if MapInfo['Host ID'] is None:
- out = 'command operates successfully, but no information.'
- else:
- out = """/>showhost
-=======================================================
- Host Information
--------------------------------------------------------
- Host ID Host Name Host Group ID Os Type
--------------------------------------------------------
- %s %s %s Linux
-=======================================================
-""" % (MapInfo['Host ID'], MapInfo['Host Name'], MapInfo['Host Group ID'])
-
- elif cmd == 'createluncopy':
- LUNCopy['Name'] = LUNInfoCopy['Name']
- LUNCopy['ID'] = FakeLUNCopy['ID']
- LUNCopy['Type'] = FakeLUNCopy['Type']
- LUNCopy['State'] = FakeLUNCopy['State']
- LUNCopy['Status'] = FakeLUNCopy['Status']
- out = 'command operates successfully'
-
- elif cmd == 'delluncopy':
- LUNCopy['Name'] = None
- LUNCopy['ID'] = None
- LUNCopy['Type'] = None
- LUNCopy['State'] = None
- LUNCopy['Status'] = None
- out = 'command operates successfully'
-
- elif cmd == 'chgluncopystatus':
- LUNCopy['State'] = 'Complete'
- out = 'command operates successfully'
-
- elif cmd == 'showluncopy':
- if LUNCopy['ID'] is None:
- out = 'command operates successfully, but no information.'
- else:
- out = """/>showluncopy
-============================================================================
- LUN Copy Information
-----------------------------------------------------------------------------
- LUN Copy Name LUN Copy ID Type LUN Copy State LUN Copy Status
-----------------------------------------------------------------------------
- %s %s %s %s %s
-============================================================================
-""" % (LUNCopy['Name'], LUNCopy['ID'], LUNCopy['Type'],
- LUNCopy['State'], LUNCopy['Status'])
-
- elif cmd == 'showiscsitgtname':
- if ((self._test_flg == 'check_for_fail') or
- (self._test_flg == 'check_for_T')):
- out = """/>showiscsitgtname
-============================================================================
- ISCSI Name
-----------------------------------------------------------------------------
- Iscsi Name | %s
-============================================================================
-""" % FakeConfInfo['TargetIQN']
- elif (self._test_flg == 'check_for_Dorado2100G2' or
- self._test_flg == 'check_for_Dorado5100'):
- out = """/>showiscsitgtname
-============================================================================
- ISCSI Name
-----------------------------------------------------------------------------
- Iscsi Name | %s
-============================================================================
-""" % FakeConfInfo['TargetIQN']
-
- elif cmd == 'showiscsiip':
- out = """/>showiscsiip
-============================================================================
- iSCSI IP Information
-----------------------------------------------------------------------------
- Controller ID Interface Module ID Port ID IP Address Mask
-----------------------------------------------------------------------------
- A 0 P1 %s 255.255.255.0
-============================================================================
-""" % FakeConfInfo['Initiator TargetIP']
-
- elif cmd == 'addhostport':
- MapInfo['INI Port ID'] = HostPort['ID']
- MapInfo['INI Port Name'] = HostPort['Name']
- MapInfo['INI Port Info'] = HostPort['Info']
- out = 'command operates successfully'
-
- elif cmd == 'delhostport':
- MapInfo['INI Port ID'] = None
- MapInfo['INI Port Name'] = None
- MapInfo['INI Port Info'] = None
- out = 'command operates successfully'
-
- elif cmd == 'showhostport':
- if MapInfo['INI Port ID'] is None:
- out = 'command operates successfully, but no information.'
- else:
- out = """/>showhostport -host 3
-==============================================================================
- Host Port Information
-------------------------------------------------------------------------------
-Port ID Port Name Port Information Port Type Host ID \
-Link Status Multipath Type
-------------------------------------------------------------------------------
- %s %s %s ISCSITGT %s Unconnected Default
-==============================================================================
-""" % (MapInfo['INI Port ID'], MapInfo['INI Port Name'],
- MapInfo['INI Port Info'], MapInfo['Host ID'])
-
- elif cmd == 'addiscsiini':
- HostPort['ID'] = '1'
- HostPort['Name'] = 'iSCSIInitiator001'
- HostPort['Info'] = FakeConfInfo['Initiator Name']
- out = 'command operates successfully'
-
- elif cmd == 'deliscsiini':
- HostPort['ID'] = None
- HostPort['Name'] = None
- HostPort['Info'] = None
- out = 'command operates successfully'
-
- elif cmd == 'showiscsiini':
- if HostPort['ID'] is None:
- out = 'Error: The parameter is wrong.'
- else:
- out = """/>showiscsiini -ini iqn.1993-08.org\
-.debian:01:503629a9d3f
-========================================================
- Initiator Information
---------------------------------------------------------
- Initiator Name Chap Status
---------------------------------------------------------
- %s Disable
-========================================================
-""" % (HostPort['Info'])
-
- elif cmd == 'showrg':
- out = """/>showrg
-=====================================================================
- RAID Group Information
----------------------------------------------------------------------
- ID Level Status Free Capacity(MB) Disk List Name
----------------------------------------------------------------------
- 0 RAID6 Normal 1024 0,0;0,2;0,4;0,5;0,6;0,7; RAID003
- %s %s %s %s %s %s
-=====================================================================
-""" % (FakePoolInfo['ID'], FakePoolInfo['Level'],
- FakePoolInfo['Status'], FakePoolInfo['Free Capacity'],
- FakePoolInfo['Disk List'], FakePoolInfo['Name'])
-
- elif cmd == 'showrespool':
- out = """/>showrespool
-============================================================================
- Resource Pool Information
-----------------------------------------------------------------------------
- Pool ID Size(MB) Usage(MB) Valid Size(MB) Alarm Threshold(%)
-----------------------------------------------------------------------------
- A 5130.0 0.0 5130.0 80
- B 3082.0 0.0 3082.0 80
-============================================================================
-"""
-
- elif cmd == 'chglun':
- out = 'command operates successfully'
-
- out = out.replace('\n', '\r\n')
- return out
-
- def _get_lun_controller(self, lunid):
- pass
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Unit Tests for Huawei T and Dorado volume drivers.
+"""
+
+import mox
+import os
+import shutil
+import socket
+import tempfile
+import time
+
+from xml.dom.minidom import Document
+from xml.etree import ElementTree as ET
+
+from cinder import context
+from cinder import exception
+from cinder import test
+from cinder import utils
+from cinder.volume import configuration as conf
+from cinder.volume.drivers.huawei import HuaweiVolumeDriver
+from cinder.volume.drivers.huawei import ssh_common
+from cinder.volume import volume_types
+
+
+LUN_INFO = {'ID': None,
+ 'Name': None,
+ 'Size': None,
+ 'LUN WWN': None,
+ 'Status': None,
+ 'Visible Capacity': None,
+ 'Disk Pool ID': None,
+ 'Cache Prefetch Strategy': None,
+ 'Lun Type': None,
+ 'Consumed Capacity': None,
+ 'Pool ID': None,
+ 'SnapShot ID': None,
+ 'LunCopy ID': None,
+ 'Owner Controller': None,
+ 'Worker Controller': None,
+ 'RAID Group ID': None}
+
+CLONED_LUN_INFO = {'ID': None,
+ 'Name': None,
+ 'Size': None,
+ 'LUN WWN': None,
+ 'Status': None,
+ 'Visible Capacity': None,
+ 'Disk Pool ID': None,
+ 'Cache Prefetch Strategy': None,
+ 'Lun Type': None,
+ 'Consumed Capacity': None,
+ 'Pool ID': None,
+ 'SnapShot ID': None,
+ 'LunCopy ID': None,
+ 'Owner Controller': None,
+ 'Worker Controller': None,
+ 'RAID Group ID': None}
+
+SNAPSHOT_INFO = {'Source LUN ID': None,
+ 'Source LUN Name': None,
+ 'ID': None,
+ 'Name': None,
+ 'Type': 'Public',
+ 'Status': None}
+
+MAP_INFO = {'Host Group ID': None,
+ 'Host Group Name': None,
+ 'Host ID': None,
+ 'Host Name': None,
+ 'Os Type': None,
+ 'INI Port ID': None,
+ 'INI Port Name': None,
+ 'INI Port Info': None,
+ 'INI Port WWN': None,
+ 'INI Port Type': None,
+ 'Link Status': None,
+ 'LUN WWN': None,
+ 'DEV LUN ID': None,
+ 'Host LUN ID': None,
+ 'CHAP status': False}
+
+HOST_PORT_INFO = {'ID': None,
+ 'Name': None,
+ 'Info': None,
+ 'WWN': None,
+ 'Type': None}
+
+LUNCOPY_INFO = {'Name': None,
+ 'ID': None,
+ 'Type': None,
+ 'State': None,
+ 'Status': None}
+
+LUNCOPY_SETTING = {'ID': '1',
+ 'Type': 'FULL',
+ 'State': 'Created',
+ 'Status': 'Normal'}
+
+POOL_SETTING = {'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'}
+
+INITIATOR_SETTING = {'TargetIQN': 'iqn.2006-08.com.huawei:oceanspace:2103037:',
+ 'TargetIQN-form': 'iqn.2006-08.com.huawei:oceanspace:'
+ '2103037::1020001:192.168.100.2',
+ 'Initiator Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
+ 'Initiator TargetIP': '192.168.100.2'}
+
+FAKE_VOLUME = {'name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
+ 'id': 'lele34fe-223f-dd33-4423-asdfghjklqwe',
+ 'size': '2',
+ 'provider_auth': None,
+ 'volume_type_id': None,
+ 'provider_location': None}
+
+FAKE_CLONED_VOLUME = {'name': 'Volume-jeje34fe-223f-dd33-4423-asdfghjklqwg',
+ 'id': 'jeje34fe-223f-dd33-4423-asdfghjklqwg',
+ 'size': '3',
+ 'provider_auth': None,
+ 'volume_type_id': None,
+ 'provider_location': None}
+
+FAKE_SNAPSHOT = {'name': 'keke34fe-223f-dd33-4423-asdfghjklqwf',
+ 'id': '223f-dd33-4423-asdfghjklqwf',
+ 'volume_name': 'Volume-lele34fe-223f-dd33-4423-asdfghjklqwe',
+ 'provider_location': None}
+
+FAKE_CONNECTOR = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
+ 'host': 'fakehost'}
+
+RESPOOL_A_SIM = {'Size': '10240', 'Valid Size': '5120'}
+RESPOOL_B_SIM = {'Size': '10240', 'Valid Size': '10240'}
+VOLUME_SNAP_ID = {'vol': '0', 'vol_copy': '1', 'snap': '2'}
+
+cmd_error_list = [] # CLI cmds in this list will run failed
+Curr_test = [''] # show current testing driver
+
+
+class FakeChannel():
+ def __init__(self):
+ if Curr_test[0] == 'T':
+ self.simu = HuaweiTCLIResSimulator()
+
+ def resize_pty(self, width=80, height=24):
+ pass
+
+ def settimeout(self, time):
+ pass
+
+ def send(self, s):
+ self.command = s
+
+ def recv(self, nbytes):
+ command = self.command.split()
+ cmd = command[0]
+ params = command[1:]
+ if cmd in cmd_error_list:
+ reset_error_flg(cmd)
+ out = self.command[:-1] + 'ERROR' + '\nadmin:/>'
+ return out.replace('\n', '\r\n')
+
+ if cmd == 'showsys':
+ out = self.simu.cli_showsys(params)
+ elif cmd == 'createlun':
+ out = self.simu.cli_createlun(params)
+ elif cmd == 'showlun':
+ out = self.simu.cli_showlun(params)
+ elif cmd == 'dellun':
+ out = self.simu.cli_dellun(params)
+ elif cmd == 'showrg':
+ out = self.simu.cli_showrg(params)
+ elif cmd == 'showpool':
+ out = self.simu.cli_showpool(params)
+ elif cmd == 'createluncopy':
+ out = self.simu.cli_createluncopy(params)
+ elif cmd == 'chgluncopystatus':
+ out = self.simu.cli_chgluncopystatus(params)
+ elif cmd == 'showluncopy':
+ out = self.simu.cli_showluncopy(params)
+ elif cmd == 'delluncopy':
+ out = self.simu.cli_delluncopy(params)
+ elif cmd == 'createsnapshot':
+ out = self.simu.cli_createsnapshot(params)
+ elif cmd == 'actvsnapshot':
+ out = self.simu.cli_activesnapshot(params)
+ elif cmd == 'showsnapshot':
+ out = self.simu.cli_showsnapshot(params)
+ elif cmd == 'disablesnapshot':
+ out = self.simu.cli_disablesnapshot(params)
+ elif cmd == 'delsnapshot':
+ out = self.simu.cli_delsnapshot(params)
+ elif cmd == 'showrespool':
+ out = self.simu.cli_showrespool(params)
+ elif cmd == 'showiscsitgtname':
+ out = self.simu.cli_showiscsitgtname(params)
+ elif cmd == 'showiscsiip':
+ out = self.simu.cli_showiscsiip(params)
+ elif cmd == 'showhostgroup':
+ out = self.simu.cli_showhostgroup(params)
+ elif cmd == 'createhostgroup':
+ out = self.simu.cli_createhostgroup(params)
+ elif cmd == 'showhost':
+ out = self.simu.cli_showhost(params)
+ elif cmd == 'addhost':
+ out = self.simu.cli_addhost(params)
+ elif cmd == 'delhost':
+ out = self.simu.cli_delhost(params)
+ elif cmd == 'showiscsiini':
+ out = self.simu.cli_showiscsiini(params)
+ elif cmd == 'addiscsiini':
+ out = self.simu.cli_addiscsiini(params)
+ elif cmd == 'deliscsiini':
+ out = self.simu.cli_deliscsiini(params)
+ elif cmd == 'showhostport':
+ out = self.simu.cli_showhostport(params)
+ elif cmd == 'addhostport':
+ out = self.simu.cli_addhostport(params)
+ elif cmd == 'delhostport':
+ out = self.simu.cli_delhostport(params)
+ elif cmd == 'showhostmap':
+ out = self.simu.cli_showhostmap(params)
+ elif cmd == 'addhostmap':
+ out = self.simu.cli_addhostmap(params)
+ elif cmd == 'delhostmap':
+ out = self.simu.cli_delhostmap(params)
+ elif cmd == 'chglun':
+ out = self.simu.cli_chglun(params)
+ out = self.command[:-1] + out + '\nadmin:/>'
+ return out.replace('\n', '\r\n')
+
+ def close(self):
+ pass
+
+
+class FakeSSHClient():
+ def invoke_shell(self):
+ return FakeChannel()
+
+ def get_transport(self):
+
+ class transport():
+ def __init__(self):
+ self.sock = sock()
+
+ class sock():
+ def settimeout(self, time):
+ pass
+
+ return transport()
+
+ def close(self):
+ pass
+
+
+class FakeSSHPool():
+ def __init__(self, ip, port, conn_timeout, login, password=None,
+ *args, **kwargs):
+ self.ip = ip
+ self.port = port
+ self.login = login
+ self.password = password
+
+ def create(self):
+ return FakeSSHClient()
+
+ def get(self):
+ return FakeSSHClient()
+
+ def put(self, ssh):
+ pass
+
+ def remove(self, ssh):
+ pass
+
+
+def Fake_sleep(time):
+ pass
+
+
+def Fake_change_file_mode(obj, filepath):
+ pass
+
+
+def create_fake_conf_file(filename):
+ doc = Document()
+
+ config = doc.createElement('config')
+ doc.appendChild(config)
+
+ storage = doc.createElement('Storage')
+ config.appendChild(storage)
+ product = doc.createElement('Product')
+ product_text = doc.createTextNode('T')
+ product.appendChild(product_text)
+ storage.appendChild(product)
+ config.appendChild(storage)
+ protocol = doc.createElement('Protocol')
+ protocol_text = doc.createTextNode('iSCSI')
+ protocol.appendChild(protocol_text)
+ storage.appendChild(protocol)
+ controllerip0 = doc.createElement('ControllerIP0')
+ controllerip0_text = doc.createTextNode('10.10.10.1')
+ controllerip0.appendChild(controllerip0_text)
+ storage.appendChild(controllerip0)
+ controllerip1 = doc.createElement('ControllerIP1')
+ controllerip1_text = doc.createTextNode('10.10.10.2')
+ controllerip1.appendChild(controllerip1_text)
+ storage.appendChild(controllerip1)
+ username = doc.createElement('UserName')
+ username_text = doc.createTextNode('admin')
+ username.appendChild(username_text)
+ storage.appendChild(username)
+ userpassword = doc.createElement('UserPassword')
+ userpassword_text = doc.createTextNode('123456')
+ userpassword.appendChild(userpassword_text)
+ storage.appendChild(userpassword)
+
+ lun = doc.createElement('LUN')
+ config.appendChild(lun)
+ storagepool = doc.createElement('StoragePool')
+ storagepool.setAttribute('Name', 'RAID_001')
+ lun.appendChild(storagepool)
+ luntype = doc.createElement('LUNType')
+ luntype_text = doc.createTextNode('Thick')
+ luntype.appendChild(luntype_text)
+ lun.appendChild(luntype)
+
+ iscsi = doc.createElement('iSCSI')
+ config.appendChild(iscsi)
+ defaulttargetip = doc.createElement('DefaultTargetIP')
+ defaulttargetip_text = doc.createTextNode('192.168.100.1')
+ defaulttargetip.appendChild(defaulttargetip_text)
+ iscsi.appendChild(defaulttargetip)
+ initiator = doc.createElement('Initiator')
+ initiator.setAttribute('Name', 'iqn.1993-08.debian:01:ec2bff7ac3a3')
+ initiator.setAttribute('TargetIP', '192.168.100.2')
+ iscsi.appendChild(initiator)
+
+ tmp_file = open(filename, 'w')
+ tmp_file.write(doc.toprettyxml(indent=''))
+ tmp_file.close()
+
+
+def modify_conf(conf, item, val, attrib=None):
+ tree = ET.parse(conf)
+ root = tree.getroot()
+ conf_item = root.find('%s' % item)
+ if not attrib:
+ conf_item.text = '%s' % val
+ else:
+ conf_item.attrib['%s' % attrib] = '%s' % val
+ tree.write(conf, 'UTF-8')
+
+
+def set_error_flg(cmd):
+ cmd_error_list.append(cmd)
+
+
+def reset_error_flg(cmd):
+ cmd_error_list.remove(cmd)
+
+
+class HuaweiTCLIResSimulator():
+ def _name_translate(self, name):
+ return 'OpenStack_' + str(hash(name))
+
+ def cli_showsys(self, params):
+ pass
+
+ def cli_createlun(self, params):
+ lun_type = ('THIN' if '-pool' in params else 'THICK')
+ if LUN_INFO['ID'] is None:
+ LUN_INFO['Name'] = self._name_translate(FAKE_VOLUME['name'])
+ LUN_INFO['ID'] = VOLUME_SNAP_ID['vol']
+ LUN_INFO['Size'] = FAKE_VOLUME['size']
+ LUN_INFO['Lun Type'] = lun_type
+ LUN_INFO['Owner Controller'] = 'A'
+ LUN_INFO['Worker Controller'] = 'A'
+ LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
+ FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
+ else:
+ CLONED_LUN_INFO['Name'] = \
+ self._name_translate(FAKE_CLONED_VOLUME['name'])
+ CLONED_LUN_INFO['ID'] = VOLUME_SNAP_ID['vol_copy']
+ CLONED_LUN_INFO['Size'] = FAKE_CLONED_VOLUME['size']
+ CLONED_LUN_INFO['Lun Type'] = lun_type
+ CLONED_LUN_INFO['Owner Controller'] = 'A'
+ CLONED_LUN_INFO['Worker Controller'] = 'A'
+ CLONED_LUN_INFO['RAID Group ID'] = POOL_SETTING['ID']
+ CLONED_LUN_INFO['provider_location'] = CLONED_LUN_INFO['ID']
+ FAKE_CLONED_VOLUME['provider_location'] = CLONED_LUN_INFO['ID']
+ out = 'command operates successfully'
+ return out
+
+ def cli_showlun(self, params):
+ if '-lun' not in params:
+ if LUN_INFO['ID'] is None:
+ out = 'command operates successfully, but no information.'
+ elif CLONED_LUN_INFO['ID'] is None:
+ out = """/>showlun
+===========================================================================
+ LUN Information
+---------------------------------------------------------------------------
+ ID 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
+===========================================================================
+""" % (LUN_INFO['ID'], LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'],
+ str(int(LUN_INFO['Size']) * 1024), LUN_INFO['Name'])
+ else:
+ out = """/>showlun
+============================================================================
+ LUN Information
+----------------------------------------------------------------------------
+ ID 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
+============================================================================
+""" % (LUN_INFO['ID'], LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'],
+ str(int(LUN_INFO['Size']) * 1024), LUN_INFO['Name'],
+ CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['RAID Group ID'],
+ CLONED_LUN_INFO['Owner Controller'],
+ str(int(CLONED_LUN_INFO['Size']) * 1024),
+ CLONED_LUN_INFO['Name'])
+
+ elif params[params.index('-lun') + 1] in VOLUME_SNAP_ID.values():
+ out = """/>showlun
+================================================
+ LUN Information
+------------------------------------------------
+ ID | %s
+ Name | %s
+ LUN WWN | --
+ Visible Capacity | %s
+ RAID GROUP ID | %s
+ Owning Controller | %s
+ Workong Controller | %s
+ Lun Type | %s
+ SnapShot ID | %s
+ LunCopy ID | %s
+================================================
+""" % ((LUN_INFO['ID'], LUN_INFO['Name'], LUN_INFO['Visible Capacity'],
+ LUN_INFO['RAID Group ID'], LUN_INFO['Owner Controller'],
+ LUN_INFO['Worker Controller'], LUN_INFO['Lun Type'],
+ LUN_INFO['SnapShot ID'], LUN_INFO['LunCopy ID'])
+ if params[params.index('-lun') + 1] == VOLUME_SNAP_ID['vol'] else
+ (CLONED_LUN_INFO['ID'], CLONED_LUN_INFO['Name'],
+ CLONED_LUN_INFO['Visible Capacity'], CLONED_LUN_INFO['RAID Group ID'],
+ CLONED_LUN_INFO['Owner Controller'],
+ CLONED_LUN_INFO['Worker Controller'],
+ CLONED_LUN_INFO['Lun Type'], CLONED_LUN_INFO['SnapShot ID'],
+ CLONED_LUN_INFO['LunCopy ID']))
+ else:
+ out = 'ERROR: The object does not exist.'
+ return out
+
+ def cli_dellun(self, params):
+ if params[params.index('-lun') + 1] == VOLUME_SNAP_ID['vol']:
+ LUN_INFO['Name'] = None
+ LUN_INFO['ID'] = None
+ LUN_INFO['Size'] = None
+ LUN_INFO['Lun Type'] = None
+ LUN_INFO['LUN WWN'] = None
+ LUN_INFO['Owner Controller'] = None
+ LUN_INFO['Worker Controller'] = None
+ LUN_INFO['RAID Group ID'] = None
+ FAKE_VOLUME['provider_location'] = None
+ else:
+ CLONED_LUN_INFO['Name'] = None
+ CLONED_LUN_INFO['ID'] = None
+ CLONED_LUN_INFO['Size'] = None
+ CLONED_LUN_INFO['Lun Type'] = None
+ CLONED_LUN_INFO['LUN WWN'] = None
+ CLONED_LUN_INFO['Owner Controller'] = None
+ CLONED_LUN_INFO['Worker Controller'] = None
+ CLONED_LUN_INFO['RAID Group ID'] = None
+ CLONED_LUN_INFO['provider_location'] = None
+ FAKE_CLONED_VOLUME['provider_location'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_showrg(self, params):
+ out = """/>showrg
+=====================================================================
+ RAID Group Information
+---------------------------------------------------------------------
+ ID Level Status Free Capacity(MB) Disk List Name
+---------------------------------------------------------------------
+ 0 RAID6 Normal 1024 0,0;0,2; RAID003
+ %s %s %s %s %s %s
+=====================================================================
+-""" % (POOL_SETTING['ID'], POOL_SETTING['Level'],
+ POOL_SETTING['Status'], POOL_SETTING['Free Capacity'],
+ POOL_SETTING['Disk List'], POOL_SETTING['Name'])
+ return out
+
+ def cli_showpool(self, params):
+ out = """/>showpool
+=====================================================================
+ Pool Information
+---------------------------------------------------------------------
+ Level Status Available Capacity(MB) Disk List
+---------------------------------------------------------------------
+ RAID6 Normal %s 0,0;0,2;0,4;0,5;
+=====================================================================
+-""" % POOL_SETTING['Free Capacity']
+ return out
+
+ def cli_createluncopy(self, params):
+ src_id = params[params.index('-slun') + 1]
+ tgt_id = params[params.index('-tlun') + 1]
+ LUNCOPY_INFO['Name'] = 'OpenStack_%s_%s' % (src_id, tgt_id)
+ LUNCOPY_INFO['ID'] = LUNCOPY_SETTING['ID']
+ LUNCOPY_INFO['Type'] = LUNCOPY_SETTING['Type']
+ LUNCOPY_INFO['State'] = LUNCOPY_SETTING['State']
+ LUNCOPY_INFO['Status'] = LUNCOPY_SETTING['Status']
+ out = 'command operates successfully'
+ return out
+
+ def cli_chgluncopystatus(self, params):
+ LUNCOPY_INFO['State'] = 'Start'
+ out = 'command operates successfully'
+ return out
+
+ def cli_showluncopy(self, params):
+ if LUNCOPY_INFO['State'] == 'Start':
+ LUNCOPY_INFO['State'] = 'Copying'
+ elif LUNCOPY_INFO['State'] == 'Copying':
+ LUNCOPY_INFO['State'] = 'Complete'
+ out = """/>showluncopy
+============================================================================
+ LUN Copy Information
+----------------------------------------------------------------------------
+ LUN Copy Name LUN Copy ID Type LUN Copy State LUN Copy Status
+----------------------------------------------------------------------------
+ %s %s %s %s %s
+============================================================================
+""" % (LUNCOPY_INFO['Name'], LUNCOPY_INFO['ID'], LUNCOPY_INFO['Type'],
+ LUNCOPY_INFO['State'], LUNCOPY_INFO['Status'])
+ return out
+
+ def cli_delluncopy(self, params):
+ LUNCOPY_INFO['Name'] = None
+ LUNCOPY_INFO['ID'] = None
+ LUNCOPY_INFO['Type'] = None
+ LUNCOPY_INFO['State'] = None
+ LUNCOPY_INFO['Status'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_createsnapshot(self, params):
+ SNAPSHOT_INFO['Source LUN ID'] = LUN_INFO['ID']
+ SNAPSHOT_INFO['Source LUN Name'] = LUN_INFO['Name']
+ SNAPSHOT_INFO['ID'] = VOLUME_SNAP_ID['snap']
+ SNAPSHOT_INFO['Name'] =\
+ self._name_translate(FAKE_SNAPSHOT['name'])
+ SNAPSHOT_INFO['Status'] = 'Disable'
+ out = 'command operates successfully'
+ return out
+
+ def cli_showsnapshot(self, params):
+ if SNAPSHOT_INFO['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
+==========================================================================
+""" % (SNAPSHOT_INFO['Name'], SNAPSHOT_INFO['ID'], SNAPSHOT_INFO['Status'])
+ return out
+
+ def cli_activesnapshot(self, params):
+ SNAPSHOT_INFO['Status'] = 'Active'
+ FAKE_SNAPSHOT['provider_location'] = SNAPSHOT_INFO['ID']
+ out = 'command operates successfully'
+ return out
+
+ def cli_disablesnapshot(self, params):
+ SNAPSHOT_INFO['Status'] = 'Disable'
+ out = 'command operates successfully'
+ return out
+
+ def cli_delsnapshot(self, params):
+ SNAPSHOT_INFO['Source LUN ID'] = None
+ SNAPSHOT_INFO['Source LUN Name'] = None
+ SNAPSHOT_INFO['ID'] = None
+ SNAPSHOT_INFO['Name'] = None
+ SNAPSHOT_INFO['Status'] = None
+ FAKE_SNAPSHOT['provider_location'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_showrespool(self, params):
+ out = """/>showrespool
+===========================================================================
+ Resource Pool Information
+---------------------------------------------------------------------------
+ Pool ID Size(MB) Usage(MB) Valid Size(MB) Alarm Threshold
+---------------------------------------------------------------------------
+ A %s 0.0 %s 80
+ B %s 0.0 %s 80
+===========================================================================
+-""" % (RESPOOL_A_SIM['Size'], RESPOOL_A_SIM['Valid Size'],
+ RESPOOL_B_SIM['Size'], RESPOOL_B_SIM['Valid Size'])
+ return out
+
+ def cli_showiscsitgtname(self, params):
+ iqn = INITIATOR_SETTING['TargetIQN']
+ out = """/>showiscsitgtname
+===================================================================
+ ISCSI Name
+-------------------------------------------------------------------
+ Iscsi Name | %s
+===================================================================
+""" % iqn
+ return out
+
+ def cli_showiscsiip(self, params):
+ out = """/>showiscsiip
+============================================================================
+ iSCSI IP Information
+----------------------------------------------------------------------------
+ Controller ID Interface Module ID Port ID IP Address Mask
+----------------------------------------------------------------------------
+ N 0 P1 %s 255.255.255.0
+============================================================================
+-""" % INITIATOR_SETTING['Initiator TargetIP']
+ return out
+
+ def cli_showhostgroup(self, params):
+ if MAP_INFO['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
+============================================================
+""" % (MAP_INFO['Host Group ID'], MAP_INFO['Host Group Name'])
+ return out
+
+ def cli_createhostgroup(self, params):
+ MAP_INFO['Host Group ID'] = '1'
+ MAP_INFO['Host Group Name'] = 'HostGroup_OpenStack'
+ out = 'command operates successfully'
+ return out
+
+ def cli_showhost(self, params):
+ if MAP_INFO['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
+=======================================================
+""" % (MAP_INFO['Host ID'], MAP_INFO['Host Name'], MAP_INFO['Host Group ID'])
+ return out
+
+ def cli_addhost(self, params):
+ MAP_INFO['Host ID'] = '1'
+ MAP_INFO['Host Name'] = 'Host_' + FAKE_CONNECTOR['host']
+ MAP_INFO['Os Type'] = 'Linux'
+ out = 'command operates successfully'
+ return out
+
+ def cli_delhost(self, params):
+ MAP_INFO['Host ID'] = None
+ MAP_INFO['Host Name'] = None
+ MAP_INFO['Os Type'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_showiscsiini(self, params):
+ if HOST_PORT_INFO['ID'] is None:
+ out = 'Error: The parameter is wrong.'
+ else:
+ out = """/>showiscsiini
+========================================================
+ Initiator Information
+--------------------------------------------------------
+ Initiator Name Chap Status
+--------------------------------------------------------
+ %s Disable
+========================================================
+""" % HOST_PORT_INFO['Info']
+ return out
+
+ def cli_addiscsiini(self, params):
+ HOST_PORT_INFO['ID'] = '1'
+ HOST_PORT_INFO['Name'] = 'iSCSIInitiator001'
+ HOST_PORT_INFO['Info'] = INITIATOR_SETTING['Initiator Name']
+ HOST_PORT_INFO['Type'] = 'ISCSITGT'
+ out = 'command operates successfully'
+ return out
+
+ def cli_deliscsiini(self, params):
+ HOST_PORT_INFO['ID'] = None
+ HOST_PORT_INFO['Name'] = None
+ HOST_PORT_INFO['Info'] = None
+ HOST_PORT_INFO['Type'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_showhostport(self, params):
+ if MAP_INFO['INI Port ID'] is None:
+ out = 'command operates successfully, but no information.'
+ else:
+ out = """/>showhostport
+============================================================================
+ Host Port Information
+----------------------------------------------------------------------------
+Port ID Port Name Port Information Port Type Host ID Link Status \
+Multipath Type
+----------------------------------------------------------------------------
+ %s %s %s %s %s Unconnected Default
+============================================================================
+""" % (MAP_INFO['INI Port ID'], MAP_INFO['INI Port Name'],
+ MAP_INFO['INI Port Info'], MAP_INFO['INI Port Type'],
+ MAP_INFO['Host ID'])
+ return out
+
+ def cli_addhostport(self, params):
+ MAP_INFO['INI Port ID'] = HOST_PORT_INFO['ID']
+ MAP_INFO['INI Port Name'] = HOST_PORT_INFO['Name']
+ MAP_INFO['INI Port Info'] = HOST_PORT_INFO['Info']
+ MAP_INFO['INI Port Type'] = HOST_PORT_INFO['Type']
+ out = 'command operates successfully'
+ return out
+
+ def cli_delhostport(self, params):
+ MAP_INFO['INI Port ID'] = None
+ MAP_INFO['INI Port Name'] = None
+ MAP_INFO['INI Port Info'] = None
+ MAP_INFO['INI Port Type'] = None
+ HOST_PORT_INFO['ID'] = None
+ HOST_PORT_INFO['Name'] = None
+ HOST_PORT_INFO['Info'] = None
+ HOST_PORT_INFO['Type'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_showhostmap(self, params):
+ if MAP_INFO['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 --
+============================================================================
+""" % (LUN_INFO['Worker Controller'], LUN_INFO['ID'], LUN_INFO['LUN WWN'],
+ MAP_INFO['Host LUN ID'], MAP_INFO['Host ID'], LUN_INFO['RAID Group ID'],
+ str(int(LUN_INFO['Size']) * 1024))
+ return out
+
+ def cli_addhostmap(self, params):
+ MAP_INFO['DEV LUN ID'] = LUN_INFO['ID']
+ MAP_INFO['LUN WWN'] = LUN_INFO['LUN WWN']
+ MAP_INFO['Host LUN ID'] = '2'
+ MAP_INFO['Link Status'] = 'Linked'
+ out = 'command operates successfully'
+ return out
+
+ def cli_delhostmap(self, params):
+ if MAP_INFO['Link Status'] == 'Linked':
+ MAP_INFO['Link Status'] = 'Deleting'
+ out = 'there are IOs accessing the system, please try later'
+ else:
+ MAP_INFO['Link Status'] = None
+ MAP_INFO['DEV LUN ID'] = None
+ MAP_INFO['LUN WWN'] = None
+ MAP_INFO['Host LUN ID'] = None
+ out = 'command operates successfully'
+ return out
+
+ def cli_chglun(self, params):
+ if params[params.index('-lun') + 1] == VOLUME_SNAP_ID['vol']:
+ LUN_INFO['Owner Controller'] = 'B'
+ else:
+ CLONED_LUN_INFO['Owner Controller'] = 'B'
+ out = 'command operates successfully'
+ return out
+
+
+class HuaweiTISCSIDriverTestCase(test.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(HuaweiTISCSIDriverTestCase, self).__init__(*args, **kwargs)
+
+ def setUp(self):
+ super(HuaweiTISCSIDriverTestCase, self).setUp()
+
+ self.tmp_dir = tempfile.mkdtemp()
+ self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+ create_fake_conf_file(self.fake_conf_file)
+ self.configuration = mox.MockObject(conf.Configuration)
+ self.configuration.cinder_huawei_conf_file = self.fake_conf_file
+ self.configuration.append_config_values(mox.IgnoreArg())
+
+ self.stubs.Set(time, 'sleep', Fake_sleep)
+ self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
+ self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
+ Fake_change_file_mode)
+ self._init_driver()
+
+ def _init_driver(self):
+ Curr_test[0] = 'T'
+ self.driver = HuaweiVolumeDriver(configuration=self.configuration)
+ self.driver.do_setup(None)
+
+ def tearDown(self):
+ if os.path.exists(self.fake_conf_file):
+ os.remove(self.fake_conf_file)
+ shutil.rmtree(self.tmp_dir)
+ super(HuaweiTISCSIDriverTestCase, self).tearDown()
+
+ def test_conf_invalid(self):
+ # Test config file not found
+ tmp_fonf_file = '/xxx/cinder_huawei_conf.xml'
+ tmp_configuration = mox.MockObject(conf.Configuration)
+ tmp_configuration.cinder_huawei_conf_file = tmp_fonf_file
+ tmp_configuration.append_config_values(mox.IgnoreArg())
+ self.assertRaises(exception.ConfigNotFound,
+ HuaweiVolumeDriver,
+ configuration=tmp_configuration)
+ # Test Product and Protocol invalid
+ tmp_dict = {'Storage/Product': 'T', 'Storage/Protocol': 'iSCSI'}
+ for k, v in tmp_dict.items():
+ modify_conf(self.fake_conf_file, k, 'xx')
+ self.assertRaises(exception.InvalidInput,
+ HuaweiVolumeDriver,
+ configuration=self.configuration)
+ modify_conf(self.fake_conf_file, k, v)
+ # Test ctr ip, UserName and password unspecified
+ tmp_dict = {'Storage/ControllerIP0': '10.10.10.1',
+ 'Storage/ControllerIP1': '10.10.10.2',
+ 'Storage/UserName': 'admin',
+ 'Storage/UserPassword': '123456'}
+ for k, v in tmp_dict.items():
+ modify_conf(self.fake_conf_file, k, '')
+ tmp_driver = HuaweiVolumeDriver(configuration=self.configuration)
+ self.assertRaises(exception.InvalidInput,
+ tmp_driver.do_setup, None)
+ modify_conf(self.fake_conf_file, k, v)
+ # Test StoragePool unspecified
+ modify_conf(self.fake_conf_file, 'LUN/StoragePool', '', attrib='Name')
+ tmp_driver = HuaweiVolumeDriver(configuration=self.configuration)
+ self.assertRaises(exception.InvalidInput,
+ tmp_driver.do_setup, None)
+ modify_conf(self.fake_conf_file, 'LUN/StoragePool', 'RAID_001',
+ attrib='Name')
+ # Test LUN type invalid
+ modify_conf(self.fake_conf_file, 'LUN/LUNType', 'thick')
+ tmp_driver = HuaweiVolumeDriver(configuration=self.configuration)
+ tmp_driver.do_setup(None)
+ self.assertRaises(exception.InvalidInput,
+ tmp_driver.create_volume, FAKE_VOLUME)
+ modify_conf(self.fake_conf_file, 'LUN/LUNType', 'Thick')
+ # Test TargetIP not found
+ modify_conf(self.fake_conf_file, 'iSCSI/DefaultTargetIP', '')
+ modify_conf(self.fake_conf_file, 'iSCSI/Initiator', '', attrib='Name')
+ tmp_driver = HuaweiVolumeDriver(configuration=self.configuration)
+ tmp_driver.do_setup(None)
+ tmp_driver.create_volume(FAKE_VOLUME)
+ self.assertRaises(exception.InvalidInput,
+ tmp_driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ tmp_driver.delete_volume(FAKE_VOLUME)
+ modify_conf(self.fake_conf_file, 'iSCSI/DefaultTargetIP',
+ '192.168.100.1')
+ modify_conf(self.fake_conf_file, 'iSCSI/Initiator',
+ 'iqn.1993-08.debian:01:ec2bff7ac3a3', attrib='Name')
+
+ def test_volume_type(self):
+ ctxt = context.get_admin_context()
+ extra_specs = {'drivers:LUNType': 'Thin'}
+ type_ref = volume_types.create(ctxt, 'THIN', extra_specs)
+ FAKE_VOLUME['volume_type_id'] = type_ref['id']
+ self.driver.create_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO["ID"], VOLUME_SNAP_ID['vol'])
+ self.assertEqual(LUN_INFO['Lun Type'], 'THIN')
+ self.driver.delete_volume(FAKE_VOLUME)
+ FAKE_VOLUME['volume_type_id'] = None
+
+ # Test volume type invalid
+ extra_specs = {'drivers:InvalidLUNType': 'Thin'}
+ type_ref = volume_types.create(ctxt, 'Invalid_THIN', extra_specs)
+ FAKE_VOLUME['volume_type_id'] = type_ref['id']
+ self.driver.create_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO["ID"], VOLUME_SNAP_ID['vol'])
+ self.assertNotEqual(LUN_INFO['Lun Type'], 'THIN')
+ self.driver.delete_volume(FAKE_VOLUME)
+ FAKE_VOLUME['volume_type_id'] = None
+
+ def test_create_delete_volume(self):
+ # Test create lun cli exception
+ set_error_flg('createlun')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_volume, FAKE_VOLUME)
+
+ ret = self.driver.create_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ self.assertEqual(ret['provider_location'], LUN_INFO['ID'])
+
+ # Test delete lun cli exception
+ set_error_flg('dellun')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.delete_volume, FAKE_VOLUME)
+
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], None)
+ self.assertEqual(FAKE_VOLUME['provider_location'], None)
+
+ def test_create_delete_cloned_volume(self):
+ # Test no source volume
+ self.assertRaises(exception.VolumeNotFound,
+ self.driver.create_cloned_volume,
+ FAKE_CLONED_VOLUME, FAKE_VOLUME)
+
+ self.driver.create_volume(FAKE_VOLUME)
+ # Test create luncopy failed
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ set_error_flg('createluncopy')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_cloned_volume,
+ FAKE_CLONED_VOLUME, FAKE_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+ self.driver.delete_volume(FAKE_CLONED_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ # Test start luncopy failed
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ set_error_flg('chgluncopystatus')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_cloned_volume,
+ FAKE_CLONED_VOLUME, FAKE_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ # Test luncopy status abnormal
+ LUNCOPY_SETTING['Status'] = 'Disable'
+ self.assertEqual(LUN_INFO['ID'], '0')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_cloned_volume,
+ FAKE_CLONED_VOLUME, FAKE_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ LUNCOPY_SETTING['Status'] = 'Normal'
+ # Test delete luncopy failed
+ set_error_flg('delluncopy')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_cloned_volume,
+ FAKE_CLONED_VOLUME, FAKE_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+ self.driver.delete_volume(FAKE_CLONED_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ # need to clean up LUNCopy
+ LUNCOPY_INFO['Name'] = None
+ LUNCOPY_INFO['ID'] = None
+ LUNCOPY_INFO['Type'] = None
+ LUNCOPY_INFO['State'] = None
+ LUNCOPY_INFO['Status'] = None
+
+ # Test normal create and delete cloned volume
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ ret = self.driver.create_cloned_volume(FAKE_CLONED_VOLUME, FAKE_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+ self.assertEqual(ret['provider_location'], CLONED_LUN_INFO['ID'])
+ self.driver.delete_volume(FAKE_CLONED_VOLUME)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ self.assertEqual(FAKE_CLONED_VOLUME['provider_location'], None)
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], None)
+
+ def test_create_delete_snapshot(self):
+ # Test no resource pool
+ RESPOOL_A_SIM['Valid Size'] = '0'
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_snapshot, FAKE_SNAPSHOT)
+ RESPOOL_A_SIM['Valid Size'] = '5120'
+ # Test no source volume
+ self.assertRaises(exception.VolumeNotFound,
+ self.driver.create_snapshot, FAKE_SNAPSHOT)
+ # Test create snapshot cli exception
+ self.driver.create_volume(FAKE_VOLUME)
+ set_error_flg('createsnapshot')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_snapshot,
+ FAKE_SNAPSHOT)
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ # Test active snapshot failed
+ set_error_flg('actvsnapshot')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_snapshot,
+ FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['ID'], None)
+ self.assertEqual(SNAPSHOT_INFO['Status'], None)
+ # Test disable snapshot failed
+ set_error_flg('disablesnapshot')
+ self.driver.create_snapshot(FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['ID'], VOLUME_SNAP_ID['snap'])
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.delete_snapshot,
+ FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['Status'], 'Active')
+ # Test delsnapshot failed
+ set_error_flg('delsnapshot')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.delete_snapshot,
+ FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['Status'], 'Disable')
+
+ self.driver.delete_snapshot(FAKE_SNAPSHOT)
+
+ # Test normal create and delete snapshot
+ self.driver.create_volume(FAKE_VOLUME)
+ ret = self.driver.create_snapshot(FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['ID'], VOLUME_SNAP_ID['snap'])
+ self.assertEqual(SNAPSHOT_INFO['Status'], 'Active')
+ self.assertEqual(ret['provider_location'], SNAPSHOT_INFO['ID'])
+ self.driver.delete_snapshot(FAKE_SNAPSHOT)
+ self.assertEqual(SNAPSHOT_INFO['ID'], None)
+ self.assertEqual(SNAPSHOT_INFO['Status'], None)
+
+ def test_create_delete_snapshot_volume(self):
+ # Test no source snapshot
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_volume_from_snapshot,
+ FAKE_CLONED_VOLUME, FAKE_SNAPSHOT)
+ # Test normal create and delete snapshot volume
+ self.driver.create_volume(FAKE_VOLUME)
+ self.driver.create_snapshot(FAKE_SNAPSHOT)
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ self.assertEqual(SNAPSHOT_INFO['ID'], VOLUME_SNAP_ID['snap'])
+ ret = self.driver.create_volume_from_snapshot(FAKE_CLONED_VOLUME,
+ FAKE_SNAPSHOT)
+ self.assertEqual(CLONED_LUN_INFO['ID'], VOLUME_SNAP_ID['vol_copy'])
+ self.assertEqual(ret['provider_location'], CLONED_LUN_INFO['ID'])
+ self.driver.delete_snapshot(FAKE_SNAPSHOT)
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.driver.delete_volume(FAKE_CLONED_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], None)
+ self.assertEqual(CLONED_LUN_INFO['ID'], None)
+ self.assertEqual(SNAPSHOT_INFO['ID'], None)
+
+ def test_initialize_connection(self):
+ # Test can not get iscsi iqn
+ set_error_flg('showiscsitgtname')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test failed to get iSCSI port info
+ set_error_flg('showiscsiip')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test create hostgroup failed
+ set_error_flg('createhostgroup')
+ MAP_INFO['Host Group ID'] = None
+ MAP_INFO['Host Group Name'] = None
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test create host failed
+ set_error_flg('addhost')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test add iSCSI initiator failed
+ set_error_flg('addiscsiini')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test add hostport failed
+ set_error_flg('addhostport')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test no volume
+ FAKE_VOLUME['provider_location'] = '100'
+ self.assertRaises(exception.VolumeNotFound,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ FAKE_VOLUME['provider_location'] = None
+ # Test map volume failed
+ self.driver.create_volume(FAKE_VOLUME)
+ set_error_flg('addhostmap')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test normal initialize connection
+ self.assertEqual(FAKE_VOLUME['provider_location'],
+ VOLUME_SNAP_ID['vol'])
+ self.assertEqual(LUN_INFO['Owner Controller'], 'A')
+ ret = self.driver.initialize_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+ iscsi_propers = ret['data']
+ self.assertEquals(iscsi_propers['target_iqn'],
+ INITIATOR_SETTING['TargetIQN-form'])
+ self.assertEquals(iscsi_propers['target_portal'],
+ INITIATOR_SETTING['Initiator TargetIP'] + ':3260')
+ self.assertEqual(MAP_INFO["DEV LUN ID"], LUN_INFO['ID'])
+ self.assertEqual(MAP_INFO["INI Port Info"],
+ FAKE_CONNECTOR['initiator'])
+ self.assertEqual(LUN_INFO['Owner Controller'], 'B')
+ self.driver.terminate_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], None)
+
+ def test_terminate_connection(self):
+ # Test no host was found
+ self.assertRaises(exception.HostNotFound,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test no volume was found
+ self.driver .create_volume(FAKE_VOLUME)
+ self.driver.initialize_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+ FAKE_VOLUME['provider_location'] = None
+ self.assertRaises(exception.VolumeNotFound,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ FAKE_VOLUME['provider_location'] = LUN_INFO['ID']
+ # Test delete map failed
+ set_error_flg('delhostmap')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Delete hostport failed
+ set_error_flg('delhostport')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test delete initiator failed
+ set_error_flg('deliscsiini')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test delete host failed
+ set_error_flg('delhost')
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ FAKE_VOLUME, FAKE_CONNECTOR)
+ # Test normal terminate connection
+ self.assertEqual(LUN_INFO['ID'], VOLUME_SNAP_ID['vol'])
+ self.driver.initialize_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+ self.driver.terminate_connection(FAKE_VOLUME, FAKE_CONNECTOR)
+ self.assertEqual(MAP_INFO["DEV LUN ID"], None)
+ self.driver.delete_volume(FAKE_VOLUME)
+ self.assertEqual(LUN_INFO['ID'], None)
+
+ def test_get_volume_stats(self):
+ stats = self.driver.get_volume_stats(True)
+ free_capacity = float(POOL_SETTING['Free Capacity']) / 1024
+ self.assertEqual(stats['free_capacity_gb'], free_capacity)
+ self.assertEqual(stats['storage_protocol'], 'iSCSI')
+
+
+class SSHMethodTestCase(test.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(SSHMethodTestCase, self).__init__(*args, **kwargs)
+
+ def setUp(self):
+ super(SSHMethodTestCase, self).setUp()
+
+ self.tmp_dir = tempfile.mkdtemp()
+ self.fake_conf_file = self.tmp_dir + '/cinder_huawei_conf.xml'
+ create_fake_conf_file(self.fake_conf_file)
+ self.configuration = mox.MockObject(conf.Configuration)
+ self.configuration.cinder_huawei_conf_file = self.fake_conf_file
+ self.configuration.append_config_values(mox.IgnoreArg())
+
+ self.stubs.Set(time, 'sleep', Fake_sleep)
+ self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
+ self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
+ Fake_change_file_mode)
+ Curr_test[0] = 'T'
+ self.driver = HuaweiVolumeDriver(configuration=self.configuration)
+ self.driver.do_setup(None)
+
+ def tearDown(self):
+ if os.path.exists(self.fake_conf_file):
+ os.remove(self.fake_conf_file)
+ shutil.rmtree(self.tmp_dir)
+ super(SSHMethodTestCase, self).tearDown()
+
+ def test_reach_max_connection_limit(self):
+ self.stubs.Set(FakeChannel, 'recv', self._fake_recv1)
+ self.assertRaises(exception.CinderException,
+ self.driver.create_volume, FAKE_VOLUME)
+
+ def test_socket_timeout(self):
+ self.stubs.Set(FakeChannel, 'recv', self._fake_recv2)
+ self.assertRaises(exception.CinderException,
+ self.driver.create_volume, FAKE_VOLUME)
+
+ def _fake_recv1(self, nbytes):
+ return "No response message"
+
+ def _fake_recv2(self, nBytes):
+ raise socket.timeout()
-# Copyright (c) 2012 Huawei Technologies Co., Ltd.
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
# Copyright (c) 2012 OpenStack LLC.
# All Rights Reserved.
#
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+"""
+Provide a unified driver class for users.
+
+The product type and the protocol should be specified in confige file before.
+"""
+
+from oslo.config import cfg
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder.volume.configuration import Configuration
+from cinder.volume import driver
+from cinder.volume.drivers.huawei import huawei_t
+from cinder.volume.drivers.huawei import ssh_common
+
+LOG = logging.getLogger(__name__)
+
+huawei_opt = [
+ cfg.StrOpt('cinder_huawei_conf_file',
+ default='/etc/cinder/cinder_huawei_conf.xml',
+ help='config data for cinder huawei plugin')]
+
+CONF = cfg.CONF
+CONF.register_opts(huawei_opt)
+
+
+class HuaweiVolumeDriver(object):
+ """Define an unified driver for Huawei drivers."""
+
+ def __init__(self, *args, **kwargs):
+ super(HuaweiVolumeDriver, self).__init__()
+ self._product = {'T': huawei_t}
+ self._protocol = {'iSCSI': 'ISCSIDriver'}
+
+ self.driver = self._instantiate_driver(*args, **kwargs)
+
+ def _instantiate_driver(self, *args, **kwargs):
+ """Instantiate the specified driver."""
+ self.configuration = kwargs.get('configuration', None)
+ if not self.configuration:
+ msg = (_('_instantiate_driver: configuration not found.'))
+ raise exception.InvalidInput(reason=msg)
+
+ self.configuration.append_config_values(huawei_opt)
+ conf_file = self.configuration.cinder_huawei_conf_file
+ (product, protocol) = self._get_conf_info(conf_file)
+
+ LOG.debug(_('_instantiate_driver: Loading %(protocol)s driver for '
+ 'Huawei OceanStor %(product)s series storage arrays.')
+ % {'protocol': protocol,
+ 'product': product})
+
+ driver_module = self._product[product]
+ driver_class = 'Huawei' + product + self._protocol[protocol]
+
+ driver_class = getattr(driver_module, driver_class)
+ return driver_class(*args, **kwargs)
+
+ def _get_conf_info(self, filename):
+ """Get product type and connection protocol from config file."""
+ root = ssh_common.parse_xml_file(filename)
+ product = root.findtext('Storage/Product').strip()
+ protocol = root.findtext('Storage/Protocol').strip()
+ if (product in self._product.keys() and
+ protocol in self._protocol.keys()):
+ return (product, protocol)
+ else:
+ msg = (_('"Product" or "Protocol" is illegal. "Product" should '
+ 'be set to T. "Protocol" should be set '
+ 'to iSCSI. Product: %(product)s '
+ 'Protocol: %(protocol)s')
+ % {'product': str(product),
+ 'protocol': str(protocol)})
+ raise exception.InvalidInput(reason=msg)
+
+ def __setattr__(self, name, value):
+ """Set the attribute."""
+ if getattr(self, 'driver', None):
+ self.driver.__setattr__(name, value)
+ return
+ object.__setattr__(self, name, value)
+
+ def __getattr__(self, name):
+ """"Get the attribute."""
+ drver = object.__getattribute__(self, 'driver')
+ return getattr(drver, name)
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" ?>
-<config>
- <Storage>
- <ControllerIP0>x.x.x.x</ControllerIP0>
- <ControllerIP1>x.x.x.x</ControllerIP1>
- <UserName>xxxxxx</UserName>
- <UserPassword>xxxxxx</UserPassword>
- </Storage>
- <LUN>
- <!--LUN Type: Thick, or Thin. Default: Thick-->
- <LUNType>Thick</LUNType>
- <!--The stripe size can be 4, 8, 16, 32, 64, 128, 256, and 512 in the unit of KB.Default: 64-->
- <StripUnitSize>64</StripUnitSize>
- <!--The write cache policy of the LUN:-->
- <!--1 specifies write back, 2 specifies write through, 3 specifies write back mandatorily.Default: 1-->
- <WriteType>1</WriteType>
- <!--Enables or disbles cahce mirroring: 0 Disable, or 1 Enable. Default: Enable-->
- <MirrorSwitch>1</MirrorSwitch>
- <!--The prefetch policy of the reading cache:-->
- <!--prefetch type 0 specifies non-preftch and prefetch value is 0,-->
- <!--prefetch type 1 specifies constant prefetch and prefetch value ranges from 0 to 1024 in the unit of KB,-->
- <!--prefetch type 2 specifies variable prefetch and value specifies cache prefetch multiple ranges from 0 to 65535,-->
- <!--prefetch type 3 specifies intelligent prefetch Intelligent and Vaule is 0,-->
- <!--Default: prefetch type 0 and prefetch value 0-->
- <Prefetch Type="0" Value="0"/>
- <StoragePool Name="xxxxxx"/>
- <StoragePool Name="xxxxxx"/>
- </LUN>
- <iSCSI>
- <DefaultTargetIP>x.x.x.x</DefaultTargetIP>
- <Initiator Name="xxxxxx" TargetIP="x.x.x.x"/>
- <Initiator Name="xxxxxx" TargetIP="x.x.x.x"/>
- </iSCSI>
-</config>
+++ /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 base64
-import os
-import paramiko
-import re
-import socket
-import threading
-import time
-
-from oslo.config import cfg
-from xml.etree import ElementTree as ET
-
-from cinder import exception
-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')]
-
-HOST_GROUP_NAME = 'HostGroup_OpenStack'
-HOST_NAME_PREFIX = 'Host_'
-HOST_PORT_PREFIX = 'HostPort_'
-VOL_AND_SNAP_NAME_PREFIX = 'OpenStack_'
-READBUFFERSIZE = 8192
-
-
-CONF = cfg.CONF
-CONF.register_opts(huawei_opt)
-
-
-class SSHConn(utils.SSHPool):
- """Define a new class inherited to SSHPool.
-
- This class rewrites method create() and defines a private method
- ssh_read() which reads results of ssh commands.
- """
-
- def __init__(self, ip, port, conn_timeout, login, password,
- privatekey=None, *args, **kwargs):
-
- super(SSHConn, self).__init__(ip, port, conn_timeout, login,
- password, privatekey=None,
- *args, **kwargs)
- self.lock = threading.Lock()
-
- def create(self):
- """Create an SSH client.
-
- Because seting socket timeout to be None will cause client.close()
- blocking, here we have to rewrite method create() and use default
- socket timeout value 0.1.
- """
- try:
- ssh = paramiko.SSHClient()
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- if self.password:
- ssh.connect(self.ip,
- port=self.port,
- username=self.login,
- password=self.password,
- timeout=self.conn_timeout)
- elif self.privatekey:
- pkfile = os.path.expanduser(self.privatekey)
- privatekey = paramiko.RSAKey.from_private_key_file(pkfile)
- ssh.connect(self.ip,
- port=self.port,
- username=self.login,
- pkey=privatekey,
- timeout=self.conn_timeout)
- else:
- msg = _("Specify a password or private_key")
- raise exception.CinderException(msg)
-
- if self.conn_timeout:
- transport = ssh.get_transport()
- transport.set_keepalive(self.conn_timeout)
- return ssh
- except Exception as e:
- msg = _("Error connecting via ssh: %s") % e
- LOG.error(msg)
- raise paramiko.SSHException(msg)
-
- def ssh_read(self, channel, cmd, timeout):
- """Get results of CLI commands."""
- result = ''
- user = self.login
- user_flg = user + ':/>$'
- channel.settimeout(timeout)
- while True:
- try:
- result = result + channel.recv(READBUFFERSIZE)
- except socket.timeout:
- raise exception.VolumeBackendAPIException(_('read timed out'))
- else:
- if re.search(cmd, result) and re.search(user_flg, result):
- if not re.search('Welcome', result):
- break
- elif re.search(user + ':/>' + cmd, result):
- break
- elif re.search('(y/n)', result):
- break
- return '\r\n'.join(result.split('\r\n')[:-1])
-
-
-class HuaweiISCSIDriver(driver.ISCSIDriver):
- """Huawei T series and Dorado iSCSI volume driver."""
-
- VERSION = "1.0.0"
-
- def __init__(self, *args, **kwargs):
- super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
- self.configuration.append_config_values(huawei_opt)
- self.device_type = {}
- self.login_info = {}
- self.hostgroup_id = None
- self.ssh_pool = None
-
- def do_setup(self, context):
- """Check config file."""
- LOG.debug(_('do_setup.'))
-
- self._check_conf_file()
-
- 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)
-
- # Check whether storage pools are configured.
- # Dorado2100 G2 needn't to configure this.
- if self.device_type['type'] != 'Dorado2100 G2':
- root = self._read_xml()
- pool_node = root.findall('LUN/StoragePool')
- if not pool_node:
- err_msg = (_('_get_device_type: Storage Pool must be '
- 'configured.'))
- 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_size = '100M'
- else:
- volume_size = '%sG' % volume['size']
-
- self._create_volume(volume_name, volume_size)
-
- 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 is not None:
- 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 lun_id is None:
- 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})
-
- self.login_info = self._get_login_info()
- # 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:
- if not iscsi_conf['DefaultTargetIP']:
- err_msg = (_('initialize_connection:Failed to find target ip '
- 'for initiator:%(initiatorname)s, '
- 'please check config file.')
- % {'initiatorname': initiator_name})
- LOG.error(err_msg)
- raise exception.VolumeBackendAPIException(data=err_msg)
- target_ip = iscsi_conf['DefaultTargetIP']
-
- (target_iqn, controller) = self._get_tgt_iqn(target_ip)
- if not target_iqn:
- err_msg = (_('initialize_connection:Failed to find target iSCSI '
- 'iqn. Target IP:%(ip)s')
- % {'ip': target_ip})
- LOG.error(err_msg)
- raise exception.VolumeBackendAPIException(data=err_msg)
-
- # Create hostgroup and host.
- hostgroup_name = HOST_GROUP_NAME
- self.hostgroup_id = self._find_hostgroup(hostgroup_name)
- if self.hostgroup_id is None:
- self._create_hostgroup(hostgroup_name)
- self.hostgroup_id = self._find_hostgroup(hostgroup_name)
-
- host_name = HOST_NAME_PREFIX + str(hash(initiator_name))
- host_id = self._find_host_in_hostgroup(host_name, self.hostgroup_id)
- if host_id is None:
- 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})
-
- # Map a LUN to a host if not mapped.
- lun_id = self._find_lun(volume_name)
- if lun_id is None:
- 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)
- # Make sure the hostLUN ID starts from 1.
- new_hostlun_id = 1
- new_hostlunid_found = False
- if map_info:
- for map in map_info:
- if map['devlunid'] == lun_id:
- hostlun_id = map['hostlunid']
- break
- elif not new_hostlunid_found:
- if new_hostlun_id < int(map['hostlunid']):
- new_hostlunid_found = True
- else:
- new_hostlun_id = int(map['hostlunid']) + 1
- # The LUN is not mapped to the host.
- if not hostlun_id:
- self._map_lun(lun_id, host_id, new_hostlun_id)
- hostlun_id = self._get_hostlunid(host_id, lun_id)
-
- # Change lun ownning controller for better performance.
- if self._get_lun_controller(lun_id) != controller:
- self._change_lun_controller(lun_id, controller)
-
- # 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 host_id is None:
- 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 lun_id is None:
- 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 is not None:
- 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)
-
- if self._is_resource_pool_enough() is False:
- err_msg = (_('create_snapshot:'
- 'Resource pool needs 1GB valid size at least.'))
- LOG.error(err_msg)
- raise exception.VolumeBackendAPIException(data=err_msg)
-
- lun_id = self._find_lun(volume_name)
- if lun_id is None:
- 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 is not None:
- 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 snapshot_id is None:
- 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 stats.
-
- If 'refresh' is True, run update the stats first.
- """
- if refresh:
- self._update_volume_stats()
-
- 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')
-
- isconfwrong = False
- if ((not IP1 and not IP2) or
- (not username) or
- (not pwd)):
- err_msg = (_('Config file is wrong. Controler IP, '
- 'UserName and UserPassword 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 = self.configuration.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 = {}
- try:
- filename = self.configuration.cinder_huawei_conf_file
- tree = ET.parse(filename)
- root = tree.getroot()
- logininfo['ControllerIP0'] = root.findtext('Storage/ControllerIP0')
- logininfo['ControllerIP1'] = root.findtext('Storage/ControllerIP1')
-
- need_encode = False
- for key in ['UserName', 'UserPassword']:
- node = root.find('Storage/%s' % key)
- node_text = node.text
- if node_text.find('!$$$') == 0:
- logininfo[key] = base64.b64decode(node_text[4:])
- else:
- logininfo[key] = node_text
- node.text = '!$$$' + base64.b64encode(node_text)
- need_encode = True
- if need_encode:
- try:
- tree.write(filename, 'UTF-8')
- except Exception as err:
- LOG.error(_('Write login information to xml error. %s')
- % err)
-
- except Exception as err:
- LOG.error(_('_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:
- 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:
- lunsetinfo['StripUnitSize'] = stripunitsize
- writetype = root.findtext('LUN/WriteType')
- if writetype:
- lunsetinfo['WriteType'] = writetype
- mirrorswitch = root.findtext('LUN/MirrorSwitch')
- if mirrorswitch:
- lunsetinfo['MirrorSwitch'] = mirrorswitch
-
- if self.device_type['type'] == 'Tseries':
- pooltype = lunsetinfo['LUNType']
- prefetch = root.find('LUN/Prefetch')
- if prefetch 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 is not None:
- 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.
- """
- LOG.debug(_('CLI command:%s') % cmd)
- connect_times = 0
- ip0 = self.login_info['ControllerIP0']
- ip1 = self.login_info['ControllerIP1']
- user = self.login_info['UserName']
- pwd = self.login_info['UserPassword']
- if not self.ssh_pool:
- self.ssh_pool = SSHConn(ip0, 22, 30, user, pwd)
- ssh_client = None
- while True:
- if connect_times == 1:
- # Switch to the other controller.
- self.ssh_pool.lock.acquire()
- if ssh_client:
- if ssh_client.server_ip == self.ssh_pool.ip:
- if self.ssh_pool.ip == ip0:
- self.ssh_pool.ip = ip1
- else:
- self.ssh_pool.ip = ip0
- # Create a new client.
- if ssh_client.chan:
- ssh_client.chan.close()
- ssh_client.chan = None
- ssh_client.server_ip = None
- ssh_client.close()
- ssh_client = None
- ssh_client = self.ssh_pool.create()
- else:
- self.ssh_pool.ip = ip1
- self.ssh_pool.lock.release()
- try:
- if not ssh_client:
- ssh_client = self.ssh_pool.get()
- # "server_ip" shows controller connecting with the ssh client.
- if ('server_ip' not in ssh_client.__dict__ or
- not ssh_client.server_ip):
- self.ssh_pool.lock.acquire()
- ssh_client.server_ip = self.ssh_pool.ip
- self.ssh_pool.lock.release()
- # An SSH client owns one "chan".
- if ('chan' not in ssh_client.__dict__ or
- not ssh_client.chan):
- ssh_client.chan =\
- utils.create_channel(ssh_client, 600, 800)
-
- while True:
- ssh_client.chan.send(cmd + '\n')
- out = self.ssh_pool.ssh_read(ssh_client.chan, cmd, 20)
- if out.find('(y/n)') > -1:
- cmd = 'y'
- else:
- break
- self.ssh_pool.put(ssh_client)
-
- index = out.find(user + ':/>')
- if index > -1:
- return out[index:]
- else:
- return out
-
- except Exception as err:
- if connect_times < 1:
- connect_times += 1
- continue
- else:
- if ssh_client:
- self.ssh_pool.remove(ssh_client)
- LOG.error(_('_execute_cli:%s') % err)
- raise exception.VolumeBackendAPIException(data=err)
-
- def _name_translate(self, name):
- """Form new names because of the 32-character limit on names."""
- newname = VOL_AND_SNAP_NAME_PREFIX + 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].replace('Not format', 'Notformat').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
-
- 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']
- 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, 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, iscsiip_info['ctrid'])
- else:
- return (None, 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, new_hostlun_id):
- """Map a lun to a host.
-
- Here we give the hostlun ID which starts from 1.
- """
- cli_cmd = ('addhostmap -host %(hostid)s -devlun %(lunid)s '
- '-hostlun %(hostlunid)s'
- % {'hostid': hostid,
- 'lunid': lunid,
- 'hostlunid': new_hostlun_id})
- 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 '
- 'hostlunid:%(hostlunid)s '
- 'out:%(out)s')
- % {'host': hostid,
- 'lun': lunid,
- 'hostlunid': new_hostlun_id,
- '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 = (_('_delete_host: 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.
-
- This method return a map information list. Every item in the list
- is a dictionary. The dictionary includes three keys: mapid,
- devlunid, hostlunid. These items are sorted by hostlunid value
- from small to large.
- """
- 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']
- 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))
- inserted = False
- mapinfo_length = len(mapinfo)
- if mapinfo_length == 0:
- mapinfo.append(dic)
- continue
- for index in range(0, mapinfo_length):
- if (int(mapinfo[mapinfo_length - index - 1]['hostlunid']) <
- int(dic['hostlunid'])):
- mapinfo.insert(mapinfo_length - index, dic)
- inserted = True
- break
- if not inserted:
- mapinfo.insert(0, 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 is not 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 _get_lun_controller(self, lun_id):
- cli_cmd = ('showlun -lun %s' % lun_id)
- out = self._execute_cli(cli_cmd)
- en = out.split('\r\n')
- if len(en) <= 4:
- return None
-
- if "Dorado2100 G2" == self.device_type['type']:
- return en[10].split()[3]
- else:
- return en[12].split()[3]
-
- def _change_lun_controller(self, lun_id, controller):
- cli_cmd = ('chglun -lun %s -c %s' % (lun_id, controller))
- out = self._execute_cli(cli_cmd)
- if not re.search('command operates successfully', out):
- err_msg = (_('_change_lun_controller:Failed to change lun owning '
- 'controller. lun id:%(lunid)s. '
- 'new controller:%(controller)s. '
- 'out:%(out)s')
- % {'lunid': lun_id,
- 'controller': controller,
- 'out': out})
- LOG.error(err_msg)
- raise exception.VolumeBackendAPIException(data=err_msg)
-
- def _is_resource_pool_enough(self):
- """Check whether resource pools' valid size is more than 1G."""
- cli_cmd = ('showrespool')
- out = self._execute_cli(cli_cmd)
- en = re.split('\r\n', out)
- if len(en) <= 6:
- LOG.error(_('_is_resource_pool_enough:Resource pool for snapshot '
- 'not be added.'))
- return False
- resource_pools = []
- list_key = ['pool id', 'size', 'usage', 'valid size',
- 'alarm threshold']
- for i in range(6, len(en) - 2):
- list_val = en[i].split()
- dic = dict(map(None, list_key, list_val))
- resource_pools.append(dic)
-
- for pool in resource_pools:
- if float(pool['valid size']) < 1024.0:
- return False
- return True
-
- def _update_volume_stats(self):
- """Retrieve stats info from volume group."""
-
- LOG.debug(_("Updating volume stats"))
- data = {}
- backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = backend_name or 'HuaweiISCSIDriver'
- data['vendor_name'] = 'Huawei'
- data['driver_version'] = self.VERSION
- 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()
- root = self._read_xml()
- lun_type = root.findtext('LUN/LUNType')
- if self.device_type['type'] == 'Dorado2100 G2':
- lun_type = 'Thin'
- elif (self.device_type['type'] == 'Dorado5100' or not lun_type):
- lun_type = 'Thick'
- poolinfo_dev = self._find_pool_info(lun_type)
- pools_conf = root.findall('LUN/StoragePool')
- total_free_capacity = 0.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 total_free_capacity / 1024
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Volume Drivers for Huawei OceanStor T series storage arrays.
+"""
+
+import re
+import time
+
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder.volume import driver
+from cinder.volume.drivers.huawei import ssh_common
+
+
+LOG = logging.getLogger(__name__)
+
+HOST_PORT_PREFIX = 'HostPort_'
+
+
+class HuaweiTISCSIDriver(driver.ISCSIDriver):
+ """ISCSI driver for Huawei OceanStor T series storage arrays."""
+
+ VERSION = '1.1.0'
+
+ def __init__(self, *args, **kwargs):
+ super(HuaweiTISCSIDriver, self).__init__(*args, **kwargs)
+
+ def do_setup(self, context):
+ """Instantiate common class."""
+ self.common = ssh_common.TseriesCommon(configuration=
+ self.configuration)
+ self.common.do_setup(context)
+ self._assert_cli_out = self.common._assert_cli_out
+ self._assert_cli_operate_out = self.common._assert_cli_operate_out
+
+ def check_for_setup_error(self):
+ """Check something while starting."""
+ self.common.check_for_setup_error()
+
+ def create_volume(self, volume):
+ """Create a new volume."""
+ volume_id = self.common.create_volume(volume)
+ return {'provider_location': volume_id}
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Create a volume from a snapshot."""
+ volume_id = self.common.create_volume_from_snapshot(volume, snapshot)
+ return {'provider_location': volume_id}
+
+ def create_cloned_volume(self, volume, src_vref):
+ """Create a clone of the specified volume."""
+ volume_id = self.common.create_cloned_volume(volume, src_vref)
+ return {'provider_location': volume_id}
+
+ def delete_volume(self, volume):
+ """Delete a volume."""
+ self.common.delete_volume(volume)
+
+ def create_export(self, context, volume):
+ """Export the volume."""
+ pass
+
+ def ensure_export(self, context, volume):
+ """Synchronously recreate an export for a volume."""
+ pass
+
+ def remove_export(self, context, volume):
+ """Remove an export for a volume."""
+ pass
+
+ def create_snapshot(self, snapshot):
+ """Create a snapshot."""
+ snapshot_id = self.common.create_snapshot(snapshot)
+ return {'provider_location': snapshot_id}
+
+ def delete_snapshot(self, snapshot):
+ """Delete a snapshot."""
+ self.common.delete_snapshot(snapshot)
+
+ def initialize_connection(self, volume, connector):
+ """Map a volume to a host and return target iSCSI information."""
+ LOG.debug(_('initialize_connection: volume name: %(vol)s '
+ 'host: %(host)s initiator: %(ini)s')
+ % {'vol': volume['name'],
+ 'host': connector['host'],
+ 'ini': connector['initiator']})
+
+ self.common._update_login_info()
+ (iscsi_iqn, target_ip, port_ctr) =\
+ self._get_iscsi_params(connector['initiator'])
+
+ # First, add a host if not added before.
+ host_id = self.common.add_host(connector['host'])
+
+ # Then, add the iSCSI port to the host.
+ self._add_iscsi_port_to_host(host_id, connector)
+
+ # Finally, map the volume to the host.
+ volume_id = volume['provider_location']
+ hostlun_id = self.common.map_volume(host_id, volume_id)
+
+ # Change LUN ctr for better performance, just for single path.
+ lun_details = self.common.get_lun_details(volume_id)
+ if (lun_details['LunType'] == 'THICK' and
+ lun_details['OwningController'] != port_ctr):
+ self.common.change_lun_ctr(volume_id, port_ctr)
+
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_portal'] = ('%s:%s' % (target_ip, '3260'))
+ properties['target_iqn'] = iscsi_iqn
+ properties['target_lun'] = int(hostlun_id)
+ properties['volume_id'] = volume['id']
+ auth = volume['provider_auth']
+ if auth:
+ (auth_method, auth_username, auth_secret) = auth.split()
+
+ properties['auth_method'] = auth_method
+ properties['auth_username'] = auth_username
+ properties['auth_password'] = auth_secret
+
+ return {'driver_volume_type': 'iscsi', 'data': properties}
+
+ def _get_iscsi_params(self, initiator):
+ """Get target iSCSI params, including iqn and IP."""
+ conf_file = self.common.configuration.cinder_huawei_conf_file
+ iscsi_conf = self._get_iscsi_conf(conf_file)
+ target_ip = None
+ for ini in iscsi_conf['Initiator']:
+ if ini['Name'] == initiator:
+ target_ip = ini['TargetIP']
+ break
+ # If didn't specify target IP for some initiator, use default IP.
+ if not target_ip:
+ if iscsi_conf['DefaultTargetIP']:
+ target_ip = iscsi_conf['DefaultTargetIP']
+
+ else:
+ msg = (_('_get_iscsi_params: Failed to get target IP '
+ 'for initiator %(ini)s, please check config file.')
+ % {'ini': initiator})
+ LOG.error(msg)
+ raise exception.InvalidInput(reason=msg)
+
+ (target_iqn, port_ctr) = self._get_tgt_iqn(target_ip)
+ return (target_iqn, target_ip, port_ctr)
+
+ def _get_iscsi_conf(self, filename):
+ """Get iSCSI info from config file.
+
+ This function returns a dict:
+ {'DefaultTargetIP': '11.11.11.11',
+ 'Initiator': [{'Name': 'iqn.xxxxxx.1', 'TargetIP': '11.11.11.12'},
+ {'Name': 'iqn.xxxxxx.2', 'TargetIP': '11.11.11.13'}
+ ]
+ }
+
+ """
+
+ iscsiinfo = {}
+ root = ssh_common.parse_xml_file(filename)
+
+ default_ip = root.findtext('iSCSI/DefaultTargetIP')
+ if default_ip:
+ iscsiinfo['DefaultTargetIP'] = default_ip.strip()
+ else:
+ iscsiinfo['DefaultTargetIP'] = None
+ initiator_list = []
+ tmp_dic = {}
+ for dic in root.findall('iSCSI/Initiator'):
+ # Strip the values of dict.
+ for k, v in dic.items():
+ tmp_dic[k] = v.strip()
+ initiator_list.append(tmp_dic)
+ iscsiinfo['Initiator'] = initiator_list
+ return iscsiinfo
+
+ def _get_tgt_iqn(self, port_ip):
+ """Run CLI command to get target iSCSI iqn.
+
+ The iqn is formed with three parts:
+ iSCSI target name + iSCSI port info + iSCSI IP
+
+ """
+
+ LOG.debug(_('_get_tgt_iqn: iSCSI IP is %s.') % port_ip)
+
+ cli_cmd = 'showiscsitgtname'
+ out = self.common._execute_cli(cli_cmd)
+
+ self._assert_cli_out(re.search('ISCSI Name', out),
+ '_get_tgt_iqn',
+ 'Failed to get iSCSI target %s iqn.' % port_ip,
+ cli_cmd, out)
+
+ lines = out.split('\r\n')
+ index = lines[4].index('iqn')
+ iqn_prefix = lines[4][index:].strip()
+ # Here we make sure port_info won't be None.
+ port_info = self._get_iscsi_tgt_port_info(port_ip)
+ ctr = ('0' if port_info[0] == 'A' else '1')
+ interface = '0' + port_info[1]
+ port = '0' + port_info[2][1:]
+ iqn_suffix = ctr + '02' + interface + port
+ # iqn_suffix should not start with 0
+ while(True):
+ if iqn_suffix.startswith('0'):
+ iqn_suffix = iqn_suffix[1:]
+ else:
+ break
+
+ iqn = iqn_prefix + ':' + iqn_suffix + ':' + port_info[3]
+
+ LOG.debug(_('_get_tgt_iqn: iSCSI target iqn is %s') % iqn)
+
+ return (iqn, port_info[0])
+
+ def _get_iscsi_tgt_port_info(self, port_ip):
+ """Get iSCSI Port information of storage device."""
+ cli_cmd = 'showiscsiip'
+ out = self.common._execute_cli(cli_cmd)
+ if re.search('iSCSI IP Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if tmp_line[3] == port_ip:
+ return tmp_line
+
+ err_msg = _('_get_iscsi_tgt_port_info: Failed to get iSCSI port '
+ 'info. Please make sure the iSCSI port IP %s is '
+ 'configured in array.') % port_ip
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _add_iscsi_port_to_host(self, hostid, connector, multipathtype=0):
+ """Add an iSCSI port to the given host.
+
+ First, add an initiator if needed, the initiator is equivalent to
+ an iSCSI port. Then, add the initiator to host if not added before.
+
+ """
+
+ initiator = connector['initiator']
+ # Add an iSCSI initiator.
+ if not self._initiator_added(initiator):
+ self._add_initiator(initiator)
+ # Add the initiator to host if not added before.
+ port_name = HOST_PORT_PREFIX + str(hash(initiator))
+ portadded = False
+ hostport_info = self.common._get_host_port_info(hostid)
+ if hostport_info:
+ for hostport in hostport_info:
+ if hostport[2] == initiator:
+ portadded = True
+ break
+ if not portadded:
+ cli_cmd = ('addhostport -host %(id)s -type 5 '
+ '-info %(info)s -n %(name)s -mtype %(multype)s'
+ % {'id': hostid,
+ 'info': initiator,
+ 'name': port_name,
+ 'multype': multipathtype})
+ out = self.common._execute_cli(cli_cmd)
+
+ msg = ('Failed to add iSCSI port %(port)s to host %(host)s'
+ % {'port': port_name,
+ 'host': hostid})
+ self._assert_cli_operate_out('_add_iscsi_port_to_host',
+ msg, cli_cmd, out)
+
+ def _initiator_added(self, ininame):
+ """Check whether the initiator is already added."""
+ cli_cmd = 'showiscsiini -ini %(name)s' % {'name': ininame}
+ out = self.common._execute_cli(cli_cmd)
+ return (True if re.search('Initiator Information', out) else False)
+
+ def _add_initiator(self, ininame):
+ """Add a new initiator to storage device."""
+ cli_cmd = 'addiscsiini -n %(name)s' % {'name': ininame}
+ out = self.common._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_add_iscsi_host_port',
+ 'Failed to add initiator %s' % ininame,
+ cli_cmd, out)
+
+ def _delete_initiator(self, ininame, attempts=2):
+ """Delete an initiator."""
+ cli_cmd = 'deliscsiini -n %(name)s' % {'name': ininame}
+ while(attempts > 0):
+ out = self.common._execute_cli(cli_cmd)
+ if re.search('the port is in use', out):
+ attempts -= 1
+ time.sleep(2)
+ else:
+ break
+
+ self._assert_cli_operate_out('_map_lun',
+ 'Failed to delete initiator %s.'
+ % ininame,
+ cli_cmd, out)
+
+ def terminate_connection(self, volume, connector, **kwargs):
+ """Terminate the map."""
+ LOG.debug(_('terminate_connection: volume: %(vol)s host: %(host)s '
+ 'connector: %(initiator)s')
+ % {'vol': volume['name'],
+ 'host': connector['host'],
+ 'initiator': connector['initiator']})
+
+ self.common._update_login_info()
+ host_id = self.common.remove_map(volume['provider_location'],
+ connector['host'])
+ if not self.common._get_host_map_info(host_id):
+ self._remove_iscsi_port(host_id, connector)
+
+ def _remove_iscsi_port(self, hostid, connector):
+ """Remove iSCSI ports and delete host."""
+ initiator = connector['initiator']
+ # Delete the host initiator if no LUN mapped to it.
+ port_num = 0
+ port_info = self.common._get_host_port_info(hostid)
+ if port_info:
+ port_num = len(port_info)
+ for port in port_info:
+ if port[2] == initiator:
+ self.common._delete_hostport(port[0])
+ self._delete_initiator(initiator)
+ port_num -= 1
+ break
+ else:
+ LOG.warn(_('_remove_iscsi_port: iSCSI port was not found '
+ 'on host %(hostid)s') % {'hostid': hostid})
+
+ # Delete host if no initiator added to it.
+ if port_num == 0:
+ self.common._delete_host(hostid)
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume stats."""
+ self._stats = self.common.get_volume_stats(refresh)
+ self._stats['storage_protocol'] = 'iSCSI'
+ self._stats['driver_version'] = self.VERSION
+ backend_name = self.configuration.safe_get('volume_backend_name')
+ self._stats['volume_backend_name'] = (backend_name or
+ self.__class__.__name__)
+ return self._stats
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2013 Huawei Technologies Co., Ltd.
+# Copyright (c) 2012 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Common classes for Huawei OceanStor T series and Dorado series storage arrays.
+
+The common classes provide the drivers command line operation using SSH.
+"""
+
+import base64
+import re
+import socket
+import threading
+import time
+
+from xml.etree import ElementTree as ET
+
+from cinder import context
+from cinder import exception
+from cinder.openstack.common import excutils
+from cinder.openstack.common import log as logging
+from cinder import utils
+from cinder.volume import volume_types
+
+
+LOG = logging.getLogger(__name__)
+
+HOST_GROUP_NAME = 'HostGroup_OpenStack'
+HOST_NAME_PREFIX = 'Host_'
+VOL_AND_SNAP_NAME_PREFIX = 'OpenStack_'
+
+
+def parse_xml_file(filepath):
+ """Get root of xml file."""
+ try:
+ tree = ET.parse(filepath)
+ root = tree.getroot()
+ return root
+ except IOError as err:
+ LOG.error(_('parse_xml_file: %s') % err)
+ raise exception.ConfigNotFound(path=err)
+
+
+def ssh_read(user, channel, cmd, timeout):
+ """Get results of CLI commands."""
+ result = ''
+ channel.settimeout(timeout)
+ while True:
+ try:
+ result = result + channel.recv(8192)
+ except socket.timeout:
+ raise exception.CinderException(_('ssh_read: Read '
+ 'SSH timeout'))
+ else:
+ # Complete CLI response starts with CLI cmd and
+ # ends with "username:/>".
+ if result.startswith(cmd) and result.endswith(user + ':/>'):
+ if not re.search('Welcome', result):
+ break
+ # CLI returns welcome information when first log in.
+ elif re.search(user + ':/>' + cmd, result):
+ break
+ # Some commands need to send 'y'.
+ elif re.search('(y/n)', result):
+ break
+ # Reach maximum limit of SSH connection.
+ elif re.search('No response message', result):
+ msg = _('No response message. Please check system status.')
+ raise exception.CinderException(msg)
+ # Filter the last line: username:/> .
+ result = '\r\n'.join(result.split('\r\n')[:-1])
+ # Filter welcome information.
+ index = result.find(user + ':/>')
+
+ return (result[index:] if index > -1 else result)
+
+
+class TseriesCommon():
+ """Common class for Huawei T series storage arrays."""
+
+ def __init__(self, configuration=None):
+ self.configuration = configuration
+ self.xml_conf = self.configuration.cinder_huawei_conf_file
+ self.login_info = {}
+ self.lun_distribution = [0, 0]
+ self.hostgroup_id = None
+ self.ssh_pool = None
+ self.lock_ip = threading.Lock()
+ self.luncopy_list = [] # to storage LUNCopy name
+
+ def do_setup(self, context):
+ """Check config file."""
+ LOG.debug(_('do_setup.'))
+
+ self._check_conf_file()
+ self.login_info = self._get_login_info()
+ self.lun_distribution = self._get_lun_distribution_info()
+ self.luncopy_list = self._get_all_luncopy_name()
+
+ def _check_conf_file(self):
+ """Check config file, make sure essential items are set."""
+ root = parse_xml_file(self.xml_conf)
+ IP1 = root.findtext('Storage/ControllerIP0')
+ IP2 = root.findtext('Storage/ControllerIP1')
+ username = root.findtext('Storage/UserName')
+ pwd = root.findtext('Storage/UserPassword')
+ pool_node = root.findall('LUN/StoragePool')
+
+ if (not IP1 or not IP2) or (not username) or (not pwd):
+ err_msg = (_('_check_conf_file: Config file invalid. Controler IP,'
+ ' UserName and UserPassword must be set.'))
+ LOG.error(err_msg)
+ raise exception.InvalidInput(reason=err_msg)
+
+ for pool in pool_node:
+ if pool.attrib['Name']:
+ return
+ # If pool_node is None or pool.attrib['Name'] is None.
+ err_msg = (_('_check_conf_file: Config file invalid. '
+ 'StoragePool must be set.'))
+ LOG.error(err_msg)
+ raise exception.InvalidInput(reason=err_msg)
+
+ def _get_login_info(self):
+ """Get login IP, username and password from config file."""
+ logininfo = {}
+ filename = self.configuration.cinder_huawei_conf_file
+ tree = ET.parse(filename)
+ root = tree.getroot()
+ logininfo['ControllerIP0'] =\
+ root.findtext('Storage/ControllerIP0').strip()
+ logininfo['ControllerIP1'] =\
+ root.findtext('Storage/ControllerIP1').strip()
+
+ need_encode = False
+ for key in ['UserName', 'UserPassword']:
+ node = root.find('Storage/%s' % key)
+ node_text = node.text.strip()
+ # Prefix !$$$ means encoded already.
+ if node_text.find('!$$$') > -1:
+ logininfo[key] = base64.b64decode(node_text[4:])
+ else:
+ logininfo[key] = node_text
+ node.text = '!$$$' + base64.b64encode(node_text)
+ need_encode = True
+ if need_encode:
+ self._change_file_mode(filename)
+ try:
+ tree.write(filename, 'UTF-8')
+ except Exception as err:
+ LOG.info(_('_get_login_info: %s') % err)
+
+ return logininfo
+
+ def _change_file_mode(self, filepath):
+ utils.execute('chmod', '777', filepath, run_as_root=True)
+
+ def _get_lun_distribution_info(self):
+ """Get LUN distribution information.
+
+ For we have two controllers for each array, we want to make all
+ LUNs(just for Thick LUN) distributed evenly. The driver uses the
+ LUN distribution info to determine in which controller to create
+ a new LUN.
+
+ """
+
+ luns = self._get_all_luns_info()
+ ctr_info = [0, 0]
+ for lun in luns:
+ if (lun[6].startswith(VOL_AND_SNAP_NAME_PREFIX) and
+ lun[8] == 'THICK'):
+ if lun[4] == 'A':
+ ctr_info[0] += 1
+ else:
+ ctr_info[1] += 1
+ return ctr_info
+
+ def check_for_setup_error(self):
+ pass
+
+ def _get_all_luncopy_name(self):
+ cli_cmd = 'showluncopy'
+ out = self._execute_cli(cli_cmd)
+ luncopy_ids = []
+ if re.search('LUN Copy Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if tmp_line[0].startswith(VOL_AND_SNAP_NAME_PREFIX):
+ luncopy_ids.append(tmp_line[0])
+ return luncopy_ids
+
+ 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._update_login_info()
+ if int(volume['size']) == 0:
+ volume_size = '100M'
+ else:
+ volume_size = '%sG' % volume['size']
+ type_id = volume['volume_type_id']
+ parameters = self._parse_volume_type(type_id)
+ volume_id = self._create_volume(volume_name, volume_size, parameters)
+ return volume_id
+
+ def _name_translate(self, name):
+ """Form new names for volume and snapshot because of
+ 32-character limit on names.
+ """
+ newname = VOL_AND_SNAP_NAME_PREFIX + 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 _update_login_info(self):
+ """Update user name and password."""
+ self.login_info = self._get_login_info()
+
+ def _parse_volume_type(self, typeid):
+ """Parse volume type form extra_specs by type id.
+
+ The keys in extra_specs must be consistent with the element in config
+ file. And the keys can starts with "drivers" to make them distinguished
+ from capabilities keys, if you like.
+
+ """
+
+ params = self._get_lun_params()
+ if typeid is not None:
+ ctxt = context.get_admin_context()
+ volume_type = volume_types.get_volume_type(ctxt, typeid)
+ specs = volume_type.get('extra_specs')
+ for key, value in specs.iteritems():
+ key_split = key.split(':')
+ if len(key_split) > 1:
+ if key_split[0] == 'drivers':
+ key = key_split[1]
+ else:
+ continue
+ else:
+ key = key_split[0]
+
+ if key in params.keys():
+ params[key] = value.strip()
+ else:
+ conf = self.configuration.cinder_huawei_conf_file
+ LOG.warn(_('_parse_volume_type: Unacceptable parameter '
+ '%(key)s. Please check this key in extra_specs '
+ 'and make it consistent with the element in '
+ 'configuration file %(conf)s.')
+ % {'key': key,
+ 'conf': conf})
+
+ return params
+
+ def _create_volume(self, name, size, params):
+ """Create a new volume with the given name and size."""
+ cli_cmd = ('createlun -n %(name)s -lunsize %(size)s '
+ '-wrtype %(wrtype)s ' % {'name': name,
+ 'size': size,
+ 'wrtype': params['WriteType']})
+
+ # If write type is "write through", no need to set mirror switch.
+ if params['WriteType'] != '2':
+ cli_cmd = cli_cmd + ('-mirrorsw %(mirrorsw)s '
+ % {'mirrorsw': params['MirrorSwitch']})
+
+ # Differences exist between "Thin" and "thick" LUN in CLI commands.
+ luntype = params['LUNType']
+ ctr = None
+ if luntype == 'Thin':
+ cli_cmd = cli_cmd + ('-pool %(pool)s '
+ % {'pool': params['StoragePool']})
+ else:
+ # Make LUN distributed to A/B controllers evenly,
+ # just for Thick LUN.
+ ctr = self._calculate_lun_ctr()
+ cli_cmd = cli_cmd + ('-rg %(raidgroup)s -susize %(susize)s '
+ '-c %(ctr)s '
+ % {'raidgroup': params['StoragePool'],
+ 'susize': params['StripUnitSize'],
+ 'ctr': ctr})
+
+ prefetch_value_or_times = ''
+ pretype = '-pretype %s ' % params['PrefetchType']
+ # If constant prefetch, we should specify prefetch value.
+ if params['PrefetchType'] == '1':
+ prefetch_value_or_times = '-value %s' % params['PrefetchValue']
+ # If variable prefetch, we should specify prefetch mutiple.
+ elif params['PrefetchType'] == '2':
+ prefetch_value_or_times = '-times %s' % params['PrefetchTimes']
+
+ cli_cmd = cli_cmd + pretype + prefetch_value_or_times
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_create_volume',
+ 'Failed to create volume %s' % name,
+ cli_cmd, out)
+ if ctr:
+ self._update_lun_distribution(ctr)
+
+ return self._get_lun_id(name)
+
+ def _calculate_lun_ctr(self):
+ return ('a' if self.lun_distribution[0] <= self.lun_distribution[1]
+ else 'b')
+
+ def _update_lun_distribution(self, ctr):
+ index = (0 if ctr == 'a' else 1)
+ self.lun_distribution[index] += 1
+
+ def _get_lun_params(self):
+ params_conf = self._parse_conf_lun_params()
+ # Select a pool with maximum capacity.
+ pools_dev = self._get_dev_pool_info(params_conf['LUNType'])
+ params_conf['StoragePool'] = \
+ self._get_maximum_capacity_pool_id(params_conf['StoragePool'],
+ pools_dev,
+ params_conf['LUNType'])
+ return params_conf
+
+ def _parse_conf_lun_params(self):
+ """Get parameters from config file for creating LUN."""
+ # Default LUN parameters.
+ conf_params = {'LUNType': 'Thin',
+ 'StripUnitSize': '64',
+ 'WriteType': '1',
+ 'MirrorSwitch': '1',
+ 'PrefetchType': '3',
+ 'PrefetchValue': '0',
+ 'PrefetchTimes': '0',
+ 'StoragePool': []}
+
+ root = parse_xml_file(self.xml_conf)
+
+ luntype = root.findtext('LUN/LUNType')
+ if luntype:
+ if luntype.strip() in ['Thick', 'Thin']:
+ conf_params['LUNType'] = luntype.strip()
+ else:
+ err_msg = (_('LUNType must be "Thin" or "Thick". '
+ 'LUNType:%(type)s') % {'type': luntype})
+ raise exception.InvalidInput(reason=err_msg)
+
+ stripunitsize = root.findtext('LUN/StripUnitSize')
+ if stripunitsize:
+ conf_params['StripUnitSize'] = stripunitsize.strip()
+ writetype = root.findtext('LUN/WriteType')
+ if writetype:
+ conf_params['WriteType'] = writetype.strip()
+ mirrorswitch = root.findtext('LUN/MirrorSwitch')
+ if mirrorswitch:
+ conf_params['MirrorSwitch'] = mirrorswitch.strip()
+ prefetch = root.find('LUN/Prefetch')
+ if prefetch is not None and prefetch.attrib['Type']:
+ conf_params['PrefetchType'] = prefetch.attrib['Type'].strip()
+ if conf_params['PrefetchType'] == '1':
+ conf_params['PrefetchValue'] = prefetch.attrib['Value'].strip()
+ elif conf_params['PrefetchType'] == '2':
+ conf_params['PrefetchTimes'] = prefetch.attrib['Value'].strip()
+ else:
+ LOG.debug(_('_parse_conf_lun_params: Use default prefetch type. '
+ 'Prefetch type: Intelligent.'))
+
+ pools_conf = root.findall('LUN/StoragePool')
+ for pool in pools_conf:
+ conf_params['StoragePool'].append(pool.attrib['Name'].strip())
+
+ return conf_params
+
+ def _get_maximum_capacity_pool_id(self, pools_conf, pools_dev, luntype):
+ """Get the maximum pool from config file.
+
+ According to the given pools' names in config file,
+ we select the pool with maximum free capacity.
+
+ """
+
+ maxpool_id = None
+ maxpool_size = 0.0
+ nameindex, sizeindex = ((1, 4) if luntype == 'Thin' else (5, 3))
+ pools_dev = sorted(pools_dev, key=lambda x: int(x[sizeindex]))
+ while len(pools_dev) > 0:
+ pool = pools_dev.pop()
+ if pool[nameindex] in pools_conf:
+ return pool[0]
+
+ err_msg = (_('_get_maximum_capacity_pool_id: Failed to get pool '
+ 'id. Please check config file and make sure '
+ 'the StoragePool %s is created in storage '
+ 'array.') % pools_conf)
+ raise exception.InvalidInput(reason=err_msg)
+
+ def _execute_cli(self, cmd):
+ """Build SSH connection and execute CLI commands.
+
+ If the connection to first controller timeout,
+ try to connect to the other controller.
+
+ """
+
+ LOG.debug(_('CLI command: %s') % cmd)
+ connect_times = 1
+ ip0 = self.login_info['ControllerIP0']
+ ip1 = self.login_info['ControllerIP1']
+ user = self.login_info['UserName']
+ pwd = self.login_info['UserPassword']
+ if not self.ssh_pool:
+ self.ssh_pool = utils.SSHPool(ip0, 22, 30, user, pwd, max_size=2)
+ ssh_client = None
+ while True:
+ try:
+ if connect_times == 2:
+ # Switch to the other controller.
+ with self.lock_ip:
+ if ssh_client:
+ if ssh_client.server_ip == self.ssh_pool.ip:
+ self.ssh_pool.ip = (ip1
+ if self.ssh_pool.ip == ip0
+ else ip0)
+ old_ip = ssh_client.server_ip
+ # Create a new client to replace the old one.
+ if getattr(ssh_client, 'chan', None):
+ ssh_client.chan.close()
+ ssh_client.close()
+ ssh_client = self.ssh_pool.create()
+ self._reset_transport_timeout(ssh_client, 0.1)
+ else:
+ self.ssh_pool.ip = ip1
+ old_ip = ip0
+
+ LOG.info(_('_execute_cli: Can not connect to IP '
+ '%(old)s, try to connect to the other '
+ 'IP %(new)s.')
+ % {'old': old_ip, 'new': self.ssh_pool.ip})
+
+ if not ssh_client:
+ # Get an SSH client from SSH pool.
+ ssh_client = self.ssh_pool.get()
+ self._reset_transport_timeout(ssh_client, 0.1)
+ # "server_ip" shows the IP of SSH server.
+ if not getattr(ssh_client, 'server_ip', None):
+ with self.lock_ip:
+ setattr(ssh_client, 'server_ip', self.ssh_pool.ip)
+ # An SSH client owns one "chan".
+ if not getattr(ssh_client, 'chan', None):
+ setattr(ssh_client, 'chan',
+ utils.create_channel(ssh_client, 600, 800))
+
+ while True:
+ ssh_client.chan.send(cmd + '\n')
+ out = ssh_read(user, ssh_client.chan, cmd, 20)
+ if out.find('(y/n)') > -1:
+ cmd = 'y'
+ else:
+ # Put SSH client back into SSH pool.
+ self.ssh_pool.put(ssh_client)
+ return out
+
+ except Exception as err:
+ if connect_times < 2:
+ connect_times += 1
+ continue
+ else:
+ if ssh_client:
+ self.ssh_pool.remove(ssh_client)
+ raise err
+
+ def _reset_transport_timeout(self, ssh, time):
+ transport = ssh.get_transport()
+ transport.sock.settimeout(time)
+
+ def delete_volume(self, volume):
+ volume_name = self._name_translate(volume['name'])
+
+ LOG.debug(_('delete_volume: volume name: %s.') % volume_name)
+
+ self._update_login_info()
+ volume_id = volume.get('provider_location', None)
+ if (volume_id is not None) and self._check_volume_created(volume_id):
+ self._delete_volume(volume_id)
+ else:
+ err_msg = (_('delete_volume: Volume %(name)s does not exist.')
+ % {'name': volume['name']})
+ LOG.warn(err_msg)
+
+ def _check_volume_created(self, volume_id):
+ cli_cmd = 'showlun -lun %s' % volume_id
+ out = self._execute_cli(cli_cmd)
+ return (True if re.search('LUN Information', out) else False)
+
+ def _delete_volume(self, volumeid):
+ """Run CLI command to delete volume."""
+ cli_cmd = 'dellun -force -lun %s' % volumeid
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_volume',
+ ('Failed to delete volume. volume id: %s'
+ % volumeid),
+ cli_cmd, out)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Create a volume from a snapshot.
+
+ We use LUNcopy to copy a new volume from snapshot.
+ The time needed increases as volume size does.
+
+ """
+
+ 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._update_login_info()
+ snapshot_id = snapshot.get('provider_location', None)
+ if not snapshot_id:
+ snapshot_id = self._get_snapshot_id(snapshot_name)
+ if snapshot_id is None:
+ err_msg = (_('create_volume_from_snapshot: Snapshot %(name)s '
+ 'does not exist.')
+ % {'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']
+ type_id = volume['volume_type_id']
+ parameters = self._parse_volume_type(type_id)
+ tgt_vol_id = self._create_volume(volume_name, volume_size, parameters)
+ self._copy_volume(snapshot_id, tgt_vol_id)
+
+ return tgt_vol_id
+
+ def _copy_volume(self, src_vol_id, tgt_vol_id):
+ """Copy a volume or snapshot to target volume."""
+ luncopy_name = VOL_AND_SNAP_NAME_PREFIX + src_vol_id + '_' + tgt_vol_id
+ self._create_luncopy(luncopy_name, src_vol_id, tgt_vol_id)
+ self.luncopy_list.append(luncopy_name)
+ luncopy_id = self._get_luncopy_info(luncopy_name)[1]
+ try:
+ self._start_luncopy(luncopy_id)
+ self._wait_for_luncopy(luncopy_name)
+ # Delete the target volume if LUNcopy failed.
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ # Need to remove the LUNcopy of the volume first.
+ self._delete_luncopy(luncopy_id)
+ self.luncopy_list.remove(luncopy_name)
+ self._delete_volume(tgt_vol_id)
+ # Need to delete LUNcopy finally.
+ self._delete_luncopy(luncopy_id)
+ self.luncopy_list.remove(luncopy_name)
+
+ def _create_luncopy(self, luncopyname, srclunid, tgtlunid):
+ """Run CLI command to create 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)
+
+ self._assert_cli_operate_out('_create_luncopy',
+ ('Failed to create LUNcopy %s'
+ % luncopyname),
+ cli_cmd, out)
+
+ def _start_luncopy(self, luncopyid):
+ """Run CLI command to start LUNcopy."""
+ cli_cmd = ('chgluncopystatus -luncopy %(luncopyid)s -start'
+ % {'luncopyid': luncopyid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_start_luncopy',
+ 'Failed to start LUNcopy %s' % luncopyid,
+ cli_cmd, out)
+
+ def _wait_for_luncopy(self, luncopyname):
+ """Wait for LUNcopy to complete."""
+ while True:
+ luncopy_info = self._get_luncopy_info(luncopyname)
+ # If state is complete
+ if luncopy_info[3] == 'Complete':
+ break
+ # If status is not normal
+ elif luncopy_info[4] != 'Normal':
+ err_msg = (_('_wait_for_luncopy: LUNcopy %(luncopyname)s '
+ 'status is %(status)s.')
+ % {'luncopyname': luncopyname,
+ 'status': luncopy_info[4]})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ time.sleep(10)
+
+ def _get_luncopy_info(self, luncopyname):
+ """Return a LUNcopy information list."""
+ cli_cmd = 'showluncopy'
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_out(re.search('LUN Copy Information', out),
+ '_get_luncopy_info',
+ 'No LUNcopy information was found.',
+ cli_cmd, out)
+
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if tmp_line[0] == luncopyname:
+ return tmp_line
+ return None
+
+ def _delete_luncopy(self, luncopyid):
+ """Run CLI command to delete LUNcopy."""
+ cli_cmd = 'delluncopy -luncopy %(id)s' % {'id': luncopyid}
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_luncopy',
+ 'Failed to delete LUNcopy %s' % luncopyid,
+ cli_cmd, out)
+
+ def create_cloned_volume(self, tgt_volume, src_volume):
+ src_vol_name = self._name_translate(src_volume['name'])
+ tgt_vol_name = self._name_translate(tgt_volume['name'])
+
+ LOG.debug(_('create_cloned_volume: src volume: %(src)s '
+ 'tgt volume: %(tgt)s') % {'src': src_vol_name,
+ 'tgt': tgt_vol_name})
+
+ self._update_login_info()
+ src_vol_id = src_volume.get('provider_location', None)
+ if not src_vol_id:
+ src_vol_id = self._get_lun_id(src_vol_name)
+ if src_vol_id is None:
+ err_msg = (_('Source volume %(name)s does not exist.')
+ % {'name': src_vol_name})
+ LOG.error(err_msg)
+ raise exception.VolumeNotFound(volume_id=src_vol_name)
+
+ # Create a target volume.
+ if int(tgt_volume['size']) == 0:
+ tgt_vol_size = '%sG' % src_vol_name['size']
+ else:
+ tgt_vol_size = '%sG' % tgt_volume['size']
+ type_id = tgt_volume['volume_type_id']
+ params = self._parse_volume_type(type_id)
+ tgt_vol_id = self._create_volume(tgt_vol_name, tgt_vol_size, params)
+ self._copy_volume(src_vol_id, tgt_vol_id)
+
+ return tgt_vol_id
+
+ def _get_all_luns_info(self):
+ cli_cmd = 'showlun'
+ out = self._execute_cli(cli_cmd)
+ luns = []
+ if re.search('LUN Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ luns.append(line.replace('Not format', 'Notformat').split())
+ return luns
+
+ def _get_lun_id(self, lun_name):
+ luns = self._get_all_luns_info()
+ if luns:
+ for lun in luns:
+ if lun[6] == lun_name:
+ return lun[0]
+ return None
+
+ def create_snapshot(self, 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})
+
+ if self._resource_pool_enough() is False:
+ err_msg = (_('create_snapshot: '
+ 'Resource pool needs 1GB valid size at least.'))
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ lun_id = self._get_lun_id(volume_name)
+ if lun_id is None:
+ err_msg = (_('create_snapshot: Volume %(name)s does not exist.')
+ % {'name': volume_name})
+ LOG.error(err_msg)
+ raise exception.VolumeNotFound(volume_id=volume_name)
+
+ self._create_snapshot(snapshot_name, lun_id)
+ snapshot_id = self._get_snapshot_id(snapshot_name)
+ try:
+ self._active_snapshot(snapshot_id)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ self._delete_snapshot(snapshot_id)
+
+ return snapshot_id
+
+ def _resource_pool_enough(self):
+ """Check whether resource pools' valid size is more than 1GB."""
+ cli_cmd = 'showrespool'
+ out = self._execute_cli(cli_cmd)
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if float(tmp_line[3]) < 1024.0:
+ return False
+
+ return True
+
+ 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)
+
+ self._assert_cli_operate_out('_create_snapshot',
+ ('Failed to create snapshot %s'
+ % snapshotname),
+ cli_cmd, out)
+
+ def _get_snapshot_id(self, snapshotname):
+ cli_cmd = 'showsnapshot'
+ out = self._execute_cli(cli_cmd)
+ if re.search('Snapshot Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ emp_line = line.split()
+ if emp_line[0] == snapshotname:
+ return emp_line[1]
+ return None
+
+ def _active_snapshot(self, snapshotid):
+ """Run CLI command to active snapshot."""
+ cli_cmd = ('actvsnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_active_snapshot',
+ ('Failed to active snapshot %s'
+ % snapshotid),
+ cli_cmd, out)
+
+ def delete_snapshot(self, 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._update_login_info()
+ snapshot_id = snapshot.get('provider_location', None)
+ if ((snapshot_id is not None) and
+ self._check_snapshot_created(snapshot_id)):
+ # Not allow to delete snapshot if it is copying.
+ if self._snapshot_in_luncopy(snapshot_id):
+ err_msg = (_('delete_snapshot: Can not delete snapshot %s '
+ 'for it is a source LUN of LUNCopy.')
+ % snapshot_name)
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ self._delete_snapshot(snapshot_id)
+ else:
+ err_msg = (_('delete_snapshot: Snapshot %(snap)s does not exist.')
+ % {'snap': snapshot_name})
+ LOG.warn(err_msg)
+
+ def _check_snapshot_created(self, snapshot_id):
+ cli_cmd = 'showsnapshot -snapshot %(snap)s' % {'snap': snapshot_id}
+ out = self._execute_cli(cli_cmd)
+ return (True if re.search('Snapshot Information', out) else False)
+
+ def _snapshot_in_luncopy(self, snapshot_id):
+ for name in self.luncopy_list:
+ if name.startswith(VOL_AND_SNAP_NAME_PREFIX + snapshot_id):
+ return True
+ return False
+
+ def _delete_snapshot(self, snapshotid):
+ """Send CLI command to delete snapshot.
+
+ Firstly, disable the snapshot, then delete it.
+
+ """
+
+ cli_cmd = ('disablesnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_snapshot',
+ ('Failed to disable snapshot %s'
+ % snapshotid),
+ cli_cmd, out)
+
+ cli_cmd = ('delsnapshot -snapshot %(snapshotid)s'
+ % {'snapshotid': snapshotid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_snapshot',
+ ('Failed to delete snapshot %s'
+ % snapshotid),
+ cli_cmd, out)
+
+ def _assert_cli_out(self, condition, func, msg, cmd, cliout):
+ """Assertion for CLI query out."""
+ if not condition:
+ err_msg = (_('%(func)s: %(msg)s\nCLI command: %(cmd)s\n'
+ 'CLI out: %(out)s') % {'func': func,
+ 'msg': msg,
+ 'cmd': cmd,
+ 'out': cliout})
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _assert_cli_operate_out(self, func, msg, cmd, cliout):
+ """Assertion for CLI out string: command operates successfully."""
+ condition = re.search('command operates successfully', cliout)
+ self._assert_cli_out(condition, func, msg, cmd, cliout)
+
+ def map_volume(self, host_id, volume_id):
+ """Map a volume to a host."""
+ # Map a LUN to a host if not mapped.
+ if not self._check_volume_created(volume_id):
+ raise exception.VolumeNotFound(volume_id=volume_id)
+
+ hostlun_id = None
+ map_info = self._get_host_map_info(host_id)
+ # Make sure the host LUN ID starts from 1.
+ new_hostlun_id = 1
+ new_hostlunid_found = False
+ if map_info:
+ for maping in map_info:
+ if maping[2] == volume_id:
+ hostlun_id = maping[4]
+ break
+ elif not new_hostlunid_found:
+ if new_hostlun_id < int(maping[4]):
+ new_hostlunid_found = True
+ else:
+ new_hostlun_id = int(maping[4]) + 1
+
+ if not hostlun_id:
+ cli_cmd = ('addhostmap -host %(host_id)s -devlun %(lunid)s '
+ '-hostlun %(hostlunid)s'
+ % {'host_id': host_id,
+ 'lunid': volume_id,
+ 'hostlunid': new_hostlun_id})
+ out = self._execute_cli(cli_cmd)
+
+ msg = ('Failed to map lun %s to host %s. host lun ID: %s'
+ % (volume_id, host_id, new_hostlun_id))
+ self._assert_cli_operate_out('map_volume', msg, cli_cmd, out)
+
+ hostlun_id = new_hostlun_id
+
+ return hostlun_id
+
+ def add_host(self, host_name):
+ """Create a host and add it to hostgroup."""
+ # Create an OpenStack hostgroup if not created before.
+ hostgroup_name = HOST_GROUP_NAME
+ self.hostgroup_id = self._get_hostgroup_id(hostgroup_name)
+ if self.hostgroup_id is None:
+ self._create_hostgroup(hostgroup_name)
+ self.hostgroup_id = self._get_hostgroup_id(hostgroup_name)
+
+ # Create a host and add it to the hostgroup.
+ host_name = HOST_NAME_PREFIX + host_name
+ host_id = self._get_host_id(host_name, self.hostgroup_id)
+ if host_id is None:
+ self._create_host(host_name, self.hostgroup_id)
+ host_id = self._get_host_id(host_name, self.hostgroup_id)
+
+ return host_id
+
+ def _get_hostgroup_id(self, groupname):
+ """Get the given hostgroup ID.
+
+ If the hostgroup not found, return None.
+
+ """
+
+ cli_cmd = 'showhostgroup'
+ out = self._execute_cli(cli_cmd)
+ if re.search('Host Group Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if tmp_line[1] == groupname:
+ return tmp_line[0]
+ return None
+
+ def _create_hostgroup(self, hostgroupname):
+ """Run CLI command to create host group."""
+ cli_cmd = 'createhostgroup -n %(name)s' % {'name': hostgroupname}
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_create_hostgroup',
+ ('Failed to Create hostgroup %s.'
+ % hostgroupname),
+ cli_cmd, out)
+
+ def _get_host_id(self, hostname, hostgroupid):
+ """Get the given host ID."""
+ cli_cmd = 'showhost -group %(groupid)s' % {'groupid': hostgroupid}
+ out = self._execute_cli(cli_cmd)
+ if re.search('Host Information', out):
+ for line in out.split('\r\n')[6:-2]:
+ tmp_line = line.split()
+ if tmp_line[1] == hostname:
+ return tmp_line[0]
+ return None
+
+ def _create_host(self, hostname, hostgroupid):
+ """Run CLI command to add host."""
+ cli_cmd = ('addhost -group %(groupid)s -n %(hostname)s -t 0'
+ % {'groupid': hostgroupid,
+ 'hostname': hostname})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_create_host',
+ 'Failed to create host %s' % hostname,
+ cli_cmd, out)
+
+ def _get_host_port_info(self, hostid):
+ """Run CLI command to get host port information."""
+ cli_cmd = ('showhostport -host %(hostid)s' % {'hostid': hostid})
+ out = self._execute_cli(cli_cmd)
+ if re.search('Host Port Information', out):
+ return [line.split() for line in out.split('\r\n')[6:-2]]
+ else:
+ return None
+
+ def _get_host_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)
+ if re.search('Map Information', out):
+ mapinfo = [line.split() for line in out.split('\r\n')[6:-2]]
+ # Sorted by host LUN ID.
+ return sorted(mapinfo, key=lambda x: int(x[4]))
+ else:
+ return None
+
+ def get_lun_details(self, lun_id):
+ cli_cmd = 'showlun -lun %s' % lun_id
+ out = self._execute_cli(cli_cmd)
+ lun_details = {}
+ if re.search('LUN Information', out):
+ for line in out.split('\r\n')[4:-2]:
+ line = line.split('|')
+ key = ''.join(line[0].strip().split())
+ val = line[1].strip()
+ lun_details[key] = val
+ return lun_details
+
+ def change_lun_ctr(self, lun_id, ctr):
+ LOG.debug(_('change_lun_ctr: Changing LUN %(lun)s ctr to %(ctr)s')
+ % {'lun': lun_id, 'ctr': ctr})
+
+ cli_cmd = 'chglun -lun %s -c %s' % (lun_id, ctr)
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('change_lun_ctr',
+ 'Failed to change owning controller for '
+ 'LUN %s' % lun_id,
+ cli_cmd, out)
+
+ def remove_map(self, volume_id, host_name):
+ """Remove host map."""
+ host_name = HOST_NAME_PREFIX + host_name
+ host_id = self._get_host_id(host_name, self.hostgroup_id)
+ if host_id is None:
+ LOG.error(_('remove_map: Host %s does not exist.') % host_name)
+ raise exception.HostNotFound(host=host_name)
+
+ if not self._check_volume_created(volume_id):
+ LOG.error(_('remove_map: Volume %s does not exist.') % volume_id)
+ raise exception.VolumeNotFound(volume_id=volume_id)
+
+ map_id = None
+ map_info = self._get_host_map_info(host_id)
+ if map_info:
+ for maping in map_info:
+ if maping[2] == volume_id:
+ map_id = maping[0]
+ break
+ if map_id is not None:
+ self._delete_map(map_id)
+ else:
+ LOG.warn(_('remove_map: No map between host %(host)s and '
+ 'volume %(volume)s.') % {'host': host_name,
+ 'volume': volume_id})
+ return host_id
+
+ def _delete_map(self, mapid, attempts=2):
+ """Run CLI command to remove map."""
+ cli_cmd = 'delhostmap -force -map %(mapid)s' % {'mapid': mapid}
+ while True:
+ 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) and
+ (attempts > 0)):
+
+ LOG.debug(_('_delete_map: There are IOs accessing '
+ 'the system. Retry to delete host map '
+ '%(mapid)s 10s later.') % {'mapid': mapid})
+
+ time.sleep(10)
+ attempts -= 1
+ continue
+ else:
+ err_msg = (_('_delete_map: Failed to delete host map '
+ '%(mapid)s.\nCLI out: %(out)s')
+ % {'mapid': mapid,
+ 'times': attempts,
+ 'out': out})
+ LOG.error(err_msg)
+ raise exception.VolumeBackendAPIException(data=err_msg)
+
+ def _delete_hostport(self, portid):
+ """Run CLI command to delete host port."""
+ cli_cmd = ('delhostport -force -p %(portid)s' % {'portid': portid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_hostport',
+ 'Failed to delete host port %s.' % portid,
+ cli_cmd, out)
+
+ def _delete_host(self, hostid):
+ """Run CLI command to delete host."""
+ cli_cmd = ('delhost -force -host %(hostid)s' % {'hostid': hostid})
+ out = self._execute_cli(cli_cmd)
+
+ self._assert_cli_operate_out('_delete_host',
+ 'Failed to delete host. %s.' % hostid,
+ cli_cmd, out)
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume stats.
+
+ If 'refresh' is True, run update the stats first.
+ """
+ if refresh:
+ self._update_volume_stats()
+
+ return self._stats
+
+ def _update_volume_stats(self):
+ """Retrieve stats info from volume group."""
+
+ LOG.debug(_("_update_volume_stats: Updating volume stats"))
+ data = {}
+ data['vendor_name'] = 'Huawei'
+ data['total_capacity_gb'] = 'infinite'
+ data['free_capacity_gb'] = self._get_free_capacity()
+ data['reserved_percentage'] = 0
+ data['QoS_support'] = False
+
+ self._stats = data
+
+ def _get_free_capacity(self):
+ """Get total free capacity of pools."""
+ self._update_login_info()
+ params_conf = self._parse_conf_lun_params()
+ lun_type = params_conf['LUNType']
+ pools_conf = params_conf['StoragePool']
+ pools_dev = self._get_dev_pool_info(lun_type)
+ total_free_capacity = 0.0
+ for pool_dev in pools_dev:
+ for pool_conf in pools_conf:
+ if ((lun_type == 'Thick') and
+ (pool_dev[5] == pool_conf)):
+ total_free_capacity += float(pool_dev[3])
+ break
+ elif pool_dev[1] == pool_conf:
+ total_free_capacity += float(pool_dev[4])
+ break
+
+ return total_free_capacity / 1024
+
+ def _get_dev_pool_info(self, pooltype):
+ """Get pools information created in storage device.
+
+ Return a list whose elements are also list.
+
+ """
+
+ cli_cmd = ('showpool' if pooltype == 'Thin' else 'showrg')
+ out = self._execute_cli(cli_cmd)
+
+ test = (re.search('Pool Information', out) or
+ re.search('RAID Group Information', out))
+ self._assert_cli_out(test, '_get_dev_pool_info',
+ 'No pools information found.', cli_cmd, out)
+
+ pool = out.split('\r\n')[6:-2]
+ return [line.split() for line in pool]
'cinder.volume.drivers.netapp.nfs.NetAppNFSDriver':
'cinder.volume.drivers.netapp.common.Deprecated',
'cinder.volume.drivers.netapp.nfs.NetAppCmodeNfsDriver':
- 'cinder.volume.drivers.netapp.common.Deprecated'}
+ 'cinder.volume.drivers.netapp.common.Deprecated',
+ 'cinder.volume.drivers.huawei.HuaweiISCSIDriver':
+ 'cinder.volume.drivers.huawei.HuaweiVolumeDriver'}
class VolumeManager(manager.SchedulerDependentManager):
#
-# Options defined in cinder.volume.drivers.huawei.huawei_iscsi
+# Options defined in cinder.volume.drivers.huawei
#
# config data for cinder huawei plugin (string value)