VMAX driver - Live Migration is dropping connection

When Live migrating from one compute node to another the connection
drops and requires the instance to be rebooted. To prevent this
from happening we need to share the storage group and port group
between masking views.

Change-Id: I1483ca38362c5ff1724940c2abf1179e75e02c8e
Closes-Bug: #1676459
(cherry picked from commit 069dd5b80d)
This commit is contained in:
Helen Walsh 2017-03-27 20:35:46 +01:00
parent 98a11913ea
commit 23eea5313a
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.