]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add hypermetro support for Huawei driver
authorLiu Xinguo <295988511@qq.com>
Wed, 15 Jul 2015 09:52:19 +0000 (17:52 +0800)
committerWilson Liu <liuxinguo@huawei.com>
Tue, 20 Oct 2015 01:48:26 +0000 (09:48 +0800)
Hypermetro is a key storage feature and a requirement for
features such as high-availability and disaster recovery of
applications running on OpenStack clouds. This patch will
add initial support for volume hypermetro in Huawei driver.

DocImpact
Implements: blueprint support-hypermetro-for-huawei-volume-driver
Change-Id: Ibbdecccafd63a11a28c3ab12893756f23c38082a

cinder/tests/unit/test_huawei_drivers.py
cinder/volume/drivers/huawei/constants.py
cinder/volume/drivers/huawei/huawei_driver.py
cinder/volume/drivers/huawei/huawei_utils.py
cinder/volume/drivers/huawei/hypermetro.py [new file with mode: 0644]
cinder/volume/drivers/huawei/rest_client.py

index e79426891736c2d95c5772eb7ee8db4556c2d5df..886fff92d8180410436e809fb6d0c9a5326b7bf6 100644 (file)
@@ -30,11 +30,24 @@ from cinder.volume.drivers.huawei import constants
 from cinder.volume.drivers.huawei import fc_zone_helper
 from cinder.volume.drivers.huawei import huawei_driver
 from cinder.volume.drivers.huawei import huawei_utils
+from cinder.volume.drivers.huawei import hypermetro
 from cinder.volume.drivers.huawei import rest_client
 from cinder.volume.drivers.huawei import smartx
 
 LOG = logging.getLogger(__name__)
 
+hypermetro_devices = """{
+    "remote_device": {
+        "RestURL": "http://100.115.10.69:8082/deviceManager/rest",
+        "UserName": "admin",
+        "UserPassword": "Admin@storage1",
+        "StoragePool": "StoragePool001",
+        "domain_name": "hypermetro-domain",
+        "remote_target_ip": "111.111.101.241"
+    }
+}
+"""
+
 test_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
                'size': 2,
                'volume_name': 'vol1',
@@ -59,6 +72,32 @@ fake_smartx_value = {'smarttier': 'true',
                      'partitionname': 'partition-test',
                      }
 
