From d7701837493590faf91f97b169e3ed46d44f07f9 Mon Sep 17 00:00:00 2001 From: Wilson Liu Date: Fri, 30 Oct 2015 12:43:43 +0800 Subject: [PATCH] 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 --- cinder/tests/unit/test_huawei_drivers.py | 307 +++++++++++++----- cinder/volume/drivers/huawei/constants.py | 3 + cinder/volume/drivers/huawei/huawei_driver.py | 285 ++++++++++++++-- cinder/volume/drivers/huawei/rest_client.py | 61 +++- ...upport-manage-volume-2a746cd05621423d.yaml | 2 + 5 files changed, 544 insertions(+), 114 deletions(-) create mode 100644 releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py index 8f6f8e692..94fa82106 100644 --- a/cinder/tests/unit/test_huawei_drivers.py +++ b/cinder/tests/unit/test_huawei_drivers.py @@ -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) diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py index 764c0f734..eb64b24e3 100644 --- a/cinder/volume/drivers/huawei/constants.py +++ b/cinder/volume/drivers/huawei/constants.py @@ -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', diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py index c940fe93b..3d5497a2a 100644 --- a/cinder/volume/drivers/huawei/huawei_driver.py +++ b/cinder/volume/drivers/huawei/huawei_driver.py @@ -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) diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py index 4541c3bc5..90e7b8241 100644 --- a/cinder/volume/drivers/huawei/rest_client.py +++ b/cinder/volume/drivers/huawei/rest_client.py @@ -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 diff --git a/releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml b/releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml new file mode 100644 index 000000000..450f0a07d --- /dev/null +++ b/releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml @@ -0,0 +1,2 @@ +features: + - Add manage/unmanage volume support for Huawei drivers. -- 2.45.2