From 3eb855260eab83a7e3fc9a098200ee852928ead5 Mon Sep 17 00:00:00 2001
From: huananhuawei <huanan@huawei.com>
Date: Tue, 14 Jun 2016 17:28:26 +0800
Subject: [PATCH] Huawei: Support backup snapshot

Implement attach snapshot for backup snapshot feature
in Huawei driver.

DocImpact
Implements: blueprint huawei-support-backup-snapshot

Change-Id: I0173d397760bc17c01af792b4c4cc2bcbf45fedd
---
 cinder/tests/unit/test_huawei_drivers.py      | 97 ++++++++++++++++++-
 cinder/tests/unit/test_volume.py              |  4 +
 cinder/volume/driver.py                       |  5 +
 cinder/volume/drivers/huawei/constants.py     |  2 +
 .../volume/drivers/huawei/fc_zone_helper.py   | 13 ++-
 cinder/volume/drivers/huawei/huawei_driver.py | 97 ++++++++++++++-----
 cinder/volume/drivers/huawei/hypermetro.py    |  2 +-
 cinder/volume/drivers/huawei/rest_client.py   | 66 ++++++++-----
 .../backup-snapshot-6e7447db930c31f6.yaml     |  3 +
 9 files changed, 232 insertions(+), 57 deletions(-)
 create mode 100644 releasenotes/notes/backup-snapshot-6e7447db930c31f6.yaml

diff --git a/cinder/tests/unit/test_huawei_drivers.py b/cinder/tests/unit/test_huawei_drivers.py
index ec96f12b77f..3f85f0fe330 100644
--- a/cinder/tests/unit/test_huawei_drivers.py
+++ b/cinder/tests/unit/test_huawei_drivers.py
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 """Tests for huawei drivers."""
+import collections
 import copy
 import ddt
 import json
@@ -44,6 +45,9 @@ from cinder.volume import volume_types
 
 admin_contex = context.get_admin_context()
 
+vol_attrs = ('id', 'lun_type', 'provider_location', 'metadata')
+Volume = collections.namedtuple('Volume', vol_attrs)
+
 PROVIDER_LOCATION = '11'
 HOST = 'ubuntu001@backend001#OpenStack_Pool'
 ID = '21ec7341-9256-497b-97d9-ef48edcf0635'
@@ -484,6 +488,18 @@ FAKE_GET_SNAPSHOT_INFO_RESPONSE = """
 }
 """
 
