Huawei: Support backup snapshot

Implement attach snapshot for backup snapshot feature
in Huawei driver.

DocImpact
Implements: blueprint huawei-support-backup-snapshot

Change-Id: I0173d397760bc17c01af792b4c4cc2bcbf45fedd
This commit is contained in:
huananhuawei 2016-06-14 17:28:26 +08:00 committed by huanan
parent eccc58f7af
commit 3eb855260e
9 changed files with 232 additions and 57 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Tests for huawei drivers.""" """Tests for huawei drivers."""
import collections
import copy import copy
import ddt import ddt
import json import json
@ -44,6 +45,9 @@ from cinder.volume import volume_types
admin_contex = context.get_admin_context() admin_contex = context.get_admin_context()
vol_attrs = ('id', 'lun_type', 'provider_location', 'metadata')
Volume = collections.namedtuple('Volume', vol_attrs)
PROVIDER_LOCATION = '11' PROVIDER_LOCATION = '11'
HOST = 'ubuntu001@backend001#OpenStack_Pool' HOST = 'ubuntu001@backend001#OpenStack_Pool'
ID = '21ec7341-9256-497b-97d9-ef48edcf0635' 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 # A fake response of get iscsi response
FAKE_GET_ISCSI_INFO_RESPONSE = """ FAKE_GET_ISCSI_INFO_RESPONSE = """
@ -1208,6 +1224,14 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/lun/associate/cachepartition?ID=1'
'/DELETE'] = ( '/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE) 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'] = ( MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup?range=[0-8191]/GET'] = (
FAKE_QUERY_LUN_GROUP_INFO_RESPONSE) FAKE_QUERY_LUN_GROUP_INFO_RESPONSE)
@ -1236,10 +1260,26 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/lungroup/associate?ID=11&ASSOCIATEOBJTYPE=11'
'&ASSOCIATEOBJID=11/DELETE'] = ( '&ASSOCIATEOBJID=11/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE) 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' MAP_COMMAND_TO_FAKE_RESPONSE['/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256'
'&ASSOCIATEOBJID=11/GET'] = ( '&ASSOCIATEOBJID=11/GET'] = (
FAKE_LUN_COUNT_RESPONSE) 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'] = ( MAP_COMMAND_TO_FAKE_RESPONSE['/lun/expand/PUT'] = (
FAKE_LUN_INFO_RESPONSE) FAKE_LUN_INFO_RESPONSE)
@ -2104,6 +2144,9 @@ class HuaweiTestBase(test.TestCase):
def setUp(self): def setUp(self):
super(HuaweiTestBase, self).setUp() 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( self.volume = fake_volume.fake_volume_obj(
admin_contex, host=HOST, provider_location=PROVIDER_LOCATION, 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(driver_data, model_update['replication_driver_data'])
self.assertEqual('available', model_update['replication_status']) 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): def test_initialize_connection_success_multipath_portgroup(self):
temp_connector = copy.deepcopy(FakeConnector) temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True temp_connector['multipath'] = True
@ -2361,12 +2417,23 @@ class HuaweiISCSIDriverTestCase(HuaweiTestBase):
driver.initialize_connection, driver.initialize_connection,
self.volume, temp_connector) 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): def test_terminate_connection_success(self):
self.driver.terminate_connection(self.volume, FakeConnector) self.driver.terminate_connection(self.volume, FakeConnector)
def test_get_volume_status(self): def test_get_volume_status(self):
data = self.driver.get_volume_stats() 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', @mock.patch.object(rest_client.RestClient, 'get_lun_info',
return_value={"CAPACITY": 6291456}) return_value={"CAPACITY": 6291456})
@ -3650,6 +3717,19 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
self.volume) self.volume)
self.assertEqual('1', lun_info['provider_location']) 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): def test_initialize_connection_success(self):
iscsi_properties = self.driver.initialize_connection(self.volume, iscsi_properties = self.driver.initialize_connection(self.volume,
FakeConnector) FakeConnector)
@ -3683,6 +3763,17 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
FakeConnector) FakeConnector)
self.assertEqual(1, fc_properties['data']['target_lun']) 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): def test_terminate_connection_success(self):
self.driver.client.terminateFlag = True self.driver.client.terminateFlag = True
self.driver.terminate_connection(self.volume, FakeConnector) self.driver.terminate_connection(self.volume, FakeConnector)
@ -3715,7 +3806,7 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
'get_remote_device_by_wwn', 'get_remote_device_by_wwn',
mock.Mock(return_value=remote_device_info)) mock.Mock(return_value=remote_device_info))
data = self.driver.get_volume_stats() 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.assertTrue(data['pools'][0]['replication_enabled'])
self.assertListEqual(['sync', 'async'], self.assertListEqual(['sync', 'async'],
data['pools'][0]['replication_type']) data['pools'][0]['replication_type'])
@ -3732,7 +3823,7 @@ class HuaweiFCDriverTestCase(HuaweiTestBase):
'try_get_remote_wwn', 'try_get_remote_wwn',
mock.Mock(return_value={})) mock.Mock(return_value={}))
data = self.driver.get_volume_stats() 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]) self.assertNotIn('replication_enabled', data['pools'][0])
def test_extend_volume(self): def test_extend_volume(self):

View File

@ -4553,6 +4553,10 @@ class VolumeTestCase(BaseVolumeTestCase):
'is_snapshot': False} 'is_snapshot': False}
self.assertEqual(expected_result, result) 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', @mock.patch.object(QUOTAS, 'reserve',
side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION) side_effect = OVER_SNAPSHOT_QUOTA_EXCEPTION)
def test_existing_snapshot_failed_quota_reserve(self, mock_reserve): def test_existing_snapshot_failed_quota_reserve(self, mock_reserve):

View File

@ -258,6 +258,11 @@ volume_opts = [
choices=['iscsi', 'fc'], choices=['iscsi', 'fc'],
help='Protocol for transferring data between host and ' help='Protocol for transferring data between host and '
'storage back-end.'), '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 # for backward compatibility

View File

@ -19,6 +19,8 @@ STATUS_RUNNING = '10'
STATUS_VOLUME_READY = '27' STATUS_VOLUME_READY = '27'
STATUS_LUNCOPY_READY = '40' STATUS_LUNCOPY_READY = '40'
STATUS_QOS_ACTIVE = '2' STATUS_QOS_ACTIVE = '2'
LUN_TYPE = '11'
SNAPSHOT_TYPE = '27'
BLOCK_STORAGE_POOL_TYPE = '1' BLOCK_STORAGE_POOL_TYPE = '1'
FILE_SYSTEM_POOL_TYPE = '2' FILE_SYSTEM_POOL_TYPE = '2'

View File

@ -61,7 +61,7 @@ class FCZoneHelper(object):
{"portg": portg, "views": views[0]}) {"portg": portg, "views": views[0]})
# In fact, there is just one view for one port group. # In fact, there is just one view for one port group.
lungroup = self.client.get_lungroup_by_view(views[0]) 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) ports_in_portg = self.client.get_ports_by_portg(portg)
LOG.debug("PortGroup %(portg)s contains ports: %(ports)s.", LOG.debug("PortGroup %(portg)s contains ports: %(ports)s.",
{"portg": portg, "ports": ports_in_portg}) {"portg": portg, "ports": ports_in_portg})
@ -133,10 +133,11 @@ class FCZoneHelper(object):
'initiators': fabric_connected_initiators}) 'initiators': fabric_connected_initiators})
return fabric_connected_ports, 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 = [] contrs = []
engine_id = None 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'] lun_contr_id = lun_info['OWNINGCONTROLLER']
for engine in engines: for engine in engines:
contrs = json.loads(engine['NODELIST']) contrs = json.loads(engine['NODELIST'])
@ -172,11 +173,13 @@ class FCZoneHelper(object):
new_portg_id = self.client.create_portg(portg_name, description) new_portg_id = self.client.create_portg(portg_name, description)
return new_portg_id 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() engines = self.client.get_all_engines()
LOG.debug("Get array engines: %s", 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. # Check if there is already a port group in the view.
# If yes and have already considered the engine, # If yes and have already considered the engine,

View File

@ -74,6 +74,8 @@ CONF.register_opts(huawei_opts)
snap_attrs = ('id', 'volume_id', 'volume', 'provider_location') snap_attrs = ('id', 'volume_id', 'volume', 'provider_location')
Snapshot = collections.namedtuple('Snapshot', snap_attrs) Snapshot = collections.namedtuple('Snapshot', snap_attrs)
vol_attrs = ('id', 'lun_type', 'provider_location', 'metadata')
Volume = collections.namedtuple('Volume', vol_attrs)
class HuaweiBaseDriver(driver.VolumeDriver): class HuaweiBaseDriver(driver.VolumeDriver):
@ -1158,6 +1160,18 @@ class HuaweiBaseDriver(driver.VolumeDriver):
"""Remove an export for a volume.""" """Remove an export for a volume."""
pass 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): def _copy_volume(self, volume, copy_name, src_lun, tgt_lun):
luncopy_id = self.client.create_luncopy(copy_name, luncopy_id = self.client.create_luncopy(copy_name,
src_lun, src_lun,
@ -1756,6 +1770,37 @@ class HuaweiBaseDriver(driver.VolumeDriver):
return secondary_id, volumes_update 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 @interface.volumedriver
class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver): class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
@ -1784,9 +1829,10 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
Hypermetro consistency group support Hypermetro consistency group support
Consistency group support Consistency group support
Cgsnapshot support Cgsnapshot support
2.0.8 - Backup snapshot optimal path support
""" """
VERSION = "2.0.7" VERSION = "2.0.8"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(HuaweiISCSIDriver, self).__init__(*args, **kwargs) super(HuaweiISCSIDriver, self).__init__(*args, **kwargs)
@ -1804,9 +1850,7 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
@utils.synchronized('huawei', external=True) @utils.synchronized('huawei', external=True)
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
"""Map a volume to a host and return target iSCSI information.""" """Map a volume to a host and return target iSCSI information."""
lun_id = self._check_volume_exist_on_array( lun_id, lun_type = self.get_lun_id_and_type(volume)
volume, constants.VOLUME_NOT_EXISTS_RAISE)
initiator_name = connector['initiator'] initiator_name = connector['initiator']
LOG.info(_LI( LOG.info(_LI(
'initiator name: %(initiator_name)s, ' 'initiator name: %(initiator_name)s, '
@ -1837,9 +1881,11 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
# Mapping lungroup and hostgroup to view. # Mapping lungroup and hostgroup to view.
self.client.do_mapping(lun_id, hostgroup_id, 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."), LOG.info(_LI("initialize_connection, host lun id is: %s."),
hostlun_id) hostlun_id)
@ -1877,8 +1923,7 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
@utils.synchronized('huawei', external=True) @utils.synchronized('huawei', external=True)
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
"""Delete map between a volume and a host.""" """Delete map between a volume and a host."""
lun_id = self._check_volume_exist_on_array( lun_id, lun_type = self.get_lun_id_and_type(volume)
volume, constants.VOLUME_NOT_EXISTS_WARN)
initiator_name = connector['initiator'] initiator_name = connector['initiator']
host_name = connector['host'] host_name = connector['host']
lungroup_id = None lungroup_id = None
@ -1912,10 +1957,12 @@ class HuaweiISCSIDriver(HuaweiBaseDriver, driver.ISCSIDriver):
# Remove lun from lungroup. # Remove lun from lungroup.
if lun_id and lungroup_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: if lungroup_id in lungroup_ids:
self.client.remove_lun_from_lungroup(lungroup_id, self.client.remove_lun_from_lungroup(lungroup_id,
lun_id) lun_id,
lun_type)
else: else:
LOG.warning(_LW("LUN is not in lungroup. " LOG.warning(_LW("LUN is not in lungroup. "
"LUN ID: %(lun_id)s. " "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. # Remove portgroup from mapping view if no lun left in lungroup.
if lungroup_id: 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 portgroup_id and view_id and (int(left_lunnum) <= 0):
if self.client.is_portgroup_associated_to_view(view_id, if self.client.is_portgroup_associated_to_view(view_id,
@ -1981,9 +2028,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
Hypermetro consistency group support Hypermetro consistency group support
Consistency group support Consistency group support
Cgsnapshot support Cgsnapshot support
2.0.8 - Backup snapshot optimal path support
""" """
VERSION = "2.0.7" VERSION = "2.0.8"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(HuaweiFCDriver, self).__init__(*args, **kwargs) super(HuaweiFCDriver, self).__init__(*args, **kwargs)
@ -2002,9 +2050,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
@utils.synchronized('huawei', external=True) @utils.synchronized('huawei', external=True)
@fczm_utils.AddFCZone @fczm_utils.AddFCZone
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
lun_id = self._check_volume_exist_on_array( lun_id, lun_type = self.get_lun_id_and_type(volume)
volume, constants.VOLUME_NOT_EXISTS_RAISE)
wwns = connector['wwpns'] wwns = connector['wwpns']
LOG.info(_LI( LOG.info(_LI(
'initialize_connection, initiator: %(wwpns)s,' 'initialize_connection, initiator: %(wwpns)s,'
@ -2027,7 +2073,8 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
zone_helper = fc_zone_helper.FCZoneHelper(self.fcsan, self.client) zone_helper = fc_zone_helper.FCZoneHelper(self.fcsan, self.client)
try: try:
(tgt_port_wwns, portg_id, init_targ_map) = ( (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: except Exception as err:
self.remove_host_with_check(host_id) self.remove_host_with_check(host_id)
msg = _('build_ini_targ_map fails. %s') % err msg = _('build_ini_targ_map fails. %s') % err
@ -2065,8 +2112,10 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
# Add host into hostgroup. # Add host into hostgroup.
hostgroup_id = self.client.add_host_to_hostgroup(host_id) hostgroup_id = self.client.add_host_to_hostgroup(host_id)
map_info = self.client.do_mapping(lun_id, hostgroup_id, map_info = self.client.do_mapping(lun_id, hostgroup_id,
host_id, portg_id) host_id, portg_id,
host_lun_id = self.client.get_host_lun_id(host_id, lun_id) lun_type)
host_lun_id = self.client.get_host_lun_id(host_id, lun_id,
lun_type)
# Return FC properties. # Return FC properties.
fc_info = {'driver_volume_type': 'fibre_channel', fc_info = {'driver_volume_type': 'fibre_channel',
@ -2143,9 +2192,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
@fczm_utils.RemoveFCZone @fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
"""Delete map between a volume and a host.""" """Delete map between a volume and a host."""
lun_id = self._check_volume_exist_on_array( lun_id, lun_type = self.get_lun_id_and_type(volume)
volume, constants.VOLUME_NOT_EXISTS_WARN)
wwns = connector['wwpns'] wwns = connector['wwpns']
host_name = connector['host'] host_name = connector['host']
@ -2165,10 +2212,12 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
lungroup_id = self.client.find_lungroup_from_map(view_id) lungroup_id = self.client.find_lungroup_from_map(view_id)
if lun_id and lungroup_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: if lungroup_id in lungroup_ids:
self.client.remove_lun_from_lungroup(lungroup_id, self.client.remove_lun_from_lungroup(lungroup_id,
lun_id) lun_id,
lun_type)
else: else:
LOG.warning(_LW("LUN is not in lungroup. " LOG.warning(_LW("LUN is not in lungroup. "
"LUN ID: %(lun_id)s. " "LUN ID: %(lun_id)s. "
@ -2179,7 +2228,7 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
else: else:
LOG.warning(_LW("Can't find lun on the array.")) LOG.warning(_LW("Can't find lun on the array."))
if lungroup_id: 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: if int(left_lunnum) > 0:
fc_info = {'driver_volume_type': 'fibre_channel', fc_info = {'driver_volume_type': 'fibre_channel',
'data': {}} 'data': {}}

View File

@ -235,7 +235,7 @@ class HuaweiHyperMetro(object):
lungroup_id = self.rmt_client.find_lungroup_from_map( lungroup_id = self.rmt_client.find_lungroup_from_map(
view_id) view_id)
if lungroup_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) lungroup_id)
if int(left_lunnum) > 0: if int(left_lunnum) > 0:

View File

@ -414,7 +414,8 @@ class RestClient(object):
return True return True
return False 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.""" """Add hostgroup and lungroup to mapping view."""
lungroup_name = constants.LUNGROUP_PREFIX + host_id lungroup_name = constants.LUNGROUP_PREFIX + host_id
mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
@ -434,9 +435,10 @@ class RestClient(object):
if lungroup_id is None: if lungroup_id is None:
lungroup_id = self._create_lungroup(lungroup_name) lungroup_id = self._create_lungroup(lungroup_name)
is_associated = self._is_lun_associated_to_lungroup(lungroup_id, is_associated = self._is_lun_associated_to_lungroup(lungroup_id,
lun_id) lun_id,
lun_type)
if not is_associated: 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: if view_id is None:
view_id = self._add_mapping_view(mapping_view_name) view_id = self._add_mapping_view(mapping_view_name)
@ -468,7 +470,7 @@ class RestClient(object):
LOG.error(_LE( LOG.error(_LE(
'Error occurred when adding hostgroup and lungroup to ' 'Error occurred when adding hostgroup and lungroup to '
'view. Remove lun from lungroup now.')) '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 return map_info
@ -602,9 +604,10 @@ class RestClient(object):
return True return True
return False return False
def get_host_lun_id(self, host_id, lun_id): def get_host_lun_id(self, host_id, lun_id, lun_type=constants.LUN_TYPE):
url = ("/lun/associate?TYPE=11&ASSOCIATEOBJTYPE=21" cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
"&ASSOCIATEOBJID=%s" % (host_id)) url = ("/%s/associate?TYPE=%s&ASSOCIATEOBJTYPE=21"
"&ASSOCIATEOBJID=%s" % (cmd_type, lun_type, host_id))
result = self.call(url, None, "GET") result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Find host lun id error.')) self._assert_rest_result(result, _('Find host lun id error.'))
@ -692,10 +695,13 @@ class RestClient(object):
return False 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.""" """Check whether the lun is associated to the lungroup."""
url = ("/lun/associate?TYPE=11&" cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
"ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s" % lungroup_id) url = ("/%s/associate?TYPE=%s&"
"ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=%s"
% (cmd_type, lun_type, lungroup_id))
result = self.call(url, None, "GET") result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Check lungroup associate error.')) 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 ' self._assert_rest_result(result, _('Associate host to hostgroup '
'error.')) '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.""" """Associate lun to lungroup."""
url = "/lungroup/associate" url = "/lungroup/associate"
data = {"ID": lungroup_id, data = {"ID": lungroup_id,
"ASSOCIATEOBJTYPE": "11", "ASSOCIATEOBJTYPE": lun_type,
"ASSOCIATEOBJID": lun_id} "ASSOCIATEOBJID": lun_id}
result = self.call(url, data) result = self.call(url, data)
self._assert_rest_result(result, _('Associate lun to lungroup error.')) 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.""" """Remove lun from lungroup."""
url = ("/lungroup/associate?ID=%s&ASSOCIATEOBJTYPE=11" url = ("/lungroup/associate?ID=%s&ASSOCIATEOBJTYPE=%s"
"&ASSOCIATEOBJID=%s" % (lungroup_id, lun_id)) "&ASSOCIATEOBJID=%s" % (lungroup_id, lun_type, lun_id))
result = self.call(url, None, 'DELETE') result = self.call(url, None, 'DELETE')
self._assert_rest_result( self._assert_rest_result(
@ -942,16 +950,25 @@ class RestClient(object):
result = self.call(url, None, "DELETE") result = self.call(url, None, "DELETE")
self._assert_rest_result(result, _('Delete mapping view error.')) self._assert_rest_result(result, _('Delete mapping view error.'))
def get_lunnum_from_lungroup(self, lungroup_id): def get_obj_count_from_lungroup(self, lungroup_id):
"""Check if there are still other luns associated to the lungroup.""" """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 lunnum = 0
if not lungroup_id: if not lungroup_id:
return lunnum return lunnum
url = ("/lun/count?TYPE=11&ASSOCIATEOBJTYPE=256&" url = ("/%s/count?TYPE=%s&ASSOCIATEOBJTYPE=256&"
"ASSOCIATEOBJID=%s" % lungroup_id) "ASSOCIATEOBJID=%s" % (cmd_type, lun_type, lungroup_id))
result = self.call(url, None, "GET") 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: if 'data' in result:
lunnum = int(result['data']['COUNT']) lunnum = int(result['data']['COUNT'])
return lunnum return lunnum
@ -1457,10 +1474,10 @@ class RestClient(object):
return result['data']['IOCLASSID'] 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.""" """Get lungroup ids by lun id."""
url = ("/lungroup/associate?TYPE=256" 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") result = self.call(url, None, "GET")
self._assert_rest_result(result, _('Get lungroup id by lun id error.')) self._assert_rest_result(result, _('Get lungroup id by lun id error.'))
@ -1472,8 +1489,9 @@ class RestClient(object):
return lungroup_ids return lungroup_ids
def get_lun_info(self, lun_id): def get_lun_info(self, lun_id, lun_type = constants.LUN_TYPE):
url = "/lun/" + lun_id cmd_type = 'lun' if lun_type == constants.LUN_TYPE else 'snapshot'
url = ("/%s/%s" % (cmd_type, lun_id))
result = self.call(url, None, "GET") result = self.call(url, None, "GET")
msg = _('Get volume error.') msg = _('Get volume error.')

View File

@ -0,0 +1,3 @@
---
features:
- Huawei support backup snapshot optimal path