+fake_hypermetro_opts = {'hypermetro': 'true',
+                        'smarttier': False,
+                        'smartcache': False,
+                        'smartpartition': False,
+                        'thin_provisioning_support': False,
+                        'thick_provisioning_support': False,
+                        }
+
+hyper_volume = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
+                'size': 2,
+                'volume_name': 'vol1',
+                'id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+                'volume_id': '21ec7341-9256-497b-97d9-ef48edcf0635',
+                'provider_auth': None,
+                'project_id': 'project',
+                'display_name': 'vol1',
+                'display_description': 'test volume',
+                'volume_type_id': None,
+                'host': 'ubuntu@huawei#OpenStack_Pool',
+                'provider_location': '11',
+                'volume_metadata': [{'key': 'hypermetro_id',
+                                     'value': '1'},
+                                    {'key': 'remote_lun_id',
+                                     'value': '11'}],
+                }
+
 test_snap = {'name': 'volume-21ec7341-9256-497b-97d9-ef48edcf0635',
              'size': 1,
              'volume_name': 'vol1',
@@ -118,6 +157,24 @@ test_new_type = {
     'description': None,
 }
 
+hypermetro_devices = """
+{
+    "remote_device": {
+        "RestURL": "http://100.115.10.69:8082/deviceManager/rest",
+        "UserName":"admin",
+        "UserPassword":"Admin@storage2",
+        "StoragePool":"StoragePool001",
+        "domain_name":"hypermetro_test"}
+}
+"""
+
+FAKE_FIND_POOL_RESPONSE = {'CAPACITY': '985661440',
+                           'ID': '0',
+                           'TOTALCAPACITY': '985661440'}
+
+FAKE_CREATE_VOLUME_RESPONSE = {"ID": "1",
+                               "NAME": "5mFHcBv4RkCcD+JyrWc0SA"}
+
 FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
                  'wwpns': ['10000090fa0d6754'],
                  'wwnns': ['10000090fa0d6755'],
@@ -265,7 +322,7 @@ FAKE_QUERY_ALL_LUN_RESPONSE = """
         "code": 0
     },
     "data": [{
-        "ID": "11",
+        "ID": "1",
         "NAME": "IexzQZJWSXuX2e9I7c8GNQ"
     }]
 }
@@ -920,6 +977,20 @@ FAKE_GET_FC_INI_RESPONSE = """
 }
 """
 
+FAKE_HYPERMETRODOMAIN_RESPONSE = """
+{
+    "error":{
+        "code": 0
+    },
+    "data":{
+        "PRODUCTVERSION": "V100R001C10",
+        "ID": "11",
+        "NAME": "hypermetro_test",
+        "RUNNINGSTATUS": "42"
+    }
+}
+"""
+
 FAKE_QOS_INFO_RESPONSE = """
 {
     "error":{
@@ -944,8 +1015,42 @@ FAKE_GET_FC_PORT_RESPONSE = """
 }
 """
 
+FAKE_SMARTCACHEPARTITION_RESPONSE = """
+{
+    "error":{
+        "code":0
+    },
+    "data":{
+        "ID":"11",
+        "NAME":"cache-name"
+    }
+}
+"""
+
+FAKE_CONNECT_FC_RESPONCE = {
+    "driver_volume_type": 'fibre_channel',
+    "data": {
+        "target_wwn": ["10000090fa0d6754"],
+        "target_lun": "1",
+        "volume_id": "21ec7341-9256-497b-97d9-ef48edcf0635"
+    }
+}
+
+FAKE_METRO_INFO_RESPONCE = {
+    "error": {
+        "code": 0
+    },
+    "data": {
+        "PRODUCTVERSION": "V100R001C10",
+        "ID": "11",
+        "NAME": "hypermetro_test",
+        "RUNNINGSTATUS": "42"
+    }
+}
+
 # mock login info map
 MAP_COMMAND_TO_FAKE_RESPONSE = {}
+
 MAP_COMMAND_TO_FAKE_RESPONSE['/xx/sessions'] = (
     FAKE_GET_LOGIN_STORAGE_RESPONSE)
 
@@ -1243,6 +1348,12 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/fc_port/GET'] = (
 MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator/GET'] = (
     FAKE_GET_FC_PORT_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['fc_initiator?range=[0-100]/GET'] = (
+    FAKE_GET_FC_PORT_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/fc_initiator?PARENTTYPE=21&PARENTID=1/GET'] = (
+    FAKE_GET_FC_PORT_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['/lun/associate/cachepartition/POST'] = (
     FAKE_SYSTEM_VERSION_RESPONSE)
 
@@ -1252,6 +1363,33 @@ 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)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/SMARTCACHEPARTITION/REMOVE_ASSOCIATE/PUT'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/cachepartition/0/GET'] = (
+    FAKE_SMARTCACHEPARTITION_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroDomain?range=[0-100]/GET'] = (
+    FAKE_HYPERMETRODOMAIN_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/POST'] = (
+    FAKE_HYPERMETRODOMAIN_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/GET'] = (
+    FAKE_HYPERMETRODOMAIN_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/disable_hcpair/PUT'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/HyperMetroPair/11/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 
 def Fake_sleep(time):
     pass
@@ -1373,6 +1511,7 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
         self.configuration = mock.Mock(spec=conf.Configuration)
         self.configuration.cinder_huawei_conf_file = self.fake_conf_file
         self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        self.configuration.hypermetro_devices = hypermetro_devices
         self.stubs.Set(time, 'sleep', Fake_sleep)
         driver = Fake18000ISCSIStorage(configuration=self.configuration)
         self.driver = driver
@@ -1722,26 +1861,133 @@ class Huawei18000ISCSIDriverTestCase(test.TestCase):
             (qos_id, lun_list) = self.driver.restclient.find_available_qos(qos)
             self.assertEqual(("11", u'["0", "1", "2"]'), (qos_id, lun_list))
 
-    @mock.patch.object(rest_client.RestClient, 'get_volume_by_name',
+    @mock.patch.object(huawei_utils, 'get_volume_params',
+                       return_value=fake_hypermetro_opts)
+    @mock.patch.object(rest_client.RestClient, 'login_with_ip',
+                       return_value='123456789')
+    @mock.patch.object(rest_client.RestClient, 'find_all_pools',
+                       return_value=FAKE_STORAGE_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'find_pool_info',
+                       return_value=FAKE_FIND_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'create_volume',
+                       return_value=FAKE_CREATE_VOLUME_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
                        return_value='11')
-    @mock.patch.object(rest_client.RestClient, 'get_lun_info',
-                       return_value={'ID': '11'})
-    def test_create_volume_exist(self, mock_lun_info, mock_volume_info):
+    @mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
+                       return_value=True)
+    @mock.patch.object(hypermetro.HuaweiHyperMetro,
+                       '_create_hypermetro_pair',
+                       return_value={"ID": '11',
+                                     "NAME": 'hypermetro-pair'})
+    @mock.patch.object(rest_client.RestClient, 'logout',
+                       return_value=None)
+    def test_create_hypermetro_success(self, mock_logout,
+                                       mock_hyper_pair_info,
+                                       mock_volume_ready,
+                                       mock_hyper_domain,
+                                       mock_create_volume,
+                                       mock_pool_info,
+                                       mock_all_pool_info,
+                                       mock_login_return,
+                                       mock_hypermetro_opts):
         self.driver.restclient.login()
-        lun_param = {'NAME': 'IexzQZJWSXuX2e9I7c8GNQ'}
+        metadata = {"hypermetro_id": '11',
+                    "remote_lun_id": '1'}
+        lun_info = self.driver.create_volume(hyper_volume)
+        mock_logout.assert_called_with()
+        self.assertEqual(metadata, lun_info['metadata'])
 
-        fack_error_volume_exist = {"error": {"code": 1077948993}}
-        with mock.patch.object(rest_client.RestClient, 'call',
-                               return_value=fack_error_volume_exist):
-            lun_info = self.driver.restclient.create_volume(lun_param)
-            self.assertEqual('11', lun_info['ID'])
+    @mock.patch.object(huawei_utils, 'get_volume_params',
+                       return_value=fake_hypermetro_opts)
+    @mock.patch.object(rest_client.RestClient, 'login_with_ip',
+                       return_value='123456789')
+    @mock.patch.object(rest_client.RestClient, 'find_all_pools',
+                       return_value=FAKE_STORAGE_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'find_pool_info',
+                       return_value=FAKE_FIND_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'create_volume',
+                       return_value=FAKE_CREATE_VOLUME_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
+                       return_value='11')
+    @mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
+                       return_value=True)
+    @mock.patch.object(hypermetro.HuaweiHyperMetro,
+                       '_create_hypermetro_pair')
+    @mock.patch.object(rest_client.RestClient, 'delete_lun',
+                       return_value=None)
+    @mock.patch.object(rest_client.RestClient, 'logout',
+                       return_value=None)
+    def test_create_hypermetro_fail(self, mock_logout,
+                                    mock_delete_lun,
+                                    mock_hyper_pair_info,
+                                    mock_volume_ready,
+                                    mock_hyper_domain,
+                                    mock_create_volume,
+                                    mock_pool_info,
+                                    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,
+                          self.driver.create_volume, hyper_volume)
+        mock_delete_lun.assert_called_with('1')
+        mock_logout.assert_called_with()
+
+    @mock.patch.object(rest_client.RestClient, 'login_with_ip',
+                       return_value='123456789')
+    @mock.patch.object(rest_client.RestClient, 'check_lun_exist',
+                       return_value=True)
+    @mock.patch.object(rest_client.RestClient, 'check_hypermetro_exist',
+                       return_value=True)
+    @mock.patch.object(rest_client.RestClient, 'get_hypermetro_by_id',
+                       return_value=FAKE_METRO_INFO_RESPONCE)
+    @mock.patch.object(rest_client.RestClient, 'delete_hypermetro',
+                       return_value=FAKE_COMMON_SUCCESS_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'delete_lun',
+                       return_value=None)
+    @mock.patch.object(rest_client.RestClient, 'logout',
+                       return_value=None)
+    def test_delete_hypermetro_success(self, mock_logout,
+                                       mock_delete_lun,
+                                       mock_delete_hypermetro,
+                                       mock_metro_info,
+                                       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)
 
-        fack_error_volume_exist = {"error": {"code": 123456789}}
-        with mock.patch.object(rest_client.RestClient, 'call',
-                               return_value=fack_error_volume_exist):
-            self.assertRaises(exception.VolumeBackendAPIException,
-                              self.driver.restclient.create_volume,
-                              lun_param)
+    @mock.patch.object(rest_client.RestClient, 'login_with_ip',
+                       return_value='123456789')
+    @mock.patch.object(rest_client.RestClient, 'check_lun_exist',
+                       return_value=True)
+    @mock.patch.object(rest_client.RestClient, 'check_hypermetro_exist',
+                       return_value=True)
+    @mock.patch.object(rest_client.RestClient, 'get_hypermetro_by_id',
+                       return_value=FAKE_METRO_INFO_RESPONCE)
+    @mock.patch.object(rest_client.RestClient, 'delete_hypermetro')
+    @mock.patch.object(rest_client.RestClient, 'delete_lun',
+                       return_value=None)
+    @mock.patch.object(rest_client.RestClient, 'logout',
+                       return_value=None)
+    def test_delete_hypermetro_fail(self, mock_logout,
+                                    mock_delete_lun,
+                                    mock_delete_hypermetro,
+                                    mock_metro_info,
+                                    mock_check_hyermetro,
+                                    mock_lun_exit,
+                                    mock_login_info):
+        self.driver.restclient.login()
+        mock_delete_hypermetro.side_effect = (
+            exception.VolumeBackendAPIException(data='Delete hypermetro '
+                                                'error.'))
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.driver.delete_volume, hyper_volume)
+        mock_delete_lun.assert_called_with('11')
 
     def create_fake_conf_file(self):
         """Create a fake Config file.
@@ -1848,6 +2094,7 @@ class Huawei18000FCDriverTestCase(test.TestCase):
         self.configuration = mock.Mock(spec=conf.Configuration)
         self.configuration.cinder_huawei_conf_file = self.fake_conf_file
         self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        self.configuration.hypermetro_devices = hypermetro_devices
         self.stubs.Set(time, 'sleep', Fake_sleep)
         driver = Fake18000FCStorage(configuration=self.configuration)
         self.driver = driver
@@ -2243,6 +2490,41 @@ class Huawei18000FCDriverTestCase(test.TestCase):
                                                              None)
         self.assertEqual(expected_pool_capacity, pool_capacity)
 
+    @mock.patch.object(huawei_utils, 'get_volume_params',
+                       return_value=fake_hypermetro_opts)
+    @mock.patch.object(rest_client.RestClient, 'login_with_ip',
+                       return_value='123456789')
+    @mock.patch.object(rest_client.RestClient, 'find_all_pools',
+                       return_value=FAKE_STORAGE_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'find_pool_info',
+                       return_value=FAKE_FIND_POOL_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'create_volume',
+                       return_value=FAKE_CREATE_VOLUME_RESPONSE)
+    @mock.patch.object(rest_client.RestClient, 'get_hyper_domain_id',
+                       return_value='11')
+    @mock.patch.object(hypermetro.HuaweiHyperMetro, '_wait_volume_ready',
+                       return_value=True)
+    @mock.patch.object(hypermetro.HuaweiHyperMetro,
+                       '_create_hypermetro_pair',
+                       return_value={"ID": '11',
+                                     "NAME": 'hypermetro-pair'})
+    @mock.patch.object(rest_client.RestClient, 'logout',
+                       return_value=None)
+    def test_create_hypermetro_success(self, mock_hypermetro_opts,
+                                       mock_login_return,
+                                       mock_all_pool_info,
+                                       mock_pool_info,
+                                       mock_create_volume,
+                                       mock_hyper_domain,
+                                       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)
+        self.assertEqual(metadata, lun_info['metadata'])
+
     def create_fake_conf_file(self):
         """Create a fake Config file
 