+FAKE_SNAPSHOT_COUNT_RESPONSE = """
+{
+    "data":{
+        "COUNT":"2"
+    },
+    "error":{
+        "code":0,
+        "description":"0"
+    }
+}
+"""
+
 # A fake response of get iscsi response
 
 FAKE_GET_ISCSI_INFO_RESPONSE = """
@@ -1208,6 +1224,14 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/lun/associate/cachepartition?ID=1'
                              '/DELETE'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['/snapshot/associate?TYPE=27&ASSOCIATEOBJTYPE=21'
+                             '&ASSOCIATEOBJID=1/GET'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/snapshot/associate?TYPE=27&ASSOCIATEOBJTYPE=256'
+                             '&ASSOCIATEOBJID=11/GET'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup?range=[0-8191]/GET'] = (
     FAKE_QUERY_LUN_GROUP_INFO_RESPONSE)
 
@@ -1236,10 +1260,26 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11'
                              '&ASSOCIATEOBJID=11/DELETE'] = (
     FAKE_COMMON_SUCCESS_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup/associate?ID=11&ASSOCIATEOBJTYPE=27'
+                             '&ASSOCIATEOBJID=11/DELETE'] = (
+    FAKE_COMMON_SUCCESS_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256'
                              '&ASSOCIATEOBJID=11/GET'] = (
     FAKE_LUN_COUNT_RESPONSE)
 
+MAP_COMMAND_TO_FAKE_RESPONSE['/snapshot/count?TYPE=27&ASSOCIATEOBJTYPE=256'
+                             '&ASSOCIATEOBJID=1/GET'] = (
+    FAKE_SNAPSHOT_COUNT_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/snapshot/count?TYPE=27&ASSOCIATEOBJTYPE=256'
+                             '&ASSOCIATEOBJID=11/GET'] = (
+    FAKE_SNAPSHOT_COUNT_RESPONSE)
+
+MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup/associate?TYPE=256&ASSOCIATEOBJTYPE=27'
+                             '&ASSOCIATEOBJID=11/GET'] = (
+    FAKE_LUN_ASSOCIATE_RESPONSE)
+
 MAP_COMMAND_TO_FAKE_RESPONSE['/lun/expand/PUT'] = (
     FAKE_LUN_INFO_RESPONSE)
 
@@ -2104,6 +2144,9 @@ class HuaweiTestBase(test.TestCase):
 
     def setUp(self):
         super(HuaweiTestBase, self).setUp()
+        self.configuration = mock.Mock(spec=conf.Configuration)
+        self.driver = FakeISCSIStorage(configuration=self.configuration)
+        self.driver.do_setup()
 
         self.volume = fake_volume.fake_volume_obj(
             admin_contex, host=HOST, provider_location=PROVIDER_LOCATION,
@@ -2232,6 +2275,19 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase):
         self.assertEqual(driver_data, model_update['replication_driver_data'])
         self.assertEqual('available', model_update['replication_status'])
 
+    @mock.patch.object(huawei_driver.HuaweiISCSIDriver,
+                       'initialize_connection',
+                       return_value={"data": {'target_lun': 1}})
+    def test_initialize_connection_snapshot_success(self, mock_iscsi_init):
+        iscsi_properties = self.driver.initialize_connection_snapshot(
+            self.snapshot, FakeConnector)
+        volume = Volume(id=self.snapshot.id,
+                        provider_location=self.snapshot.provider_location,
+                        lun_type='27',
+                        metadata=None)
+        self.assertEqual(1, iscsi_properties['data']['target_lun'])
+        mock_iscsi_init.assert_called_with(volume, FakeConnector)
+
     def test_initialize_connection_success_multipath_portgroup(self):
         temp_connector = copy.deepcopy(FakeConnector)
         temp_connector['multipath'] = True
@@ -2361,12 +2417,23 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase):
                           driver.initialize_connection,
                           self.volume, temp_connector)
 
+    @mock.patch.object(huawei_driver.HuaweiISCSIDriver,
+                       'terminate_connection')
+    def test_terminate_connection_snapshot_success(self, mock_iscsi_term):
+        self.driver.terminate_connection_snapshot(self.snapshot,
+                                                  FakeConnector)
+        volume = Volume(id=self.snapshot.id,
+                        provider_location=self.snapshot.provider_location,
+                        lun_type='27',
+                        metadata=None)
+        mock_iscsi_term.assert_called_with(volume, FakeConnector)
+
     def test_terminate_connection_success(self):
         self.driver.terminate_connection(self.volume, FakeConnector)
 
     def test_get_volume_status(self):
         data = self.driver.get_volume_stats()
-        self.assertEqual('2.0.7', data['driver_version'])
+        self.assertEqual('2.0.8', data['driver_version'])
 
     @mock.patch.object(rest_client.RestClient, 'get_lun_info',
                        return_value={"CAPACITY": 6291456})
@@ -3650,6 +3717,19 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
                                                            self.volume)
         self.assertEqual('1', lun_info['provider_location'])
 
+    @mock.patch.object(huawei_driver.HuaweiFCDriver,
+                       'initialize_connection',
+                       return_value={"data": {'target_lun': 1}})
+    def test_initialize_connection_snapshot_success(self, mock_fc_init):
+        iscsi_properties = self.driver.initialize_connection_snapshot(
+            self.snapshot, FakeConnector)
+        volume = Volume(id=self.snapshot.id,
+                        provider_location=self.snapshot.provider_location,
+                        lun_type='27',
+                        metadata=None)
+        self.assertEqual(1, iscsi_properties['data']['target_lun'])
+        mock_fc_init.assert_called_with(volume, FakeConnector)
+
     def test_initialize_connection_success(self):
         iscsi_properties = self.driver.initialize_connection(self.volume,
                                                              FakeConnector)
@@ -3683,6 +3763,17 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
                                                           FakeConnector)
         self.assertEqual(1, fc_properties['data']['target_lun'])
 
+    @mock.patch.object(huawei_driver.HuaweiFCDriver,
+                       'terminate_connection')
+    def test_terminate_connection_snapshot_success(self, mock_fc_term):
+        self.driver.terminate_connection_snapshot(self.snapshot,
+                                                  FakeConnector)
+        volume = Volume(id=self.snapshot.id,
+                        provider_location=self.snapshot.provider_location,
+                        lun_type='27',
+                        metadata=None)
+        mock_fc_term.assert_called_with(volume, FakeConnector)
+
     def test_terminate_connection_success(self):
         self.driver.client.terminateFlag = True
         self.driver.terminate_connection(self.volume, FakeConnector)
@@ -3715,7 +3806,7 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
             'get_remote_device_by_wwn',
             mock.Mock(return_value=remote_device_info))
         data = self.driver.get_volume_stats()
-        self.assertEqual('2.0.7', data['driver_version'])
+        self.assertEqual('2.0.8', data['driver_version'])
         self.assertTrue(data['pools'][0]['replication_enabled'])
         self.assertListEqual(['sync', 'async'],
                              data['pools'][0]['replication_type'])
@@ -3732,7 +3823,7 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
             'try_get_remote_wwn',
             mock.Mock(return_value={}))
         data = self.driver.get_volume_stats()
-        self.assertEqual('2.0.7', data['driver_version'])
+        self.assertEqual('2.0.8', data['driver_version'])
         self.assertNotIn('replication_enabled', data['pools'][0])
 
     def test_extend_volume(self):
diff --git a/cinder/tests/unit/test_volume.py b/cinder/tests/unit/test_volume.py
index 8f5e1796dac..542ceeb73a7 100644
--- a/cinder/tests/unit/test_volume.py
+++ b/cinder/tests/unit/test_volume.py
@@ -4553,6 +4553,10 @@ class VolumeTestCase(BaseVolumeTestCase):
                            'is_snapshot': False}
         self.assertEqual(expected_result, result)
 
+    def test_backup_use_temp_snapshot_config(self):
+        local_conf = self.volume.driver.configuration.local_conf
+        self.assertFalse(local_conf.backup_use_temp_snapshot)
+
     @mock.patch.object(QUOTAS, 'reserve',
                        side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION)
     def test_existing_snapshot_failed_quota_reserve(self, mock_reserve):
diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
index 44383e78d00..e3876ebe92e 100644
--- a/cinder/volume/driver.py
+++ b/cinder/volume/driver.py
@@ -258,6 +258,11 @@ volume_opts = [
                choices=['iscsi', 'fc'],
                help='Protocol for transferring data between host and '
                     'storage back-end.'),
+    cfg.BoolOpt('backup_use_temp_snapshot',
+                default=False,
+                help='If this is set to True, the backup_use_temp_snapshot '
+                     'path will be used during the backup. Otherwise, it '
+                     'will use backup_use_temp_volume path.'),
 ]
 
 # for backward compatibility
diff --git a/cinder/volume/drivers/huawei/constants.py b/cinder/volume/drivers/huawei/constants.py
index 140e2562cb6..63de3bd3211 100644
--- a/cinder/volume/drivers/huawei/constants.py
+++ b/cinder/volume/drivers/huawei/constants.py
@@ -19,6 +19,8 @@ STATUS_RUNNING = '10'
 STATUS_VOLUME_READY = '27'
 STATUS_LUNCOPY_READY = '40'
 STATUS_QOS_ACTIVE = '2'
+LUN_TYPE = '11'
+SNAPSHOT_TYPE = '27'
 
 BLOCK_STORAGE_POOL_TYPE = '1'
 FILE_SYSTEM_POOL_TYPE = '2'
diff --git a/cinder/volume/drivers/huawei/fc_zone_helper.py b/cinder/volume/drivers/huawei/fc_zone_helper.py
index ff196642e33..03c0943b580 100644
--- a/cinder/volume/drivers/huawei/fc_zone_helper.py
+++ b/cinder/volume/drivers/huawei/fc_zone_helper.py
@@ -61,7 +61,7 @@ class FCZoneHelper(object):
                       {"portg": portg, "views": views[0]})
             # In fact, there is just one view for one port group.
             lungroup = self.client.get_lungroup_by_view(views[0])
-            lun_num = self.client.get_lunnum_from_lungroup(lungroup)
+            lun_num = self.client.get_obj_count_from_lungroup(lungroup)
             ports_in_portg = self.client.get_ports_by_portg(portg)
             LOG.debug("PortGroup %(portg)s contains ports: %(ports)s.",
                       {"portg": portg, "ports": ports_in_portg})
@@ -133,10 +133,11 @@ class FCZoneHelper(object):
                    'initiators': fabric_connected_initiators})
         return fabric_connected_ports, fabric_connected_initiators
 
-    def _get_lun_engine_contrs(self, engines, lun_id):
+    def _get_lun_engine_contrs(self, engines, lun_id,
+                               lun_type=constants.LUN_TYPE):
         contrs = []
         engine_id = None
-        lun_info = self.client.get_lun_info(lun_id)
+        lun_info = self.client.get_lun_info(lun_id, lun_type)
         lun_contr_id = lun_info['OWNINGCONTROLLER']
         for engine in engines:
             contrs = json.loads(engine['NODELIST'])
@@ -172,11 +173,13 @@ class FCZoneHelper(object):
         new_portg_id = self.client.create_portg(portg_name, description)
         return new_portg_id
 
-    def build_ini_targ_map(self, wwns, host_id, lun_id):
+    def build_ini_targ_map(self, wwns, host_id, lun_id,
+                           lun_type=constants.LUN_TYPE):
         engines = self.client.get_all_engines()
         LOG.debug("Get array engines: %s", engines)
 
-        contrs, engine_id = self._get_lun_engine_contrs(engines, lun_id)
+        contrs, engine_id = self._get_lun_engine_contrs(engines, lun_id,
+                                                        lun_type)
 
         # Check if there is already a port group in the view.
         # If yes and have already considered the engine,
diff --git a/cinder/volume/drivers/huawei/huawei_driver.py b/cinder/volume/drivers/huawei/huawei_driver.py
index 89933acc46a..595df666578 100644
--- a/cinder/volume/drivers/huawei/huawei_driver.py
+++ b/cinder/volume/drivers/huawei/huawei_driver.py
@@ -74,6 +74,8 @@ CONF.register_opts(huawei_opts)
 
 snap_attrs = ('id', 'volume_id', 'volume', 'provider_location')
 Snapshot = collections.namedtuple('Snapshot', snap_attrs)
+vol_attrs = ('id', 'lun_type', 'provider_location', 'metadata')
+Volume = collections.namedtuple('Volume', vol_attrs)
 
 
 class HuaweiBaseDriver(driver.VolumeDriver):
@@ -1158,6 +1160,18 @@ class HuaweiBaseDriver(driver.VolumeDriver):
         """Remove an export for a volume."""
         pass
 
+    def create_export_snapshot(self, context, snapshot, connector):
+        """Export a snapshot."""
+        pass
+
+    def remove_export_snapshot(self, context, snapshot):
+        """Remove an export for a snapshot."""
+        pass
+
+    def backup_use_temp_snapshot(self):
+        # This config option has a default to be False, So just return it.
+        return self.configuration.safe_get("backup_use_temp_snapshot")
+
     def _copy_volume(self, volume, copy_name, src_lun, tgt_lun):
         luncopy_id = self.client.create_luncopy(copy_name,
                                                 src_lun,
@@ -1756,6 +1770,37 @@ class HuaweiBaseDriver(driver.VolumeDriver):
 
         return secondary_id, volumes_update
 
+    def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
+        """Map a snapshot to a host and return target iSCSI information."""
+        # From the volume structure.
+        volume = Volume(id=snapshot.id,
+                        provider_location=snapshot.provider_location,
+                        lun_type=constants.SNAPSHOT_TYPE,
+                        metadata=None)
+
+        return self.initialize_connection(volume, connector)
+
+    def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
+        """Delete map between a snapshot and a host."""
+        # From the volume structure.
+        volume = Volume(id=snapshot.id,
+                        provider_location=snapshot.provider_location,
+                        lun_type=constants.SNAPSHOT_TYPE,
+                        metadata=None)
+
+        return self.terminate_connection(volume, connector)
+
+    def get_lun_id_and_type(self, volume):
+        if hasattr(volume, 'lun_type'):
+            lun_id = volume.provider_location
+            lun_type = constants.SNAPSHOT_TYPE
+        else:
+            lun_id = self._check_volume_exist_on_array(
+                volume, constants.VOLUME_NOT_EXISTS_RAISE)
+            lun_type = constants.LUN_TYPE
+
+        return lun_id, lun_type
+
 
 @interface.volumedriver
 class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
@@ -1784,9 +1829,10 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
                 Hypermetro consistency group support
                 Consistency group support
                 Cgsnapshot support
+        2.0.8 - Backup snapshot optimal path support
     """
 
