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