index 47f59a566b943dd7456dda23a145ad86f2e32727..764c0f73411a2f94c3df53d7ae85752be8b219d4 100644 (file)
@@ -42,6 +42,10 @@ ERROR_UNAUTHORIZED_TO_SERVER = -401
 SOCKET_TIMEOUT = 52
 ERROR_VOLUME_ALREADY_EXIST = 1077948993
 LOGIN_SOCKET_TIMEOUT = 4
+ERROR_VOLUME_NOT_EXIST = 1077939726
+RELOGIN_ERROR_PASS = [ERROR_VOLUME_NOT_EXIST]
+HYPERMETRO_RUNNSTATUS_STOP = 41
+HYPERMETRO_RUNNSTATUS_NORMAL = 1
 
 THICK_LUNTYPE = 0
 THIN_LUNTYPE = 1
@@ -62,3 +66,4 @@ HUAWEI_VALID_KEYS = ['maxIOPS', 'minIOPS', 'minBandWidth',
 QOS_KEYS = ['MAXIOPS', 'MINIOPS', 'MINBANDWidth',
             'MAXBANDWidth', 'LATENCY', 'IOTYPE']
 MAX_LUN_NUM_IN_QOS = 64
+HYPERMETRO_CLASS = "cinder.volume.drivers.huawei.hypermetro.HuaweiHyperMetro"
index 90b269420be8c0122c24592f1a0ef10ac91e4910..3e1a1a2e54dd3627c31b9d4d6772a45d42cd7f9e 100644 (file)
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import json
 import six
 import uuid
 
@@ -28,6 +29,7 @@ from cinder.volume import driver
 from cinder.volume.drivers.huawei import constants
 from cinder.volume.drivers.huawei import fc_zone_helper
 from cinder.volume.drivers.huawei import huawei_utils
+from cinder.volume.drivers.huawei import hypermetro
 from cinder.volume.drivers.huawei import rest_client
 from cinder.volume.drivers.huawei import smartx
 from cinder.volume import utils as volume_utils
@@ -39,8 +41,11 @@ LOG = logging.getLogger(__name__)
 huawei_opts = [
     cfg.StrOpt('cinder_huawei_conf_file',
                default='/etc/cinder/cinder_huawei_conf.xml',
-               help='The configuration file for the Cinder Huawei '
-                    'driver.')]
+               help='The configuration file for the Cinder Huawei driver.'),
+    cfg.StrOpt('hypermetro_devices',
+               default=None,
+               help='The remote device hypermetro will use.'),
+]
 
 CONF = cfg.CONF
 CONF.register_opts(huawei_opts)
