]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Huawei: Add manage/unmanage volume support
authorWilson Liu <liuxinguo@huawei.com>
Fri, 30 Oct 2015 04:43:43 +0000 (12:43 +0800)
committerWilson Liu <liuxinguo@huawei.com>
Wed, 13 Jan 2016 03:55:28 +0000 (11:55 +0800)
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
cinder/volume/drivers/huawei/constants.py
cinder/volume/drivers/huawei/huawei_driver.py
cinder/volume/drivers/huawei/rest_client.py
releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml [new file with mode: 0644]

index 8f6f8e69214e1d48e2bc445378230ecdb2efae0f..94fa82106498bcdfa252df3d50247967fadf16c8 100644 (file)
 #    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)
index 764c0f73411a2f94c3df53d7ae85752be8b219d4..eb64b24e310d07f574ae487ec17cb902d993fec6 100644 (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',
index c940fe93b8a4e69375df86dba5e72fde76032db7..3d5497a2ab410a9f428f90e0dfa4f91dff5cdd38 100644 (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)
index 4541c3bc510f46402b37408564602492e5e832c3..90e7b82419bf87d1d5ee035a78163425cad286f9 100644 (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
diff --git a/releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml b/releasenotes/notes/huawei-support-manage-volume-2a746cd05621423d.yaml
new file mode 100644 (file)
index 0000000..450f0a0
--- /dev/null
@@ -0,0 +1,2 @@
+features:
+  - Add manage/unmanage volume support for Huawei drivers.