-    VERSION = "2.0.7"
+    VERSION = "2.0.8"
 
     def __init__(self, *args, **kwargs):
         super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
@@ -1804,9 +1850,7 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
     @utils.synchronized('huawei', external=True)
     def initialize_connection(self, volume, connector):
         """Map a volume to a host and return target iSCSI information."""
-        lun_id = self._check_volume_exist_on_array(
-            volume, constants.VOLUME_NOT_EXISTS_RAISE)
-
+        lun_id, lun_type = self.get_lun_id_and_type(volume)
         initiator_name = connector['initiator']
         LOG.info(_LI(
             'initiator name: %(initiator_name)s, '
@@ -1837,9 +1881,11 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
 
         # Mapping lungroup and hostgroup to view.
         self.client.do_mapping(lun_id, hostgroup_id,
-                               host_id, portgroup_id)
+                               host_id, portgroup_id,
+                               lun_type)
 
-        hostlun_id = self.client.get_host_lun_id(host_id, lun_id)
+        hostlun_id = self.client.get_host_lun_id(host_id, lun_id,
+                                                 lun_type)
 
         LOG.info(_LI("initialize_connection, host lun id is: %s."),
                  hostlun_id)
@@ -1877,8 +1923,7 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
     @utils.synchronized('huawei', external=True)
     def terminate_connection(self, volume, connector, **kwargs):
         """Delete map between a volume and a host."""
-        lun_id = self._check_volume_exist_on_array(
-            volume, constants.VOLUME_NOT_EXISTS_WARN)
+        lun_id, lun_type = self.get_lun_id_and_type(volume)
         initiator_name = connector['initiator']
         host_name = connector['host']
         lungroup_id = None
@@ -1912,10 +1957,12 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
 
         # Remove lun from lungroup.
         if lun_id and lungroup_id:
-            lungroup_ids = self.client.get_lungroupids_by_lunid(lun_id)
+            lungroup_ids = self.client.get_lungroupids_by_lunid(
+                lun_id, lun_type)
             if lungroup_id in lungroup_ids:
                 self.client.remove_lun_from_lungroup(lungroup_id,
-                                                     lun_id)
+                                                     lun_id,
+                                                     lun_type)
             else:
                 LOG.warning(_LW("LUN is not in lungroup. "
                                 "LUN ID: %(lun_id)s. "
@@ -1925,7 +1972,7 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
 
         # Remove portgroup from mapping view if no lun left in lungroup.
         if lungroup_id:
-            left_lunnum = self.client.get_lunnum_from_lungroup(lungroup_id)
+            left_lunnum = self.client.get_obj_count_from_lungroup(lungroup_id)
 
         if portgroup_id and view_id and (int(left_lunnum) <= 0):
             if self.client.is_portgroup_associated_to_view(view_id,
@@ -1981,9 +2028,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
                 Hypermetro consistency group support
                 Consistency group support
                 Cgsnapshot support
+        2.0.8 - Backup snapshot optimal path support
     """
 
-    VERSION = "2.0.7"
+    VERSION = "2.0.8"
 
     def __init__(self, *args, **kwargs):
         super(HuaweiFCDriver, self).__init__(*args, **kwargs)
@@ -2002,9 +2050,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
     @utils.synchronized('huawei', external=True)
     @fczm_utils.AddFCZone
     def initialize_connection(self, volume, connector):
-        lun_id = self._check_volume_exist_on_array(
-            volume, constants.VOLUME_NOT_EXISTS_RAISE)
-
+        lun_id, lun_type = self.get_lun_id_and_type(volume)
         wwns = connector['wwpns']
         LOG.info(_LI(
             'initialize_connection, initiator: %(wwpns)s,'
@@ -2027,7 +2073,8 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
             zone_helper = fc_zone_helper.FCZoneHelper(self.fcsan, self.client)
             try:
                 (tgt_port_wwns, portg_id, init_targ_map) = (
-                    zone_helper.build_ini_targ_map(wwns, host_id, lun_id))
+                    zone_helper.build_ini_targ_map(wwns, host_id, lun_id,
+                                                   lun_type))
             except Exception as err:
                 self.remove_host_with_check(host_id)
                 msg = _('build_ini_targ_map fails. %s') % err
@@ -2065,8 +2112,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
         # Add host into hostgroup.
         hostgroup_id = self.client.add_host_to_hostgroup(host_id)
         map_info = self.client.do_mapping(lun_id, hostgroup_id,
-                                          host_id, portg_id)
-        host_lun_id = self.client.get_host_lun_id(host_id, lun_id)
+                                          host_id, portg_id,
+                                          lun_type)
+        host_lun_id = self.client.get_host_lun_id(host_id, lun_id,
+                                                  lun_type)
 
         # Return FC properties.
         fc_info = {'driver_volume_type': 'fibre_channel',
@@ -2143,9 +2192,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
     @fczm_utils.RemoveFCZone
     def terminate_connection(self, volume, connector, **kwargs):
         """Delete map between a volume and a host."""
-        lun_id = self._check_volume_exist_on_array(
-            volume, constants.VOLUME_NOT_EXISTS_WARN)
-
+        lun_id, lun_type = self.get_lun_id_and_type(volume)
         wwns = connector['wwpns']
 
         host_name = connector['host']
@@ -2165,10 +2212,12 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
                 lungroup_id = self.client.find_lungroup_from_map(view_id)
 
         if lun_id and lungroup_id:
-            lungroup_ids = self.client.get_lungroupids_by_lunid(lun_id)
+            lungroup_ids = self.client.get_lungroupids_by_lunid(lun_id,
+                                                                lun_type)
             if lungroup_id in lungroup_ids:
                 self.client.remove_lun_from_lungroup(lungroup_id,
-                                                     lun_id)
+                                                     lun_id,
+                                                     lun_type)
             else:
                 LOG.warning(_LW("LUN is not in lungroup. "
                                 "LUN ID: %(lun_id)s. "
@@ -2179,7 +2228,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
         else:
             LOG.warning(_LW("Can't find lun on the array."))
         if lungroup_id:
-            left_lunnum = self.client.get_lunnum_from_lungroup(lungroup_id)
+            left_lunnum = self.client.get_obj_count_from_lungroup(lungroup_id)
         if int(left_lunnum) > 0:
             fc_info = {'driver_volume_type': 'fibre_channel',
                        'data': {}}
diff --git a/cinder/volume/drivers/huawei/hypermetro.py b/cinder/volume/drivers/huawei/hypermetro.py
index 468d60bb457..535dd938bf9 100644
--- a/cinder/volume/drivers/huawei/hypermetro.py
+++ b/cinder/volume/drivers/huawei/hypermetro.py
@@ -235,7 +235,7 @@ class HuaweiHyperMetro(object):
                 lungroup_id = self.rmt_client.find_lungroup_from_map(
                     view_id)
         if lungroup_id:
-            left_lunnum = self.rmt_client.get_lunnum_from_lungroup(
+            left_lunnum = self.rmt_client.get_obj_count_from_lungroup(
                 lungroup_id)
 
         if int(left_lunnum) > 0:
diff --git a/cinder/volume/drivers/huawei/rest_client.py b/cinder/volume/drivers/huawei/rest_client.py
index 84098d2c3b8..ffd94f12906 100644
--- a/cinder/volume/drivers/huawei/rest_client.py
+++ b/cinder/volume/drivers/huawei/rest_client.py
@@ -414,7 +414,8 @@ class RestClient(object):
             return True
         return False
 
-    def do_mapping(self, lun_id, hostgroup_id, host_id, portgroup_id=None):
+    def do_mapping(self, lun_id, hostgroup_id, host_id, portgroup_id=None,
+                   lun_type=constants.LUN_TYPE):
         """Add hostgroup and lungroup to mapping view."""
         lungroup_name = constants.LUNGROUP_PREFIX + host_id
         mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
@@ -434,9 +435,10 @@ class RestClient(object):
             if lungroup_id is None:
                 lungroup_id = self._create_lungroup(lungroup_name)
             is_associated = self._is_lun_associated_to_lungroup(lungroup_id,
-                                                                lun_id)
+                                                                lun_id,
+                                                                lun_type)
             if not is_associated:
-                self.associate_lun_to_lungroup(lungroup_id, lun_id)
+                self.associate_lun_to_lungroup(lungroup_id, lun_id, lun_type)
 
             if view_id is None:
                 view_id = self._add_mapping_view(mapping_view_name)
@@ -468,7 +470,7 @@ class RestClient(object):
                 LOG.error(_LE(
                     'Error occurred when adding hostgroup and lungroup to '
                     'view. Remove lun from lungroup now.'))
-                self.remove_lun_from_lungroup(lungroup_id, lun_id)
+                self.remove_lun_from_lungroup(lungroup_id, lun_id, lun_type)
 
         return map_info
 
@@ -602,9 +604,10 @@ class RestClient(object):
             return True
         return False
 
-    def get_host_lun_id(self, host_id, lun_id):
-        url = ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21"
-               "&ASSOCIATEOBJID=%s" % (host_id))
+    def get_host_lun_id(self, host_id, lun_id, lun_type=constants.LUN_TYPE):
+        cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
+        url = ("/%s/associate?TYPE=%s&ASSOCIATEOBJTYPE=21"
+               "&ASSOCIATEOBJID=%s" % (cmd_type, lun_type, host_id))
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Find host lun id error.'))
 
@@ -692,10 +695,13 @@ class RestClient(object):
 
         return False
 
-    def _is_lun_associated_to_lungroup(self, lungroup_id, lun_id):
+    def _is_lun_associated_to_lungroup(self, lungroup_id, lun_id,
+                                       lun_type=constants.LUN_TYPE):
         """Check whether the lun is associated to the lungroup."""
-        url = ("/lun/associate?TYPE=11&"
-               "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroup_id)
+        cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
+        url = ("/%s/associate?TYPE=%s&"
+               "ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s"
+               % (cmd_type, lun_type, lungroup_id))
 
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Check lungroup associate error.'))
@@ -716,19 +722,21 @@ class RestClient(object):
         self._assert_rest_result(result, _('Associate host to hostgroup '
                                  'error.'))
 
-    def associate_lun_to_lungroup(self, lungroup_id, lun_id):
+    def associate_lun_to_lungroup(self, lungroup_id, lun_id,
+                                  lun_type=constants.LUN_TYPE):
         """Associate lun to lungroup."""
         url = "/lungroup/associate"
         data = {"ID": lungroup_id,
-                "ASSOCIATEOBJTYPE": "11",
+                "ASSOCIATEOBJTYPE": lun_type,
                 "ASSOCIATEOBJID": lun_id}
         result = self.call(url, data)
         self._assert_rest_result(result, _('Associate lun to lungroup error.'))
 
-    def remove_lun_from_lungroup(self, lungroup_id, lun_id):
+    def remove_lun_from_lungroup(self, lungroup_id, lun_id,
+                                 lun_type=constants.LUN_TYPE):
         """Remove lun from lungroup."""
-        url = ("/lungroup/associate?ID=%s&ASSOCIATEOBJTYPE=11"
-               "&ASSOCIATEOBJID=%s" % (lungroup_id, lun_id))
+        url = ("/lungroup/associate?ID=%s&ASSOCIATEOBJTYPE=%s"
+               "&ASSOCIATEOBJID=%s" % (lungroup_id, lun_type, lun_id))
 
         result = self.call(url, None, 'DELETE')
         self._assert_rest_result(
@@ -942,16 +950,25 @@ class RestClient(object):
         result = self.call(url, None, "DELETE")
         self._assert_rest_result(result, _('Delete mapping view error.'))
 
-    def get_lunnum_from_lungroup(self, lungroup_id):
-        """Check if there are still other luns associated to the lungroup."""
+    def get_obj_count_from_lungroup(self, lungroup_id):
+        """Get all objects count associated to the lungroup."""
+        lun_count = self._get_obj_count_from_lungroup_by_type(
+            lungroup_id, constants.LUN_TYPE)
+        snapshot_count = self._get_obj_count_from_lungroup_by_type(
+            lungroup_id, constants.SNAPSHOT_TYPE)
+        return int(lun_count) + int(snapshot_count)
+
+    def _get_obj_count_from_lungroup_by_type(self, lungroup_id,
+                                             lun_type=constants.LUN_TYPE):
+        cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
         lunnum = 0
         if not lungroup_id:
             return lunnum
 
-        url = ("/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256&"
-               "ASSOCIATEOBJID=%s" % lungroup_id)
+        url = ("/%s/count?TYPE=%s&ASSOCIATEOBJTYPE=256&"
+               "ASSOCIATEOBJID=%s" % (cmd_type, lun_type, lungroup_id))
         result = self.call(url, None, "GET")
-        self._assert_rest_result(result, _('Find lun number error.'))
+        self._assert_rest_result(result, _('Find obj number error.'))
         if 'data' in result:
             lunnum = int(result['data']['COUNT'])
         return lunnum
@@ -1457,10 +1474,10 @@ class RestClient(object):
 
         return result['data']['IOCLASSID']
 
-    def get_lungroupids_by_lunid(self, lun_id):
+    def get_lungroupids_by_lunid(self, lun_id, lun_type=constants.LUN_TYPE):
         """Get lungroup ids by lun id."""
         url = ("/lungroup/associate?TYPE=256"
-               "&ASSOCIATEOBJTYPE=11&ASSOCIATEOBJID=%s" % lun_id)
+               "&ASSOCIATEOBJTYPE=%s&ASSOCIATEOBJID=%s" % (lun_type, lun_id))
 
         result = self.call(url, None, "GET")
         self._assert_rest_result(result, _('Get lungroup id by lun id error.'))
@@ -1472,8 +1489,9 @@ class RestClient(object):
 
         return lungroup_ids
 
-    def get_lun_info(self, lun_id):
-        url = "/lun/" + lun_id
+    def get_lun_info(self, lun_id, lun_type = constants.LUN_TYPE):
+        cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
+        url = ("/%s/%s" % (cmd_type, lun_id))
         result = self.call(url, None, "GET")
 
         msg = _('Get volume error.')
diff --git a/releasenotes/notes/backup-snapshot-6e7447db930c31f6.yaml b/releasenotes/notes/backup-snapshot-6e7447db930c31f6.yaml
new file mode 100644
index 00000000000..9a65c19c254
--- /dev/null
+++ b/releasenotes/notes/backup-snapshot-6e7447db930c31f6.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Huawei support backup snapshot optimal path
\ No newline at end of file