Merge "VMAX driver - Live Migration is dropping connection" into stable/newton

This commit is contained in:
Jenkins 2017-04-05 19:50:24 +00:00 committed by Gerrit Code Review
commit e4dc6ada79
5 changed files with 442 additions and 352 deletions

View File

@ -15,7 +15,6 @@
import os
import shutil
import sys
import tempfile
import unittest
from xml.dom import minidom
@ -269,6 +268,10 @@ class EMCVMAXCommonData(object):
'CreationClassName': 'CIM_DeviceMaskingGroup',
'ElementName': 'OS_default_GOLD1_SG',
'SystemName': 'SYMMETRIX+000195900551'}
sg_instance_name = {
'CreationClassName': 'CIM_DeviceMaskingGroup',
'ElementName': 'OS-fakehost-SRP_1-Bronze-DSS-I-SG',
'SystemName': 'SYMMETRIX+000195900551'}
storage_system = 'SYMMETRIX+000195900551'
storage_system_v3 = 'SYMMETRIX-+-000197200056'
port_group = 'OS-portgroup-PG'
@ -2193,8 +2196,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
def test_find_device_number(self):
host = 'fakehost'
data = (
self.driver.common.find_device_number(self.data.test_volume_v2,
data, __, __ = (
self.driver.common.find_device_number(self.data.test_volume,
host))
self.assertEqual('OS-fakehost-MV', data['maskingview'])
@ -2204,16 +2207,16 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
return_value=[])
def test_find_device_number_false(self, mock_ref_name):
host = 'bogushost'
data = (
self.driver.common.find_device_number(self.data.test_volume_v2,
data, __, __ = (
self.driver.common.find_device_number(self.data.test_volume,
host))
self.assertFalse(data)
def test_find_device_number_long_host(self):
# Long host name
host = 'myhost.mydomain.com'
data = (
self.driver.common.find_device_number(self.data.test_volume_v2,
data, __, __ = (
self.driver.common.find_device_number(self.data.test_volume,
host))
self.assertEqual('OS-myhost-MV', data['maskingview'])
@ -2225,7 +2228,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
v2_host_over_38 = self.data.test_volume_v2.copy()
# Pool aware scheduler enabled
v2_host_over_38['host'] = host
data = (
data, __, __ = (
self.driver.common.find_device_number(v2_host_over_38,
host))
self.assertEqual(amended, data['maskingview'])
@ -3234,12 +3237,13 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system})
return_value=({'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system},
False, {}))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_wrap_get_storage_group_from_volume',
return_value=None)
return_value=({}, False, {}))
@mock.patch.object(
volume_types,
'get_volume_type_extra_specs',
@ -3272,10 +3276,19 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
self, _mock_volume_type, mock_wrap_group,
mock_storage_group, mock_add_volume):
self.driver.common._wrap_find_device_number = mock.Mock(
return_value={})
return_value=({}, False, {}))
self.driver.initialize_connection(self.data.test_volume,
self.data.connector)
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_get_port_group_from_source',
return_value={'CreationClassName': 'CIM_TargetMaskingGroup',
'ElementName': 'OS-portgroup-PG'})
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_get_storage_group_from_source',
return_value=EMCVMAXCommonData.default_sg_instance_name)
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_check_adding_volume_to_storage_group',
@ -3291,8 +3304,17 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system})
return_value=({'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system},
True,
{'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system}))
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_wrap_find_device_number',
return_value=({}, True,
{'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system}))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_wrap_get_storage_group_from_volume',
@ -3305,11 +3327,12 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
_mock_volume_type,
mock_wrap_group,
mock_wrap_device,
mock_device,
mock_storage_group,
mock_same_host,
mock_check):
emc_vmax_utils.LIVE_MIGRATION_FILE = (self.tempdir +
'/livemigrationarray')
mock_check,
mock_sg_from_mv,
mock_pg_from_mv):
self.driver.initialize_connection(self.data.test_volume,
self.data.connector)
@ -3342,7 +3365,8 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'storagesystem': EMCVMAXCommonData.storage_system})
return_value=({'storagesystem': EMCVMAXCommonData.storage_system},
False, {}))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_wrap_get_storage_group_from_volume',
@ -4223,8 +4247,9 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system})
return_value=({'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system},
False, {}))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_wrap_get_storage_group_from_volume',
@ -4244,7 +4269,8 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'storagesystem': EMCVMAXCommonData.storage_system})
return_value=({'storagesystem': EMCVMAXCommonData.storage_system},
False, {}))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_wrap_get_storage_group_from_volume',
@ -4856,7 +4882,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'Name': "0001"})
return_value=({'Name': "0001"}, False, {}))
@mock.patch.object(
volume_types,
'get_volume_type_extra_specs',
@ -5089,7 +5115,7 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase):
return_value=volumeInstanceName)
masking = self.driver.common.masking
masking.get_masking_view_from_storage_group = mock.Mock(
return_value=None)
return_value={})
self.driver.manage_existing(volume, external_ref)
utils.rename_volume.assert_called_once_with(
common.conn, volumeInstanceName, volume['name'])
@ -5448,7 +5474,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'Name': "0001"})
return_value=({'Name': "0001"}, False, {}))
@mock.patch.object(
volume_types,
'get_volume_type_extra_specs',
@ -6522,7 +6548,7 @@ class EMCV3DriverTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'Name': "0001"})
return_value=({'Name': "0001"}, False, {}))
@mock.patch.object(
volume_types,
'get_volume_type_extra_specs',
@ -8041,6 +8067,37 @@ class EMCVMAXMaskingTest(test.TestCase):
masking.get_devices_from_storage_group.assert_called_with(
conn, storageGroupInstanceName)
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_get_storage_group_from_masking_view_instance',
return_value=EMCVMAXCommonData.sg_instance_name)
def test_check_existing_storage_group(self, mock_sg_from_mv):
common = self.driver.common
conn = self.fake_ecom_connection()
mv_instance_name = {'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'}
masking = common.masking
sgFromMvInstanceName, msg = (
masking._check_existing_storage_group(conn, mv_instance_name))
self.assertEqual(EMCVMAXCommonData.sg_instance_name,
sgFromMvInstanceName)
self.assertIsNone(msg)
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_get_storage_group_from_masking_view_instance',
return_value=None)
def test_check_existing_storage_group_none(self, mock_sg_from_mv):
common = self.driver.common
conn = self.fake_ecom_connection()
mv_instance_name = {'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'}
masking = common.masking
sgFromMvInstanceName, msg = (
masking._check_existing_storage_group(conn, mv_instance_name))
self.assertIsNone(sgFromMvInstanceName)
self.assertIsNotNone(msg)
class EMCVMAXFCTest(test.TestCase):
def setUp(self):
@ -8406,87 +8463,6 @@ class EMCVMAXUtilsTest(test.TestCase):
self.assertEqual('CIM_DeviceMaskingGroup',
modifiedInstance['CreationClassName'])
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_find_lun',
return_value={'SystemName': EMCVMAXCommonData.storage_system})
@mock.patch('builtins.open' if sys.version_info >= (3,)
else '__builtin__.open')
def test_insert_live_migration_record(self, mock_open, mock_lun):
conn = FakeEcomConnection()
self.driver.common.conn = conn
extraSpecs = self.data.extra_specs
connector = {'initiator': self.data.iscsi_initiator,
'ip': '10.0.0.2',
'platform': u'x86_64',
'host': 'fakehost',
'os_type': 'linux2',
'multipath': False}
maskingviewdict = self.driver.common._populate_masking_dict(
self.data.test_volume, self.data.connector, extraSpecs)
emc_vmax_utils.LIVE_MIGRATION_FILE = ('/tempdir/livemigrationarray')
self.driver.utils.insert_live_migration_record(
self.data.test_volume, maskingviewdict, connector, extraSpecs)
mock_open.assert_called_once_with(
emc_vmax_utils.LIVE_MIGRATION_FILE, "wb")
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_find_lun',
return_value={'SystemName': EMCVMAXCommonData.storage_system})
def test_delete_live_migration_record(self, mock_lun):
conn = FakeEcomConnection()
self.driver.common.conn = conn
extraSpecs = self.data.extra_specs
connector = {'initiator': self.data.iscsi_initiator,
'ip': '10.0.0.2',
'platform': u'x86_64',
'host': 'fakehost',
'os_type': 'linux2',
'multipath': False}
maskingviewdict = self.driver.common._populate_masking_dict(
self.data.test_volume, self.data.connector, extraSpecs)
tempdir = tempfile.mkdtemp()
emc_vmax_utils.LIVE_MIGRATION_FILE = (tempdir +
'/livemigrationarray')
m = mock.mock_open()
with mock.patch('{}.open'.format(__name__), m, create=True):
with open(emc_vmax_utils.LIVE_MIGRATION_FILE, "wb") as f:
f.write('live migration details')
self.driver.utils.insert_live_migration_record(
self.data.test_volume, maskingviewdict, connector, extraSpecs)
self.driver.utils.delete_live_migration_record(self.data.test_volume)
m.assert_called_once_with(emc_vmax_utils.LIVE_MIGRATION_FILE, "wb")
shutil.rmtree(tempdir)
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_find_lun',
return_value={'SystemName': EMCVMAXCommonData.storage_system})
def test_get_live_migration_record(self, mock_lun):
conn = FakeEcomConnection()
self.driver.common.conn = conn
extraSpecs = self.data.extra_specs
connector = {'initiator': self.data.iscsi_initiator,
'ip': '10.0.0.2',
'platform': u'x86_64',
'host': 'fakehost',
'os_type': 'linux2',
'multipath': False}
maskingviewdict = self.driver.common._populate_masking_dict(
self.data.test_volume, self.data.connector, extraSpecs)
tempdir = tempfile.mkdtemp()
emc_vmax_utils.LIVE_MIGRATION_FILE = (tempdir +
'/livemigrationarray')
self.driver.utils.insert_live_migration_record(
self.data.test_volume, maskingviewdict, connector, extraSpecs)
record = self.driver.utils.get_live_migration_record(
self.data.test_volume, False)
self.assertEqual(maskingviewdict, record[0])
self.assertEqual(connector, record[1])
os.remove(emc_vmax_utils.LIVE_MIGRATION_FILE)
shutil.rmtree(tempdir)
def test_get_iqn(self):
conn = FakeEcomConnection()
iqn = "iqn.1992-04.com.emc:600009700bca30c01b9c012000000003,t,0x0001"
@ -8712,6 +8688,102 @@ class EMCVMAXCommonTest(test.TestCase):
EMCVMAXCommonData.test_volume_type_QOS.get('specs'), extraSpecs[
'qos'])
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_associated_masking_groups_from_device',
return_value=[EMCVMAXCommonData.sg_instance_name])
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_masking_view_from_storage_group',
return_value=[{'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'}])
def test_is_volume_multiple_masking_views_false(self, mock_mv_from_sg,
mock_sg_from_dev):
common = self.driver.common
common.conn = FakeEcomConnection()
volumeInstanceName = (
common.conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
volumeInstance = common.conn.GetInstance(volumeInstanceName)
self.assertFalse(
common._is_volume_multiple_masking_views(volumeInstance))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_associated_masking_groups_from_device',
return_value=[EMCVMAXCommonData.sg_instance_name])
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_masking_view_from_storage_group',
return_value=[{'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'},
{'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-bronze-I-MV'}])
def test_is_volume_multiple_masking_views_true(self, mock_mv_from_sg,
mock_sg_from_dev):
common = self.driver.common
common.conn = FakeEcomConnection()
volumeInstanceName = (
common.conn.EnumerateInstanceNames("EMC_StorageVolume")[0])
volumeInstance = common.conn.GetInstance(volumeInstanceName)
self.assertTrue(
common._is_volume_multiple_masking_views(volumeInstance))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_get_storage_group_from_masking_view_instance',
return_value=EMCVMAXCommonData.sg_instance_name)
def test_get_storage_group_from_source(self, mock_sg_from_mv):
common = self.driver.common
common.conn = FakeEcomConnection()
mv_instance_name = {'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'}
deviceInfoDict = {'controller': mv_instance_name}
self.assertEqual(EMCVMAXCommonData.sg_instance_name,
common._get_storage_group_from_source(
deviceInfoDict))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'_get_storage_group_from_masking_view_instance',
return_value=EMCVMAXCommonData.sg_instance_name)
def test_get_storage_group_from_source_except(self, mock_sg_from_mv):
common = self.driver.common
common.conn = FakeEcomConnection()
deviceInfoDict = {}
self.assertRaises(
exception.VolumeBackendAPIException,
common._get_storage_group_from_source, deviceInfoDict)
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_port_group_from_masking_view_instance',
return_value={'CreationClassName': 'CIM_TargetMaskingGroup',
'ElementName': 'OS-portgroup-PG'})
def test_get_port_group_from_source(self, mock_pg_from_mv):
common = self.driver.common
common.conn = FakeEcomConnection()
pg_instance_name = {'CreationClassName': 'CIM_TargetMaskingGroup',
'ElementName': 'OS-portgroup-PG'}
mv_instance_name = {'CreationClassName': 'Symm_LunMaskingView',
'ElementName': 'OS-fakehost-gold-I-MV'}
deviceInfoDict = {'controller': mv_instance_name}
self.assertEqual(pg_instance_name,
common._get_port_group_from_source(
deviceInfoDict))
@mock.patch.object(
emc_vmax_masking.EMCVMAXMasking,
'get_port_group_from_masking_view_instance',
return_value={'CreationClassName': 'CIM_TargetMaskingGroup',
'ElementName': 'OS-portgroup-PG'})
def test_get_port_group_from_source_except(self, mock_pg_from_mv):
common = self.driver.common
common.conn = FakeEcomConnection()
deviceInfoDict = {}
self.assertRaises(
exception.VolumeBackendAPIException,
common._get_port_group_from_source, deviceInfoDict)
class EMCVMAXProvisionTest(test.TestCase):
def setUp(self):
@ -8801,10 +8873,11 @@ class EMCVMAXISCSITest(test.TestCase):
driver.db = FakeDB()
self.driver = driver
def test_smis_get_iscsi_properties(self):
device_info = {'hostlunid': 1}
self.driver.common.find_device_number = (
mock.Mock(return_value=device_info))
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value=({'hostlunid': 1}, False, {}))
def test_smis_get_iscsi_properties(self, mock_device):
iqns_and_ips = (
[{'iqn': 'iqn.1992-04.com.emc:50000973f006dd80,t,0x0001',
'ip': '10.10.0.50'},

View File

@ -338,7 +338,8 @@ class EMCVMAXCommon(object):
LOG.info(_LI("Unmap volume: %(volume)s."),
{'volume': volumename})
device_info = self.find_device_number(volume, connector['host'])
device_info, __, __ = self.find_device_number(
volume, connector['host'])
if 'hostlunid' not in device_info:
LOG.info(_LI("Volume %s is not mapped. No volume to unmap."),
volumename)
@ -347,6 +348,9 @@ class EMCVMAXCommon(object):
vol_instance = self._find_lun(volume)
storage_system = vol_instance['SystemName']
if self._is_volume_multiple_masking_views(vol_instance):
return
configservice = self.utils.find_controller_configuration_service(
self.conn, storage_system)
if configservice is None:
@ -358,16 +362,23 @@ class EMCVMAXCommon(object):
self._remove_members(configservice, vol_instance, connector,
extraSpecs)
livemigrationrecord = self.utils.get_live_migration_record(volume,
False)
if livemigrationrecord:
live_maskingviewdict = livemigrationrecord[0]
live_connector = livemigrationrecord[1]
live_extraSpecs = livemigrationrecord[2]
self._attach_volume(
volume, live_connector, live_extraSpecs,
live_maskingviewdict, True)
self.utils.delete_live_migration_record(volume)
def _is_volume_multiple_masking_views(self, vol_instance):
"""Check if volume is in more than one MV.
:param vol_instance: the volume instance
:returns: boolean
"""
storageGroupInstanceNames = (
self.masking.get_associated_masking_groups_from_device(
self.conn, vol_instance.path))
for storageGroupInstanceName in storageGroupInstanceNames:
mvInstanceNames = self.masking.get_masking_view_from_storage_group(
self.conn, storageGroupInstanceName)
if len(mvInstanceNames) > 1:
return True
return False
def initialize_connection(self, volume, connector):
"""Initializes the connection and returns device and connection info.
@ -406,35 +417,35 @@ class EMCVMAXCommon(object):
LOG.info(_LI("Initialize connection: %(volume)s."),
{'volume': volumeName})
self.conn = self._get_ecom_connection()
deviceInfoDict = self._wrap_find_device_number(
volume, connector['host'])
deviceInfoDict, isLiveMigration, sourceInfoDict = (
self._wrap_find_device_number(
volume, connector['host']))
maskingViewDict = self._populate_masking_dict(
volume, connector, extraSpecs)
if ('hostlunid' in deviceInfoDict and
deviceInfoDict['hostlunid'] is not None):
isSameHost = self._is_same_host(connector, deviceInfoDict)
if isSameHost:
# Device is already mapped to same host so we will leave
# the state as is.
deviceNumber = deviceInfoDict['hostlunid']
LOG.info(_LI("Volume %(volume)s is already mapped. "
"The device number is %(deviceNumber)s."),
{'volume': volumeName,
'deviceNumber': deviceNumber})
# Special case, we still need to get the iscsi ip address.
portGroupName = (
self._get_correct_port_group(
deviceInfoDict, maskingViewDict['storageSystemName']))
else:
deviceNumber = deviceInfoDict['hostlunid']
LOG.info(_LI("Volume %(volume)s is already mapped. "
"The device number is %(deviceNumber)s."),
{'volume': volumeName,
'deviceNumber': deviceNumber})
# Special case, we still need to get the iscsi ip address.
portGroupName = (
self._get_correct_port_group(
deviceInfoDict, maskingViewDict['storageSystemName']))
else:
if isLiveMigration:
maskingViewDict['storageGroupInstanceName'] = (
self._get_storage_group_from_source(sourceInfoDict))
maskingViewDict['portGroupInstanceName'] = (
self._get_port_group_from_source(sourceInfoDict))
deviceInfoDict, portGroupName = self._attach_volume(
volume, connector, extraSpecs, maskingViewDict, True)
else:
deviceInfoDict, portGroupName = (
self._attach_volume(
volume, connector, extraSpecs, maskingViewDict))
else:
deviceInfoDict, portGroupName = (
self._attach_volume(
volume, connector, extraSpecs, maskingViewDict))
if self.protocol.lower() == 'iscsi':
deviceInfoDict['ip_and_iqn'] = (
@ -462,12 +473,8 @@ class EMCVMAXCommon(object):
:raises: VolumeBackendAPIException
"""
volumeName = volume['name']
maskingViewDict = self._populate_masking_dict(
volume, connector, extraSpecs)
if isLiveMigration:
maskingViewDict['isLiveMigration'] = True
self.utils.insert_live_migration_record(volume, maskingViewDict,
connector, extraSpecs)
else:
maskingViewDict['isLiveMigration'] = False
@ -475,7 +482,8 @@ class EMCVMAXCommon(object):
self.conn, maskingViewDict, extraSpecs)
# Find host lun id again after the volume is exported to the host.
deviceInfoDict = self.find_device_number(volume, connector['host'])
deviceInfoDict, __, __ = self.find_device_number(
volume, connector['host'])
if 'hostlunid' not in deviceInfoDict:
# Did not successfully attach to host,
# so a rollback for FAST is required.
@ -485,7 +493,6 @@ class EMCVMAXCommon(object):
(rollbackDict['isV3'] is not None)):
(self.masking._check_if_rollback_action_for_masking_required(
self.conn, rollbackDict))
self.utils.delete_live_migration_record(volume)
exception_message = (_("Error Attaching volume %(vol)s.")
% {'vol': volumeName})
raise exception.VolumeBackendAPIException(
@ -554,6 +561,52 @@ class EMCVMAXCommon(object):
data=exception_message)
return portGroupName
def _get_storage_group_from_source(self, deviceInfoDict):
"""Get the storage group from the existing masking view.
:params deviceInfoDict: the device info dictionary
:returns: storage group instance
"""
storageGroupInstanceName = None
if ('controller' in deviceInfoDict and
deviceInfoDict['controller'] is not None):
maskingViewInstanceName = deviceInfoDict['controller']
# Get the storage group from masking view
storageGroupInstanceName = (
self.masking._get_storage_group_from_masking_view_instance(
self.conn,
maskingViewInstanceName))
else:
exception_message = (_("Cannot get the storage group from "
"the masking view."))
raise exception.VolumeBackendAPIException(
data=exception_message)
return storageGroupInstanceName
def _get_port_group_from_source(self, deviceInfoDict):
"""Get the port group from the existing masking view.
:params deviceInfoDict: the device info dictionary
:returns: port group instance
"""
portGroupInstanceName = None
if ('controller' in deviceInfoDict and
deviceInfoDict['controller'] is not None):
maskingViewInstanceName = deviceInfoDict['controller']
# Get the port group from masking view
portGroupInstanceName = (
self.masking.get_port_group_from_masking_view_instance(
self.conn,
maskingViewInstanceName))
else:
exception_message = (_("Cannot get the port group from "
"the masking view."))
raise exception.VolumeBackendAPIException(
data=exception_message)
return portGroupInstanceName
def check_ig_instance_name(self, initiatorGroupInstanceName):
"""Check if an initiator group instance is on the array.
@ -570,8 +623,10 @@ class EMCVMAXCommon(object):
:params connector: the connector Object
"""
volumename = volume['name']
LOG.info(_LI("Terminate connection: %(volume)s."),
{'volume': volumename})
LOG.info(_LI("Terminate connection: %(volume)s from "
"host %(host)s."),
{'volume': volumename,
'host': connector['host']})
self._unmap_lun(volume, connector)
@ -1587,6 +1642,8 @@ class EMCVMAXCommon(object):
volumeName = volume['name']
volumeInstance = self._find_lun(volume)
storageSystemName = volumeInstance['SystemName']
isLiveMigration = False
source_data = {}
unitnames = self.conn.ReferenceNames(
volumeInstance.path,
@ -1631,14 +1688,15 @@ class EMCVMAXCommon(object):
data = maskedvol
if not data:
if len(maskedvols) > 0:
data = maskedvols[0]
source_data = maskedvols[0]
LOG.warning(_LW(
"Volume is masked but not to host %(host)s as is "
"expected. Assuming live migration."),
{'host': hoststr})
isLiveMigration = True
LOG.debug("Device info: %(data)s.", {'data': data})
return data
return data, isLiveMigration, source_data
def get_target_wwns(self, storageSystem, connector):
"""Find target WWNs.
@ -1927,6 +1985,10 @@ class EMCVMAXCommon(object):
maskingViewDict['maskingViewName'] = ("%(prefix)s-MV"
% {'prefix': prefix})
maskingViewDict['maskingViewNameLM'] = ("%(prefix)s-%(volid)s-MV"
% {'prefix': prefix,
'volid': volume['id'][:8]})
volumeName = volume['name']
volumeInstance = self._find_lun(volume)
storageSystemName = volumeInstance['SystemName']
@ -4188,9 +4250,10 @@ class EMCVMAXCommon(object):
self.conn, volumeInstanceName))
for sgInstanceName in sgInstanceNames:
mvInstanceName = self.masking.get_masking_view_from_storage_group(
self.conn, sgInstanceName)
if mvInstanceName:
mvInstanceNames = (
self.masking.get_masking_view_from_storage_group(
self.conn, sgInstanceName))
for mvInstanceName in mvInstanceNames:
exceptionMessage = (_(
"Unable to import volume %(deviceId)s to cinder. "
"Volume is in masking view %(mv)s.")

View File

@ -241,7 +241,7 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
meaning use CHAP with the specified credentials.
"""
device_info = self.common.find_device_number(
device_info, __, __ = self.common.find_device_number(
volume, connector['host'])
isError = False

View File

@ -89,7 +89,11 @@ class EMCVMAXMasking(object):
defaultStorageGroupInstanceName = None
fastPolicyName = None
storageGroupInstanceName = None
if isLiveMigration is False:
if isLiveMigration:
maskingViewDict['maskingViewName'] = (
maskingViewDict['maskingViewNameLM'])
maskingViewName = maskingViewDict['maskingViewNameLM']
else:
if isV3:
defaultStorageGroupInstanceName = (
self._get_v3_default_storagegroup_instancename(
@ -106,11 +110,6 @@ class EMCVMAXMasking(object):
volumeInstance.path,
volumeName, fastPolicyName,
extraSpecs))
else:
# Live Migration
self.remove_and_reset_members(
conn, controllerConfigService, volumeInstance, volumeName,
extraSpecs, maskingViewDict['connector'], False)
# If anything has gone wrong with the masking view we rollback
try:
@ -118,6 +117,8 @@ class EMCVMAXMasking(object):
self._validate_masking_view(conn, maskingViewDict,
defaultStorageGroupInstanceName,
extraSpecs))
instance = conn.GetInstance(storageGroupInstanceName)
maskingViewDict['sgGroupName'] = instance['ElementName']
LOG.debug(
"The masking view in the attach operation is "
"%(maskingViewInstanceName)s. The storage group "
@ -279,17 +280,38 @@ class EMCVMAXMasking(object):
maskingViewName = maskingViewDict['maskingViewName']
pgGroupName = maskingViewDict['pgGroupName']
storageGroupInstanceName, errorMessage = (
self._check_storage_group(
conn, maskingViewDict, defaultStorageGroupInstanceName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
if maskingViewDict['isLiveMigration']:
try:
# We are sharing the storage group and port group
# between host and target
storageGroupInstanceName = (
maskingViewDict['storageGroupInstanceName'])
storageGroupinstance = conn.GetInstance(
storageGroupInstanceName)
maskingViewDict['sgGroupName'] = (
storageGroupinstance['ElementName'])
portGroupInstanceName = (
maskingViewDict['portGroupInstanceName'])
portGroupInstance = conn.GetInstance(
portGroupInstanceName)
maskingViewDict['pgGroupName'] = (
portGroupInstance['ElementName'])
except Exception:
errorMessage = (_(
"Unable to get storage group for live migration."))
return None, None, errorMessage
else:
storageGroupInstanceName, errorMessage = (
self._check_storage_group(
conn, maskingViewDict, defaultStorageGroupInstanceName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
portGroupInstanceName, errorMessage = (
self._check_port_group(conn, controllerConfigService,
pgGroupName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
portGroupInstanceName, errorMessage = (
self._check_port_group(conn, controllerConfigService,
pgGroupName))
if errorMessage:
return None, storageGroupInstanceName, errorMessage
initiatorGroupInstanceName, errorMessage = (
self._check_initiator_group(conn, controllerConfigService,
@ -330,7 +352,6 @@ class EMCVMAXMasking(object):
"""
storageGroupInstanceName = None
controllerConfigService = maskingViewDict['controllerConfigService']
sgGroupName = maskingViewDict['sgGroupName']
igGroupName = maskingViewDict['igGroupName']
connector = maskingViewDict['connector']
storageSystemName = maskingViewDict['storageSystemName']
@ -346,10 +367,9 @@ class EMCVMAXMasking(object):
if errorMessage:
return storageGroupInstanceName, errorMessage
# Get the storage group from masking view
storageGroupInstanceName, errorMessage = (
self._check_existing_storage_group(
conn, controllerConfigService, sgGroupName,
maskingViewInstanceName))
self._check_existing_storage_group(conn, maskingViewInstanceName))
return storageGroupInstanceName, errorMessage
@ -378,19 +398,15 @@ class EMCVMAXMasking(object):
return storageGroupInstanceName, msg
def _check_existing_storage_group(
self, conn, controllerConfigService,
sgGroupName, maskingViewInstanceName):
self, conn, maskingViewInstanceName):
"""Check that we can get the existing storage group.
:param conn: the ecom connection
:param controllerConfigService: controller configuration service
:param sgGroupName: the storage group name
:param maskingViewInstanceName: the masking view instance name
:returns: storageGroupInstanceName
:returns: string -- msg, the error message
"""
msg = None
sgFromMvInstanceName = (
self._get_storage_group_from_masking_view_instance(
conn, maskingViewInstanceName))
@ -398,10 +414,9 @@ class EMCVMAXMasking(object):
if sgFromMvInstanceName is None:
# This may be used in exception hence _ instead of _LE.
msg = (_(
"Cannot get storage group: %(sgGroupName)s "
"from masking view %(maskingViewInstanceName)s. ") %
{'sgGroupName': sgGroupName,
'maskingViewInstanceName': maskingViewInstanceName})
"Cannot get storage group from masking view "
"%(maskingViewInstanceName)s. ") %
{'maskingViewInstanceName': maskingViewInstanceName})
LOG.error(msg)
return sgFromMvInstanceName, msg
@ -636,27 +651,12 @@ class EMCVMAXMasking(object):
:raises: VolumeBackendAPIException
"""
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage group "
"before removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'volumeName': volumeName})
volInstance = conn.GetInstance(volumeInstanceName, LocalOnly=False)
self._remove_volume_from_sg(
conn, controllerConfigService, storageGroupInstanceName,
volInstance, maskingViewDict['extraSpecs'])
assocVolumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
LOG.debug(
"There are %(length)lu associated with the default storage group "
"after removing volume %(volumeName)s.",
{'length': len(assocVolumeInstanceNames),
'volumeName': volumeName})
# Required for unit tests.
emptyStorageGroupInstanceName = (
self._wrap_get_storage_group_from_volume(
@ -1616,19 +1616,36 @@ class EMCVMAXMasking(object):
foundView = self._find_masking_view(
conn, maskingViewName, storageSystemName)
if foundView:
groups = conn.AssociatorNames(
foundView,
ResultClass='CIM_TargetMaskingGroup')
if len(groups) > 0:
foundPortMaskingGroupInstanceName = groups[0]
foundPortMaskingGroupInstanceName = (
self.get_port_group_from_masking_view_instance(
conn, foundView))
LOG.debug(
"Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.",
"Masking view: %(view)s portMaskingGroup: %(masking)s.",
{'view': maskingViewName,
'masking': foundPortMaskingGroupInstanceName})
return foundPortMaskingGroupInstanceName
def get_port_group_from_masking_view_instance(
self, conn, maskingViewInstanceName):
"""Given the masking view name get the port group from it.
:param conn: connection to the ecom server
:param maskingViewInstanceName: the masking view instance name
:returns: instance name foundPortMaskingGroupInstanceName
"""
foundPortMaskingGroupInstanceName = None
groups = conn.AssociatorNames(
maskingViewInstanceName,
ResultClass='CIM_TargetMaskingGroup')
if len(groups) > 0:
foundPortMaskingGroupInstanceName = groups[0]
return foundPortMaskingGroupInstanceName
def _delete_masking_view(
self, conn, controllerConfigService, maskingViewName,
maskingViewInstanceName, extraSpecs):
@ -1670,14 +1687,11 @@ class EMCVMAXMasking(object):
:param storageGroupInstanceName: the storage group instance name
:returns: instance name foundMaskingViewInstanceName
"""
foundMaskingViewInstanceName = None
maskingViews = conn.AssociatorNames(
storageGroupInstanceName,
ResultClass='Symm_LunMaskingView')
if len(maskingViews) > 0:
foundMaskingViewInstanceName = maskingViews[0]
return foundMaskingViewInstanceName
return maskingViews
def add_volume_to_storage_group(
self, conn, controllerConfigService, storageGroupInstanceName,
@ -1908,12 +1922,10 @@ class EMCVMAXMasking(object):
"""
instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False)
storageGroupName = instance['ElementName']
mvInstanceName = self.get_masking_view_from_storage_group(
mvInstanceNames = self.get_masking_view_from_storage_group(
conn, storageGroupInstanceName)
if mvInstanceName is None:
LOG.debug("Unable to get masking view %(maskingView)s "
"from storage group.",
{'maskingView': mvInstanceName})
if not mvInstanceNames:
LOG.debug("Unable to get masking views from storage group.")
@coordination.synchronized("emc-sg-{storageGroup}")
def do_remove_volume_from_sg(storageGroup):
@ -1943,43 +1955,46 @@ class EMCVMAXMasking(object):
return do_remove_volume_from_sg(storageGroupName)
else:
# need to lock masking view when we are locking the storage
# group to avoid possible deadlock situations from concurrent
# processes
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
for mvInstanceName in mvInstanceNames:
# need to lock masking view when we are locking the storage
# group to avoid possible deadlock situations from concurrent
# processes
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
@coordination.synchronized("emc-sg-{maskingView}")
def do_remove_volume_from_sg(maskingView):
@coordination.synchronized("emc-mv-{storageGroup}")
def inner_do_remove_volume_from_sg(storageGroup):
volumeInstanceNames = self.get_devices_from_storage_group(
conn, storageGroupInstanceName)
numVolInStorageGroup = len(volumeInstanceNames)
LOG.debug(
"There are %(numVol)d volumes in the storage group "
"%(maskingGroup)s associated with %(mvName)s",
{'numVol': numVolInStorageGroup,
'maskingGroup': storageGroup,
'mvName': maskingViewName})
@coordination.synchronized("emc-sg-{maskingView}")
def do_remove_volume_from_sg(maskingView):
@coordination.synchronized("emc-mv-{storageGroup}")
def inner_do_remove_volume_from_sg(storageGroup):
volumeInstanceNames = (
self.get_devices_from_storage_group(
conn, storageGroupInstanceName))
numVolInStorageGroup = len(volumeInstanceNames)
LOG.debug(
"There are %(numVol)d volumes in the storage "
"group %(sg)s associated with %(mvName)s",
{'numVol': numVolInStorageGroup,
'sg': storageGroup,
'mvName': maskingViewName})
if numVolInStorageGroup == 1:
# Last volume in the storage group.
self._last_vol_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstance,
volumeInstance['ElementName'], extraSpecs)
else:
# Not the last volume so remove it from storage group
self._multiple_vols_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
volumeInstance, volumeInstance['ElementName'],
numVolInStorageGroup, extraSpecs)
return inner_do_remove_volume_from_sg(storageGroupName)
return do_remove_volume_from_sg(maskingViewName)
if numVolInStorageGroup == 1:
# Last volume in the storage group.
self._last_vol_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
storageGroupName, volumeInstance,
volumeInstance['ElementName'], extraSpecs)
else:
# Not the last volume so remove it from storage
# group
self._multiple_vols_in_SG(
conn, controllerConfigService,
storageGroupInstanceName,
volumeInstance, volumeInstance['ElementName'],
numVolInStorageGroup, extraSpecs)
return inner_do_remove_volume_from_sg(storageGroupName)
return do_remove_volume_from_sg(maskingViewName)
def _last_vol_in_SG(
self, conn, controllerConfigService, storageGroupInstanceName,
@ -2007,9 +2022,9 @@ class EMCVMAXMasking(object):
LOG.debug("Only one volume remains in storage group "
"%(sgname)s. Driver will attempt cleanup.",
{'sgname': storageGroupName})
mvInstanceName = self.get_masking_view_from_storage_group(
mvInstanceNames = self.get_masking_view_from_storage_group(
conn, storageGroupInstanceName)
if mvInstanceName is None:
if not mvInstanceNames:
# Remove the volume from the storage group and delete the SG.
self._remove_last_vol_and_delete_sg(
conn, controllerConfigService,
@ -2018,18 +2033,21 @@ class EMCVMAXMasking(object):
volumeName, extraSpecs)
status = True
else:
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
mv_count = len(mvInstanceNames)
for mvInstanceName in mvInstanceNames:
maskingViewInstance = conn.GetInstance(
mvInstanceName, LocalOnly=False)
maskingViewName = maskingViewInstance['ElementName']
def do_delete_mv_ig_and_sg():
return self._delete_mv_ig_and_sg(
conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName,
storageGroupName, volumeInstance, volumeName,
extraSpecs)
do_delete_mv_ig_and_sg()
status = True
def do_delete_mv_ig_and_sg():
return self._delete_mv_ig_and_sg(
conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName,
storageGroupName, volumeInstance, volumeName,
extraSpecs, mv_count)
do_delete_mv_ig_and_sg()
status = True
mv_count -= 1
return status
def _multiple_vols_in_SG(
@ -2067,7 +2085,7 @@ class EMCVMAXMasking(object):
def _delete_mv_ig_and_sg(
self, conn, controllerConfigService, mvInstanceName,
maskingViewName, storageGroupInstanceName, storageGroupName,
volumeInstance, volumeName, extraSpecs):
volumeInstance, volumeName, extraSpecs, mv_count):
"""Delete the Masking view, the storage Group and the initiator group.
:param conn: connection to the ecom server
@ -2079,6 +2097,7 @@ class EMCVMAXMasking(object):
:param volumeInstance: the volume Instance
:param volumeName: the volume name
:param extraSpecs: extra specs
:param mv_count: number of masking views
"""
isV3 = extraSpecs[ISV3]
fastPolicyName = extraSpecs.get(FASTPOLICY, None)
@ -2106,16 +2125,20 @@ class EMCVMAXMasking(object):
storageSystemInstanceName['Name'],
storageGroupInstanceName, extraSpecs)
self._remove_last_vol_and_delete_sg(
conn, controllerConfigService, storageGroupInstanceName,
storageGroupName, volumeInstance.path, volumeName,
extraSpecs)
if mv_count == 1:
if self._is_volume_in_storage_group(
conn, storageGroupInstanceName,
volumeInstance, storageGroupName):
self._remove_last_vol_and_delete_sg(
conn, controllerConfigService, storageGroupInstanceName,
storageGroupName, volumeInstance.path, volumeName,
extraSpecs)
LOG.debug(
"Volume %(volumeName)s successfully removed from SG and "
"Storage Group %(storageGroupName)s successfully deleted. ",
{'volumeName': volumeName,
'storageGroupName': storageGroupName})
LOG.debug(
"Volume %(volumeName)s successfully removed from SG and "
"Storage Group %(storageGroupName)s successfully deleted. ",
{'volumeName': volumeName,
'storageGroupName': storageGroupName})
def _return_back_to_default_sg(
self, conn, controllerConfigService, volumeInstance, volumeName,
@ -2466,10 +2489,10 @@ class EMCVMAXMasking(object):
# Get the SG by IGs.
for sgInstanceName in storageGroupInstanceNames:
# Get maskingview from storage group.
mvInstanceName = self.get_masking_view_from_storage_group(
mvInstanceNames = self.get_masking_view_from_storage_group(
conn, sgInstanceName)
# Get initiator group from masking view.
if mvInstanceName:
for mvInstanceName in mvInstanceNames:
LOG.debug("Found masking view associated with SG "
"%(storageGroup)s: %(maskingview)s",
{'maskingview': mvInstanceName,
@ -2628,9 +2651,13 @@ class EMCVMAXMasking(object):
:param hardwareIdManagementService - hardware id management service
:param hardwareIdPath - The path of the initiator object
"""
ret = conn.InvokeMethod('DeleteStorageHardwareID',
hardwareIdManagementService,
HardwareID = hardwareIdPath)
ret = -1
try:
ret = conn.InvokeMethod('DeleteStorageHardwareID',
hardwareIdManagementService,
HardwareID = hardwareIdPath)
except Exception:
pass
if ret == 0:
LOG.debug("Deletion of initiator path %(hardwareIdPath)s "
"is successful.", {'hardwareIdPath': hardwareIdPath})

View File

@ -15,8 +15,6 @@
import datetime
import hashlib
import os
import pickle
import random
import re
from xml.dom import minidom
@ -2727,77 +2725,6 @@ class EMCVMAXUtils(object):
PropertyList=propertylist)
return modifiedInstance
def insert_live_migration_record(self, volume, maskingviewdict,
connector, extraSpecs):
"""Insert a record of live migration destination into a temporary file
:param volume: the volume dictionary
:param maskingviewdict: the storage group instance name
:param connector: the connector Object
:param extraSpecs: the extraSpecs dict
"""
live_migration_details = self.get_live_migration_record(volume, True)
if live_migration_details:
if volume['id'] not in live_migration_details:
live_migration_details[volume['id']] = [maskingviewdict,
connector, extraSpecs]
else:
live_migration_details = {volume['id']: [maskingviewdict,
connector, extraSpecs]}
try:
with open(LIVE_MIGRATION_FILE, "wb") as f:
pickle.dump(live_migration_details, f)
except Exception:
exceptionMessage = (_(
"Error in processing live migration file."))
LOG.exception(exceptionMessage)
raise exception.VolumeBackendAPIException(
data=exceptionMessage)
def delete_live_migration_record(self, volume):
"""Delete record of live migration
Delete record of live migration destination from file and if
after deletion of record, delete file if empty.
:param volume: the volume dictionary
"""
live_migration_details = self.get_live_migration_record(volume, True)
if live_migration_details:
if volume['id'] in live_migration_details:
del live_migration_details[volume['id']]
with open(LIVE_MIGRATION_FILE, "wb") as f:
pickle.dump(live_migration_details, f)
else:
LOG.debug("%(Volume)s doesn't exist in live migration "
"record.",
{'Volume': volume['id']})
if not live_migration_details:
os.remove(LIVE_MIGRATION_FILE)
def get_live_migration_record(self, volume, returnallrecords):
"""get record of live migration destination from a temporary file
:param volume: the volume dictionary
:param returnallrecords: if true, return all records in file
:returns: returns a single record or all records depending on
returnallrecords flag
"""
returned_record = None
if os.path.isfile(LIVE_MIGRATION_FILE):
with open(LIVE_MIGRATION_FILE, "rb") as f:
live_migration_details = pickle.load(f)
if returnallrecords:
returned_record = live_migration_details
else:
if volume['id'] in live_migration_details:
returned_record = live_migration_details[volume['id']]
else:
LOG.debug("%(Volume)s doesn't exist in live migration "
"record.",
{'Volume': volume['id']})
return returned_record
def get_iqn(self, conn, ipendpointinstancename):
"""Get the IPv4Address from the ip endpoint instance name.