Huawei: Add manage/unmanage volume support

Add manage/unmanage volume support for Huawei
drivers. Also implement the required
manage_existing_get_size function.

DocImpact
Implements: blueprint huawei-support-manage-volume
Change-Id: I964d49b9979b710bca445c2d209099dcea64d3da
This commit is contained in:
Wilson Liu 2015-10-30 12:43:43 +08:00
parent 49507b61cb
commit d770183749
5 changed files with 544 additions and 114 deletions

View File

@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Tests for huawei drivers."""
import ddt
import json
import mock
import os
import re
import shutil
import tempfile
import time
@ -1363,9 +1365,6 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?range=[0-100]&PARENTID=1/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?PARENTTYPE=21&PARENTID=1/GET'] = (
FAKE_GET_FC_PORT_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/system/'] = (
FAKE_SYSTEM_VERSION_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/SMARTCACHEPARTITION/0/GET'] = (
FAKE_SMARTCACHEPARTITION_RESPONSE)
@ -1390,6 +1389,12 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/disable_hcpair/PUT'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair?range=[0-100]/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/splitmirror?range=[0-100]/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
def Fake_sleep(time):
pass
@ -1499,6 +1504,7 @@ class FakeFCStorage(huawei_driver.HuaweiFCDriver):
self.restclient = FakeClient(configuration=self.configuration)
@ddt.ddt
class HuaweiISCSIDriverTestCase(test.TestCase):
def setUp(self):
@ -1516,6 +1522,7 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
driver = FakeISCSIStorage(configuration=self.configuration)
self.driver = driver
self.driver.do_setup()
self.device_id = self.driver.restclient.login()
self.portgroup = 'portgroup-test'
self.iscsi_iqns = ['iqn.2006-08.com.huawei:oceanstor:21000022a:'
':20503:192.168.1.1',
@ -1526,12 +1533,9 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.portgroup_id = 11
def test_login_success(self):
device_id = self.driver.restclient.login()
self.assertEqual('210235G7J20000000000', device_id)
self.assertEqual('210235G7J20000000000', self.device_id)
def test_create_volume_success(self):
self.driver.restclient.login()
# Have pool info in the volume.
test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
'size': 2,
@ -1567,12 +1571,10 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual('1', lun_info['provider_location'])
def test_delete_volume_success(self):
self.driver.restclient.login()
delete_flag = self.driver.delete_volume(test_volume)
self.assertTrue(delete_flag)
def test_create_snapshot_success(self):
self.driver.restclient.login()
lun_info = self.driver.create_snapshot(test_snap)
self.assertEqual(11, lun_info['provider_location'])
@ -1585,35 +1587,29 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual(11, lun_info['provider_location'])
def test_delete_snapshot_success(self):
self.driver.restclient.login()
delete_flag = self.driver.delete_snapshot(test_snap)
self.assertTrue(delete_flag)
def test_create_volume_from_snapsuccess(self):
self.driver.restclient.login()
lun_info = self.driver.create_volume_from_snapshot(test_volume,
test_volume)
self.assertEqual('1', lun_info['ID'])
def test_initialize_connection_success(self):
self.driver.restclient.login()
iscsi_properties = self.driver.initialize_connection(test_volume,
FakeConnector)
self.assertEqual(1, iscsi_properties['data']['target_lun'])
def test_terminate_connection_success(self):
self.driver.restclient.login()
self.driver.restclient.terminateFlag = True
self.driver.terminate_connection(test_volume, FakeConnector)
self.assertTrue(self.driver.restclient.terminateFlag)
def test_get_volume_status(self):
self.driver.restclient.login()
data = self.driver.get_volume_stats()
self.assertEqual('2.0.0', data['driver_version'])
self.assertEqual('2.0.1', data['driver_version'])
def test_extend_volume(self):
self.driver.restclient.login()
lun_info = self.driver.extend_volume(test_volume, 3)
self.assertEqual('1', lun_info['provider_location'])
@ -1623,31 +1619,26 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.driver.restclient.login)
def test_create_snapshot_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, test_snap)
def test_create_volume_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, test_volume)
def test_delete_volume_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
delete_flag = self.driver.delete_volume(test_volume)
self.assertTrue(delete_flag)
def test_delete_snapshot_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
delete_flag = self.driver.delete_volume(test_snap)
self.assertTrue(delete_flag)
def test_initialize_connection_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
@ -1664,14 +1655,12 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual(2, result)
def test_lun_is_associated_to_lungroup(self):
self.driver.restclient.login()
self.driver.restclient.associate_lun_to_lungroup('11', '11')
result = self.driver.restclient._is_lun_associated_to_lungroup('11',
'11')
self.assertTrue(result)
def test_lun_is_not_associated_to_lun_group(self):
self.driver.restclient.login()
self.driver.restclient.associate_lun_to_lungroup('12', '12')
self.driver.restclient.remove_lun_from_lungroup('12', '12')
result = self.driver.restclient._is_lun_associated_to_lungroup('12',
@ -1679,13 +1668,11 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertFalse(result)
def test_get_tgtip(self):
self.driver.restclient.login()
portg_id = self.driver.restclient.find_tgt_port_group(self.portgroup)
target_ip = self.driver.restclient._get_tgt_ip_from_portgroup(portg_id)
self.assertEqual(self.target_ips, target_ip)
def test_get_iscsi_params(self):
self.driver.restclient.login()
(iscsi_iqns, target_ips, portgroup_id) = (
self.driver.restclient.get_iscsi_params(self.xml_file_path,
FakeConnector))
@ -1694,7 +1681,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual(self.portgroup_id, portgroup_id)
def test_get_lun_conf_params(self):
self.driver.restclient.login()
luninfo = huawei_utils.get_lun_conf_params(self.xml_file_path)
luninfo['pool_id'] = '0'
luninfo['volume_size'] = 2
@ -1704,25 +1690,21 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual('5mFHcBv4RkCcD+JyrWc0SA', luninfo['NAME'])
def tset_get_iscsi_conf(self):
self.driver.restclient.login()
iscsiinfo = huawei_utils.get_iscsi_conf(self.xml_file_path)
self.assertEqual('iqn.1993-08.debian:01:ec2bff7ac3a3',
iscsiinfo['Initiator'])
def test_check_conf_file(self):
self.driver.restclient.login()
self.driver.restclient.checkFlag = True
huawei_utils.check_conf_file(self.xml_file_path)
self.assertTrue(self.driver.restclient.checkFlag)
def test_get_conf_host_os_type(self):
self.driver.restclient.login()
host_os = huawei_utils.get_conf_host_os_type('100.97.10.30',
self.configuration)
self.assertEqual('0', host_os)
def test_find_chap_info(self):
self.driver.restclient.login()
tmp_dict = {}
iscsi_info = {}
tmp_dict['Name'] = 'iqn.1993-08.debian:01:ec2bff7ac3a3'
@ -1737,7 +1719,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual('mm-user@storage', chap_password)
def test_find_alua_info(self):
self.driver.restclient.login()
tmp_dict = {}
iscsi_info = {}
tmp_dict['Name'] = 'iqn.1993-08.debian:01:ec2bff7ac3a3'
@ -1750,7 +1731,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual('1', type)
def test_find_pool_info(self):
self.driver.restclient.login()
pools = {
"error": {"code": 0},
"data": [{
@ -1791,7 +1771,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual(test_info, pool_info)
def test_get_smartx_specs_opts(self):
self.driver.restclient.login()
smartx_opts = smartx.SmartX().get_smartx_specs_opts(smarttier_opts)
self.assertEqual('3', smartx_opts['policy'])
@ -1799,7 +1778,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
return_value={'MAXIOPS': '100',
'IOType': '2'})
def test_create_smartqos(self, mock_qos_value):
self.driver.restclient.login()
lun_info = self.driver.create_volume(test_volume)
self.assertEqual('1', lun_info['provider_location'])
@ -1814,12 +1792,10 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
'cachename': 'cache-test',
'partitionname': 'partition-test'})
def test_creat_smartx(self, mock_volume_types, mock_add_lun_to_partition):
self.driver.restclient.login()
lun_info = self.driver.create_volume(test_volume)
self.assertEqual('1', lun_info['provider_location'])
def test_find_available_qos(self):
self.driver.restclient.login()
qos = {'MAXIOPS': '100', 'IOType': '2'}
fake_qos_info_response_equal = {
"error": {
@ -1890,7 +1866,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
mock_all_pool_info,
mock_login_return,
mock_hypermetro_opts):
self.driver.restclient.login()
metadata = {"hypermetro_id": '11',
"remote_lun_id": '1'}
lun_info = self.driver.create_volume(hyper_volume)
@ -1927,7 +1902,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
mock_all_pool_info,
mock_login_return,
mock_hypermetro_opts):
self.driver.restclient.login()
mock_hyper_pair_info.side_effect = exception.VolumeBackendAPIException(
data='Create hypermetro error.')
self.assertRaises(exception.VolumeBackendAPIException,
@ -1956,7 +1930,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
mock_check_hyermetro,
mock_lun_exit,
mock_login_info):
self.driver.restclient.login()
result = self.driver.delete_volume(hyper_volume)
mock_logout.assert_called_with()
self.assertTrue(result)
@ -1981,7 +1954,6 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
mock_check_hyermetro,
mock_lun_exit,
mock_login_info):
self.driver.restclient.login()
mock_delete_hypermetro.side_effect = (
exception.VolumeBackendAPIException(data='Delete hypermetro '
'error.'))
@ -1989,6 +1961,221 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.driver.delete_volume, hyper_volume)
mock_delete_lun.assert_called_with('11')
def test_manage_existing_get_size_invalid_reference(self):
# Can't find LUN by source-name.
external_ref = {'source-name': 'LUN1'}
with mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value=None):
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size,
test_volume, external_ref)
self.assertIsNotNone(re.search('please check the source-name '
'or source-id', ex.msg))
# Can't find LUN by source-id.
external_ref = {'source-id': 'ID1'}
with mock.patch.object(rest_client.RestClient, 'get_lun_info') as m_gt:
m_gt.side_effect = exception.VolumeBackendAPIException(
data='Error')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.manage_existing_get_size,
test_volume, external_ref)
self.assertIsNotNone(re.search('please check the source-name '
'or source-id', ex.msg))
def test_manage_existing_get_size_improper_lunsize(self):
# LUN size is not multiple of 1 GB.
external_ref = {'source-id': 'ID1'}
with mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097150}):
ex = self.assertRaises(exception.VolumeBackendAPIException,
self.driver.manage_existing_get_size,
test_volume, external_ref)
self.assertIsNotNone(
re.search('Volume size must be multiple of 1 GB', ex.msg))
@ddt.data({'source-id': 'ID1'}, {'source-name': 'LUN1'},
{'source-name': 'LUN1', 'source-id': 'ID1'})
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_get_size_success(self, mock_get_volume_by_name,
mock_get_lun_info, external_ref):
size = self.driver.manage_existing_get_size(test_volume,
external_ref)
self.assertEqual(1, size)
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001'})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_pool_mismatch(self, mock_get_by_name,
mock_get_info):
# LUN does not belong to the specified pool.
with mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_lun_by_ref',
return_value={'PARENTNAME': 'StoragePool001'}):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool002',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf'}
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('The specified LUN does not belong'
' to the given pool', ex.msg))
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001'})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_lun_abnormal(self, mock_get_by_name,
mock_get_info):
# Has snapshot.
ret = {'PARENTNAME': "StoragePool001",
'HEALTHSTATUS': '2'}
with mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_lun_by_ref',
return_value=ret):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf'}
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('LUN status is not normal', ex.msg))
@mock.patch.object(rest_client.RestClient, 'get_hypermetro_pairs',
return_value=[{'LOCALOBJID': 'ID1'}])
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001',
'HEALTHSTATUS': constants.STATUS_HEALTH})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_with_hypermetro(self, mock_get_by_name,
mock_get_info,
mock_get_hyper_pairs):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf'}
# Exists in a HyperMetroPair.
with mock.patch.object(rest_client.RestClient,
'get_hypermetro_pairs',
return_value=[{'LOCALOBJID': 'ID1'}]):
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('HyperMetroPair', ex.msg))
@ddt.data([[{'PRILUNID': 'ID1'}], []],
[[{'PRILUNID': 'ID2'}], ['ID1', 'ID2']])
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001',
'HEALTHSTATUS': constants.STATUS_HEALTH})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_with_splitmirror(self, ddt_data, mock_get_by_name,
mock_get_info):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf',
'id': '21ec7341-9256-497b-97d9-ef48edcf'}
# Exists in a SplitMirror.
with mock.patch.object(rest_client.RestClient, 'get_split_mirrors',
return_value=ddt_data[0]), \
mock.patch.object(rest_client.RestClient, 'get_target_luns',
return_value=ddt_data[1]):
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('SplitMirror', ex.msg))
@ddt.data([{'PARENTID': 'ID1'}], [{'TARGETLUNID': 'ID1'}])
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001',
'HEALTHSTATUS': constants.STATUS_HEALTH})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_under_migration(self, ddt_data, mock_get_by_name,
mock_get_info):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf',
'id': '21ec7341-9256-497b-97d9-ef48edcf'}
# Exists in a migration task.
with mock.patch.object(rest_client.RestClient, 'get_migration_task',
return_value=ddt_data):
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('migration', ex.msg))
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ID': 'ID1',
'PARENTNAME': 'StoragePool001',
'SNAPSHOTIDS': [],
'EXPOSEDTOINITIATOR': 'false',
'ISADD2LUNGROUP': 'true',
'HEALTHSTATUS': constants.STATUS_HEALTH})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_with_lungroup(self, mock_get_by_name,
mock_get_info):
# Already in LUN group.
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf'}
external_ref = {'source-name': 'LUN1'}
ex = self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing,
test_volume, external_ref)
self.assertIsNotNone(re.search('Already exists in a LUN group',
ex.msg))
@ddt.data({'source-name': 'LUN1'}, {'source-id': 'ID1'})
@mock.patch.object(rest_client.RestClient, 'rename_lun')
@mock.patch.object(huawei_driver.HuaweiBaseDriver,
'_get_lun_by_ref',
return_value={'PARENTNAME': 'StoragePool001',
'SNAPSHOTIDS': [],
'EXPOSEDTOINITIATOR': 'false',
'ID': 'ID1',
'HEALTHSTATUS': constants.STATUS_HEALTH})
@mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={'CAPACITY': 2097152,
'ALLOCTYPE': 1})
@mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value='ID1')
def test_manage_existing_success(self, mock_get_by_name, mock_get_info,
mock_check_lun, mock_rename,
external_ref):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf'}
model_update = self.driver.manage_existing(test_volume,
external_ref)
self.assertEqual({'provider_location': 'ID1'}, model_update)
@ddt.data([None, 0], ['ID1', 1])
@mock.patch.object(rest_client.RestClient, 'rename_lun')
def test_unmanage(self, ddt_data, mock_rename):
test_volume = {'host': 'ubuntu-204@v3r3#StoragePool001',
'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
with mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
return_value=ddt_data[0]):
self.driver.unmanage(test_volume)
self.assertEqual(ddt_data[1], mock_rename.call_count)
def create_fake_conf_file(self):
"""Create a fake Config file.
@ -2099,23 +2286,20 @@ class HuaweiFCDriverTestCase(test.TestCase):
driver = FakeFCStorage(configuration=self.configuration)
self.driver = driver
self.driver.do_setup()
self.device_id = self.driver.restclient.login()
def test_login_success(self):
device_id = self.driver.restclient.login()
self.assertEqual('210235G7J20000000000', device_id)
self.assertEqual('210235G7J20000000000', self.device_id)
def test_create_volume_success(self):
self.driver.restclient.login()
lun_info = self.driver.create_volume(test_volume)
self.assertEqual('1', lun_info['provider_location'])
def test_delete_volume_success(self):
self.driver.restclient.login()
delete_flag = self.driver.delete_volume(test_volume)
self.assertTrue(delete_flag)
def test_create_snapshot_success(self):
self.driver.restclient.login()
lun_info = self.driver.create_snapshot(test_snap)
self.assertEqual(11, lun_info['provider_location'])
@ -2128,35 +2312,29 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(11, lun_info['provider_location'])
def test_delete_snapshot_success(self):
self.driver.restclient.login()
delete_flag = self.driver.delete_snapshot(test_snap)
self.assertTrue(delete_flag)
def test_create_volume_from_snapsuccess(self):
self.driver.restclient.login()
lun_info = self.driver.create_volume_from_snapshot(test_volume,
test_volume)
self.assertEqual('1', lun_info['ID'])
def test_initialize_connection_success(self):
self.driver.restclient.login()
iscsi_properties = self.driver.initialize_connection(test_volume,
FakeConnector)
self.assertEqual(1, iscsi_properties['data']['target_lun'])
def test_terminate_connection_success(self):
self.driver.restclient.login()
self.driver.restclient.terminateFlag = True
self.driver.terminate_connection(test_volume, FakeConnector)
self.assertTrue(self.driver.restclient.terminateFlag)
def test_get_volume_status(self):
self.driver.restclient.login()
data = self.driver.get_volume_stats()
self.assertEqual('2.0.0', data['driver_version'])
self.assertEqual('2.0.1', data['driver_version'])
def test_extend_volume(self):
self.driver.restclient.login()
lun_info = self.driver.extend_volume(test_volume, 3)
self.assertEqual('1', lun_info['provider_location'])
@ -2166,31 +2344,26 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.driver.restclient.login)
def test_create_snapshot_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, test_snap)
def test_create_volume_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, test_volume)
def test_delete_volume_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
delete_flag = self.driver.delete_volume(test_volume)
self.assertTrue(delete_flag)
def test_delete_snapshot_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
delete_flag = self.driver.delete_snapshot(test_snap)
self.assertTrue(delete_flag)
def test_initialize_connection_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
@ -2206,14 +2379,12 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(2, result)
def test_lun_is_associated_to_lungroup(self):
self.driver.restclient.login()
self.driver.restclient.associate_lun_to_lungroup('11', '11')
result = self.driver.restclient._is_lun_associated_to_lungroup('11',
'11')
self.assertTrue(result)
def test_lun_is_not_associated_to_lun_group(self):
self.driver.restclient.login()
self.driver.restclient.associate_lun_to_lungroup('12', '12')
self.driver.restclient.remove_lun_from_lungroup('12', '12')
result = self.driver.restclient._is_lun_associated_to_lungroup('12',
@ -2221,7 +2392,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertFalse(result)
def test_get_lun_conf_params(self):
self.driver.restclient.login()
luninfo = huawei_utils.get_lun_conf_params(self.xml_file_path)
luninfo['pool_id'] = '0'
luninfo['volume_size'] = 2
@ -2231,21 +2401,17 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual('5mFHcBv4RkCcD+JyrWc0SA', luninfo['NAME'])
def test_check_conf_file(self):
self.driver.restclient.login()
self.driver.restclient.checkFlag = True
huawei_utils.check_conf_file(self.xml_file_path)
self.assertTrue(self.driver.restclient.checkFlag)
def test_get_conf_host_os_type(self):
self.driver.restclient.login()
host_os = huawei_utils.get_conf_host_os_type('100.97.10.30',
self.configuration)
self.assertEqual('0', host_os)
@mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
def test_migrate_volume_success(self, mock_add_lun_to_partition):
self.driver.restclient.login()
# Migrate volume without new type.
model_update = None
moved = False
@ -2278,7 +2444,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(empty_dict, model_update)
def test_migrate_volume_fail(self):
self.driver.restclient.login()
self.driver.restclient.test_fail = True
# Migrate volume without new type.
@ -2301,7 +2466,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
test_volume, test_host, new_type)
def test_check_migration_valid(self):
self.driver.restclient.login()
is_valid = self.driver._check_migration_valid(test_host,
test_volume)
self.assertTrue(is_valid)
@ -2359,7 +2523,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
@mock.patch.object(rest_client.RestClient, 'rename_lun')
def test_update_migrated_volume_success(self, mock_rename_lun):
self.driver.restclient.login()
original_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
current_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0636'}
model_update = self.driver.update_migrated_volume(None,
@ -2370,7 +2533,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
@mock.patch.object(rest_client.RestClient, 'rename_lun')
def test_update_migrated_volume_fail(self, mock_rename_lun):
self.driver.restclient.login()
mock_rename_lun.side_effect = exception.VolumeBackendAPIException(
data='Error occurred.')
original_volume = {'id': '21ec7341-9256-497b-97d9-ef48edcf0635'}
@ -2385,28 +2547,24 @@ class HuaweiFCDriverTestCase(test.TestCase):
@mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
def test_retype_volume_success(self, mock_add_lun_to_partition):
self.driver.restclient.login()
retype = self.driver.retype(None, test_volume,
test_new_type, None, test_host)
self.assertTrue(retype)
def test_retype_volume_cache_fail(self):
self.driver.restclient.cache_not_exist = True
self.driver.restclient.login()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.retype, None,
test_volume, test_new_type, None, test_host)
def test_retype_volume_partition_fail(self):
self.driver.restclient.partition_not_exist = True
self.driver.restclient.login()
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.retype, None,
test_volume, test_new_type, None, test_host)
@mock.patch.object(rest_client.RestClient, 'add_lun_to_partition')
def test_retype_volume_fail(self, mock_add_lun_to_partition):
self.driver.restclient.login()
mock_add_lun_to_partition.side_effect = (
exception.VolumeBackendAPIException(data='Error occurred.'))
retype = self.driver.retype(None, test_volume,
@ -2414,7 +2572,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertFalse(retype)
def test_build_ini_targ_map(self):
self.driver.restclient.login()
fake_lookup_service = FCSanLookupService()
fake_lookup_service.get_device_mapping_from_network = mock.Mock(
return_value=fake_fabric_mapping)
@ -2430,7 +2587,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(ini_target_map, init_targ_map)
def test_filter_port_by_contr(self):
self.driver.restclient.login()
# Six ports in one fabric.
ports_in_fabric = ['1', '2', '3', '4', '5', '6']
# Ports 1,3,4,7 belonged to controller A
@ -2446,13 +2602,11 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(expected_filtered_ports, filtered_ports)
def test_multi_resturls_success(self):
self.driver.restclient.login()
self.driver.restclient.test_multi_url_flag = True
lun_info = self.driver.create_volume(test_volume)
self.assertEqual('1', lun_info['provider_location'])
def test_get_id_from_result(self):
self.driver.restclient.login()
result = {}
name = 'test_name'
key = 'NAME'
@ -2519,7 +2673,6 @@ class HuaweiFCDriverTestCase(test.TestCase):
mock_volume_ready,
mock_pair_info,
mock_logout):
self.driver.restclient.login()
metadata = {"hypermetro_id": '11',
"remote_lun_id": '1'}
lun_info = self.driver.create_volume(hyper_volume)