@@ -57,6 +62,7 @@ class HuaweiBaseDriver(driver.VolumeDriver):
 
         self.configuration.append_config_values(huawei_opts)
         self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        self.hypermetro_devices = self.configuration.hypermetro_devices
 
     def do_setup(self, context):
         """Instantiate common class and login storage system."""
@@ -127,9 +133,31 @@ class HuaweiBaseDriver(driver.VolumeDriver):
             raise exception.InvalidInput(
                 reason=_('Create volume error. Because %s.') % err)
 
-        return {'provider_location': lun_info['ID'],
+        # Update the metadata.
+        LOG.info(_LI('Create volume option: %s.'), opts)
+        metadata = huawei_utils.get_volume_metadata(volume)
+        if opts.get('hypermetro'):
+            hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
+                                                 self.configuration)
+            try:
+                metro_id, remote_lun_id = hyperm.create_hypermetro(lun_id,
+                                                                   lun_param)
+            except exception.VolumeBackendAPIException as err:
+                LOG.exception(_LE('Create hypermetro error: %s.'), err)
+                self._delete_lun_with_check(lun_id)
+                raise
+
+            LOG.info(_LI("Hypermetro id: %(metro_id)s. "
+                         "Remote lun id: %(remote_lun_id)s."),
+                     {'metro_id': metro_id,
+                      'remote_lun_id': remote_lun_id})
+
+            metadata.update({'hypermetro_id': metro_id,
+                             'remote_lun_id': remote_lun_id})
+
+        return {'provider_location': lun_id,
                 'ID': lun_id,
-                'lun_info': lun_info}
+                'metadata': metadata}
 
     @utils.synchronized('huawei', external=True)
     def delete_volume(self, volume):
@@ -150,6 +178,17 @@ class HuaweiBaseDriver(driver.VolumeDriver):
                 if qos_id:
                     self.remove_qos_lun(lun_id, qos_id)
 
+                metadata = huawei_utils.get_volume_metadata(volume)
+                if 'hypermetro_id' in metadata:
+                    hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
+                                                         self.configuration)
+                    try:
+                        hyperm.delete_hypermetro(volume)
+                    except exception.VolumeBackendAPIException as err:
+                        LOG.exception(_LE('Delete hypermetro error: %s.'), err)
+                        self.restclient.delete_lun(lun_id)
+                        raise
+
                 self.restclient.delete_lun(lun_id)
         else:
             LOG.warning(_LW("Can't find lun %s on the array."), lun_id)
@@ -188,11 +227,11 @@ class HuaweiBaseDriver(driver.VolumeDriver):
                     if constants.MIGRATION_COMPLETE == item['RUNNINGSTATUS']:
                         return True
                     if constants.MIGRATION_FAULT == item['RUNNINGSTATUS']:
-                        err_msg = (_('Lun migration error.'))
+                        err_msg = _('Lun migration error.')
                         LOG.error(err_msg)
                         raise exception.VolumeBackendAPIException(data=err_msg)
         if not found_migration_task:
-            err_msg = (_("Cannot find migration task."))
+            err_msg = _("Cannot find migration task.")
             LOG.error(err_msg)
             raise exception.VolumeBackendAPIException(data=err_msg)
 
@@ -1010,6 +1049,7 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
                 Volume migration support
                 Volume retype support
                 FC zone enhancement
