Merge "VMAX driver - Live Migration is dropping connection"
This commit is contained in:
commit
39e7f70dee
@ -16,7 +16,6 @@
|
|||||||
import ast
|
import ast
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import uuid
|
import uuid
|
||||||
@ -287,6 +286,10 @@ class VMAXCommonData(object):
|
|||||||
'CreationClassName': 'CIM_DeviceMaskingGroup',
|
'CreationClassName': 'CIM_DeviceMaskingGroup',
|
||||||
'ElementName': 'OS_default_GOLD1_SG',
|
'ElementName': 'OS_default_GOLD1_SG',
|
||||||
'SystemName': 'SYMMETRIX+000195900551'}
|
'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 = 'SYMMETRIX+000195900551'
|
||||||
storage_system_v3 = 'SYMMETRIX-+-000197200056'
|
storage_system_v3 = 'SYMMETRIX-+-000197200056'
|
||||||
port_group = 'OS-portgroup-PG'
|
port_group = 'OS-portgroup-PG'
|
||||||
@ -2351,7 +2354,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
|
|
||||||
def test_find_device_number(self):
|
def test_find_device_number(self):
|
||||||
host = 'fakehost'
|
host = 'fakehost'
|
||||||
data = (
|
data, __, __ = (
|
||||||
self.driver.common.find_device_number(self.data.test_volume,
|
self.driver.common.find_device_number(self.data.test_volume,
|
||||||
host))
|
host))
|
||||||
self.assertEqual('OS-fakehost-MV', data['maskingview'])
|
self.assertEqual('OS-fakehost-MV', data['maskingview'])
|
||||||
@ -2362,7 +2365,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
return_value=[])
|
return_value=[])
|
||||||
def test_find_device_number_false(self, mock_ref_name):
|
def test_find_device_number_false(self, mock_ref_name):
|
||||||
host = 'bogushost'
|
host = 'bogushost'
|
||||||
data = (
|
data, __, __ = (
|
||||||
self.driver.common.find_device_number(self.data.test_volume,
|
self.driver.common.find_device_number(self.data.test_volume,
|
||||||
host))
|
host))
|
||||||
self.assertFalse(data)
|
self.assertFalse(data)
|
||||||
@ -2370,7 +2373,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
def test_find_device_number_long_host(self):
|
def test_find_device_number_long_host(self):
|
||||||
# Long host name
|
# Long host name
|
||||||
host = 'myhost.mydomain.com'
|
host = 'myhost.mydomain.com'
|
||||||
data = (
|
data, __, __ = (
|
||||||
self.driver.common.find_device_number(self.data.test_volume,
|
self.driver.common.find_device_number(self.data.test_volume,
|
||||||
host))
|
host))
|
||||||
self.assertEqual('OS-myhost-MV', data['maskingview'])
|
self.assertEqual('OS-myhost-MV', data['maskingview'])
|
||||||
@ -2383,7 +2386,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
v2_host_over_38 = self.data.test_volume.copy()
|
v2_host_over_38 = self.data.test_volume.copy()
|
||||||
# Pool aware scheduler enabled
|
# Pool aware scheduler enabled
|
||||||
v2_host_over_38['host'] = host
|
v2_host_over_38['host'] = host
|
||||||
data = (
|
data, __, __ = (
|
||||||
self.driver.common.find_device_number(v2_host_over_38,
|
self.driver.common.find_device_number(v2_host_over_38,
|
||||||
host))
|
host))
|
||||||
self.assertEqual(amended, data['maskingview'])
|
self.assertEqual(amended, data['maskingview'])
|
||||||
@ -3411,8 +3414,9 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'hostlunid': 1,
|
return_value=({'hostlunid': 1,
|
||||||
'storagesystem': VMAXCommonData.storage_system})
|
'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
'_wrap_get_storage_group_from_volume',
|
'_wrap_get_storage_group_from_volume',
|
||||||
@ -3449,10 +3453,19 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
self, _mock_volume_type, mock_wrap_group,
|
self, _mock_volume_type, mock_wrap_group,
|
||||||
mock_storage_group, mock_add_volume):
|
mock_storage_group, mock_add_volume):
|
||||||
self.driver.common._wrap_find_device_number = mock.Mock(
|
self.driver.common._wrap_find_device_number = mock.Mock(
|
||||||
return_value={})
|
return_value=({}, False, {}))
|
||||||
self.driver.initialize_connection(self.data.test_volume,
|
self.driver.initialize_connection(self.data.test_volume,
|
||||||
self.data.connector)
|
self.data.connector)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
common.VMAXCommon,
|
||||||
|
'_get_port_group_from_source',
|
||||||
|
return_value={'CreationClassName': 'CIM_TargetMaskingGroup',
|
||||||
|
'ElementName': 'OS-portgroup-PG'})
|
||||||
|
@mock.patch.object(
|
||||||
|
common.VMAXCommon,
|
||||||
|
'_get_storage_group_from_source',
|
||||||
|
return_value=VMAXCommonData.default_sg_instance_name)
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'_is_same_host',
|
'_is_same_host',
|
||||||
@ -3460,8 +3473,17 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'hostlunid': 1,
|
return_value=({'hostlunid': 1,
|
||||||
'storagesystem': VMAXCommonData.storage_system})
|
'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
True,
|
||||||
|
{'hostlunid': 1,
|
||||||
|
'storagesystem': VMAXCommonData.storage_system}))
|
||||||
|
@mock.patch.object(
|
||||||
|
common.VMAXCommon,
|
||||||
|
'_wrap_find_device_number',
|
||||||
|
return_value=({}, True,
|
||||||
|
{'hostlunid': 1,
|
||||||
|
'storagesystem': VMAXCommonData.storage_system}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
volume_types,
|
volume_types,
|
||||||
'get_volume_type_extra_specs',
|
'get_volume_type_extra_specs',
|
||||||
@ -3469,9 +3491,10 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
def test_map_live_migration_no_fast_success(self,
|
def test_map_live_migration_no_fast_success(self,
|
||||||
_mock_volume_type,
|
_mock_volume_type,
|
||||||
mock_wrap_device,
|
mock_wrap_device,
|
||||||
mock_same_host):
|
mock_device,
|
||||||
utils.LIVE_MIGRATION_FILE = (self.tempdir +
|
mock_same_host,
|
||||||
'/livemigrationarray')
|
mock_sg_from_mv,
|
||||||
|
mock_pg_from_mv):
|
||||||
extraSpecs = self.data.extra_specs
|
extraSpecs = self.data.extra_specs
|
||||||
rollback_dict = self.driver.common._populate_masking_dict(
|
rollback_dict = self.driver.common._populate_masking_dict(
|
||||||
self.data.test_volume, self.data.connector, extraSpecs)
|
self.data.test_volume, self.data.connector, extraSpecs)
|
||||||
@ -3510,7 +3533,8 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'storagesystem': VMAXCommonData.storage_system})
|
return_value=({'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
'_wrap_get_storage_group_from_volume',
|
'_wrap_get_storage_group_from_volume',
|
||||||
@ -4415,8 +4439,9 @@ class VMAXISCSIDriverFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'hostlunid': 1,
|
return_value=({'hostlunid': 1,
|
||||||
'storagesystem': VMAXCommonData.storage_system})
|
'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
'_wrap_get_storage_group_from_volume',
|
'_wrap_get_storage_group_from_volume',
|
||||||
@ -4436,7 +4461,8 @@ class VMAXISCSIDriverFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'storagesystem': VMAXCommonData.storage_system})
|
return_value=({'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
'_wrap_get_storage_group_from_volume',
|
'_wrap_get_storage_group_from_volume',
|
||||||
@ -5063,7 +5089,7 @@ class VMAXFCDriverNoFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'Name': "0001"})
|
return_value=({'Name': "0001"}, False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
volume_types,
|
volume_types,
|
||||||
'get_volume_type_extra_specs',
|
'get_volume_type_extra_specs',
|
||||||
@ -5282,7 +5308,7 @@ class VMAXFCDriverNoFastTestCase(test.TestCase):
|
|||||||
return_value=volumeInstanceName)
|
return_value=volumeInstanceName)
|
||||||
masking = self.driver.common.masking
|
masking = self.driver.common.masking
|
||||||
masking.get_masking_view_from_storage_group = mock.Mock(
|
masking.get_masking_view_from_storage_group = mock.Mock(
|
||||||
return_value=None)
|
return_value={})
|
||||||
self.driver.manage_existing(volume, external_ref)
|
self.driver.manage_existing(volume, external_ref)
|
||||||
utils.rename_volume.assert_called_once_with(
|
utils.rename_volume.assert_called_once_with(
|
||||||
common.conn, volumeInstanceName, volume['name'])
|
common.conn, volumeInstanceName, volume['name'])
|
||||||
@ -5650,7 +5676,7 @@ class VMAXFCDriverFastTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'Name': "0001"})
|
return_value=({'Name': "0001"}, False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
volume_types,
|
volume_types,
|
||||||
'get_volume_type_extra_specs',
|
'get_volume_type_extra_specs',
|
||||||
@ -6716,7 +6742,7 @@ class EMCV3DriverTestCase(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'Name': "0001"})
|
return_value=({'Name': "0001"}, False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
volume_types,
|
volume_types,
|
||||||
'get_volume_type_extra_specs',
|
'get_volume_type_extra_specs',
|
||||||
@ -8193,7 +8219,7 @@ class VMAXMaskingTest(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
"_validate_masking_view",
|
"_validate_masking_view",
|
||||||
return_value=("mv_instance", "sg_instance", None))
|
return_value=("mv_instance", VMAXCommonData.sg_instance_name, None))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
masking.VMAXMasking,
|
masking.VMAXMasking,
|
||||||
"_get_and_remove_from_storage_group_v3")
|
"_get_and_remove_from_storage_group_v3")
|
||||||
@ -8251,6 +8277,37 @@ class VMAXMaskingTest(test.TestCase):
|
|||||||
masking.get_or_create_masking_view_and_map_lun,
|
masking.get_or_create_masking_view_and_map_lun,
|
||||||
common.conn, maskingViewDict, extraSpecs)
|
common.conn, maskingViewDict, extraSpecs)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'_get_storage_group_from_masking_view_instance',
|
||||||
|
return_value=VMAXCommonData.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(VMAXCommonData.sg_instance_name,
|
||||||
|
sgFromMvInstanceName)
|
||||||
|
self.assertIsNone(msg)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'_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 VMAXFCTest(test.TestCase):
|
class VMAXFCTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -8653,87 +8710,6 @@ class VMAXUtilsTest(test.TestCase):
|
|||||||
self.assertEqual('CIM_DeviceMaskingGroup',
|
self.assertEqual('CIM_DeviceMaskingGroup',
|
||||||
modifiedInstance['CreationClassName'])
|
modifiedInstance['CreationClassName'])
|
||||||
|
|
||||||
@mock.patch.object(
|
|
||||||
common.VMAXCommon,
|
|
||||||
'_find_lun',
|
|
||||||
return_value={'SystemName': VMAXCommonData.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)
|
|
||||||
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(
|
|
||||||
utils.LIVE_MIGRATION_FILE, "w")
|
|
||||||
|
|
||||||
@mock.patch.object(
|
|
||||||
common.VMAXCommon,
|
|
||||||
'_find_lun',
|
|
||||||
return_value={'SystemName': VMAXCommonData.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()
|
|
||||||
utils.LIVE_MIGRATION_FILE = (tempdir +
|
|
||||||
'/livemigrationarray')
|
|
||||||
m = mock.mock_open()
|
|
||||||
with mock.patch('{}.open'.format(__name__), m, create=True):
|
|
||||||
with open(utils.LIVE_MIGRATION_FILE, "w") 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(utils.LIVE_MIGRATION_FILE, "w")
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
|
|
||||||
@mock.patch.object(
|
|
||||||
common.VMAXCommon,
|
|
||||||
'_find_lun',
|
|
||||||
return_value={'SystemName': VMAXCommonData.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()
|
|
||||||
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(utils.LIVE_MIGRATION_FILE)
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
|
|
||||||
def test_get_iqn(self):
|
def test_get_iqn(self):
|
||||||
conn = FakeEcomConnection()
|
conn = FakeEcomConnection()
|
||||||
iqn = "iqn.1992-04.com.emc:600009700bca30c01b9c012000000003,t,0x0001"
|
iqn = "iqn.1992-04.com.emc:600009700bca30c01b9c012000000003,t,0x0001"
|
||||||
@ -9503,6 +9479,102 @@ class VMAXCommonTest(test.TestCase):
|
|||||||
self.driver.create_snapshot(snapshot)
|
self.driver.create_snapshot(snapshot)
|
||||||
self.driver.delete_snapshot(snapshot)
|
self.driver.delete_snapshot(snapshot)
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'get_associated_masking_groups_from_device',
|
||||||
|
return_value=[VMAXCommonData.sg_instance_name])
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'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(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'get_associated_masking_groups_from_device',
|
||||||
|
return_value=[VMAXCommonData.sg_instance_name])
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'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(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'_get_storage_group_from_masking_view_instance',
|
||||||
|
return_value=VMAXCommonData.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(VMAXCommonData.sg_instance_name,
|
||||||
|
common._get_storage_group_from_source(
|
||||||
|
deviceInfoDict))
|
||||||
|
|
||||||
|
@mock.patch.object(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'_get_storage_group_from_masking_view_instance',
|
||||||
|
return_value=VMAXCommonData.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(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'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(
|
||||||
|
masking.VMAXMasking,
|
||||||
|
'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 VMAXProvisionTest(test.TestCase):
|
class VMAXProvisionTest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -9606,10 +9678,11 @@ class VMAXISCSITest(test.TestCase):
|
|||||||
driver.db = FakeDB()
|
driver.db = FakeDB()
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
def test_smis_get_iscsi_properties(self):
|
@mock.patch.object(
|
||||||
device_info = {'hostlunid': 1}
|
common.VMAXCommon,
|
||||||
self.driver.common.find_device_number = (
|
'find_device_number',
|
||||||
mock.Mock(return_value=device_info))
|
return_value=({'hostlunid': 1}, False, {}))
|
||||||
|
def test_smis_get_iscsi_properties(self, mock_device):
|
||||||
iqns_and_ips = (
|
iqns_and_ips = (
|
||||||
[{'iqn': 'iqn.1992-04.com.emc:50000973f006dd80,t,0x0001',
|
[{'iqn': 'iqn.1992-04.com.emc:50000973f006dd80,t,0x0001',
|
||||||
'ip': '10.10.0.50'},
|
'ip': '10.10.0.50'},
|
||||||
@ -9627,8 +9700,9 @@ class VMAXISCSITest(test.TestCase):
|
|||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'find_device_number',
|
'find_device_number',
|
||||||
return_value={'hostlunid': 1,
|
return_value=({'hostlunid': 1,
|
||||||
'storagesystem': VMAXCommonData.storage_system})
|
'storagesystem': VMAXCommonData.storage_system},
|
||||||
|
False, {}))
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
common.VMAXCommon,
|
common.VMAXCommon,
|
||||||
'initialize_connection',
|
'initialize_connection',
|
||||||
|
@ -518,7 +518,8 @@ class VMAXCommon(object):
|
|||||||
LOG.info("Unmap volume: %(volume)s.",
|
LOG.info("Unmap volume: %(volume)s.",
|
||||||
{'volume': volumename})
|
{'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:
|
if 'hostlunid' not in device_info:
|
||||||
LOG.info("Volume %s is not mapped. No volume to unmap.",
|
LOG.info("Volume %s is not mapped. No volume to unmap.",
|
||||||
volumename)
|
volumename)
|
||||||
@ -527,6 +528,9 @@ class VMAXCommon(object):
|
|||||||
vol_instance = self._find_lun(volume)
|
vol_instance = self._find_lun(volume)
|
||||||
storage_system = vol_instance['SystemName']
|
storage_system = vol_instance['SystemName']
|
||||||
|
|
||||||
|
if self._is_volume_multiple_masking_views(vol_instance):
|
||||||
|
return
|
||||||
|
|
||||||
configservice = self.utils.find_controller_configuration_service(
|
configservice = self.utils.find_controller_configuration_service(
|
||||||
self.conn, storage_system)
|
self.conn, storage_system)
|
||||||
if configservice is None:
|
if configservice is None:
|
||||||
@ -538,16 +542,23 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
self._remove_members(configservice, vol_instance, connector,
|
self._remove_members(configservice, vol_instance, connector,
|
||||||
extraSpecs)
|
extraSpecs)
|
||||||
livemigrationrecord = self.utils.get_live_migration_record(volume,
|
|
||||||
False)
|
def _is_volume_multiple_masking_views(self, vol_instance):
|
||||||
if livemigrationrecord:
|
"""Check if volume is in more than one MV.
|
||||||
live_maskingviewdict = livemigrationrecord[0]
|
|
||||||
live_connector = livemigrationrecord[1]
|
:param vol_instance: the volume instance
|
||||||
live_extraSpecs = livemigrationrecord[2]
|
:returns: boolean
|
||||||
self._attach_volume(
|
"""
|
||||||
volume, live_connector, live_extraSpecs,
|
storageGroupInstanceNames = (
|
||||||
live_maskingviewdict, True)
|
self.masking.get_associated_masking_groups_from_device(
|
||||||
self.utils.delete_live_migration_record(volume)
|
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):
|
def initialize_connection(self, volume, connector):
|
||||||
"""Initializes the connection and returns device and connection info.
|
"""Initializes the connection and returns device and connection info.
|
||||||
@ -586,21 +597,18 @@ class VMAXCommon(object):
|
|||||||
LOG.info("Initialize connection: %(volume)s.",
|
LOG.info("Initialize connection: %(volume)s.",
|
||||||
{'volume': volumeName})
|
{'volume': volumeName})
|
||||||
self.conn = self._get_ecom_connection()
|
self.conn = self._get_ecom_connection()
|
||||||
deviceInfoDict = self._wrap_find_device_number(
|
|
||||||
volume, connector['host'])
|
|
||||||
if self.utils.is_volume_failed_over(volume):
|
if self.utils.is_volume_failed_over(volume):
|
||||||
extraSpecs = self._get_replication_extraSpecs(
|
extraSpecs = self._get_replication_extraSpecs(
|
||||||
extraSpecs, self.rep_config)
|
extraSpecs, self.rep_config)
|
||||||
|
deviceInfoDict, isLiveMigration, sourceInfoDict = (
|
||||||
|
self._wrap_find_device_number(
|
||||||
|
volume, connector['host']))
|
||||||
maskingViewDict = self._populate_masking_dict(
|
maskingViewDict = self._populate_masking_dict(
|
||||||
volume, connector, extraSpecs)
|
volume, connector, extraSpecs)
|
||||||
|
|
||||||
if ('hostlunid' in deviceInfoDict and
|
if ('hostlunid' in deviceInfoDict and
|
||||||
deviceInfoDict['hostlunid'] is not None):
|
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']
|
deviceNumber = deviceInfoDict['hostlunid']
|
||||||
LOG.info("Volume %(volume)s is already mapped. "
|
LOG.info("Volume %(volume)s is already mapped. "
|
||||||
"The device number is %(deviceNumber)s.",
|
"The device number is %(deviceNumber)s.",
|
||||||
@ -610,8 +618,12 @@ class VMAXCommon(object):
|
|||||||
portGroupName = (
|
portGroupName = (
|
||||||
self._get_correct_port_group(
|
self._get_correct_port_group(
|
||||||
deviceInfoDict, maskingViewDict['storageSystemName']))
|
deviceInfoDict, maskingViewDict['storageSystemName']))
|
||||||
|
|
||||||
else:
|
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(
|
deviceInfoDict, portGroupName = self._attach_volume(
|
||||||
volume, connector, extraSpecs, maskingViewDict, True)
|
volume, connector, extraSpecs, maskingViewDict, True)
|
||||||
else:
|
else:
|
||||||
@ -645,12 +657,8 @@ class VMAXCommon(object):
|
|||||||
:raises VolumeBackendAPIException:
|
:raises VolumeBackendAPIException:
|
||||||
"""
|
"""
|
||||||
volumeName = volume['name']
|
volumeName = volume['name']
|
||||||
maskingViewDict = self._populate_masking_dict(
|
|
||||||
volume, connector, extraSpecs)
|
|
||||||
if isLiveMigration:
|
if isLiveMigration:
|
||||||
maskingViewDict['isLiveMigration'] = True
|
maskingViewDict['isLiveMigration'] = True
|
||||||
self.utils.insert_live_migration_record(volume, maskingViewDict,
|
|
||||||
connector, extraSpecs)
|
|
||||||
else:
|
else:
|
||||||
maskingViewDict['isLiveMigration'] = False
|
maskingViewDict['isLiveMigration'] = False
|
||||||
|
|
||||||
@ -658,7 +666,8 @@ class VMAXCommon(object):
|
|||||||
self.conn, maskingViewDict, extraSpecs)
|
self.conn, maskingViewDict, extraSpecs)
|
||||||
|
|
||||||
# Find host lun id again after the volume is exported to the host.
|
# 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:
|
if 'hostlunid' not in deviceInfoDict:
|
||||||
# Did not successfully attach to host,
|
# Did not successfully attach to host,
|
||||||
# so a rollback for FAST is required.
|
# so a rollback for FAST is required.
|
||||||
@ -668,7 +677,6 @@ class VMAXCommon(object):
|
|||||||
(rollbackDict['isV3'] is not None)):
|
(rollbackDict['isV3'] is not None)):
|
||||||
(self.masking._check_if_rollback_action_for_masking_required(
|
(self.masking._check_if_rollback_action_for_masking_required(
|
||||||
self.conn, rollbackDict))
|
self.conn, rollbackDict))
|
||||||
self.utils.delete_live_migration_record(volume)
|
|
||||||
exception_message = (_("Error Attaching volume %(vol)s.")
|
exception_message = (_("Error Attaching volume %(vol)s.")
|
||||||
% {'vol': volumeName})
|
% {'vol': volumeName})
|
||||||
raise exception.VolumeBackendAPIException(
|
raise exception.VolumeBackendAPIException(
|
||||||
@ -737,6 +745,52 @@ class VMAXCommon(object):
|
|||||||
data=exception_message)
|
data=exception_message)
|
||||||
return portGroupName
|
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):
|
def check_ig_instance_name(self, initiatorGroupInstanceName):
|
||||||
"""Check if an initiator group instance is on the array.
|
"""Check if an initiator group instance is on the array.
|
||||||
|
|
||||||
@ -1898,6 +1952,8 @@ class VMAXCommon(object):
|
|||||||
volumeName = volume['name']
|
volumeName = volume['name']
|
||||||
volumeInstance = self._find_lun(volume)
|
volumeInstance = self._find_lun(volume)
|
||||||
storageSystemName = volumeInstance['SystemName']
|
storageSystemName = volumeInstance['SystemName']
|
||||||
|
isLiveMigration = False
|
||||||
|
source_data = {}
|
||||||
|
|
||||||
unitnames = self.conn.ReferenceNames(
|
unitnames = self.conn.ReferenceNames(
|
||||||
volumeInstance.path,
|
volumeInstance.path,
|
||||||
@ -1942,14 +1998,15 @@ class VMAXCommon(object):
|
|||||||
data = maskedvol
|
data = maskedvol
|
||||||
if not data:
|
if not data:
|
||||||
if len(maskedvols) > 0:
|
if len(maskedvols) > 0:
|
||||||
data = maskedvols[0]
|
source_data = maskedvols[0]
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
"Volume is masked but not to host %(host)s as is "
|
"Volume is masked but not to host %(host)s as is "
|
||||||
"expected. Assuming live migration.",
|
"expected. Assuming live migration.",
|
||||||
{'host': hoststr})
|
{'host': hoststr})
|
||||||
|
isLiveMigration = True
|
||||||
|
|
||||||
LOG.debug("Device info: %(data)s.", {'data': data})
|
LOG.debug("Device info: %(data)s.", {'data': data})
|
||||||
return data
|
return data, isLiveMigration, source_data
|
||||||
|
|
||||||
def get_target_wwns(self, storageSystem, connector):
|
def get_target_wwns(self, storageSystem, connector):
|
||||||
"""Find target WWNs.
|
"""Find target WWNs.
|
||||||
@ -2259,6 +2316,10 @@ class VMAXCommon(object):
|
|||||||
|
|
||||||
maskingViewDict['maskingViewName'] = ("%(prefix)s-MV"
|
maskingViewDict['maskingViewName'] = ("%(prefix)s-MV"
|
||||||
% {'prefix': prefix})
|
% {'prefix': prefix})
|
||||||
|
|
||||||
|
maskingViewDict['maskingViewNameLM'] = ("%(prefix)s-%(volid)s-MV"
|
||||||
|
% {'prefix': prefix,
|
||||||
|
'volid': volume['id'][:8]})
|
||||||
volumeName = volume['name']
|
volumeName = volume['name']
|
||||||
volumeInstance = self._find_lun(volume)
|
volumeInstance = self._find_lun(volume)
|
||||||
storageSystemName = volumeInstance['SystemName']
|
storageSystemName = volumeInstance['SystemName']
|
||||||
@ -4597,9 +4658,10 @@ class VMAXCommon(object):
|
|||||||
self.conn, volumeInstanceName))
|
self.conn, volumeInstanceName))
|
||||||
|
|
||||||
for sgInstanceName in sgInstanceNames:
|
for sgInstanceName in sgInstanceNames:
|
||||||
mvInstanceName = self.masking.get_masking_view_from_storage_group(
|
mvInstanceNames = (
|
||||||
self.conn, sgInstanceName)
|
self.masking.get_masking_view_from_storage_group(
|
||||||
if mvInstanceName:
|
self.conn, sgInstanceName))
|
||||||
|
for mvInstanceName in mvInstanceNames:
|
||||||
exceptionMessage = (_(
|
exceptionMessage = (_(
|
||||||
"Unable to import volume %(deviceId)s to cinder. "
|
"Unable to import volume %(deviceId)s to cinder. "
|
||||||
"Volume is in masking view %(mv)s.")
|
"Volume is in masking view %(mv)s.")
|
||||||
|
@ -233,7 +233,7 @@ class VMAXISCSIDriver(driver.ISCSIDriver):
|
|||||||
meaning use CHAP with the specified credentials.
|
meaning use CHAP with the specified credentials.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
device_info = self.common.find_device_number(
|
device_info, __, __ = self.common.find_device_number(
|
||||||
volume, connector['host'])
|
volume, connector['host'])
|
||||||
|
|
||||||
isError = False
|
isError = False
|
||||||
|
@ -89,7 +89,11 @@ class VMAXMasking(object):
|
|||||||
defaultStorageGroupInstanceName = None
|
defaultStorageGroupInstanceName = None
|
||||||
fastPolicyName = None
|
fastPolicyName = None
|
||||||
storageGroupInstanceName = None
|
storageGroupInstanceName = None
|
||||||
if isLiveMigration is False:
|
if isLiveMigration:
|
||||||
|
maskingViewDict['maskingViewName'] = (
|
||||||
|
maskingViewDict['maskingViewNameLM'])
|
||||||
|
maskingViewName = maskingViewDict['maskingViewNameLM']
|
||||||
|
else:
|
||||||
if isV3:
|
if isV3:
|
||||||
defaultStorageGroupInstanceName = (
|
defaultStorageGroupInstanceName = (
|
||||||
self._get_v3_default_storagegroup_instancename(
|
self._get_v3_default_storagegroup_instancename(
|
||||||
@ -106,11 +110,6 @@ class VMAXMasking(object):
|
|||||||
volumeInstance.path,
|
volumeInstance.path,
|
||||||
volumeName, fastPolicyName,
|
volumeName, fastPolicyName,
|
||||||
extraSpecs))
|
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
|
# If anything has gone wrong with the masking view we rollback
|
||||||
try:
|
try:
|
||||||
@ -118,6 +117,8 @@ class VMAXMasking(object):
|
|||||||
self._validate_masking_view(conn, maskingViewDict,
|
self._validate_masking_view(conn, maskingViewDict,
|
||||||
defaultStorageGroupInstanceName,
|
defaultStorageGroupInstanceName,
|
||||||
extraSpecs))
|
extraSpecs))
|
||||||
|
instance = conn.GetInstance(storageGroupInstanceName)
|
||||||
|
maskingViewDict['sgGroupName'] = instance['ElementName']
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"The masking view in the attach operation is "
|
"The masking view in the attach operation is "
|
||||||
"%(maskingViewInstanceName)s. The storage group "
|
"%(maskingViewInstanceName)s. The storage group "
|
||||||
@ -286,6 +287,27 @@ class VMAXMasking(object):
|
|||||||
LOG.info("Returning random Port Group: %(portGroupName)s.",
|
LOG.info("Returning random Port Group: %(portGroupName)s.",
|
||||||
{'portGroupName': pgGroupName})
|
{'portGroupName': pgGroupName})
|
||||||
|
|
||||||
|
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 = (
|
storageGroupInstanceName, errorMessage = (
|
||||||
self._check_storage_group(
|
self._check_storage_group(
|
||||||
conn, maskingViewDict, defaultStorageGroupInstanceName))
|
conn, maskingViewDict, defaultStorageGroupInstanceName))
|
||||||
@ -337,7 +359,6 @@ class VMAXMasking(object):
|
|||||||
"""
|
"""
|
||||||
storageGroupInstanceName = None
|
storageGroupInstanceName = None
|
||||||
controllerConfigService = maskingViewDict['controllerConfigService']
|
controllerConfigService = maskingViewDict['controllerConfigService']
|
||||||
sgGroupName = maskingViewDict['sgGroupName']
|
|
||||||
igGroupName = maskingViewDict['igGroupName']
|
igGroupName = maskingViewDict['igGroupName']
|
||||||
connector = maskingViewDict['connector']
|
connector = maskingViewDict['connector']
|
||||||
storageSystemName = maskingViewDict['storageSystemName']
|
storageSystemName = maskingViewDict['storageSystemName']
|
||||||
@ -353,10 +374,9 @@ class VMAXMasking(object):
|
|||||||
if errorMessage:
|
if errorMessage:
|
||||||
return storageGroupInstanceName, errorMessage
|
return storageGroupInstanceName, errorMessage
|
||||||
|
|
||||||
|
# Get the storage group from masking view
|
||||||
storageGroupInstanceName, errorMessage = (
|
storageGroupInstanceName, errorMessage = (
|
||||||
self._check_existing_storage_group(
|
self._check_existing_storage_group(conn, maskingViewInstanceName))
|
||||||
conn, controllerConfigService, sgGroupName,
|
|
||||||
maskingViewInstanceName))
|
|
||||||
|
|
||||||
return storageGroupInstanceName, errorMessage
|
return storageGroupInstanceName, errorMessage
|
||||||
|
|
||||||
@ -385,19 +405,15 @@ class VMAXMasking(object):
|
|||||||
return storageGroupInstanceName, msg
|
return storageGroupInstanceName, msg
|
||||||
|
|
||||||
def _check_existing_storage_group(
|
def _check_existing_storage_group(
|
||||||
self, conn, controllerConfigService,
|
self, conn, maskingViewInstanceName):
|
||||||
sgGroupName, maskingViewInstanceName):
|
|
||||||
"""Check that we can get the existing storage group.
|
"""Check that we can get the existing storage group.
|
||||||
|
|
||||||
:param conn: the ecom connection
|
:param conn: the ecom connection
|
||||||
:param controllerConfigService: controller configuration service
|
|
||||||
:param sgGroupName: the storage group name
|
|
||||||
:param maskingViewInstanceName: the masking view instance name
|
:param maskingViewInstanceName: the masking view instance name
|
||||||
:returns: storageGroupInstanceName
|
:returns: storageGroupInstanceName
|
||||||
:returns: string -- msg, the error message
|
:returns: string -- msg, the error message
|
||||||
"""
|
"""
|
||||||
msg = None
|
msg = None
|
||||||
|
|
||||||
sgFromMvInstanceName = (
|
sgFromMvInstanceName = (
|
||||||
self._get_storage_group_from_masking_view_instance(
|
self._get_storage_group_from_masking_view_instance(
|
||||||
conn, maskingViewInstanceName))
|
conn, maskingViewInstanceName))
|
||||||
@ -405,10 +421,9 @@ class VMAXMasking(object):
|
|||||||
if sgFromMvInstanceName is None:
|
if sgFromMvInstanceName is None:
|
||||||
# This may be used in exception hence the use of _.
|
# This may be used in exception hence the use of _.
|
||||||
msg = (_(
|
msg = (_(
|
||||||
"Cannot get storage group: %(sgGroupName)s "
|
"Cannot get storage group from masking view "
|
||||||
"from masking view %(maskingViewInstanceName)s. ") %
|
"%(maskingViewInstanceName)s. ") %
|
||||||
{'sgGroupName': sgGroupName,
|
{'maskingViewInstanceName': maskingViewInstanceName})
|
||||||
'maskingViewInstanceName': maskingViewInstanceName})
|
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
return sgFromMvInstanceName, msg
|
return sgFromMvInstanceName, msg
|
||||||
|
|
||||||
@ -1603,17 +1618,34 @@ class VMAXMasking(object):
|
|||||||
foundView = self._find_masking_view(
|
foundView = self._find_masking_view(
|
||||||
conn, maskingViewName, storageSystemName)
|
conn, maskingViewName, storageSystemName)
|
||||||
if foundView:
|
if foundView:
|
||||||
|
foundPortMaskingGroupInstanceName = (
|
||||||
|
self.get_port_group_from_masking_view_instance(
|
||||||
|
conn, foundView))
|
||||||
|
|
||||||
|
LOG.debug(
|
||||||
|
"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(
|
groups = conn.AssociatorNames(
|
||||||
foundView,
|
maskingViewInstanceName,
|
||||||
ResultClass='CIM_TargetMaskingGroup')
|
ResultClass='CIM_TargetMaskingGroup')
|
||||||
if len(groups) > 0:
|
if len(groups) > 0:
|
||||||
foundPortMaskingGroupInstanceName = groups[0]
|
foundPortMaskingGroupInstanceName = groups[0]
|
||||||
|
|
||||||
LOG.debug(
|
|
||||||
"Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.",
|
|
||||||
{'view': maskingViewName,
|
|
||||||
'masking': foundPortMaskingGroupInstanceName})
|
|
||||||
|
|
||||||
return foundPortMaskingGroupInstanceName
|
return foundPortMaskingGroupInstanceName
|
||||||
|
|
||||||
def _delete_masking_view(
|
def _delete_masking_view(
|
||||||
@ -1657,14 +1689,11 @@ class VMAXMasking(object):
|
|||||||
:param storageGroupInstanceName: the storage group instance name
|
:param storageGroupInstanceName: the storage group instance name
|
||||||
:returns: instance name foundMaskingViewInstanceName
|
:returns: instance name foundMaskingViewInstanceName
|
||||||
"""
|
"""
|
||||||
foundMaskingViewInstanceName = None
|
|
||||||
maskingViews = conn.AssociatorNames(
|
maskingViews = conn.AssociatorNames(
|
||||||
storageGroupInstanceName,
|
storageGroupInstanceName,
|
||||||
ResultClass='Symm_LunMaskingView')
|
ResultClass='Symm_LunMaskingView')
|
||||||
if len(maskingViews) > 0:
|
|
||||||
foundMaskingViewInstanceName = maskingViews[0]
|
|
||||||
|
|
||||||
return foundMaskingViewInstanceName
|
return maskingViews
|
||||||
|
|
||||||
def add_volume_to_storage_group(
|
def add_volume_to_storage_group(
|
||||||
self, conn, controllerConfigService, storageGroupInstanceName,
|
self, conn, controllerConfigService, storageGroupInstanceName,
|
||||||
@ -1896,12 +1925,10 @@ class VMAXMasking(object):
|
|||||||
"""
|
"""
|
||||||
instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False)
|
instance = conn.GetInstance(storageGroupInstanceName, LocalOnly=False)
|
||||||
storageGroupName = instance['ElementName']
|
storageGroupName = instance['ElementName']
|
||||||
mvInstanceName = self.get_masking_view_from_storage_group(
|
mvInstanceNames = self.get_masking_view_from_storage_group(
|
||||||
conn, storageGroupInstanceName)
|
conn, storageGroupInstanceName)
|
||||||
if mvInstanceName is None:
|
if not mvInstanceNames:
|
||||||
LOG.debug("Unable to get masking view %(maskingView)s "
|
LOG.debug("Unable to get masking views from storage group.")
|
||||||
"from storage group.",
|
|
||||||
{'maskingView': mvInstanceName})
|
|
||||||
|
|
||||||
@coordination.synchronized("emc-sg-{storageGroup}")
|
@coordination.synchronized("emc-sg-{storageGroup}")
|
||||||
def do_remove_volume_from_sg(storageGroup):
|
def do_remove_volume_from_sg(storageGroup):
|
||||||
@ -1931,6 +1958,7 @@ class VMAXMasking(object):
|
|||||||
|
|
||||||
return do_remove_volume_from_sg(storageGroupName)
|
return do_remove_volume_from_sg(storageGroupName)
|
||||||
else:
|
else:
|
||||||
|
for mvInstanceName in mvInstanceNames:
|
||||||
# need to lock masking view when we are locking the storage
|
# need to lock masking view when we are locking the storage
|
||||||
# group to avoid possible deadlock situations from concurrent
|
# group to avoid possible deadlock situations from concurrent
|
||||||
# processes
|
# processes
|
||||||
@ -1965,7 +1993,8 @@ class VMAXMasking(object):
|
|||||||
storageGroupInstanceName,
|
storageGroupInstanceName,
|
||||||
volumeInstance, volumeInstance['ElementName'],
|
volumeInstance, volumeInstance['ElementName'],
|
||||||
numVolInStorageGroup, extraSpecs)
|
numVolInStorageGroup, extraSpecs)
|
||||||
return do_remove_volume_from_sg(maskingViewName, storageGroupName)
|
return do_remove_volume_from_sg(maskingViewName,
|
||||||
|
storageGroupName)
|
||||||
|
|
||||||
def _last_vol_in_SG(
|
def _last_vol_in_SG(
|
||||||
self, conn, controllerConfigService, storageGroupInstanceName,
|
self, conn, controllerConfigService, storageGroupInstanceName,
|
||||||
@ -1993,9 +2022,9 @@ class VMAXMasking(object):
|
|||||||
LOG.debug("Only one volume remains in storage group "
|
LOG.debug("Only one volume remains in storage group "
|
||||||
"%(sgname)s. Driver will attempt cleanup.",
|
"%(sgname)s. Driver will attempt cleanup.",
|
||||||
{'sgname': storageGroupName})
|
{'sgname': storageGroupName})
|
||||||
mvInstanceName = self.get_masking_view_from_storage_group(
|
mvInstanceNames = self.get_masking_view_from_storage_group(
|
||||||
conn, storageGroupInstanceName)
|
conn, storageGroupInstanceName)
|
||||||
if mvInstanceName is None:
|
if not mvInstanceNames:
|
||||||
# Remove the volume from the storage group and delete the SG.
|
# Remove the volume from the storage group and delete the SG.
|
||||||
self._remove_last_vol_and_delete_sg(
|
self._remove_last_vol_and_delete_sg(
|
||||||
conn, controllerConfigService,
|
conn, controllerConfigService,
|
||||||
@ -2004,6 +2033,8 @@ class VMAXMasking(object):
|
|||||||
volumeName, extraSpecs)
|
volumeName, extraSpecs)
|
||||||
status = True
|
status = True
|
||||||
else:
|
else:
|
||||||
|
mv_count = len(mvInstanceNames)
|
||||||
|
for mvInstanceName in mvInstanceNames:
|
||||||
maskingViewInstance = conn.GetInstance(
|
maskingViewInstance = conn.GetInstance(
|
||||||
mvInstanceName, LocalOnly=False)
|
mvInstanceName, LocalOnly=False)
|
||||||
maskingViewName = maskingViewInstance['ElementName']
|
maskingViewName = maskingViewInstance['ElementName']
|
||||||
@ -2013,9 +2044,10 @@ class VMAXMasking(object):
|
|||||||
conn, controllerConfigService, mvInstanceName,
|
conn, controllerConfigService, mvInstanceName,
|
||||||
maskingViewName, storageGroupInstanceName,
|
maskingViewName, storageGroupInstanceName,
|
||||||
storageGroupName, volumeInstance, volumeName,
|
storageGroupName, volumeInstance, volumeName,
|
||||||
extraSpecs)
|
extraSpecs, mv_count)
|
||||||
do_delete_mv_ig_and_sg()
|
do_delete_mv_ig_and_sg()
|
||||||
status = True
|
status = True
|
||||||
|
mv_count -= 1
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def _multiple_vols_in_SG(
|
def _multiple_vols_in_SG(
|
||||||
@ -2053,7 +2085,7 @@ class VMAXMasking(object):
|
|||||||
def _delete_mv_ig_and_sg(
|
def _delete_mv_ig_and_sg(
|
||||||
self, conn, controllerConfigService, mvInstanceName,
|
self, conn, controllerConfigService, mvInstanceName,
|
||||||
maskingViewName, storageGroupInstanceName, storageGroupName,
|
maskingViewName, storageGroupInstanceName, storageGroupName,
|
||||||
volumeInstance, volumeName, extraSpecs):
|
volumeInstance, volumeName, extraSpecs, mv_count):
|
||||||
"""Delete the Masking view, the storage Group and the initiator group.
|
"""Delete the Masking view, the storage Group and the initiator group.
|
||||||
|
|
||||||
:param conn: connection to the ecom server
|
:param conn: connection to the ecom server
|
||||||
@ -2065,6 +2097,7 @@ class VMAXMasking(object):
|
|||||||
:param volumeInstance: the volume Instance
|
:param volumeInstance: the volume Instance
|
||||||
:param volumeName: the volume name
|
:param volumeName: the volume name
|
||||||
:param extraSpecs: extra specs
|
:param extraSpecs: extra specs
|
||||||
|
:param mv_count: number of masking views
|
||||||
"""
|
"""
|
||||||
isV3 = extraSpecs[ISV3]
|
isV3 = extraSpecs[ISV3]
|
||||||
fastPolicyName = extraSpecs.get(FASTPOLICY, None)
|
fastPolicyName = extraSpecs.get(FASTPOLICY, None)
|
||||||
@ -2092,6 +2125,10 @@ class VMAXMasking(object):
|
|||||||
storageSystemInstanceName['Name'],
|
storageSystemInstanceName['Name'],
|
||||||
storageGroupInstanceName, extraSpecs)
|
storageGroupInstanceName, extraSpecs)
|
||||||
|
|
||||||
|
if mv_count == 1:
|
||||||
|
if self._is_volume_in_storage_group(
|
||||||
|
conn, storageGroupInstanceName,
|
||||||
|
volumeInstance, storageGroupName):
|
||||||
self._remove_last_vol_and_delete_sg(
|
self._remove_last_vol_and_delete_sg(
|
||||||
conn, controllerConfigService, storageGroupInstanceName,
|
conn, controllerConfigService, storageGroupInstanceName,
|
||||||
storageGroupName, volumeInstance.path, volumeName,
|
storageGroupName, volumeInstance.path, volumeName,
|
||||||
@ -2456,10 +2493,10 @@ class VMAXMasking(object):
|
|||||||
# Get the SG by IGs.
|
# Get the SG by IGs.
|
||||||
for sgInstanceName in storageGroupInstanceNames:
|
for sgInstanceName in storageGroupInstanceNames:
|
||||||
# Get maskingview from storage group.
|
# Get maskingview from storage group.
|
||||||
mvInstanceName = self.get_masking_view_from_storage_group(
|
mvInstanceNames = self.get_masking_view_from_storage_group(
|
||||||
conn, sgInstanceName)
|
conn, sgInstanceName)
|
||||||
# Get initiator group from masking view.
|
# Get initiator group from masking view.
|
||||||
if mvInstanceName:
|
for mvInstanceName in mvInstanceNames:
|
||||||
LOG.debug("Found masking view associated with SG "
|
LOG.debug("Found masking view associated with SG "
|
||||||
"%(storageGroup)s: %(maskingview)s",
|
"%(storageGroup)s: %(maskingview)s",
|
||||||
{'maskingview': mvInstanceName,
|
{'maskingview': mvInstanceName,
|
||||||
|
@ -16,14 +16,12 @@
|
|||||||
import ast
|
import ast
|
||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
from oslo_service import loopingcall
|
from oslo_service import loopingcall
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import six
|
import six
|
||||||
@ -2680,77 +2678,6 @@ class VMAXUtils(object):
|
|||||||
PropertyList=propertylist)
|
PropertyList=propertylist)
|
||||||
return modifiedInstance
|
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, "w") as f:
|
|
||||||
jsonutils.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, "w") as f:
|
|
||||||
jsonutils.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 = jsonutils.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):
|
def get_iqn(self, conn, ipendpointinstancename):
|
||||||
"""Get the IPv4Address from the ip endpoint instance name.
|
"""Get the IPv4Address from the ip endpoint instance name.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user