View File

@ -46,10 +46,13 @@ ERROR_VOLUME_NOT_EXIST = 1077939726
RELOGIN_ERROR_PASS = [ERROR_VOLUME_NOT_EXIST]
HYPERMETRO_RUNNSTATUS_STOP = 41
HYPERMETRO_RUNNSTATUS_NORMAL = 1
NO_SPLITMIRROR_LICENSE = 1077950233
NO_MIGRATION_LICENSE = 1073806606
THICK_LUNTYPE = 0
THIN_LUNTYPE = 1
MAX_HOSTNAME_LENGTH = 31
MAX_VOL_DESCRIPTION = 170
OS_TYPE = {'Linux': '0',
'Windows': '1',

View File

@ -14,6 +14,7 @@
# under the License.
import json
import re
import six
import uuid
@ -576,7 +577,6 @@ class HuaweiBaseDriver(driver.VolumeDriver):
'new_type': new_type,
'diff': diff,
'host': host})
# Check what changes are needed
migration, change_opts, lun_id = self.determine_changes_when_retype(
volume, new_type, host)
@ -668,45 +668,18 @@ class HuaweiBaseDriver(driver.VolumeDriver):
}
lun_info = self.restclient.get_lun_info(lun_id)
lun_opts['LUNType'] = int(lun_info['ALLOCTYPE'])
if lun_info['DATATRANSFERPOLICY']:
lun_opts['LUNType'] = int(lun_info.get('ALLOCTYPE'))
if lun_info.get('DATATRANSFERPOLICY'):
lun_opts['policy'] = lun_info['DATATRANSFERPOLICY']
if lun_info['SMARTCACHEPARTITIONID']:
if lun_info.get('SMARTCACHEPARTITIONID'):
lun_opts['cacheid'] = lun_info['SMARTCACHEPARTITIONID']
if lun_info['CACHEPARTITIONID']:
if lun_info.get('CACHEPARTITIONID'):
lun_opts['partitionid'] = lun_info['CACHEPARTITIONID']
return lun_opts
def determine_changes_when_retype(self, volume, new_type, host):
migration = False
change_opts = {
'policy': None,
'partitionid': None,
'cacheid': None,
'qos': None,
'host': None,
'LUNType': None,
}
lun_id = volume.get('provider_location')
old_opts = self.get_lun_specs(lun_id)
new_specs = new_type['extra_specs']
new_opts = huawei_utils._get_extra_spec_value(new_specs)
new_opts = smartx.SmartX().get_smartx_specs_opts(new_opts)
if 'LUNType' not in new_opts:
new_opts['LUNType'] = huawei_utils.find_luntype_in_xml(
self.xml_file_path)
if volume['host'] != host['host']:
migration = True
change_opts['host'] = (volume['host'], host['host'])
if old_opts['LUNType'] != new_opts['LUNType']:
migration = True
change_opts['LUNType'] = (old_opts['LUNType'], new_opts['LUNType'])
def _check_needed_changes(self, lun_id, old_opts, new_opts,
change_opts, new_type):
new_cache_id = None
new_cache_name = new_opts['cachename']
if new_cache_name:
@ -764,6 +737,40 @@ class HuaweiBaseDriver(driver.VolumeDriver):
if old_qos != new_qos:
change_opts['qos'] = ([old_qos_id, old_qos], new_qos)
return change_opts
def determine_changes_when_retype(self, volume, new_type, host):
migration = False
change_opts = {
'policy': None,
'partitionid': None,
'cacheid': None,
'qos': None,
'host': None,
'LUNType': None,
}
lun_id = volume.get('provider_location')
old_opts = self.get_lun_specs(lun_id)
new_specs = new_type['extra_specs']
new_opts = huawei_utils._get_extra_spec_value(new_specs)
new_opts = smartx.SmartX().get_smartx_specs_opts(new_opts)
if 'LUNType' not in new_opts:
new_opts['LUNType'] = huawei_utils.find_luntype_in_xml(
self.xml_file_path)
if volume['host'] != host['host']:
migration = True
change_opts['host'] = (volume['host'], host['host'])
if old_opts['LUNType'] != new_opts['LUNType']:
migration = True
change_opts['LUNType'] = (old_opts['LUNType'], new_opts['LUNType'])
change_opts = self._check_needed_changes(lun_id, old_opts, new_opts,
change_opts, new_type)
LOG.debug("Determine changes when retype. Migration: "
"%(migration)s, change_opts: %(change_opts)s.",
{'migration': migration, 'change_opts': change_opts})
@ -838,6 +845,210 @@ class HuaweiBaseDriver(driver.VolumeDriver):
self.restclient.delete_luncopy(luncopy_id)
def _check_lun_valid_for_manage(self, lun_info, external_ref):
lun_id = lun_info.get('ID')
# Check whether the LUN is Normal.
if lun_info.get('HEALTHSTATUS') != constants.STATUS_HEALTH:
msg = _("Can't import LUN %s to Cinder. LUN status is not "
"normal.") % lun_id
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a HyperMetroPair.
try:
hypermetro_pairs = self.restclient.get_hypermetro_pairs()
except exception.VolumeBackendAPIException:
msg = _("Failed to get HyperMetroPair.")
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
for pair in hypermetro_pairs:
if pair.get('LOCALOBJID') == lun_id:
msg = (_("Can't import LUN %s to Cinder. Already exists in a "
"HyperMetroPair.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a SplitMirror.
try:
split_mirrors = self.restclient.get_split_mirrors()
except exception.VolumeBackendAPIException as ex:
if re.search('License is unavailable', ex.msg):
# Can't check whether the LUN has SplitMirror with it,
# just pass the check and log it.
split_mirrors = []
LOG.warning(_LW('No license for SplitMirror.'))
else:
msg = _("Failed to get SplitMirror.")
raise exception.VolumeBackendAPIException(data=msg)
for mirror in split_mirrors:
try:
target_luns = self.restclient.get_target_luns(mirror.get('ID'))
except exception.VolumeBackendAPIException:
msg = _("Failed to get target LUN of SplitMirror.")
raise exception.VolumeBackendAPIException(data=msg)
if (mirror.get('PRILUNID') == lun_id) or (lun_id in target_luns):
msg = (_("Can't import LUN %s to Cinder. Already exists in a "
"SplitMirror.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a migration task.
try:
migration_tasks = self.restclient.get_migration_task()
except exception.VolumeBackendAPIException as ex:
if re.search('License is unavailable', ex.msg):
# Can't check whether the LUN has migration task with it,
# just pass the check and log it.
migration_tasks = []
LOG.warning(_LW('No license for migration.'))
else:
msg = _("Failed to get migration task.")
raise exception.VolumeBackendAPIException(data=msg)
for migration in migration_tasks:
if lun_id in (migration.get('PARENTID'),
migration.get('TARGETLUNID')):
msg = (_("Can't import LUN %s to Cinder. Already exists in a "
"migration task.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a LUN copy task.
lun_copy = lun_info.get('LUNCOPYIDS')
if lun_copy and lun_copy[1:-1]:
msg = (_("Can't import LUN %s to Cinder. Already exists in "
"a LUN copy task.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a remote replication task.
rmt_replication = lun_info.get('REMOTEREPLICATIONIDS')
if rmt_replication and rmt_replication[1:-1]:
msg = (_("Can't import LUN %s to Cinder. Already exists in "
"a remote replication task.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN exists in a LUN mirror.
if self.restclient.is_lun_in_mirror(lun_id):
msg = (_("Can't import LUN %s to Cinder. Already exists in "
"a LUN mirror.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check whether the LUN has already in LUN group.
if lun_info.get('ISADD2LUNGROUP') == 'true':
msg = (_("Can't import LUN %s to Cinder. Already exists in a LUN "
"group.") % lun_id)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
def manage_existing(self, volume, external_ref):
"""Manage an existing volume on the backend storage."""
# Check whether the LUN is belonged to the specified pool.
pool = volume_utils.extract_host(volume['host'], 'pool')
LOG.debug("Pool specified is: %s.", pool)
lun_info = self._get_lun_by_ref(external_ref)
lun_id = lun_info.get('ID')
description = lun_info.get('DESCRIPTION', '')
if len(description) <= (
constants.MAX_VOL_DESCRIPTION - len(volume['name']) - 1):
description = volume['name'] + ' ' + description
lun_pool = lun_info.get('PARENTNAME')
LOG.debug("Storage pool of existing LUN %(lun)s is %(pool)s.",
{"lun": lun_id, "pool": lun_pool})
if pool != lun_pool:
msg = (_("The specified LUN does not belong to the given "
"pool: %s.") % pool)
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
# Check other stuffs to determine whether this LUN can be imported.
self._check_lun_valid_for_manage(lun_info, external_ref)
type_id = volume.get('volume_type_id')
if type_id:
# Handle volume type if specified.
old_opts = self.get_lun_specs(lun_id)
volume_type = volume_types.get_volume_type(None, type_id)
new_specs = volume_type.get('extra_specs')
new_opts = huawei_utils._get_extra_spec_value(new_specs)
new_opts = smartx.SmartX().get_smartx_specs_opts(new_opts)
if ('LUNType' in new_opts and
old_opts['LUNType'] != new_opts['LUNType']):
msg = (_("Can't import LUN %(lun_id)s to Cinder. "
"LUN type mismatched.") % lun_id)
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
if volume_type:
change_opts = {'policy': None, 'partitionid': None,
'cacheid': None, 'qos': None}
change_opts = self._check_needed_changes(lun_id, old_opts,
new_opts, change_opts,
volume_type)
self.modify_lun(lun_id, change_opts)
# Rename the LUN to make it manageable for Cinder.
new_name = huawei_utils.encode_name(volume['id'])
LOG.debug("Rename LUN %(old_name)s to %(new_name)s.",
{'old_name': lun_info.get('NAME'),
'new_name': new_name})
self.restclient.rename_lun(lun_id, new_name, description)
return {'provider_location': lun_id}
def _get_lun_by_ref(self, external_ref):
LOG.debug("Get external_ref: %s", external_ref)
name = external_ref.get('source-name')
id = external_ref.get('source-id')
if not (name or id):
msg = _('Must specify source-name or source-id.')
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
lun_id = id or self.restclient.get_volume_by_name(name)
if not lun_id:
msg = _("Can't find LUN on the array, please check the "
"source-name or source-id.")
raise exception.ManageExistingInvalidReference(
existing_ref=external_ref, reason=msg)
lun_info = self.restclient.get_lun_info(lun_id)
return lun_info
def unmanage(self, volume):
"""Export Huawei volume from Cinder."""
volume_id = volume['id']
LOG.debug("Unmanage volume: %s.", volume_id)
lun_name = huawei_utils.encode_name(volume_id)
lun_id = self.restclient.get_volume_by_name(lun_name)
if not lun_id:
LOG.error(_LE("Can't find LUN on the array for volume: %s."),
volume_id)
return
new_name = 'unmged_' + lun_name
LOG.debug("Rename LUN %(lun_name)s to %(new_name)s.",
{'lun_name': lun_name,
'new_name': new_name})
try:
self.restclient.rename_lun(lun_id, new_name)
except Exception:
LOG.warning(_LW("Rename lun %(lun_id)s fails when "
"unmanaging volume %(volume)s."),
{"lun_id": lun_id, "volume": volume['id']})
def manage_existing_get_size(self, volume, external_ref):
"""Get the size of the existing volume."""
lun_info = self._get_lun_by_ref(external_ref)
size = float(lun_info.get('CAPACITY')) // constants.CAPACITY_UNIT
remainder = float(lun_info.get('CAPACITY')) % constants.CAPACITY_UNIT
if int(remainder) > 0:
msg = _("Volume size must be multiple of 1 GB.")
raise exception.VolumeBackendAPIException(data=msg)
return int(size)
class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
"""ISCSI driver for Huawei storage arrays.
@ -853,9 +1064,10 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
Volume migration support
Volume retype support
2.0.0 - Rename to HuaweiISCSIDriver
2.0.1 - Manage/unmanage volume support
"""
VERSION = "2.0.0"
VERSION = "2.0.1"
def __init__(self, *args, **kwargs):
super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
@ -1051,9 +1263,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
FC zone enhancement
Volume hypermetro support
2.0.0 - Rename to HuaweiFCDriver
2.0.1 - Manage/unmanage volume support
"""
VERSION = "2.0.0"
VERSION = "2.0.1"
def __init__(self, *args, **kwargs):
super(HuaweiFCDriver, self).__init__(*args, **kwargs)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ast
import json
import six
import socket
@ -1653,9 +1654,11 @@ class RestClient(object):
return initiators
def rename_lun(self, lun_id, new_name):
def rename_lun(self, lun_id, new_name, description=None):
url = "/lun/" + lun_id
data = json.dumps({"NAME": new_name})
if description:
data.update({"DESCRIPTION": description})
result = self.call(url, data, "PUT")
msg = _('Rename lun on array error.')
self._assert_rest_result(result, msg)
@ -1842,3 +1845,59 @@ class RestClient(object):
self._assert_rest_result(result, msg)
if 'data' in result:
return result["data"]["AVAILABLEHOSTLUNIDLIST"]
def get_hypermetro_pairs(self):
url = "/HyperMetroPair?range=[0-100]"
result = self.call(url, None, "GET")
msg = _('Get HyperMetroPair error.')
self._assert_rest_result(result, msg)
return result.get('data', [])
def get_split_mirrors(self):
url = "/splitmirror?range=[0-100]"
result = self.call(url, None, "GET")
if result['error']['code'] == constants.NO_SPLITMIRROR_LICENSE:
msg = _('License is unavailable.')
raise exception.VolumeBackendAPIException(data=msg)
msg = _('Get SplitMirror error.')
self._assert_rest_result(result, msg)
return result.get('data', [])
def get_target_luns(self, id):
url = ("/SPLITMIRRORTARGETLUN/targetLUN?TYPE=228&PARENTID=%s&"
"PARENTTYPE=220") % id
result = self.call(url, None, "GET")
msg = _('Get target LUN of SplitMirror error.')
self._assert_rest_result(result, msg)
target_luns = []
for item in result.get('data', []):
target_luns.append(item.get('ID'))
return target_luns
def get_migration_task(self):
url = "/LUN_MIGRATION?range=[0-100]"
result = self.call(url, None, "GET")
if result['error']['code'] == constants.NO_MIGRATION_LICENSE:
msg = _('License is unavailable.')
raise exception.VolumeBackendAPIException(data=msg)
msg = _('Get migration task error.')
self._assert_rest_result(result, msg)
return result.get('data', [])
def is_lun_in_mirror(self, lun_id):
url = "/lun?range=[0-65535]"
result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Get volume by name error.'))
if 'data' in result:
for item in result['data']:
rss_obj = item.get('HASRSSOBJECT')
if rss_obj:
rss_obj = ast.literal_eval(rss_obj)
if (item.get('ID') == lun_id and
rss_obj.get('LUNMirror') == 'TRUE'):
return True
return False

View File

@ -0,0 +1,2 @@
features:
- Add manage/unmanage volume support for Huawei drivers.