+                Volume hypermetro support
     """
 
     VERSION = "1.1.1"
@@ -1090,23 +1130,82 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
 
         # Add host into hostgroup.
         hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)
-        self.restclient.do_mapping(lun_id, hostgroup_id, host_id)
+        map_info = self.restclient.do_mapping(lun_id,
+                                              hostgroup_id,
+                                              host_id)
         host_lun_id = self.restclient.find_host_lun_id(host_id, lun_id)
 
         # Return FC properties.
-        info = {'driver_volume_type': 'fibre_channel',
-                'data': {'target_lun': int(host_lun_id),
-                         'target_discovered': True,
-                         'target_wwn': tgt_port_wwns,
-                         'volume_id': volume['id'],
-                         'initiator_target_map': init_targ_map}, }
+        fc_info = {'driver_volume_type': 'fibre_channel',
+                   'data': {'target_lun': int(host_lun_id),
+                            'target_discovered': True,
+                            'target_wwn': tgt_port_wwns,
+                            'volume_id': volume['id'],
+                            'initiator_target_map': init_targ_map,
+                            'map_info': map_info}, }
+
+        loc_tgt_wwn = fc_info['data']['target_wwn']
+        local_ini_tgt_map = fc_info['data']['initiator_target_map']
+
+        # Deal with hypermetro connection.
+        metadata = huawei_utils.get_volume_metadata(volume)
+        LOG.info(_LI("initialize_connection, metadata is: %s."), metadata)
+        if 'hypermetro_id' in metadata:
+            hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
+                                                 self.configuration)
+            rmt_fc_info = hyperm.connect_volume_fc(volume, connector)
+
+            rmt_tgt_wwn = rmt_fc_info['data']['target_wwn']
+            rmt_ini_tgt_map = rmt_fc_info['data']['initiator_target_map']
+            fc_info['data']['target_wwn'] = (loc_tgt_wwn + rmt_tgt_wwn)
+            wwns = connector['wwpns']
+            for wwn in wwns:
+                if (wwn in local_ini_tgt_map
+                        and wwn in rmt_ini_tgt_map):
+                    fc_info['data']['initiator_target_map'][wwn].extend(
+                        rmt_ini_tgt_map[wwn])
 
-        LOG.info(_LI("initialize_connection, return data is: %s."),
-                 info)
+                elif (wwn not in local_ini_tgt_map
+                        and wwn in rmt_ini_tgt_map):
+                    fc_info['data']['initiator_target_map'][wwn] = (
+                        rmt_ini_tgt_map[wwn])
+                # else, do nothing
 
-        return info
+            loc_map_info = fc_info['data']['map_info']
+            rmt_map_info = rmt_fc_info['data']['map_info']
+            same_host_id = self._get_same_hostid(loc_map_info,
+                                                 rmt_map_info)
+
+            self.restclient.change_hostlun_id(loc_map_info, same_host_id)
+            hyperm.rmt_client.change_hostlun_id(rmt_map_info, same_host_id)
+
+            fc_info['data']['target_lun'] = same_host_id
+            hyperm.rmt_client.logout()
+
+        LOG.info(_LI("Return FC info is: %s."), fc_info)
+        return fc_info
+
+    def _get_same_hostid(self, loc_fc_info, rmt_fc_info):
+        loc_aval_luns = loc_fc_info['aval_luns']
+        loc_aval_luns = json.loads(loc_aval_luns)
+
+        rmt_aval_luns = rmt_fc_info['aval_luns']
+        rmt_aval_luns = json.loads(rmt_aval_luns)
+        same_host_id = None
+
+        for i in range(1, 512):
+            if i in rmt_aval_luns and i in loc_aval_luns:
+                same_host_id = i
+                break
+
+        LOG.info(_LI("The same hostid is: %s."), same_host_id)
+        if not same_host_id:
+            msg = _("Can't find the same host id from arrays.")
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        return same_host_id
 
-    @utils.synchronized('huawei', external=True)
     @fczm_utils.RemoveFCZone
     def terminate_connection(self, volume, connector, **kwargs):
         """Delete map between a volume and a host."""
@@ -1150,8 +1249,8 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
         if lungroup_id:
             left_lunnum = self.restclient.get_lunnum_from_lungroup(lungroup_id)
         if int(left_lunnum) > 0:
-            info = {'driver_volume_type': 'fibre_channel',
-                    'data': {}}
+            fc_info = {'driver_volume_type': 'fibre_channel',
+                       'data': {}}
         else:
             if not self.fcsan_lookup_service:
                 self.fcsan_lookup_service = fczm_utils.create_lookup_service()
@@ -1195,10 +1294,19 @@ class Huawei18000FCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
             if view_id:
                 self.restclient.delete_mapping_view(view_id)
 
-            info = {'driver_volume_type': 'fibre_channel',
-                    'data': {'target_wwn': tgt_port_wwns,
-                             'initiator_target_map': init_targ_map}}
+            fc_info = {'driver_volume_type': 'fibre_channel',
+                       'data': {'target_wwn': tgt_port_wwns,
+                                'initiator_target_map': init_targ_map}}
+
+        # Deal with hypermetro connection.
+        metadata = huawei_utils.get_volume_metadata(volume)
+        LOG.info(_LI("Detach Volume, metadata is: %s."), metadata)
+        if 'hypermetro_id' in metadata:
+            hyperm = hypermetro.HuaweiHyperMetro(self.restclient, None,
+                                                 self.configuration)
+            hyperm.disconnect_volume_fc(volume, connector)
+
         LOG.info(_LI("terminate_connection, return data is: %s."),
-                 info)
+                 fc_info)
 
-        return info
+        return fc_info
index bb55b369cddf8a4543358739047f1ff9fe3650a2..0da3efa5d6b98f83f64cac1e4f1c7312f7a6b2ee 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 
 import base64
+import json
 import six
 import time
 import uuid
@@ -40,6 +41,7 @@ opts_capability = {
     'smartpartition': False,
     'thin_provisioning_support': False,
     'thick_provisioning_support': False,
+    'hypermetro': False,
 }
 
 
@@ -539,3 +541,29 @@ def get_pools(xml_file_path):
         LOG.error(msg)
         raise exception.InvalidInput(msg)
     return pool_names
+
+
+def get_remote_device_info(valid_hypermetro_devices):
+    remote_device_info = {}
+    try:
+        if valid_hypermetro_devices:
+            remote_device_info = json.loads(valid_hypermetro_devices)
+        else:
+            return
+
+    except ValueError as err:
+        msg = _("Get remote device info error. %s.") % err
+        LOG.error(msg)
+        raise exception.VolumeBackendAPIException(data=msg)
+
+    if len(remote_device_info) == 1:
+        for device_key, device_value in remote_device_info.items():
+            return remote_device_info.get(device_key)
+
+
+def get_volume_metadata(volume):
+    if 'volume_metadata' in volume:
+        metadata = volume.get('volume_metadata')
+        return {item['key']: item['value'] for item in metadata}
+
+    return {}
diff --git a/cinder/volume/drivers/huawei/hypermetro.py b/cinder/volume/drivers/huawei/hypermetro.py
new file mode 100644 (file)
index 0000000..22eea83
--- /dev/null
@@ -0,0 +1,321 @@
+# Copyright (c) 2015 Huawei Technologies Co., Ltd.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+import six
+
+from oslo_log import log as logging
+
+from cinder import exception
+from cinder.i18n import _, _LI, _LW
+from cinder.volume.drivers.huawei import constants
+from cinder.volume.drivers.huawei import huawei_utils
+from cinder.volume.drivers.huawei import rest_client
+
+LOG = logging.getLogger(__name__)
+
+
+class HuaweiHyperMetro(object):
+
+    def __init__(self, client, rmt_client, configuration):
+        self.client = client
+        self.rmt_client = rmt_client
+        self.configuration = configuration
+        self.xml_file_path = self.configuration.cinder_huawei_conf_file
+
+    def create_hypermetro(self, local_lun_id, lun_param):
+        """Create hypermetro."""
+        metro_devices = self.configuration.hypermetro_devices
+        device_info = huawei_utils.get_remote_device_info(metro_devices)
+        self.rmt_client = rest_client.RestClient(self.configuration)
+        self.rmt_client.login_with_ip(device_info)
+
+        try:
+            # Get the remote pool info.
+            config_pool = device_info['StoragePool']
+            remote_pool = self.rmt_client.find_all_pools()
+            pool = self.rmt_client.find_pool_info(config_pool,
+                                                  remote_pool)
+            # Create remote lun
+            lun_param['PARENTID'] = pool['ID']
+            remotelun_info = self.rmt_client.create_volume(lun_param)
+            remote_lun_id = remotelun_info['ID']
+
+            # Get hypermetro domain
+            try:
+                domain_name = device_info['domain_name']
+                domain_id = self.rmt_client.get_hyper_domain_id(domain_name)
+                self._wait_volume_ready(remote_lun_id)
+                hypermetro = self._create_hypermetro_pair(domain_id,
+                                                          local_lun_id,
+                                                          remote_lun_id)
+
+                return hypermetro['ID'], remote_lun_id
+            except Exception as err:
+                self.rmt_client.delete_lun(remote_lun_id)
+                msg = _('Create hypermetro error. %s.') % err
+                raise exception.VolumeBackendAPIException(data=msg)
+        except exception.VolumeBackendAPIException:
+            raise
+        except Exception as err:
+            msg = _("Create remote LUN error. %s.") % err
+            LOG.exception(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+        finally:
+            self.rmt_client.logout()
+
+    def delete_hypermetro(self, volume):
+        """Delete hypermetro."""
+        metadata = huawei_utils.get_volume_metadata(volume)
+        metro_id = metadata['hypermetro_id']
+        remote_lun_id = metadata['remote_lun_id']
+
+        if metro_id:
+            exst_flag = self.client.check_hypermetro_exist(metro_id)
+            if exst_flag:
+                metro_info = self.client.get_hypermetro_by_id(metro_id)
+                metro_status = int(metro_info['data']['RUNNINGSTATUS'])
+
+                LOG.debug("Hypermetro status is: %s.", metro_status)
+                if constants.HYPERMETRO_RUNNSTATUS_STOP != metro_status:
+                    self.client.stop_hypermetro(metro_id)
+
+                # Delete hypermetro
+                self.client.delete_hypermetro(metro_id)
+
+        # Delete remote lun.
+        if remote_lun_id:
+            metro_devices = self.configuration.hypermetro_devices
+            device_info = huawei_utils.get_remote_device_info(metro_devices)
+            self.rmt_client = rest_client.RestClient(self.configuration)
+            self.rmt_client.login_with_ip(device_info)
+
+            try:
+                if self.rmt_client.check_lun_exist(remote_lun_id):
+                    self.rmt_client.delete_lun(remote_lun_id)
+            except Exception as err:
+                msg = _("Delete remote lun err. %s.") % err
+                LOG.exception(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+            finally:
+                self.rmt_client.logout()
+
+    def _create_hypermetro_pair(self, domain_id, lun_id, remote_lun_id):
+        """Create a HyperMetroPair."""
+        hcp_param = {"DOMAINID": domain_id,
+                     "HCRESOURCETYPE": '1',
+                     "ISFIRSTSYNC": False,
+                     "LOCALOBJID": lun_id,
+                     "RECONVERYPOLICY": '1',
+                     "REMOTEOBJID": remote_lun_id,
+                     "SPEED": '2'}
+
+        return self.client.create_hypermetro(hcp_param)
+
+    def connect_volume_fc(self, volume, connector):
+        """Create map between a volume and a host for FC."""
+        self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        metro_devices = self.configuration.hypermetro_devices
+        device_info = huawei_utils.get_remote_device_info(metro_devices)
+        self.rmt_client = rest_client.RestClient(self.configuration)
+        self.rmt_client.login_with_ip(device_info)
+
+        try:
+            wwns = connector['wwpns']
+            volume_name = huawei_utils.encode_name(volume['id'])
+
+            LOG.info(_LI(
+                'initialize_connection_fc, initiator: %(wwpns)s,'
+                ' volume name: %(volume)s.'),
+                {'wwpns': wwns,
+                 'volume': volume_name})
+
+            metadata = huawei_utils.get_volume_metadata(volume)
+            lun_id = metadata['remote_lun_id']
+
+            if lun_id is None:
+                lun_id = self.rmt_client.get_volume_by_name(volume_name)
+            if lun_id is None:
+                msg = _("Can't get volume id. Volume name: %s.") % volume_name
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+
+            host_name_before_hash = None
+            host_name = connector['host']
+            if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
+                host_name_before_hash = host_name
+                host_name = six.text_type(hash(host_name))
+
+            # Create hostgroup if not exist.
+            host_id = self.rmt_client.add_host_with_check(
+                host_name, host_name_before_hash)
+
+            online_wwns_in_host = (
+                self.rmt_client.get_host_online_fc_initiators(host_id))
+            online_free_wwns = self.rmt_client.get_online_free_wwns()
+            for wwn in wwns:
+                if (wwn not in online_wwns_in_host
+                        and wwn not in online_free_wwns):
+                    wwns_in_host = (
+                        self.rmt_client.get_host_fc_initiators(host_id))
+                    iqns_in_host = (
+                        self.rmt_client.get_host_iscsi_initiators(host_id))
+                    if not wwns_in_host and not iqns_in_host:
+                        self.rmt_client.remove_host(host_id)
+
+                    msg = _('Can not add FC port to host.')
+                    LOG.error(msg)
+                    raise exception.VolumeBackendAPIException(data=msg)
+
+            for wwn in wwns:
+                if wwn in online_free_wwns:
+                    self.rmt_client.add_fc_port_to_host(host_id, wwn)
+
+            (tgt_port_wwns, init_targ_map) = (
+                self.rmt_client.get_init_targ_map(wwns))
+
+            # Add host into hostgroup.
+            hostgroup_id = self.rmt_client.add_host_into_hostgroup(host_id)
+            map_info = self.rmt_client.do_mapping(lun_id,
+                                                  hostgroup_id,
+                                                  host_id)
+            host_lun_id = self.rmt_client.find_host_lun_id(host_id, lun_id)
+        except exception.VolumeBackendAPIException:
+            raise
+        except Exception as err:
+            msg = _("Connect volume fc: connect volume error. %s.") % err
+            LOG.exception(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        # Return FC properties.
+        fc_info = {'driver_volume_type': 'fibre_channel',
+                   'data': {'target_lun': int(host_lun_id),
+                            'target_discovered': True,
+                            'target_wwn': tgt_port_wwns,
+                            'volume_id': volume['id'],
+                            'initiator_target_map': init_targ_map,
+                            'map_info': map_info},
+                   }
+
+        LOG.info(_LI('Remote return FC info is: %s.'), fc_info)
+
+        return fc_info
+
+    def disconnect_volume_fc(self, volume, connector):
+        """Delete map between a volume and a host for FC."""
+        # Login remote storage device.
+        self.xml_file_path = self.configuration.cinder_huawei_conf_file
+        metro_devices = self.configuration.hypermetro_devices
+        device_info = huawei_utils.get_remote_device_info(metro_devices)
+        self.rmt_client = rest_client.RestClient(self.configuration)
+        self.rmt_client.login_with_ip(device_info)
+
+        try:
+            wwns = connector['wwpns']
+            volume_name = huawei_utils.encode_name(volume['id'])
+            metadata = huawei_utils.get_volume_metadata(volume)
+            lun_id = metadata['remote_lun_id']
+            host_name = connector['host']
+            left_lunnum = -1
+            lungroup_id = None
+            view_id = None
+
+            LOG.info(_LI('terminate_connection_fc: volume name: %(volume)s, '
+                         'wwpns: %(wwns)s, '
+                         'lun_id: %(lunid)s.'),
+                     {'volume': volume_name,
+                      'wwns': wwns,
+                      'lunid': lun_id},)
+
+            if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
+                host_name = six.text_type(hash(host_name))
+
+            hostid = self.rmt_client.find_host(host_name)
+            if hostid:
+                mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
+                view_id = self.rmt_client.find_mapping_view(
+                    mapping_view_name)
+                if view_id:
+                    lungroup_id = self.rmt_client.find_lungroup_from_map(
+                        view_id)
+
+            if lun_id and self.rmt_client.check_lun_exist(lun_id):
+                if lungroup_id:
+                    lungroup_ids = self.rmt_client.get_lungroupids_by_lunid(
+                        lun_id)
+                    if lungroup_id in lungroup_ids:
+                        self.rmt_client.remove_lun_from_lungroup(
+                            lungroup_id, lun_id)
+                    else:
+                        LOG.warning(_LW("Lun is not in lungroup. "
+                                        "Lun id: %(lun_id)s, "
+                                        "lungroup id: %(lungroup_id)s"),
+                                    {"lun_id": lun_id,
+                                     "lungroup_id": lungroup_id})
+
+            (tgt_port_wwns, init_targ_map) = (
+                self.rmt_client.get_init_targ_map(wwns))
+
+            hostid = self.rmt_client.find_host(host_name)
+            if hostid:
+                mapping_view_name = constants.MAPPING_VIEW_PREFIX + hostid
+                view_id = self.rmt_client.find_mapping_view(
+                    mapping_view_name)
+                if view_id:
+                    lungroup_id = self.rmt_client.find_lungroup_from_map(
+                        view_id)
+            if lungroup_id:
+                left_lunnum = self.rmt_client.get_lunnum_from_lungroup(
+                    lungroup_id)
+
+        except Exception as err:
+            msg = _("Remote detatch volume error. %s.") % err
+            LOG.exception(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+        finally:
+            self.rmt_client.logout()
+
+        if int(left_lunnum) > 0:
+            info = {'driver_volume_type': 'fibre_channel',
+                    'data': {}}
+        else:
+            info = {'driver_volume_type': 'fibre_channel',
+                    'data': {'target_wwn': tgt_port_wwns,
+                             'initiator_target_map': init_targ_map}, }
+
+        return info
+
+    def _wait_volume_ready(self, lun_id):
+        event_type = 'LUNReadyWaitInterval'
+        wait_interval = huawei_utils.get_wait_interval(self.xml_file_path,
+                                                       event_type)
+
+        def _volume_ready():
+            result = self.rmt_client.get_lun_info(lun_id)
+            if (result['HEALTHSTATUS'] == constants.STATUS_HEALTH
+               and result['RUNNINGSTATUS'] == constants.STATUS_VOLUME_READY):
+                return True
+            return False
+
+        huawei_utils.wait_for_condition(self.xml_file_path,
+                                        _volume_ready,
+                                        wait_interval,
+                                        wait_interval * 10)
+
+    def retype(self, volume, new_type):
+        return False
+
+    def get_hypermetro_stats(self, hypermetro_id):
+        pass
index c956e1c07caeb6e50075d81b8369776264cd3aec..4541c3bc510f46402b37408564602492e5e832c3 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 
 import json
+import six
 import socket
 import time
 
@@ -38,6 +39,7 @@ class RestClient(object):
         self.configuration = configuration
         self.xml_file_path = configuration.cinder_huawei_conf_file
         self.productversion = None
+        self.init_http_head()
 
     def init_http_head(self):
         self.cookie = http_cookiejar.CookieJar()
@@ -149,8 +151,47 @@ class RestClient(object):
                       {'old_url': old_url,
                        'new_url': self.url})
             result = self.do_call(url, data, method)
+            if result['error']['code'] in constants.RELOGIN_ERROR_PASS:
+                result['error']['code'] = 0
         return result
 
+    def login_with_ip(self, login_info):
+        """Login 18000 array with the specific URL."""
+        urlstr = login_info['RestURL']
+        url_list = urlstr.split(";")
+        for item_url in url_list:
+            url = item_url + "xx/sessions"
+            data = json.dumps({"username": login_info['UserName'],
+                               "password": login_info['UserPassword'],
+                               "scope": '0'})
+            result = self.call(url, data)
+
+            if result['error']['code'] == constants.ERROR_CONNECT_TO_SERVER:
+                continue
+
+            if (result['error']['code'] != 0) or ('data' not in result):
+                msg = (_("Login error, reason is: %s.") % result)
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
+
+            device_id = result['data']['deviceid']
+            self.device_id = device_id
+            self.url = item_url + device_id
+            self.headers['iBaseToken'] = result['data']['iBaseToken']
+
+            return device_id
+
+        msg = _("Login error: Can not connect to server.")
+        LOG.error(msg)
+        raise exception.VolumeBackendAPIException(data=msg)
+
+    def logout(self):
+        """Logout the session."""
+        url = "/sessions"
+        if self.url:
+            result = self.call(url, None, "DELETE")
+            self._assert_rest_result(result, _('Logout session error.'))
+
     def _assert_rest_result(self, result, err_str):
         if result['error']['code'] != 0:
             msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
@@ -383,6 +424,7 @@ class RestClient(object):
         mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
         lungroup_id = self._find_lungroup(lungroup_name)
         view_id = self.find_mapping_view(mapping_view_name)
+        map_info = {}
 
         LOG.info(_LI(
             'do_mapping, lun_group: %(lun_group)s, '
@@ -418,6 +460,13 @@ class RestClient(object):
                         self._associate_portgroup_to_view(view_id,
                                                           tgtportgroup_id)
 
+            version = self.find_array_version()
+            if version >= constants.ARRAY_VERSION:
+                aval_luns = self.find_view_by_id(view_id)
+                map_info["lun_id"] = lun_id
+                map_info["view_id"] = view_id
+                map_info["aval_luns"] = aval_luns
+
         except Exception:
             with excutils.save_and_reraise_exception():
                 LOG.error(_LE(
@@ -425,6 +474,8 @@ class RestClient(object):
                     'view. Remove lun from lungroup now.'))
                 self.remove_lun_from_lungroup(lungroup_id, lun_id)
 
+        return map_info
+
     def ensure_initiator_added(self, xml_file_path, initiator_name, host_id):
         added = self._initiator_is_added_to_array(initiator_name)
         if not added:
@@ -1114,6 +1165,7 @@ class RestClient(object):
                 smarttier=True,
                 smartcache=True,
                 smartpartition=True,
+                hypermetro=True,
             ))
             data['pools'].append(pool)
         return data
@@ -1200,6 +1252,9 @@ class RestClient(object):
                 LOG.error(msg)
                 raise exception.InvalidInput(reason=msg)
 
+        # Deal with the remote tgt ip.
+        if 'remote_target_ip' in connector:
+            target_ips.append(connector['remote_target_ip'])
         LOG.info(_LI('Get the default ip: %s.'), target_ips)
         for ip in target_ips:
             target_iqn = self._get_tgt_iqn_from_rest(ip)
@@ -1678,3 +1733,112 @@ class RestClient(object):
                                               constants.FC_PORT_CONNECTED):
                 port_list_from_contr.append(item['WWN'])
         return port_list_from_contr
+
+    def get_hyper_domain_id(self, domain_name):
+        url = "/HyperMetroDomain?range=[0-100]"
+        result = self.call(url, None, "GET")
+        domain_id = None
+        if "data" in result:
+            for item in result['data']:
+                if domain_name == item['NAME']:
+                    domain_id = item['ID']
+                    break
+
+        msg = _('get_hyper_domain_id error.')
+        self._assert_rest_result(result, msg)
+        return domain_id
+
+    def create_hypermetro(self, hcp_param):
+        url = "/HyperMetroPair"
+        data = json.dumps(hcp_param)
+        result = self.call(url, data, "POST")
+
+        msg = _('create_hypermetro_pair error.')
+        self._assert_rest_result(result, msg)
+        self._assert_data_in_result(result, msg)
+        return result['data']
+
+    def delete_hypermetro(self, metro_id):
+        url = "/HyperMetroPair/" + metro_id
+        result = self.call(url, None, "DELETE")
+
+        msg = _('delete_hypermetro error.')
+        self._assert_rest_result(result, msg)
+
+    def sync_hypermetro(self, metro_id):
+        url = "/HyperMetroPair/synchronize_hcpair"
+
+        data = json.dumps({"ID": metro_id,
+                           "TYPE": "15361"})
+        result = self.call(url, data, "PUT")
+
+        msg = _('sync_hypermetro error.')
+        self._assert_rest_result(result, msg)
+
+    def stop_hypermetro(self, metro_id):
+        url = '/HyperMetroPair/disable_hcpair'
+
+        data = json.dumps({"ID": metro_id,
+                           "TYPE": "15361"})
+        result = self.call(url, data, "PUT")
+
+        msg = _('stop_hypermetro error.')
+        self._assert_rest_result(result, msg)
+
+    def get_hypermetro_by_id(self, metro_id):
+        url = "/HyperMetroPair/" + metro_id
+        result = self.call(url, None, "GET")
+
+        msg = _('get_hypermetro_by_id error.')
+        self._assert_rest_result(result, msg)
+        return result
+
+    def check_hypermetro_exist(self, metro_id):
+        url = "/HyperMetroPair/" + metro_id
+        result = self.call(url, None, "GET")
+        error_code = result['error']['code']
+
+        if (error_code == constants.ERROR_CONNECT_TO_SERVER
+                or error_code == constants.ERROR_UNAUTHORIZED_TO_SERVER):
+            LOG.error(_LE("Can not open the recent url, login again."))
+            self.login()
+            result = self.call(url, None, "GET")
+
+        error_code = result['error']['code']
+        if (error_code == constants.ERROR_CONNECT_TO_SERVER
+                or error_code == constants.ERROR_UNAUTHORIZED_TO_SERVER):
+            msg = _("check_hypermetro_exist error.")
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(data=msg)
+
+        if error_code != 0:
+            return False
+
+        return True
+
+    def change_hostlun_id(self, map_info, hostlun_id):
+        url = "/mappingview"
+        view_id = six.text_type(map_info['view_id'])
+        lun_id = six.text_type(map_info['lun_id'])
+        hostlun_id = six.text_type(hostlun_id)
+        data = json.dumps({"TYPE": 245,
+                           "ID": view_id,
+                           "ASSOCIATEOBJTYPE": 11,
+                           "ASSOCIATEOBJID": lun_id,
+                           "ASSOCIATEMETADATA": [{"LUNID": lun_id,
+                                                  "hostLUNId": hostlun_id}]
+                           })
+
+        result = self.call(url, data, "PUT")
+
+        msg = 'change hostlun id error.'
+        self._assert_rest_result(result, msg)
+
+    def find_view_by_id(self, view_id):
+        url = "/MAPPINGVIEW/" + view_id
+        result = self.call(url, None, "GET")
+
+        msg = _('Change hostlun id error.')
+        self._assert_rest_result(result, msg)
+        if 'data' in result:
+            return result["data"]["AVAILABLEHOSTLUNIDLIST"]