Add cinder volume drivers for NEC Storage M series

MStorageISCSIDriver is an iSCSI driver.

MStorageFCDriver is a Fibre Channel driver.

These drivers communicate with storage via SSH.

Implements: blueprint nec-volume-driver
Change-Id: Ieb16f69abb7d5fefd2dc4a51515701caaa651d1b
This commit is contained in:
Shunei Shiono 2016-10-21 17:02:28 +09:00 committed by Shingo Takeda
parent 86322d318e
commit cb668cb656
11 changed files with 4885 additions and 0 deletions

View File

@ -0,0 +1,435 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
class MStorageISMCLI(object):
def __init__(self, properties):
super(MStorageISMCLI, self).__init__()
self._properties = properties
def view_all(self, conf_ismview_path=None, delete_ismview=True,
cmd_lock=True):
out = '''<REQUEST>
<CMD_REQUEST cmd_name="/opt/iSMCliGateway/impl/query/iSMquery"
arg="-cinder -xml -all "
version="Version 9.4.001">
<CHAPTER name="Disk Array">
<OBJECT name="Disk Array">
<SECTION name="Disk Array Detail Information">
<UNIT name="Product ID">M310</UNIT>
</SECTION>
</OBJECT>
</CHAPTER>
<CHAPTER name="Logical Disk">
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0000</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">287RbQoP7VdwR1WsPC2fZT</UNIT>
<UNIT name="LD Capacity">1073741824</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">MV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0001</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">backup_SDV0001</UNIT>
<UNIT name="LD Capacity">5368709120</UNIT>
<UNIT name="Pool No.(h)">0001</UNIT>
<UNIT name="Purpose">(invalid attribute)</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0003</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">31HxzqBiAFTUxxOlcVn3EA_back</UNIT>
<UNIT name="LD Capacity">1073741824</UNIT>
<UNIT name="Pool No.(h)">0001</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">RV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0004</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">287RbQoP7VdwR1WsPC2fZT_back</UNIT>
<UNIT name="LD Capacity">1073741824</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">RV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0005</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">20000009910200140005</UNIT>
<UNIT name="LD Capacity">10737418240</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">RV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0006</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">20000009910200140006</UNIT>
<UNIT name="LD Capacity">10737418240</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0007</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">20000009910200140007</UNIT>
<UNIT name="LD Capacity">10737418240</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0008</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">20000009910200140008</UNIT>
<UNIT name="LD Capacity">10737418240</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0009</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">20000009910200140009</UNIT>
<UNIT name="LD Capacity">10737418240</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">000a</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">2000000991020012000A</UNIT>
<UNIT name="LD Capacity">6442450944</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">000b</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">2000000991020012000B</UNIT>
<UNIT name="LD Capacity">6442450944</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">000c</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">2000000991020012000C</UNIT>
<UNIT name="LD Capacity">6442450944</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">000d</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">yEUHrXa5AHMjOZZLb93eP</UNIT>
<UNIT name="LD Capacity">6442450944</UNIT>
<UNIT name="Pool No.(h)">0001</UNIT>
<UNIT name="Purpose">---</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">000e</UNIT>
<UNIT name="OS Type">LX</UNIT>
<UNIT name="LD Name">4T7JpyqI3UuPlKeT9D3VQF</UNIT>
<UNIT name="LD Capacity">6442450944</UNIT>
<UNIT name="Pool No.(h)">0001</UNIT>
<UNIT name="Purpose">RPL</UNIT>
<UNIT name="RPL Attribute">IV</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Logical Disk">
<SECTION name="LD Detail Information">
<UNIT name="LDN(h)">0fff</UNIT>
<UNIT name="OS Type"> </UNIT>
<UNIT name="LD Name">Pool0000_SYV0FFF</UNIT>
<UNIT name="LD Capacity">8589934592</UNIT>
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Purpose">(invalid attribute)</UNIT>
<UNIT name="RPL Attribute">---</UNIT>
</SECTION>
</OBJECT>
</CHAPTER>
<CHAPTER name="Pool">
<OBJECT name="Pool">
<SECTION name="Pool Detail Information">
<UNIT name="Pool No.(h)">0000</UNIT>
<UNIT name="Pool Capacity">281320357888</UNIT>
<UNIT name="Used Pool Capacity">84020297728</UNIT>
<UNIT name="Free Pool Capacity">197300060160</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Pool">
<SECTION name="Pool Detail Information">
<UNIT name="Pool No.(h)">0001</UNIT>
<UNIT name="Pool Capacity">89657442304</UNIT>
<UNIT name="Used Pool Capacity">6710886400</UNIT>
<UNIT name="Free Pool Capacity">82946555904</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Pool">
<SECTION name="Pool Detail Information">
<UNIT name="Pool No.(h)">0002</UNIT>
<UNIT name="Pool Capacity">1950988894208</UNIT>
<UNIT name="Used Pool Capacity">18446744073441116160</UNIT>
<UNIT name="Free Pool Capacity">1951257329664</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Pool">
<SECTION name="Pool Detail Information">
<UNIT name="Pool No.(h)">0003</UNIT>
<UNIT name="Pool Capacity">1950988894208</UNIT>
<UNIT name="Used Pool Capacity">18446744073441116160</UNIT>
<UNIT name="Free Pool Capacity">1951257329664</UNIT>
</SECTION>
</OBJECT>
</CHAPTER>
<CHAPTER name="Controller">
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">00-00</UNIT>
<UNIT name="WWPN">2100000991020012</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">00-01</UNIT>
<UNIT name="WWPN">2200000991020012</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">00-02</UNIT>
<UNIT name="IP Address">192.168.1.90</UNIT>
<UNIT name="Link Status">Link Down</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">00-03</UNIT>
<UNIT name="IP Address">192.168.1.91</UNIT>
<UNIT name="Link Status">Link Down</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">01-00</UNIT>
<UNIT name="WWPN">2900000991020012</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">01-01</UNIT>
<UNIT name="WWPN">2A00000991020012</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">01-02</UNIT>
<UNIT name="IP Address">192.168.2.92</UNIT>
<UNIT name="Link Status">Link Down</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="Host Port">
<SECTION name="Host Director/Host Port Information">
<UNIT name="Port No.(h)">01-03</UNIT>
<UNIT name="IP Address">192.168.2.93</UNIT>
<UNIT name="Link Status">Link Up</UNIT>
</SECTION>
</OBJECT>
</CHAPTER>
<CHAPTER name="Access Control">
<OBJECT name="LD Set(FC)">
<SECTION name="LD Set(FC) Information">
<UNIT name="Platform">LX</UNIT>
<UNIT name="LD Set Name">OpenStack1</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FAA0-786B</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FAA0-786A</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="LD Set(FC)">
<SECTION name="LD Set(FC) Information">
<UNIT name="Platform">WN</UNIT>
<UNIT name="LD Set Name">TNES120250</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FA76-9605</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FA76-9604</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="LD Set(FC)">
<SECTION name="LD Set(FC) Information">
<UNIT name="Platform">WN</UNIT>
<UNIT name="LD Set Name">TNES140098</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FA53-302C</UNIT>
</SECTION>
<SECTION name="Path List">
<UNIT name="Path">1000-0090-FA53-302D</UNIT>
</SECTION>
<SECTION name="LUN/LD List">
<UNIT name="LUN(h)">0000</UNIT>
<UNIT name="LDN(h)">0005</UNIT>
</SECTION>
<SECTION name="LUN/LD List">
<UNIT name="LUN(h)">0001</UNIT>
<UNIT name="LDN(h)">0006</UNIT>
</SECTION>
</OBJECT>
<OBJECT name="LD Set(iSCSI)">
<SECTION name="LD Set(iSCSI) Information">
<UNIT name="Platform">LX</UNIT>
<UNIT name="LD Set Name">OpenStack0</UNIT>
<UNIT name="Target Mode">Multi-Target</UNIT>
</SECTION>
<SECTION name="Portal">
<UNIT name="Portal">192.168.1.90:3260</UNIT>
</SECTION>
<SECTION name="Portal">
<UNIT name="Portal">192.168.1.91:3260</UNIT>
</SECTION>
<SECTION name="Portal">
<UNIT name="Portal">192.168.2.92:3260</UNIT>
</SECTION>
<SECTION name="Portal">
<UNIT name="Portal">192.168.2.93:3260</UNIT>
</SECTION>
<SECTION name="Initiator List">
<UNIT name="Initiator List">iqn.1994-05.com.redhat:d1d8e8f23255</UNIT>
</SECTION>
<SECTION name="Target Information For Multi-Target Mode">
<UNIT name="Target Name">iqn.2001-03.target0000</UNIT>
<UNIT name="LUN(h)">0000</UNIT>
<UNIT name="LDN(h)">0000</UNIT>
</SECTION>
</OBJECT>
</CHAPTER>
<RETURN_MSG>Command Completed Successfully!!</RETURN_MSG>
<RETURN_CODE>0</RETURN_CODE>
</CMD_REQUEST>
</REQUEST>
'''
return out
def ldbind(self, name, pool, ldn, size):
return True, 0
def unbind(self, name):
pass
def expand(self, ldn, capacity):
pass
def addldsetld(self, ldset, ldname, lun=None):
pass
def delldsetld(self, ldset, ldname):
if ldname == "LX:287RbQoP7VdwR1WsPC2fZT":
return False, 'iSM31064'
else:
return True, None
def changeldname(self, ldn, new_name, old_name=None):
pass
def setpair(self, mvname, rvname):
pass
def unpair(self, mvname, rvname, flag):
pass
def replicate(self, mvname, rvname, flag):
pass
def separate(self, mvname, rvname, flag):
pass
def query_MV_RV_status(self, ldname, rpltype):
return 'separated'
def query_MV_RV_name(self, ldname, rpltype):
pass
def query_MV_RV_diff(self, ldname, rpltype):
pass
def backup_restore(self, volume_properties, unpairWait, canPairing=True):
pass
def check_ld_existed_rplstatus(self, lds, ldname, snapshot, flag):
return {'ld_capacity': 10}
def get_pair_lds(self, ldname, lds):
return {'0004': {'ld_capacity': 1, 'pool_num': 0,
'ldname': 'LX:287RbQoP7VdwR1WsPC2fZT_back',
'ldn': 4, 'RPL Attribute': 'RV', 'Purpose': '---'}}
def snapshot_create(self, bvname, svname, poolnumber):
pass
def snapshot_delete(self, bvname, svname):
pass
def query_BV_SV_status(self, bvname, svname):
return 'snap/active'
def set_io_limit(self, ldname, specs, force_delete=True):
pass

View File

@ -0,0 +1,643 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
import ddt
import mock
import unittest
from cinder import exception
from cinder.tests.unit.volume.drivers.nec import volume_common_test
from cinder.volume.drivers.nec import volume_helper
@ddt.ddt
class VolumeIDConvertTest(volume_helper.MStorageDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
def tearDown(self):
pass
@ddt.data(("AAAAAAAA", "LX:37mA82"), ("BBBBBBBB", "LX:3R9ZwR"))
@ddt.unpack
def test_volumeid_should_change_62scale(self, volid, ldname):
self.vol['id'] = volid
actual = self._convert_id2name(self.vol)
self.assertEqual(ldname, actual,
"ID:%(volid)s should be change to %(ldname)s" %
{'volid': volid, 'ldname': ldname})
@ddt.data(("AAAAAAAA", "LX:37mA82_back"), ("BBBBBBBB", "LX:3R9ZwR_back"))
@ddt.unpack
def test_snap_volumeid_should_change_62scale_andpostfix(self,
volid,
ldname):
self.vol['id'] = volid
actual = self._convert_id2snapname(self.vol)
self.assertEqual(ldname, actual,
"ID:%(volid)s should be change to %(ldname)s" %
{'volid': volid, 'ldname': ldname})
@ddt.data(("AAAAAAAA", "LX:37mA82_m"), ("BBBBBBBB", "LX:3R9ZwR_m"))
@ddt.unpack
def test_ddrsnap_volumeid_should_change_62scale_and_m(self,
volid,
ldname):
self.vol['id'] = volid
actual = self._convert_id2migratename(self.vol)
self.assertEqual(ldname, actual,
"ID:%(volid)s should be change to %(ldname)s" %
{'volid': volid, 'ldname': ldname})
@ddt.data(("AAAAAAAA", "LX:3R9ZwR", "target:BBBBBBBB"))
@ddt.unpack
def test_migrate_volumeid_should_change_62scale_andpostfix(self,
volid,
ldname,
status):
self.vol['id'] = volid
self.vol['migration_status'] = status
actual = self._convert_id2name_in_migrate(self.vol)
self.assertEqual(ldname, actual,
"ID:%(volid)s/%(status)s should be "
"change to %(ldname)s" %
{'volid': volid,
'status': status,
'ldname': ldname})
@ddt.data(("AAAAAAAA", "LX:37mA82", "deleting:BBBBBBBB"),
("AAAAAAAA", "LX:37mA82", ""),
("AAAAAAAA", "LX:37mA82", "success"))
@ddt.unpack
def test_NOTmigrate_volumeid_should_change_62scale(self,
volid,
ldname,
status):
self.vol['id'] = volid
self.vol['migration_status'] = status
actual = self._convert_id2name_in_migrate(self.vol)
self.assertEqual(ldname, actual,
"ID:%(volid)s/%(status)s should be "
"change to %(ldname)s" %
{'volid': volid,
'status': status,
'ldname': ldname})
class NominatePoolLDTest(volume_helper.MStorageDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
self._numofld_per_pool = 1024
def tearDown(self):
pass
def test_getxml(self):
self.assertIsNotNone(self.xml, "iSMview xml should not be None")
def test_selectldn_for_normalvolume(self):
ldn = self._select_ldnumber(self.used_ldns, self.max_ld_count)
self.assertEqual(2, ldn, "selected ldn should be XXX")
def test_selectpool_for_normalvolume(self):
self.vol['size'] = 10
pool = self._select_leastused_poolnumber(self.vol,
self.pools,
self.xml)
self.assertEqual(1, pool, "selected pool should be 1")
# config:pool_pools=[1]
self.vol['size'] = 999999999999
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_leastused_poolnumber(self.vol,
self.pools,
self.xml)
def test_selectpool_for_migratevolume(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['pool_num'] = 0
pool = self._select_migrate_poolnumber(self.vol,
self.pools,
self.xml,
[1])
self.assertEqual(1, pool, "selected pool should be 1")
self.vol['id'] = "1febb976-86d0-42ed-9bc0-4aa3e158f27d"
self.vol['size'] = 10
self.vol['pool_num'] = 1
pool = self._select_migrate_poolnumber(self.vol,
self.pools,
self.xml,
[1])
self.assertEqual(-1, pool, "selected pool is the same pool(return -1)")
self.vol['size'] = 999999999999
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_migrate_poolnumber(self.vol,
self.pools,
self.xml,
[1])
def test_selectpool_for_snapvolume(self):
self.vol['size'] = 10
savePool1 = self.pools[1]['free']
self.pools[1]['free'] = 0
pool = self._select_dsv_poolnumber(self.vol, self.pools)
self.assertEqual(2, pool, "selected pool should be 2")
# config:pool_backup_pools=[2]
self.pools[1]['free'] = savePool1
if len(self.pools[0]['ld_list']) is 1024:
savePool2 = self.pools[2]['free']
savePool3 = self.pools[3]['free']
self.pools[2]['free'] = 0
self.pools[3]['free'] = 0
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_dsv_poolnumber(self.vol, self.pools)
self.pools[2]['free'] = savePool2
self.pools[3]['free'] = savePool3
self.vol['size'] = 999999999999
pool = self._select_dsv_poolnumber(self.vol, self.pools)
self.assertEqual(2, pool, "selected pool should be 2")
# config:pool_backup_pools=[2]
def test_selectpool_for_ddrvolume(self):
self.vol['size'] = 10
pool = self._select_ddr_poolnumber(self.vol,
self.pools,
self.xml,
10)
self.assertEqual(2, pool, "selected pool should be 2")
# config:pool_backup_pools=[2]
savePool2 = self.pools[2]['free']
savePool3 = self.pools[3]['free']
self.pools[2]['free'] = 0
self.pools[3]['free'] = 0
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_ddr_poolnumber(self.vol,
self.pools,
self.xml,
10)
self.pools[2]['free'] = savePool2
self.pools[3]['free'] = savePool3
self.vol['size'] = 999999999999
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_ddr_poolnumber(self.vol,
self.pools,
self.xml,
999999999999)
def test_selectpool_for_volddrvolume(self):
self.vol['size'] = 10
pool = self._select_volddr_poolnumber(self.vol,
self.pools,
self.xml,
10)
self.assertEqual(1, pool, "selected pool should be 1")
# config:pool_backup_pools=[2]
savePool0 = self.pools[0]['free']
savePool1 = self.pools[1]['free']
self.pools[0]['free'] = 0
self.pools[1]['free'] = 0
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_volddr_poolnumber(self.vol,
self.pools,
self.xml,
10)
self.pools[0]['free'] = savePool0
self.pools[1]['free'] = savePool1
self.vol['size'] = 999999999999
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'No available pools found.'):
pool = self._select_volddr_poolnumber(self.vol,
self.pools,
self.xml,
999999999999)
class VolumeCreateTest(volume_helper.MStorageDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
def tearDown(self):
pass
def test_validate_migrate_volume(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['status'] = 'available'
self._validate_migrate_volume(self.vol, self.xml)
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['status'] = 'creating'
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'Specified Logical Disk'
' LX:287RbQoP7VdwR1WsPC2fZT'
' is not available.'):
self._validate_migrate_volume(self.vol, self.xml)
self.vol['id'] = "AAAAAAAA"
self.vol['size'] = 10
self.vol['status'] = 'available'
with self.assertRaisesRegexp(exception.NotFound,
'Logical Disk `LX:37mA82`'
' does not exist.'):
self._validate_migrate_volume(self.vol, self.xml)
def test_extend_volume(self):
mv = self.lds["LX:287RbQoP7VdwR1WsPC2fZT"] # MV-LDN:0 RV-LDN:4
rvs = self._can_extend_capacity(10, self.pools, self.lds, mv)
self.assertEqual("LX:287RbQoP7VdwR1WsPC2fZT_back", rvs[4]['ldname'])
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'Not enough pool capacity.'
' pool_number=0,'
' size_increase=1073741822926258176'):
self._can_extend_capacity(1000000000,
self.pools,
self.lds, mv)
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b" # MV
self.vol['size'] = 1
self.vol['status'] = 'available'
self.extend_volume(self.vol, 10)
self.vol['id'] = "00046058-d38e-7f60-67b7-59ed65e54225" # RV
self.vol['size'] = 1
self.vol['status'] = 'available'
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'RPL Attribute Error.'
' RPL Attribute = RV'):
self.extend_volume(self.vol, 10)
class BindLDTest(volume_helper.MStorageDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
self.src = {}
mock_bindld = mock.Mock()
self._bind_ld = mock_bindld
self._bind_ld.return_value = (0, 0, 0)
def test_bindld_CreateVolume(self):
self.vol['id'] = "AAAAAAAA"
self.vol['size'] = 1
self.vol['migration_status'] = "success"
self.create_volume(self.vol)
self._bind_ld.assert_called_once_with(
self.vol, self.vol['size'], None,
self._convert_id2name_in_migrate,
self._select_leastused_poolnumber)
def test_bindld_CreateCloneVolume(self):
self.vol['id'] = "AAAAAAAA"
self.vol['size'] = 1
self.vol['migration_status'] = "success"
self.src['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.src['size'] = 1
self.create_cloned_volume(self.vol, self.src)
self._bind_ld.assert_called_once_with(
self.vol, self.vol['size'], None,
self._convert_id2name,
self._select_leastused_poolnumber)
class BindLDTest_iSCSISnap(volume_helper.MStorageDriver,
unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
self.snap = {}
mock_bindld = mock.Mock()
self._bind_ld = mock_bindld
self._bind_ld.return_value = (0, 0, 0)
def test_bindld_CreateSnapshot(self):
self.snap['id'] = "AAAAAAAA"
self.snap['volume_id'] = "1febb976-86d0-42ed-9bc0-4aa3e158f27d"
self.snap['size'] = 10
self.create_snapshot(self.snap)
self._bind_ld.assert_called_once_with(
self.snap, 10, None,
self._convert_id2snapname,
self._select_ddr_poolnumber, 10)
def test_bindld_CreateFromSnapshot(self):
self.vol['id'] = "AAAAAAAA"
self.vol['size'] = 1
self.vol['migration_status'] = "success"
self.snap['id'] = "63410c76-2f12-4473-873d-74a63dfcd3e2"
self.snap['volume_id'] = "92dbc7f4-dbc3-4a87-aef4-d5a2ada3a9af"
self.create_volume_from_snapshot(self.vol, self.snap)
self._bind_ld.assert_called_once_with(
self.vol, 10, None,
self._convert_id2name,
self._select_volddr_poolnumber, 10)
class BindLDTest_Snap(volume_helper.MStorageDSVDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
self.snap = {}
mock_bindld = mock.Mock()
self._bind_ld = mock_bindld
self._bind_ld.return_value = (0, 0, 0)
def test_bindld_CreateFromSnapshot(self):
self.vol['id'] = "AAAAAAAA"
self.vol['size'] = 1
self.vol['migration_status'] = "success"
self.snap['id'] = "63410c76-2f12-4473-873d-74a63dfcd3e2"
self.snap['volume_id'] = "1febb976-86d0-42ed-9bc0-4aa3e158f27d"
self.create_volume_from_snapshot(self.vol, self.snap)
self._bind_ld.assert_called_once_with(
self.vol, 1, None,
self._convert_id2name,
self._select_volddr_poolnumber, 1)
class ExportTest(volume_helper.MStorageDriver, unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
mock_getldset = mock.Mock()
self._common.get_ldset = mock_getldset
self._common.get_ldset.return_value = self.ldsets["LX:OpenStack0"]
def tearDown(self):
pass
def test_iscsi_portal(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['status'] = None
self.vol['migration_status'] = None
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255"}
self.iscsi_do_export(None, self.vol, connector)
def test_fc_do_export(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['status'] = None
self.vol['migration_status'] = None
connector = {'wwpns': ["1000-0090-FAA0-723A", "1000-0090-FAA0-723B"]}
self.fc_do_export(None, self.vol, connector)
def test_remove_export(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['size'] = 10
self.vol['status'] = 'uploading'
self.vol['attach_status'] = 'attached'
self.vol['migration_status'] = None
context = mock.Mock()
ret = self.remove_export(context, self.vol)
self.assertIsNone(ret)
self.vol['attach_status'] = None
self.vol['status'] = 'downloading'
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'Failed to unregister Logical Disk from'
' Logical Disk Set \(iSM31064\)'):
self.remove_export(context, self.vol)
self.vol['status'] = None
migstat = 'target:1febb976-86d0-42ed-9bc0-4aa3e158f27d'
self.vol['migration_status'] = migstat
ret = self.remove_export(context, self.vol)
self.assertIsNone(ret)
def test_iscsi_initialize_connection(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
loc = "127.0.0.1:3260:1 iqn.2010-10.org.openstack:volume-00000001 88"
self.vol['provider_location'] = loc
connector = {'initiator': "iqn.1994-05.com.redhat:d1d8e8f23255",
'multipath': True}
info = self._iscsi_initialize_connection(self.vol, connector)
self.assertEqual('iscsi', info['driver_volume_type'])
self.assertEqual('iqn.2010-10.org.openstack:volume-00000001',
info['data']['target_iqn'])
self.assertEqual('127.0.0.1:3260', info['data']['target_portal'])
self.assertEqual(88, info['data']['target_lun'])
self.assertEqual('iqn.2010-10.org.openstack:volume-00000001',
info['data']['target_iqns'][0])
self.assertEqual('127.0.0.1:3260', info['data']['target_portals'][0])
self.assertEqual(88, info['data']['target_luns'][0])
def test_fc_initialize_connection(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
self.vol['migration_status'] = None
connector = {'wwpns': ["1000-0090-FAA0-723A", "1000-0090-FAA0-723B"]}
info = self._fc_initialize_connection(self.vol, connector)
self.assertEqual('fibre_channel', info['driver_volume_type'])
self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
self.assertEqual('2900000991020012', info['data']['target_wwn'][2])
self.assertEqual('2A00000991020012', info['data']['target_wwn'][3])
self.assertEqual(
'2100000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][0])
self.assertEqual(
'2100000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][0])
self.assertEqual(
'2200000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][1])
self.assertEqual(
'2200000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][1])
self.assertEqual(
'2900000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][2])
self.assertEqual(
'2900000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][2])
self.assertEqual(
'2A00000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][3])
self.assertEqual(
'2A00000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][3])
def test_fc_terminate_connection(self):
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
connector = {'wwpns': ["1000-0090-FAA0-723A", "1000-0090-FAA0-723B"]}
info = self._fc_terminate_connection(self.vol, connector)
self.assertEqual('fibre_channel', info['driver_volume_type'])
self.assertEqual('2100000991020012', info['data']['target_wwn'][0])
self.assertEqual('2200000991020012', info['data']['target_wwn'][1])
self.assertEqual('2900000991020012', info['data']['target_wwn'][2])
self.assertEqual('2A00000991020012', info['data']['target_wwn'][3])
self.assertEqual(
'2100000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][0])
self.assertEqual(
'2100000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][0])
self.assertEqual(
'2200000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][1])
self.assertEqual(
'2200000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][1])
self.assertEqual(
'2900000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][2])
self.assertEqual(
'2900000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][2])
self.assertEqual(
'2A00000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723A'][3])
self.assertEqual(
'2A00000991020012',
info['data']['initiator_target_map']['1000-0090-FAA0-723B'][3])
class DeleteDSVVolume_test(volume_helper.MStorageDSVDriver,
unittest.TestCase):
def setUp(self):
self._common = volume_common_test.MStorageVolCommDummy(1, 2, 3)
self.do_setup(None)
self.vol = {}
self._properties = self._common.get_conf_properties()
self._cli = self._properties['cli']
self.xml = self._cli.view_all(self._properties['ismview_path'])
(self.pools,
self.lds,
self.ldsets,
self.used_ldns,
self.hostports,
self.max_ld_count) = self._common.configs(self.xml)
def patch_query_MV_RV_status(self, ldname, rpltype):
return 'replicated'
@mock.patch('cinder.tests.unit.volume.drivers.nec.cli_test.'
'MStorageISMCLI.query_MV_RV_status', patch_query_MV_RV_status)
def test_delete_volume(self):
# MV not separated
self.vol['id'] = "46045673-41e7-44a7-9333-02f07feab04b"
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'Specified Logical Disk'
' LX:287RbQoP7VdwR1WsPC2fZT'
' has been copied.'):
self.delete_volume(self.vol)
# RV not separated
self.vol['id'] = "00046058-d38e-7f60-67b7-59ed65e54225"
with self.assertRaisesRegexp(exception.VolumeBackendAPIException,
'Specified Logical Disk'
' LX:20000009910200140005'
' has been copied.'):
self.delete_volume(self.vol)
def test_delete_snapshot(self):
self.vol['id'] = "63410c76-2f12-4473-873d-74a63dfcd3e2"
self.vol['volume_id'] = "1febb976-86d0-42ed-9bc0-4aa3e158f27d"
ret = self.delete_snapshot(self.vol)
self.assertIsNone(ret)

View File

@ -0,0 +1,299 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
from lxml import etree
from oslo_utils import units
from cinder.tests.unit.volume.drivers.nec import cli_test
from cinder.volume.drivers.nec import volume_common
class MStorageVolCommDummy(object):
def __init__(self, configuration, host, driver_name):
super(MStorageVolCommDummy, self).__init__()
self._properties = self.get_conf_properties()
self._context = None
def set_context(self, context):
self._context = context
def get_conf(self, host):
return self.get_conf_properties()
def get_conf_properties(self, conf=None):
conf = {
'cli': None,
'cli_fip': '10.64.169.250',
'cli_user': 'sysadmin',
'cli_password': 'sys123',
'cli_privkey': 'sys123',
'pool_pools': [0, 1],
'pool_backup_pools': [2, 3],
'pool_actual_free_capacity': 50000000000,
'ldset_name': 'LX:OpenStack0',
'ldset_controller_node_name': 'LX:node0',
'ld_name_format': 'LX:%s',
'ld_backupname_format': 'LX:%s_back',
'ld_backend_max_count': 1024,
'thread_timeout': 5,
'ismview_dir': 'view',
'ismview_alloptimize': '',
'ssh_pool_port_number': 22,
'diskarray_name': 'node0',
'queryconfig_view': '',
'ismview_path': None,
'driver_name': 'MStorageISCSIDriver',
'config_group': '',
'configuration': '',
'vendor_name': 'nec',
'products': '',
'backend_name': '',
'portal_number': 2
}
conf['cli'] = cli_test.MStorageISMCLI(conf)
return conf
@staticmethod
def get_ldname(volid, volformat):
return volume_common.MStorageVolumeCommon.get_ldname(volid, volformat)
def get_diskarray_max_ld_count(self):
return 8192
def get_pool_config(self, xml, root):
pools = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Pool"]/'
'OBJECT[@name="Pool"]'):
section = xmlobj.find('./SECTION[@name="Pool Detail Information"]')
unit = section.find('./UNIT[@name="Pool No.(h)"]')
pool_num = int(unit.text, 16)
unit = section.find('UNIT[@name="Pool Capacity"]')
total = int(unit.text, 10)
unit = section.find('UNIT[@name="Free Pool Capacity"]')
free = int(unit.text, 10)
if self._properties['pool_actual_free_capacity']:
unit = section.find('UNIT[@name="Used Pool Capacity"]')
used = int(unit.text, 10)
for section in xmlobj.xpath('./SECTION[@name='
'"Virtual Capacity Pool '
'Information"]'):
unit = section.find('UNIT[@name="Actual Capacity"]')
total = int(unit.text, 10)
free = total - used
pool = {'pool_num': pool_num,
'total': total,
'free': free,
'ld_list': []}
pools[pool_num] = pool
return pools
def get_ld_config(self, xml, root, pools):
lds = {}
used_ldns = []
for section in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Logical Disk"]/'
'OBJECT[@name="Logical Disk"]/'
'SECTION[@name="LD Detail Information"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="OS Type"]')
ostype = unit.text if unit.text is not None else ''
unit = section.find('./UNIT[@name="LD Name"]')
ldname = ostype + ':' + unit.text
unit = section.find('./UNIT[@name="Pool No.(h)"]')
pool_num = int(unit.text, 16)
unit = section.find('./UNIT[@name="LD Capacity"]')
# byte capacity transform GB capacity.
ld_capacity = int(unit.text, 10) // units.Gi
unit = section.find('./UNIT[@name="RPL Attribute"]')
rplatr = unit.text
unit = section.find('./UNIT[@name="Purpose"]')
purpose = unit.text
ld = {'ldname': ldname,
'ldn': ldn,
'pool_num': pool_num,
'ld_capacity': ld_capacity,
'RPL Attribute': rplatr,
'Purpose': purpose}
pools[pool_num]['ld_list'].append(ld)
lds[ldname] = ld
used_ldns.append(ldn)
return lds, used_ldns
def get_iscsi_ldset_config(self, xml, root):
ldsets = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Access Control"]/'
'OBJECT[@name="LD Set(iSCSI)"]'):
ldsetlds = {}
portals = []
for unit in xmlobj.xpath('./SECTION[@name="Portal"]/'
'UNIT[@name="Portal"]'):
if not unit.text.startswith('0.0.0.0:'):
portals.append(unit.text)
section = xmlobj.find('./SECTION[@name="LD Set(iSCSI)'
' Information"]')
if section is None:
return ldsets
unit = section.find('./UNIT[@name="Platform"]')
platform = unit.text
unit = section.find('./UNIT[@name="LD Set Name"]')
ldsetname = platform + ':' + unit.text
unit = section.find('./UNIT[@name="Target Mode"]')
tmode = unit.text
if tmode == 'Normal':
unit = section.find('./UNIT[@name="Target Name"]')
iqn = unit.text
for section in xmlobj.xpath('./SECTION[@name="LUN/LD List"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun,
'iqn': iqn}
ldsetlds[ldn] = ld
elif tmode == 'Multi-Target':
for section in xmlobj.xpath('./SECTION[@name='
'"Target Information For '
'Multi-Target Mode"]'):
unit = section.find('./UNIT[@name="Target Name"]')
iqn = unit.text
unit = section.find('./UNIT[@name="LDN(h)"]')
if unit.text.startswith('-'):
continue
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
if unit.text.startswith('-'):
continue
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun,
'iqn': iqn}
ldsetlds[ldn] = ld
ldset = {'ldsetname': ldsetname,
'protocol': 'iSCSI',
'portal_list': portals,
'lds': ldsetlds}
ldsets[ldsetname] = ldset
return ldsets
def get_fc_ldset_config(self, xml, root):
ldsets = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Access Control"]/'
'OBJECT[@name="LD Set(FC)"]'):
ldsetlds = {}
section = xmlobj.find('./SECTION[@name="LD Set(FC)'
' Information"]')
if section is None:
return ldsets
unit = section.find('./UNIT[@name="Platform"]')
platform = unit.text
unit = section.find('./UNIT[@name="LD Set Name"]')
ldsetname = platform + ':' + unit.text
wwpns = []
ports = []
for section in xmlobj.xpath('./SECTION[@name="Path List"]'):
unit = section.find('./UNIT[@name="Path"]')
if unit.text.find('(') != -1:
ports.append(unit.text)
else:
wwpns.append(unit.text)
for section in xmlobj.xpath('./SECTION[@name="LUN/LD List"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun}
ldsetlds[ldn] = ld
ldset = {'ldsetname': ldsetname,
'lds': ldsetlds,
'protocol': 'FC',
'wwpn': wwpns,
'port': ports}
ldsets[ldsetname] = ldset
return ldsets
def get_hostport_config(self, xml, root):
hostports = {}
for section in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Controller"]/'
'OBJECT[@name="Host Port"]/'
'SECTION[@name="Host Director'
'/Host Port Information"]'):
unit = section.find('./UNIT[@name="Port No.(h)"]')
units = unit.text.split('-')
director = int(units[0], 16)
port = int(units[1], 16)
unit = section.find('./UNIT[@name="IP Address"]')
if unit is not None:
ip = unit.text
protocol = 'iSCSI'
wwpn = None
else:
ip = '0.0.0.0'
protocol = 'FC'
unit = section.find('./UNIT[@name="WWPN"]')
wwpn = unit.text
# Port Link Status check Start.
unit = section.find('./UNIT[@name="Link Status"]')
hostport = {
'director': director,
'port': port,
'ip': ip,
'protocol': protocol,
'wwpn': wwpn
}
if director not in hostports:
hostports[director] = []
hostports[director].append(hostport)
return hostports
def configs(self, xml):
root = etree.fromstring(xml)
pools = self.get_pool_config(xml, root)
lds, used_ldns = self.get_ld_config(xml, root, pools)
iscsi_ldsets = self.get_iscsi_ldset_config(xml, root)
fc_ldsets = self.get_fc_ldset_config(xml, root)
hostports = self.get_hostport_config(xml, root)
diskarray_max_ld_count = self.get_diskarray_max_ld_count()
ldsets = {}
ldsets.update(iscsi_ldsets)
ldsets.update(fc_ldsets)
return pools, lds, ldsets, used_ldns, hostports, diskarray_max_ld_count
def get_volume_type_qos_specs(self, volume):
return {}
def check_io_parameter(self, specs):
pass

View File

View File

@ -0,0 +1,781 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
import os
import re
import select
import time
import traceback
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import units
from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder import ssh_utils
LOG = logging.getLogger(__name__)
retry_msgids = ['iSM31005', 'iSM31015', 'iSM42408', 'iSM42412']
class MStorageISMCLI(object):
"""SSH client."""
def __init__(self, properties):
super(MStorageISMCLI, self).__init__()
self._sshpool = None
self._properties = properties
def _execute(self, command, expected_status=[0], raise_exec=True):
return self._sync_execute(command, self._properties['diskarray_name'],
expected_status, raise_exec)
@coordination.synchronized('mstorage_ismcli_execute_{diskarray_name}')
def _sync_execute(self, command, diskarray_name,
expected_status=[0], raise_exec=True):
retry_flag = True
retry_count = 0
while retry_flag is True:
try:
out, err, status = self._cli_execute(command, expected_status,
False)
if status != 0:
errflg = 0
errnum = out + err
LOG.debug('ismcli failed (errnum=%s).', errnum)
for retry_msgid in retry_msgids:
if errnum.find(retry_msgid) >= 0:
LOG.debug('`%(command)s` failed. '
'%(name)s %(errnum)s '
'retry_count=%(retry_count)d',
{'command': command,
'name': __name__,
'errnum': errnum,
'retry_count': retry_count})
errflg = 1
break
if errflg == 1:
retry_count += 1
if retry_count >= 60:
msg = (_('Timeout `%(command)s`.'
' status=%(status)d, '
'out="%(out)s", '
'err="%(err)s".') %
{'command': command,
'status': status,
'out': out,
'err': err})
raise exception.APITimeout(msg)
time.sleep(5)
continue
else:
if raise_exec is True:
msg = _('Command `%s` failed.') % command
raise exception.VolumeBackendAPIException(data=msg)
except EOFError:
with excutils.save_and_reraise_exception() as ctxt:
LOG.debug('EOFError has occurred. '
'%(name)s retry_count=%(retry_count)d',
{'name': __name__,
'retry_count': retry_count})
retry_count += 1
if retry_count < 60:
ctxt.reraise = False
time.sleep(5)
continue
retry_flag = False
return out, err, status
def _execute_nolock(self, command, expected_status=[0], raise_exec=True):
retry_flag = True
retry_count = 0
while retry_flag is True:
try:
out, err, status = self._cli_execute(command, expected_status,
raise_exec)
except EOFError:
with excutils.save_and_reraise_exception() as ctxt:
LOG.debug('EOFError has occurred. '
'%(name)s retry_count=%(retry_count)d',
{'name': __name__,
'retry_count': retry_count})
retry_count += 1
if retry_count < 60:
ctxt.reraise = False
time.sleep(5)
continue
retry_flag = False
return out, err, status
def _cli_execute(self, command, expected_status=[0], raise_exec=True):
if not self._sshpool:
LOG.debug('ssh_utils.SSHPool execute.')
self._sshpool = ssh_utils.SSHPool(
self._properties['cli_fip'],
self._properties['ssh_pool_port_number'],
self._properties['ssh_conn_timeout'],
self._properties['cli_user'],
self._properties['cli_password'],
privatekey=self._properties['cli_privkey'])
with self._sshpool.item() as ssh:
LOG.debug('`%s` executing...', command)
stdin, stdout, stderr = ssh.exec_command(command)
stdin.close()
channel = stdout.channel
_out, _err = [], []
while 1:
select.select([channel], [], [])
if channel.recv_ready():
_out.append(channel.recv(4096))
continue
if channel.recv_stderr_ready():
_err.append(channel.recv_stderr(4096))
continue
if channel.exit_status_ready():
status = channel.recv_exit_status()
break
LOG.debug('`%(command)s` done. status=%(status)d.',
{'command': command, 'status': status})
out, err = ''.join(_out), ''.join(_err)
if expected_status is not None and status not in expected_status:
LOG.debug('`%(command)s` failed. status=%(status)d, '
'out="%(out)s", err="%(err)s".',
{'command': command, 'status': status,
'out': out, 'err': err})
if raise_exec is True:
msg = _('Command `%s` failed.') % command
raise exception.VolumeBackendAPIException(data=msg)
return out, err, status
def view_all(self, conf_ismview_path=None, delete_ismview=True,
cmd_lock=True):
if self._properties['queryconfig_view'] is True:
command = 'clioutmsg xml; iSMview'
if self._properties['ismview_alloptimize'] is True:
command += ' --alloptimize'
else:
command += ' -all'
else:
command = 'iSMquery -cinder -xml -all'
if cmd_lock is True:
out, err, status = self._execute(command)
else:
out, err, status = self._execute_nolock(command)
exstats = re.compile("(.*)ExitStatus(.*)\n")
tmpout = exstats.sub('', out)
out = tmpout
if conf_ismview_path is not None:
if delete_ismview:
if os.path.exists(conf_ismview_path):
os.remove(conf_ismview_path)
LOG.debug('Remove clioutmsg xml to %s.',
conf_ismview_path)
else:
with open(conf_ismview_path, 'w+') as f:
f.write(out)
LOG.debug('Wrote clioutmsg xml to %s.',
conf_ismview_path)
return out
def get_poolnumber_and_ldnumber(self, pools, used_ldns, max_ld_count):
selected_pool = -1
min_ldn = 0
for pool in pools:
nld = len(pool['ld_list'])
if selected_pool == -1 or min_ldn > nld:
selected_pool = pool['pool_num']
min_ldn = nld
for ldn in range(0, max_ld_count + 1):
if ldn not in used_ldns:
break
if ldn > max_ld_count - 1:
msg = _('All Logical Disk numbers are used.')
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return selected_pool, ldn
def ldbind(self, name, pool, ldn, size):
"""Bind an LD and attach a nickname to it."""
errnum = ""
cmd = ('iSMcfg ldbind -poolnumber %(poolnumber)d -ldn %(ldn)d '
'-capacity %(capacity)d -immediate'
% {'poolnumber': pool, 'ldn': ldn,
'capacity': size})
out, err, status = self._execute(cmd, [0], False)
errnum = err
if status != 0:
return False, errnum
cmd = ('iSMcfg nickname -ldn %(ldn)d -newname %(newname)s '
'-immediate'
% {'ldn': ldn, 'newname': name})
self._execute(cmd)
return True, errnum
def unbind(self, name):
"""Unbind an LD."""
cmd = 'iSMcfg ldunbind -ldname %s' % name
self._execute(cmd)
def expand(self, ldn, capacity):
"""Expand a LD."""
cmd = ('iSMcfg ldexpand -ldn %(ldn)d -capacity %(capacity)d '
'-unit gb'
% {'ldn': ldn, 'capacity': capacity})
self._execute(cmd)
def addldsetld(self, ldset, ldname, lun=None):
"""Add an LD to specified LD Set."""
if lun is None:
cmd = ('iSMcfg addldsetld -ldset %(ldset)s '
'-ldname %(ldname)s'
% {'ldset': ldset, 'ldname': ldname})
self._execute(cmd)
else:
cmd = ('iSMcfg addldsetld -ldset %(ldset)s -ldname %(ldname)s '
'-lun %(lun)d'
% {'ldset': ldset, 'ldname': ldname,
'lun': lun})
self._execute(cmd)
def delldsetld(self, ldset, ldname):
"""Delete an LD from specified LD Set."""
rtn = True
errnum = ""
cmd = ('iSMcfg delldsetld -ldset %(ldset)s '
'-ldname %(ldname)s'
% {'ldset': ldset,
'ldname': ldname})
out, err, status = self._execute(cmd, [0], False)
errnum = err
if status != 0:
rtn = False
return rtn, errnum
def changeldname(self, ldn, new_name, old_name=None):
"""Rename nickname of LD."""
if old_name is None:
cmd = ('iSMcfg nickname -ldn %(ldn)d -newname %(newname)s '
'-immediate'
% {'ldn': ldn, 'newname': new_name})
self._execute(cmd)
else:
cmd = ('iSMcfg nickname -ldname %(ldname)s '
'-newname %(newname)s'
% {'ldname': old_name,
'newname': new_name})
self._execute(cmd)
def setpair(self, mvname, rvname):
"""Set pair."""
cmd = ('iSMrc_pair -pair -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
LOG.debug('Pair command completed. MV = %(mv)s RV = %(rv)s.',
{'mv': mvname, 'rv': rvname})
def unpair(self, mvname, rvname, flag):
"""Unset pair."""
if flag == 'normal':
cmd = ('iSMrc_pair -unpair -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
elif flag == 'force':
cmd = ('iSMrc_pair -unpair -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld -force all'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
else:
LOG.debug('unpair flag ERROR. flag = %s', flag)
LOG.debug('Unpair command completed. MV = %(mv)s, RV = %(rv)s.',
{'mv': mvname, 'rv': rvname})
def replicate(self, mvname, rvname, flag):
if flag == 'full':
cmd = ('iSMrc_replicate -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld -nowait -cprange full '
'-cpmode bg'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
else:
cmd = ('iSMrc_replicate -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld -nowait -cpmode bg'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
LOG.debug('Replicate command completed. MV = %(mv)s RV = %(rv)s.',
{'mv': mvname, 'rv': rvname})
def separate(self, mvname, rvname, flag):
"""Separate for backup."""
if flag == 'backup':
cmd = ('iSMrc_separate -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld '
'-rvacc ro -rvuse complete -nowait'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
elif flag == 'restore' or flag == 'clone':
cmd = ('iSMrc_separate -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld '
'-rvacc rw -rvuse immediate -nowait'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
elif flag == 'esv_restore' or flag == 'migrate':
cmd = ('iSMrc_separate -mv %(mv)s -mvflg ld '
'-rv %(rv)s -rvflg ld '
'-rvacc rw -rvuse complete -nowait'
% {'mv': mvname, 'rv': rvname})
self._execute(cmd)
else:
LOG.debug('separate flag ERROR. flag = %s', flag)
LOG.debug('Separate command completed. MV = %(mv)s RV = %(rv)s.',
{'mv': mvname, 'rv': rvname})
def query_MV_RV_status(self, ldname, rpltype):
if rpltype == 'MV':
cmd = ('iSMrc_query -mv %s -mvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "Sync State" ]]; '
'then builtin echo ${line:10};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
elif rpltype == 'RV':
cmd = ('iSMrc_query -rv %s -rvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "Sync State" ]]; '
'then builtin echo ${line:10};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
else:
LOG.debug('rpltype flag ERROR. rpltype = %s', rpltype)
query_status = out.strip()
return query_status
def query_MV_RV_name(self, ldname, rpltype):
if rpltype == 'MV':
cmd = ('iSMrc_query -mv %s -mvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "LD Name" ]]; '
'then builtin echo ${line:7};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
out = out.replace(ldname, "")
elif rpltype == 'RV':
cmd = ('iSMrc_query -rv %s -rvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "LD Name" ]]; '
'then builtin echo ${line:7};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
out = out.replace(ldname, "")
else:
LOG.debug('rpltype flag ERROR. rpltype = %s', rpltype)
query_name = out.strip()
return query_name
def query_MV_RV_diff(self, ldname, rpltype):
if rpltype == 'MV':
cmd = ('iSMrc_query -mv %s -mvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "Separate Diff" ]]; '
'then builtin echo ${line:13};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
elif rpltype == 'RV':
cmd = ('iSMrc_query -rv %s -rvflg ld | '
'while builtin read line;'
'do if [[ "$line" =~ "Separate Diff" ]]; '
'then builtin echo ${line:13};fi;'
'done' % ldname)
out, err, status = self._execute(cmd)
else:
LOG.debug('rpltype flag ERROR. rpltype = %s', rpltype)
query_status = out.strip()
return query_status
def backup_restore(self, volume_properties, unpairWait, canPairing=True):
# Setting Pair.
flag = 'full'
if canPairing is True:
self.setpair(volume_properties['mvname'][3:],
volume_properties['rvname'][3:])
else:
rv_diff = self.query_MV_RV_diff(volume_properties['rvname'][3:],
'RV')
rv_diff = int(rv_diff.replace('KB', ''), 10) // units.Ki
if rv_diff != volume_properties['capacity']:
flag = None
# Replicate.
self.replicate(volume_properties['mvname'][3:],
volume_properties['rvname'][3:], flag)
# Separate.
self.separate(volume_properties['mvname'][3:],
volume_properties['rvname'][3:],
volume_properties['flag'])
unpairProc = unpairWait(volume_properties, self)
unpairProc.run()
def check_ld_existed_rplstatus(self, lds, ldname, snapshot, flag):
if ldname not in lds:
if flag == 'backup':
LOG.debug('Volume Id not found. '
'LD name = %(name)s volume_id = %(id)s.',
{'name': ldname, 'id': snapshot['volume_id']})
raise exception.NotFound(_('Logical Disk does not exist.'))
elif flag == 'restore':
LOG.debug('Snapshot Id not found. '
'LD name = %(name)s snapshot_id = %(id)s.',
{'name': ldname, 'id': snapshot['id']})
raise exception.NotFound(_('Logical Disk does not exist.'))
elif flag == 'delete':
LOG.debug('LD `%(name)s` already unbound? '
'snapshot_id = %(id)s.',
{'name': ldname, 'id': snapshot['id']})
return None
else:
LOG.debug('check_ld_existed_rplstatus flag error flag = %s.',
flag)
raise exception.NotFound(_('Logical Disk does not exist.'))
ld = lds[ldname]
if ld['RPL Attribute'] == 'IV':
pass
elif ld['RPL Attribute'] == 'MV':
query_status = self.query_MV_RV_status(ldname[3:], 'MV')
LOG.debug('query_status : %s.', query_status)
if(query_status == 'separated'):
# unpair.
rvname = self.query_MV_RV_name(ldname[3:], 'MV')
self.unpair(ldname[3:], rvname, 'force')
else:
msg = _('Specified Logical Disk %s has been copied.') % ldname
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
elif ld['RPL Attribute'] == 'RV':
query_status = self.query_MV_RV_status(ldname[3:], 'RV')
if query_status == 'separated':
# unpair.
mvname = self.query_MV_RV_name(ldname[3:], 'RV')
self.unpair(mvname, ldname[3:], 'force')
else:
msg = _('Specified Logical Disk %s has been copied.') % ldname
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return ld
def get_pair_lds(self, ldname, lds):
query_status = self.query_MV_RV_name(ldname[3:], 'MV')
query_status = query_status.split('\n')
query_status = [query for query in query_status if query != '']
LOG.debug('query_status=%s.', query_status)
pair_lds = {}
for rvname in query_status:
rvname = self._properties['ld_backupname_format'] % rvname
if rvname not in lds:
LOG.debug('LD `%s` is RDR pair?', rvname)
else:
ld = lds[rvname]
ldn = ld['ldn']
pair_lds[ldn] = ld
LOG.debug('pair_lds=%s.', pair_lds)
return pair_lds
def snapshot_create(self, bvname, svname, poolnumber):
"""Snapshot create."""
cmd = ('iSMcfg generationadd -bvname %(bvname)s '
'-poolnumber %(poolnumber)d -count 1 '
'-svname %(svname)s'
% {'bvname': bvname,
'poolnumber': poolnumber,
'svname': svname})
self._execute(cmd)
cmd = ('iSMsc_create -bv %(bv)s -bvflg ld -sv %(sv)s '
'-svflg ld'
% {'bv': bvname[3:], 'sv': svname})
self._execute(cmd)
def snapshot_delete(self, bvname, svname):
"""Snapshot delete."""
query_status = self.query_BV_SV_status(bvname[3:], svname)
if query_status == 'snap/active':
cmd = ('iSMsc_delete -bv %(bv)s -bvflg ld -sv %(sv)s '
'-svflg ld'
% {'bv': bvname[3:], 'sv': svname})
self._execute(cmd)
while True:
query_status = self.query_BV_SV_status(bvname[3:], svname)
if query_status == 'snap/deleting':
LOG.debug('Sleep 1 seconds Start')
time.sleep(1)
else:
break
else:
LOG.debug('The snapshot data does not exist,'
' because already forced deletion.'
' bvname=%(bvname)s, svname=%(svname)s',
{'bvname': bvname, 'svname': svname})
cmd = 'iSMcfg generationdel -bvname %s -count 1' % bvname
self._execute(cmd)
def query_BV_SV_status(self, bvname, svname):
cmd = ('iSMsc_query -bv %(bv)s -bvflg ld -sv %(sv)s -svflg ld '
'-summary | '
'while builtin read line;do '
'if [[ "$line" =~ "%(line)s" ]]; '
'then builtin echo "$line";fi;done'
% {'bv': bvname, 'sv': svname, 'line': svname})
out, err, status = self._execute(cmd)
query_status = out[34:48].strip()
LOG.debug('snap/state:%s.', query_status)
return query_status
def set_io_limit(self, ldname, specs, force_delete=True):
if specs['upperlimit'] is not None:
upper = int(specs['upperlimit'], 10)
else:
upper = None
if specs['lowerlimit'] is not None:
lower = int(specs['lowerlimit'], 10)
else:
lower = None
report = specs['upperreport']
if upper is None and lower is None and report is None:
return
cmd = 'iSMioc setlimit -ldname %s' % ldname
if upper is not None:
cmd += ' -upperlimit %d' % upper
if lower is not None:
cmd += ' -lowerlimit %d' % lower
if report is not None:
cmd += ' -upperreport %s' % report
try:
self._execute(cmd)
except Exception:
with excutils.save_and_reraise_exception():
if force_delete:
self.unbind(ldname)
class UnpairWait(object):
error_updates = {'status': 'error',
'progress': '100%',
'migration_status': None}
def __init__(self, volume_properties, cli):
super(UnpairWait, self).__init__()
self._volume_properties = volume_properties
self._mvname = volume_properties['mvname'][3:]
self._rvname = volume_properties['rvname'][3:]
self._mvID = volume_properties['mvid']
self._rvID = volume_properties['rvid']
self._flag = volume_properties['flag']
self._context = volume_properties['context']
self._cli = cli
self._local_conf = self._cli._properties
def _wait(self, unpair=True):
timeout = self._local_conf['thread_timeout'] * 24
start_time = time.time()
while True:
cur_time = time.time()
if (cur_time - start_time) > timeout:
raise exception.APITimeout(_('UnpairWait wait timeout.'))
LOG.debug('Sleep 60 seconds Start')
time.sleep(60)
query_status = self._cli.query_MV_RV_status(self._rvname, 'RV')
if query_status == 'separated':
if unpair is True:
self._cli.unpair(self._mvname, self._rvname, 'normal')
break
elif query_status == 'sep/exec':
continue
else:
LOG.debug('iSMrc_query command result abnormal.'
'Query status = %(status)s, RV = %(rv)s.',
{'status': query_status, 'rv': self._rvname})
break
def run(self):
try:
self._execute()
except Exception:
with excutils.save_and_reraise_exception():
LOG.debug('UnpairWait Unexpected error. '
'exception=%(exception)s, MV = %(mv)s, RV = %(rv)s.',
{'exception': traceback.format_exc(),
'mv': self._mvname, 'rv': self._rvname})
def _execute(self):
pass
class UnpairWaitForBackup(UnpairWait):
def __init__(self, volume_properties, cli):
super(UnpairWaitForBackup, self).__init__(volume_properties, cli)
def _execute(self):
LOG.debug('UnpairWaitForBackup start.')
self._wait(True)
class UnpairWaitForRestore(UnpairWait):
def __init__(self, volume_properties, cli):
super(UnpairWaitForRestore, self).__init__(volume_properties, cli)
self._rvldn = None
if ('rvldn' in volume_properties and
volume_properties['rvldn'] is not None):
self._rvldn = volume_properties['rvldn']
self._rvcapacity = None
if ('rvcapacity' in volume_properties and
volume_properties['rvcapacity'] is not None):
self._rvcapacity = volume_properties['rvcapacity']
def _execute(self):
LOG.debug('UnpairWaitForRestore start.')
self._wait(True)
if self._rvcapacity is not None:
try:
self._cli.expand(self._rvldn, self._rvcapacity)
except exception.CinderException:
with excutils.save_and_reraise_exception():
LOG.debug('UnpairWaitForDDRRestore expand error. '
'exception=%(exception)s, '
'MV = %(mv)s, RV = %(rv)s.',
{'exception': traceback.format_exc(),
'mv': self._mvname, 'rv': self._rvname})
class UnpairWaitForClone(UnpairWait):
def __init__(self, volume_properties, cli):
super(UnpairWaitForClone, self).__init__(volume_properties, cli)
self._rvldn = None
if ('rvldn' in volume_properties and
volume_properties['rvldn'] is not None):
self._rvldn = volume_properties['rvldn']
self._rvcapacity = None
if ('rvcapacity' in volume_properties and
volume_properties['rvcapacity'] is not None):
self._rvcapacity = volume_properties['rvcapacity']
def _execute(self):
LOG.debug('UnpairWaitForClone start.')
self._wait(True)
if self._rvcapacity is not None:
try:
self._cli.expand(self._rvldn, self._rvcapacity)
except exception.CinderException:
with excutils.save_and_reraise_exception():
LOG.debug('UnpairWaitForClone expand error. '
'exception=%(exception)s, '
'MV = %(mv)s, RV = %(rv)s.',
{'exception': traceback.format_exc(),
'mv': self._mvname, 'rv': self._rvname})
class UnpairWaitForMigrate(UnpairWait):
def __init__(self, volume_properties, cli):
super(UnpairWaitForMigrate, self).__init__(volume_properties, cli)
def _execute(self):
LOG.debug('UnpairWaitForMigrate start.')
self._wait(True)
self._cli.unbind(self._volume_properties['mvname'])
self._cli.changeldname(None, self._volume_properties['mvname'],
self._volume_properties['rvname'])
class UnpairWaitForDDRBackup(UnpairWaitForBackup):
def __init__(self, volume_properties, cli):
super(UnpairWaitForDDRBackup, self).__init__(volume_properties, cli)
def _execute(self):
LOG.debug('UnpairWaitForDDRBackup start.')
self._wait(False)
class UnpairWaitForDDRRestore(UnpairWaitForRestore):
def __init__(self, volume_properties, cli):
super(UnpairWaitForDDRRestore, self).__init__(volume_properties, cli)
self._prev_mvname = None
if ('prev_mvname' in volume_properties and
volume_properties['prev_mvname'] is not None):
self._prev_mvname = volume_properties['prev_mvname'][3:]
def _execute(self):
LOG.debug('UnpairWaitForDDRRestore start.')
self._wait(True)
if self._rvcapacity is not None:
try:
self._cli.expand(self._rvldn, self._rvcapacity)
except exception.CinderException:
with excutils.save_and_reraise_exception():
LOG.debug('UnpairWaitForDDRRestore expand error. '
'exception=%(exception)s, '
'MV = %(mv)s, RV = %(rv)s.',
{'exception': traceback.format_exc(),
'mv': self._mvname, 'rv': self._rvname})
if self._prev_mvname is not None:
self._cli.setpair(self._prev_mvname, self._mvname)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<ProductInfo>
<VendorName>NEC</VendorName>
<ProductMap>
<Product Name="iStorage M700">8192</Product>
<Product Name="iStorage A5000">8192</Product>
<Product Name="iStorage M500">4096</Product>
<Product Name="iStorage M300">4096</Product>
<Product Name="iStorage M10e">1024</Product>
<Product Name="iStorage M100">1024</Product>
<Product Name="M700 Disk Array">8192</Product>
<Product Name="A5000 Disk Array">8192</Product>
<Product Name="M300 Disk Array">4096</Product>
<Product Name="M500 Disk Array">4096</Product>
<Product Name="M10e Disk Array">1024</Product>
<Product Name="M100 Disk Array">1024</Product>
<Product Name="M5000">8192</Product>
<Product Name="M710">8192</Product>
<Product Name="M710F">8192</Product>
<Product Name="A3000/100">4096</Product>
<Product Name="M310">4096</Product>
<Product Name="M310F">4096</Product>
<Product Name="M510">4096</Product>
<Product Name="M11e">1024</Product>
<Product Name="M110">1024</Product>
</ProductMap>
</ProductInfo>

View File

@ -0,0 +1,76 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
"""Drivers for M-Series Storage."""
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.nec import volume_helper
from cinder.zonemanager import utils as fczm_utils
@interface.volumedriver
class MStorageISCSIDriver(volume_helper.MStorageDSVDriver,
driver.ISCSIDriver):
"""M-Series Storage Snapshot iSCSI Driver."""
def __init__(self, *args, **kwargs):
super(MStorageISCSIDriver, self).__init__(*args, **kwargs)
self._set_config(self.configuration, self.host,
self.__class__.__name__)
def create_export(self, context, volume, connector):
return self.iscsi_do_export(context, volume, connector)
def ensure_export(self, context, volume):
pass
def get_volume_stats(self, refresh=False):
return self.iscsi_get_volume_stats(refresh)
def initialize_connection(self, volume, connector):
return self.iscsi_initialize_connection(volume, connector)
def terminate_connection(self, volume, connector, **kwargs):
return self.iscsi_terminate_connection(volume, connector)
@interface.volumedriver
class MStorageFCDriver(volume_helper.MStorageDSVDriver,
driver.FibreChannelDriver):
"""M-Series Storage Snapshot FC Driver."""
def __init__(self, *args, **kwargs):
super(MStorageFCDriver, self).__init__(*args, **kwargs)
self._set_config(self.configuration, self.host,
self.__class__.__name__)
def create_export(self, context, volume, connector):
return self.fc_do_export(context, volume, connector)
def ensure_export(self, context, volume):
pass
def get_volume_stats(self, refresh=False):
return self.fc_get_volume_stats(refresh)
@fczm_utils.AddFCZone
def initialize_connection(self, volume, connector):
return self.fc_initialize_connection(volume, connector)
@fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs):
return self.fc_terminate_connection(volume, connector)

View File

@ -0,0 +1,922 @@
#
# Copyright (c) 2016 NEC Corporation.
# 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.
import errno
from lxml import etree
import os
import re
import six
import traceback
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import units
from cinder import context
from cinder import exception
from cinder.i18n import _
from cinder.volume.drivers.nec import cli
from cinder.volume.drivers.san import san
from cinder.volume import qos_specs
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
FLAGS = cfg.CONF
mstorage_opts = [
cfg.IPOpt('nec_ismcli_fip',
default=None,
help='FIP address of M-Series Storage iSMCLI.'),
cfg.StrOpt('nec_ismcli_user',
default='',
help='User name for M-Series Storage iSMCLI.'),
cfg.StrOpt('nec_ismcli_password',
secret=True,
default='',
help='Password for M-Series Storage iSMCLI.'),
cfg.StrOpt('nec_ismcli_privkey',
default='',
help='Filename of RSA private key for '
'M-Series Storage iSMCLI.'),
cfg.StrOpt('nec_ldset',
default='',
help='M-Series Storage LD Set name for Compute Node.'),
cfg.StrOpt('nec_ldname_format',
default='LX:%s',
help='M-Series Storage LD name format for volumes.'),
cfg.StrOpt('nec_backup_ldname_format',
default='LX:%s',
help='M-Series Storage LD name format for snapshots.'),
cfg.StrOpt('nec_diskarray_name',
default='',
help='Diskarray name of M-Series Storage.'),
cfg.StrOpt('nec_ismview_dir',
default='/tmp/nec/cinder',
help='Output path of iSMview file.'),
cfg.StrOpt('nec_ldset_for_controller_node',
default='',
help='M-Series Storage LD Set name for Controller Node.'),
cfg.IntOpt('nec_ssh_pool_port_number',
default=22,
help='Port number of ssh pool.'),
cfg.IntOpt('nec_unpairthread_timeout',
default=3600,
help='Timeout value of Unpairthread.'),
cfg.IntOpt('nec_backend_max_ld_count',
default=1024,
help='Maximum number of managing sessions.'),
cfg.BoolOpt('nec_actual_free_capacity',
default=False,
help='Return actual free capacity.'),
cfg.BoolOpt('nec_ismview_alloptimize',
default=False,
help='Use legacy iSMCLI command with optimization.'),
cfg.ListOpt('nec_pools',
default=[],
help='M-Series Storage pool numbers list to be used.'),
cfg.ListOpt('nec_backup_pools',
default=[],
help='M-Series Storage backup pool number to be used.'),
cfg.BoolOpt('nec_queryconfig_view',
default=False,
help='Use legacy iSMCLI command.'),
cfg.IntOpt('nec_iscsi_portals_per_cont',
default=1,
help='Number of iSCSI portals.'),
]
FLAGS.register_opts(mstorage_opts)
def convert_to_name(uuid):
alnum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
num = int(uuid.replace(("-"), ""), 16)
convertname = ""
while num != 0:
convertname = alnum[num % len(alnum)] + convertname
num = num - num % len(alnum)
num = num // len(alnum)
return convertname
def convert_to_id(value62):
alnum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
length = len(value62)
weight = 0
value = 0
index = 0
for i in reversed(range(0, length)):
num = alnum.find(value62[i])
if index != 0:
value += int(weight * (num))
else:
value = num
index += 1
weight = 62 ** index
value = '%032x' % value
uuid = value[0:8]
uuid += '-'
uuid += value[8:12]
uuid += '-'
uuid += value[12:16]
uuid += '-'
uuid += value[16:20]
uuid += '-'
uuid += value[20:]
return uuid
class MStorageVolumeCommon(object):
"""M-Series Storage volume common class."""
def __init__(self, configuration, host, driver_name):
super(MStorageVolumeCommon, self).__init__()
self._host = host
self._driver_name = driver_name
self._configuration = configuration
self._configuration.append_config_values(mstorage_opts)
self._configuration.append_config_values(san.san_opts)
self._config_group = self._configuration.config_group
if self._config_group:
FLAGS.register_opts(mstorage_opts, group=self._config_group)
self._local_conf = FLAGS._get(self._config_group)
else:
FLAGS.register_opts(mstorage_opts)
self._local_conf = FLAGS
self._check_flags()
self._properties = self._set_properties()
self._cli = self._properties['cli']
def set_context(self, context):
self._context = context
def _check_flags(self):
for flag in ['nec_ismcli_fip', 'nec_ismcli_user']:
if getattr(self._local_conf, flag, '') == '':
raise exception.ParameterNotFound(param=flag)
if (getattr(self._local_conf, 'nec_ismcli_password', '') == '' and
getattr(self._local_conf, 'nec_ismcli_privkey', '') == ''):
msg = _('nec_ismcli_password nor nec_ismcli_privkey')
raise exception.ParameterNotFound(param=msg)
def _create_ismview_dir(self,
ismview_dir,
diskarray_name,
driver_name,
host):
"""Create ismview directory."""
filename = diskarray_name
if filename == '':
filename = driver_name + '_' + host
ismview_path = os.path.join(ismview_dir, filename)
LOG.debug('ismview_path=%s.', ismview_path)
try:
if os.path.exists(ismview_path):
os.remove(ismview_path)
except OSError as e:
with excutils.save_and_reraise_exception() as ctxt:
if e.errno == errno.ENOENT:
ctxt.reraise = False
try:
os.makedirs(ismview_dir)
except OSError as e:
with excutils.save_and_reraise_exception() as ctxt:
if e.errno == errno.EEXIST:
ctxt.reraise = False
return ismview_path
def get_conf(self, host):
"""Get another host group configurations."""
hostname = host['host']
hostname = hostname[:hostname.rindex('#')]
if '@' in hostname:
group = hostname.split('@')[1]
FLAGS.register_opts(mstorage_opts, group=group)
conf = FLAGS._get(group)
else:
FLAGS.register_opts(mstorage_opts)
conf = FLAGS
return conf
def get_conf_properties(self, conf=None):
if conf is None:
return self._properties
pool_pools = []
for pool in getattr(conf, 'nec_pools', []):
if pool.endswith('h'):
pool_pools.append(int(pool[:-1], 16))
else:
pool_pools.append(int(pool, 10))
pool_backup_pools = []
for pool in getattr(conf, 'nec_backup_pools', []):
if pool.endswith('h'):
pool_backup_pools.append(int(pool[:-1], 16))
else:
pool_backup_pools.append(int(pool, 10))
ldset_name = getattr(conf, 'nec_ldset', '')
ldset_controller_node_name = getattr(conf,
'nec_ldset_for_controller_node',
'')
return {
'cli_fip': conf.nec_ismcli_fip,
'cli_user': conf.nec_ismcli_user,
'cli_password': conf.nec_ismcli_password,
'cli_privkey': conf.nec_ismcli_privkey,
'pool_pools': pool_pools,
'pool_backup_pools': pool_backup_pools,
'pool_actual_free_capacity': conf.nec_actual_free_capacity,
'ldset_name': ldset_name,
'ldset_controller_node_name': ldset_controller_node_name,
'ld_name_format': conf.nec_ldname_format,
'ld_backupname_format': conf.nec_backup_ldname_format,
'ld_backend_max_count': conf.nec_backend_max_ld_count,
'thread_timeout': conf.nec_unpairthread_timeout,
'ismview_dir': conf.nec_ismview_dir,
'ismview_alloptimize': conf.nec_ismview_alloptimize,
'ssh_conn_timeout': conf.ssh_conn_timeout,
'ssh_pool_port_number': conf.nec_ssh_pool_port_number,
'diskarray_name': conf.nec_diskarray_name,
'queryconfig_view': conf.nec_queryconfig_view,
'portal_number': conf.nec_iscsi_portals_per_cont,
'reserved_percentage': conf.reserved_percentage
}
def _set_properties(self):
conf_properties = self.get_conf_properties(self._local_conf)
ismview_path = self._create_ismview_dir(
self._local_conf.nec_ismview_dir,
self._local_conf.nec_diskarray_name,
self._driver_name,
self._host)
vendor_name, _product_dict = self.get_oem_parameter()
backend_name = self._configuration.safe_get('volume_backend_name')
conf_properties['ismview_path'] = ismview_path
conf_properties['driver_name'] = self._driver_name
conf_properties['config_group'] = self._config_group
conf_properties['configuration'] = self._configuration
conf_properties['vendor_name'] = vendor_name
conf_properties['products'] = _product_dict
conf_properties['backend_name'] = backend_name
conf_properties['cli'] = cli.MStorageISMCLI(conf_properties)
return conf_properties
def get_oem_parameter(self):
product = os.path.join(os.path.dirname(__file__), 'product.xml')
try:
with open(product, 'r') as f:
xml = f.read()
root = etree.fromstring(xml)
vendor_name = root.xpath('./VendorName')[0].text
product_dict = {}
product_map = root.xpath('./ProductMap/Product')
for s in product_map:
product_dict[s.attrib['Name']] = int(s.text, 10)
return vendor_name, product_dict
except OSError as e:
with excutils.save_and_reraise_exception() as ctxt:
if e.errno == errno.ENOENT:
ctxt.reraise = False
raise exception.NotFound(_('%s not found.') % product)
@staticmethod
def get_ldname(volid, volformat):
alnum = ('0123456789'
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
ldname = ""
num = int(volid.replace(("-"), ""), 16)
while num != 0:
ldname = alnum[num % len(alnum)] + ldname
num = num - num % len(alnum)
num = num // len(alnum)
return volformat % ldname
def get_ldset(self, ldsets, metadata=None):
ldset = None
if metadata is not None and 'ldset' in metadata:
ldset_meta = metadata['ldset']
LOG.debug('ldset(metadata)=%s.', ldset_meta)
for tldset in six.itervalues(ldsets):
if tldset['ldsetname'] == ldset_meta:
ldset = ldsets[ldset_meta]
LOG.debug('ldset information(metadata specified)=%s.',
ldset)
break
if ldset is None:
msg = _('Logical Disk Set could not be found.')
LOG.error(msg)
raise exception.NotFound(msg)
elif self._properties['ldset_name'] == '':
nldset = len(ldsets)
if nldset == 0:
msg = _('Logical Disk Set could not be found.')
raise exception.NotFound(msg)
else:
ldset = None
else:
if self._properties['ldset_name'] not in ldsets:
msg = (_('Logical Disk Set `%s` could not be found.') %
self._properties['ldset_name'])
raise exception.NotFound(msg)
ldset = ldsets[self._properties['ldset_name']]
return ldset
def get_pool_capacity(self, pools, ldsets):
pools = [pool for (pn, pool) in six.iteritems(pools)
if len(self._properties['pool_pools']) == 0 or
pn in self._properties['pool_pools']]
free_capacity_gb = 0
total_capacity_gb = 0
for pool in pools:
# Convert to GB.
tmp_total = int(pool['total'] // units.Gi)
tmp_free = int(pool['free'] // units.Gi)
if free_capacity_gb < tmp_free:
total_capacity_gb = tmp_total
free_capacity_gb = tmp_free
return {'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb}
def set_backend_max_ld_count(self, xml, root):
section = root.xpath('./CMD_REQUEST')[0]
version = section.get('version').replace('Version ', '')[0:3]
version = float(version)
if version < 9.1:
if 512 < self._properties['ld_backend_max_count']:
self._properties['ld_backend_max_count'] = 512
else:
if 1024 < self._properties['ld_backend_max_count']:
self._properties['ld_backend_max_count'] = 1024
def get_diskarray_max_ld_count(self, xml, root):
max_ld_count = 0
for section in root.xpath(
'./'
'CMD_REQUEST/'
'CHAPTER[@name="Disk Array"]/'
'OBJECT[@name="Disk Array"]/'
'SECTION[@name="Disk Array Detail Information"]'):
unit = section.find('./UNIT[@name="Product ID"]')
if unit is None:
msg = (_('UNIT[@name="Product ID"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
product_id = unit.text
if product_id in self._properties['products']:
max_ld_count = self._properties['products'][product_id]
else:
max_ld_count = 8192
LOG.debug('UNIT[@name="Product ID"] unknown id. '
'productId=%s', product_id)
LOG.debug('UNIT[@name="Product ID"] max_ld_count=%d.',
max_ld_count)
return max_ld_count
def get_pool_config(self, xml, root):
pools = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Pool"]/'
'OBJECT[@name="Pool"]'):
section = xmlobj.find('./SECTION[@name="Pool Detail Information"]')
if section is None:
msg = (_('SECTION[@name="Pool Detail Information"] '
'not found. line=%(line)d out="%(out)s"') %
{'line': xmlobj.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
unit = section.find('./UNIT[@name="Pool No.(h)"]')
if unit is None:
msg = (_('UNIT[@name="Pool No.(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
pool_num = int(unit.text, 16)
unit = section.find('UNIT[@name="Pool Capacity"]')
if unit is None:
msg = (_('UNIT[@name="Pool Capacity"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
total = int(unit.text, 10)
unit = section.find('UNIT[@name="Free Pool Capacity"]')
if unit is None:
msg = (_('UNIT[@name="Free Pool Capacity"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
free = int(unit.text, 10)
if self._properties['pool_actual_free_capacity']:
unit = section.find('UNIT[@name="Used Pool Capacity"]')
if unit is None:
msg = (_('UNIT[@name="Used Pool Capacity"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
used = int(unit.text, 10)
for section in xmlobj.xpath('./SECTION[@name='
'"Virtual Capacity Pool '
'Information"]'):
unit = section.find('UNIT[@name="Actual Capacity"]')
if unit is None:
msg = (_('UNIT[@name="Actual Capacity"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
total = int(unit.text, 10)
free = total - used
pool = {'pool_num': pool_num,
'total': total,
'free': free,
'ld_list': []}
pools[pool_num] = pool
return pools
def get_ld_config(self, xml, root, pools):
lds = {}
used_ldns = []
for section in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Logical Disk"]/'
'OBJECT[@name="Logical Disk"]/'
'SECTION[@name="LD Detail Information"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LDN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="OS Type"]')
if unit is None:
msg = (_('UNIT[@name="OS Type"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ostype = unit.text if unit.text is not None else ''
unit = section.find('./UNIT[@name="LD Name"]')
if unit is None:
msg = (_('UNIT[@name="LD Name"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldname = ostype + ':' + unit.text
unit = section.find('./UNIT[@name="Pool No.(h)"]')
if unit is None:
msg = (_('UNIT[@name="Pool No.(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
pool_num = int(unit.text, 16)
unit = section.find('./UNIT[@name="LD Capacity"]')
if unit is None:
msg = (_('UNIT[@name="LD Capacity"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# byte capacity transform GB capacity.
ld_capacity = int(unit.text, 10) // units.Gi
unit = section.find('./UNIT[@name="RPL Attribute"]')
if unit is None:
msg = (_('UNIT[@name="RPL Attribute"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
rplatr = unit.text
unit = section.find('./UNIT[@name="Purpose"]')
if unit is None:
msg = (_('UNIT[@name="Purpose"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
purpose = unit.text
ld = {'ldname': ldname,
'ldn': ldn,
'pool_num': pool_num,
'ld_capacity': ld_capacity,
'RPL Attribute': rplatr,
'Purpose': purpose}
pools[pool_num]['ld_list'].append(ld)
lds[ldname] = ld
used_ldns.append(ldn)
return lds, used_ldns
def get_iscsi_ldset_config(self, xml, root):
ldsets = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Access Control"]/'
'OBJECT[@name="LD Set(iSCSI)"]'):
ldsetlds = {}
portals = []
initiators = []
for unit in xmlobj.xpath('./SECTION[@name="Portal"]/'
'UNIT[@name="Portal"]'):
if not unit.text.startswith('0.0.0.0:'):
portals.append(unit.text)
for unit in xmlobj.xpath('./SECTION[@name="Initiator List"]/'
'UNIT[@name="Initiator List"]'):
initiators.append(unit.text)
section = xmlobj.find('./SECTION[@name="LD Set(iSCSI)'
' Information"]')
if section is None:
return ldsets
unit = section.find('./UNIT[@name="Platform"]')
if unit is None:
msg = (_('UNIT[@name="Platform"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
platform = unit.text
unit = section.find('./UNIT[@name="LD Set Name"]')
if unit is None:
msg = (_('UNIT[@name="LD Set Name"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldsetname = platform + ':' + unit.text
unit = section.find('./UNIT[@name="Target Mode"]')
if unit is None:
msg = (_('UNIT[@name="Target Mode"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
tmode = unit.text
if tmode == 'Normal':
unit = section.find('./UNIT[@name="Target Name"]')
if unit is None:
msg = (_('UNIT[@name="Target Name"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
iqn = unit.text
for section in xmlobj.xpath('./SECTION[@name="LUN/LD List"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LDN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LUN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun,
'iqn': iqn}
ldsetlds[ldn] = ld
elif tmode == 'Multi-Target':
for section in xmlobj.xpath('./SECTION[@name='
'"Target Information For '
'Multi-Target Mode"]'):
unit = section.find('./UNIT[@name="Target Name"]')
if unit is None:
msg = (_('UNIT[@name="Target Name"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
iqn = unit.text
unit = section.find('./UNIT[@name="LDN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LDN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if unit.text.startswith('-'):
continue
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LUN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if unit.text.startswith('-'):
continue
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun,
'iqn': iqn}
ldsetlds[ldn] = ld
else:
LOG.debug('`%(mode)s` Unknown Target Mode. '
'line=%(line)d out="%(out)s"',
{'mode': tmode, 'line': unit.sourceline, 'out': xml})
ldset = {'ldsetname': ldsetname,
'protocol': 'iSCSI',
'portal_list': portals,
'lds': ldsetlds,
'initiator_list': initiators}
ldsets[ldsetname] = ldset
return ldsets
def get_fc_ldset_config(self, xml, root):
ldsets = {}
for xmlobj in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Access Control"]/'
'OBJECT[@name="LD Set(FC)"]'):
ldsetlds = {}
section = xmlobj.find('./SECTION[@name="LD Set(FC)'
' Information"]')
if section is None:
return ldsets
unit = section.find('./UNIT[@name="Platform"]')
if unit is None:
msg = (_('UNIT[@name="Platform"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
platform = unit.text
unit = section.find('./UNIT[@name="LD Set Name"]')
if unit is None:
msg = (_('UNIT[@name="LD Set Name"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldsetname = platform + ':' + unit.text
wwpns = []
ports = []
for section in xmlobj.xpath('./SECTION[@name="Path List"]'):
unit = section.find('./UNIT[@name="Path"]')
if unit is None:
msg = (_('UNIT[@name="Path"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if unit.text.find('(') != -1:
ports.append(unit.text)
else:
wwpns.append(unit.text)
for section in xmlobj.xpath('./SECTION[@name="LUN/LD List"]'):
unit = section.find('./UNIT[@name="LDN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LDN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
ldn = int(unit.text, 16)
unit = section.find('./UNIT[@name="LUN(h)"]')
if unit is None:
msg = (_('UNIT[@name="LUN(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
lun = int(unit.text, 16)
ld = {'ldn': ldn,
'lun': lun}
ldsetlds[ldn] = ld
ldset = {'ldsetname': ldsetname,
'lds': ldsetlds,
'protocol': 'FC',
'wwpn': wwpns,
'port': ports}
ldsets[ldsetname] = ldset
return ldsets
def get_hostport_config(self, xml, root):
hostports = {}
for section in root.xpath('./'
'CMD_REQUEST/'
'CHAPTER[@name="Controller"]/'
'OBJECT[@name="Host Port"]/'
'SECTION[@name="Host Director'
'/Host Port Information"]'):
unit = section.find('./UNIT[@name="Port No.(h)"]')
if unit is None:
msg = (_('UNIT[@name="Port No.(h)"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
units = unit.text.split('-')
director = int(units[0], 16)
port = int(units[1], 16)
unit = section.find('./UNIT[@name="IP Address"]')
if unit is None:
unit = section.find('./UNIT[@name="WWPN"]')
if unit is None:
msg = (_('UNIT[@name="WWPN"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
wwpn = unit.text
hostport = {
'director': director,
'port': port,
'wwpn': wwpn,
'protocol': 'FC',
}
else:
ip = unit.text
if ip == '0.0.0.0':
continue
# Port Link Status check Start.
unit = section.find('./UNIT[@name="Link Status"]')
if unit is None:
msg = (_('UNIT[@name="Link Status"] not found. '
'line=%(line)d out="%(out)s"') %
{'line': section.sourceline, 'out': xml})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
linkstatus = unit.text
if linkstatus == 'Link Down':
continue
hostport = {
'director': director,
'port': port,
'ip': ip,
'protocol': 'iSCSI',
}
if director not in hostports:
hostports[director] = []
hostports[director].append(hostport)
return hostports
def configs(self, xml):
root = etree.fromstring(xml)
pools = self.get_pool_config(xml, root)
lds, used_ldns = self.get_ld_config(xml, root, pools)
iscsi_ldsets = self.get_iscsi_ldset_config(xml, root)
fc_ldsets = self.get_fc_ldset_config(xml, root)
hostports = self.get_hostport_config(xml, root)
diskarray_max_ld_count = self.get_diskarray_max_ld_count(xml, root)
self.set_backend_max_ld_count(xml, root)
ldsets = {}
ldsets.update(iscsi_ldsets)
ldsets.update(fc_ldsets)
return pools, lds, ldsets, used_ldns, hostports, diskarray_max_ld_count
def get_xml(self):
ismview_path = self._properties['ismview_path']
if os.path.exists(ismview_path) and os.path.isfile(ismview_path):
with open(ismview_path, 'r') as f:
xml = f.read()
LOG.debug('loaded from %s.', ismview_path)
else:
xml = self._cli.view_all(ismview_path, False, False)
return xml
def parse_xml(self):
try:
xml = self.get_xml()
return self.configs(xml)
except Exception:
LOG.debug('parse_xml Unexpected error. exception=%s',
traceback.format_exc())
xml = self._cli.view_all(self._properties['ismview_path'], False)
return self.configs(xml)
def get_volume_type_qos_specs(self, volume):
specs = {}
ctxt = context.get_admin_context()
type_id = volume['volume_type_id']
if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id)
qos_specs_id = volume_type.get('qos_specs_id')
if qos_specs_id is not None:
specs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
LOG.debug('get_volume_type_qos_specs '
'volume_type=%(volume_type)s, '
'qos_specs_id=%(qos_spec_id)s '
'specs=%(specs)s',
{'volume_type': volume_type,
'qos_spec_id': qos_specs_id,
'specs': specs})
return specs
def check_io_parameter(self, specs):
if ('upperlimit' not in specs and
'lowerlimit' not in specs and
'upperreport' not in specs):
specs['upperlimit'] = None
specs['lowerlimit'] = None
specs['upperreport'] = None
LOG.debug('qos parameter not found.')
else:
if ('upperlimit' in specs) and (specs['upperlimit'] is not None):
if self.validates_number(specs['upperlimit']) is True:
upper_limit = int(specs['upperlimit'], 10)
if ((upper_limit != 0) and
((upper_limit < 10) or (upper_limit > 1000000))):
raise exception.InvalidConfigurationValue(
value=upper_limit, option='upperlimit')
else:
raise exception.InvalidConfigurationValue(
value=specs['upperlimit'], option='upperlimit')
else:
specs['upperlimit'] = None
if ('lowerlimit' in specs) and (specs['lowerlimit'] is not None):
if self.validates_number(specs['lowerlimit']) is True:
lower_limit = int(specs['lowerlimit'], 10)
if (lower_limit != 0 and (lower_limit < 10 or
lower_limit > 1000000)):
raise exception.InvalidConfigurationValue(
value=lower_limit, option='lowerlimit')
else:
raise exception.InvalidConfigurationValue(
value=specs['lowerlimit'], option='lowerlimit')
else:
specs['lowerlimit'] = None
if 'upperreport' in specs:
if specs['upperreport'] not in ['on', 'off']:
LOG.debug('Illegal arguments. '
'upperreport is not on or off.'
'upperreport=%s' % specs['upperreport'])
specs['upperreport'] = None
else:
specs['upperreport'] = None
def validates_number(self, value):
return re.match(r'^(?![-+]0+$)[-+]?([1-9][0-9]*)?[0-9](\.[0-9]+)?$',
'%s' % value) and True or False

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
---
features:
- Added backend drivers for NEC Storage.(FC/iSCSI)