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
This commit is contained in:
parent
1205ec79cd
commit
069dd5b80d
@ -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,38 +597,39 @@ 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)
|
deviceNumber = deviceInfoDict['hostlunid']
|
||||||
if isSameHost:
|
LOG.info("Volume %(volume)s is already mapped. "
|
||||||
# Device is already mapped to same host so we will leave
|
"The device number is %(deviceNumber)s.",
|
||||||
# the state as is.
|
{'volume': volumeName,
|
||||||
|
'deviceNumber': deviceNumber})
|
||||||
deviceNumber = deviceInfoDict['hostlunid']
|
# Special case, we still need to get the iscsi ip address.
|
||||||
LOG.info("Volume %(volume)s is already mapped. "
|
portGroupName = (
|
||||||
"The device number is %(deviceNumber)s.",
|
self._get_correct_port_group(
|
||||||
{'volume': volumeName,
|
deviceInfoDict, maskingViewDict['storageSystemName']))
|
||||||
'deviceNumber': deviceNumber})
|
else:
|
||||||
# Special case, we still need to get the iscsi ip address.
|
if isLiveMigration:
|
||||||
portGroupName = (
|
maskingViewDict['storageGroupInstanceName'] = (
|
||||||
self._get_correct_port_group(
|
self._get_storage_group_from_source(sourceInfoDict))
|
||||||
deviceInfoDict, maskingViewDict['storageSystemName']))
|
maskingViewDict['portGroupInstanceName'] = (
|
||||||
|
self._get_port_group_from_source(sourceInfoDict))
|
||||||
else:
|
|
||||||
deviceInfoDict, portGroupName = self._attach_volume(
|
deviceInfoDict, portGroupName = self._attach_volume(
|
||||||
volume, connector, extraSpecs, maskingViewDict, True)
|
volume, connector, extraSpecs, maskingViewDict, True)
|
||||||
else:
|
else:
|
||||||
deviceInfoDict, portGroupName = (
|
deviceInfoDict, portGroupName = (
|
||||||
self._attach_volume(
|
self._attach_volume(
|
||||||
volume, connector, extraSpecs, maskingViewDict))
|
volume, connector, extraSpecs, maskingViewDict))
|
||||||
|
|
||||||
if self.protocol.lower() == 'iscsi':
|
if self.protocol.lower() == 'iscsi':
|
||||||
deviceInfoDict['ip_and_iqn'] = (
|
deviceInfoDict['ip_and_iqn'] = (
|
||||||
@ -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,17 +287,38 @@ class VMAXMasking(object):
|
|||||||
LOG.info("Returning random Port Group: %(portGroupName)s.",
|
LOG.info("Returning random Port Group: %(portGroupName)s.",
|
||||||
{'portGroupName': pgGroupName})
|
{'portGroupName': pgGroupName})
|
||||||
|
|
||||||
storageGroupInstanceName, errorMessage = (
|
if maskingViewDict['isLiveMigration']:
|
||||||
self._check_storage_group(
|
try:
|
||||||
conn, maskingViewDict, defaultStorageGroupInstanceName))
|
# We are sharing the storage group and port group
|
||||||
if errorMessage:
|
# between host and target
|
||||||
return None, storageGroupInstanceName, errorMessage
|
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 = (
|
portGroupInstanceName, errorMessage = (
|
||||||
self._check_port_group(conn, controllerConfigService,
|
self._check_port_group(conn, controllerConfigService,
|
||||||
pgGroupName))
|
pgGroupName))
|
||||||
if errorMessage:
|
if errorMessage:
|
||||||
return None, storageGroupInstanceName, errorMessage
|
return None, storageGroupInstanceName, errorMessage
|
||||||
|
|
||||||
initiatorGroupInstanceName, errorMessage = (
|
initiatorGroupInstanceName, errorMessage = (
|
||||||
self._check_initiator_group(conn, controllerConfigService,
|
self._check_initiator_group(conn, controllerConfigService,
|
||||||
@ -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
|
||||||
|
|
||||||
@ -1619,19 +1634,36 @@ class VMAXMasking(object):
|
|||||||
foundView = self._find_masking_view(
|
foundView = self._find_masking_view(
|
||||||
conn, maskingViewName, storageSystemName)
|
conn, maskingViewName, storageSystemName)
|
||||||
if foundView:
|
if foundView:
|
||||||
groups = conn.AssociatorNames(
|
foundPortMaskingGroupInstanceName = (
|
||||||
foundView,
|
self.get_port_group_from_masking_view_instance(
|
||||||
ResultClass='CIM_TargetMaskingGroup')
|
conn, foundView))
|
||||||
if len(groups) > 0:
|
|
||||||
foundPortMaskingGroupInstanceName = groups[0]
|
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Masking view: %(view)s InitiatorMaskingGroup: %(masking)s.",
|
"Masking view: %(view)s portMaskingGroup: %(masking)s.",
|
||||||
{'view': maskingViewName,
|
{'view': maskingViewName,
|
||||||
'masking': foundPortMaskingGroupInstanceName})
|
'masking': foundPortMaskingGroupInstanceName})
|
||||||
|
|
||||||
return 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(
|
def _delete_masking_view(
|
||||||
self, conn, controllerConfigService, maskingViewName,
|
self, conn, controllerConfigService, maskingViewName,
|
||||||
maskingViewInstanceName, extraSpecs):
|
maskingViewInstanceName, extraSpecs):
|
||||||
@ -1673,14 +1705,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,
|
||||||
@ -1912,12 +1941,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):
|
||||||
@ -1947,41 +1974,43 @@ class VMAXMasking(object):
|
|||||||
|
|
||||||
return do_remove_volume_from_sg(storageGroupName)
|
return do_remove_volume_from_sg(storageGroupName)
|
||||||
else:
|
else:
|
||||||
# need to lock masking view when we are locking the storage
|
for mvInstanceName in mvInstanceNames:
|
||||||
# group to avoid possible deadlock situations from concurrent
|
# need to lock masking view when we are locking the storage
|
||||||
# processes
|
# group to avoid possible deadlock situations from concurrent
|
||||||
maskingViewInstance = conn.GetInstance(
|
# processes
|
||||||
mvInstanceName, LocalOnly=False)
|
maskingViewInstance = conn.GetInstance(
|
||||||
maskingViewName = maskingViewInstance['ElementName']
|
mvInstanceName, LocalOnly=False)
|
||||||
|
maskingViewName = maskingViewInstance['ElementName']
|
||||||
|
|
||||||
@coordination.synchronized("emc-mv-{maskingView}")
|
@coordination.synchronized("emc-mv-{maskingView}")
|
||||||
@coordination.synchronized("emc-sg-{storageGroup}")
|
@coordination.synchronized("emc-sg-{storageGroup}")
|
||||||
def do_remove_volume_from_sg(maskingView, storageGroup):
|
def do_remove_volume_from_sg(maskingView, storageGroup):
|
||||||
volumeInstanceNames = self.get_devices_from_storage_group(
|
volumeInstanceNames = self.get_devices_from_storage_group(
|
||||||
conn, storageGroupInstanceName)
|
conn, storageGroupInstanceName)
|
||||||
numVolInStorageGroup = len(volumeInstanceNames)
|
numVolInStorageGroup = len(volumeInstanceNames)
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"There are %(numVol)d volumes in the storage group "
|
"There are %(numVol)d volumes in the storage group "
|
||||||
"%(maskingGroup)s associated with %(mvName)s",
|
"%(maskingGroup)s associated with %(mvName)s",
|
||||||
{'numVol': numVolInStorageGroup,
|
{'numVol': numVolInStorageGroup,
|
||||||
'maskingGroup': storageGroup,
|
'maskingGroup': storageGroup,
|
||||||
'mvName': maskingView})
|
'mvName': maskingView})
|
||||||
|
|
||||||
if numVolInStorageGroup == 1:
|
if numVolInStorageGroup == 1:
|
||||||
# Last volume in the storage group.
|
# Last volume in the storage group.
|
||||||
self._last_vol_in_SG(
|
self._last_vol_in_SG(
|
||||||
conn, controllerConfigService,
|
conn, controllerConfigService,
|
||||||
storageGroupInstanceName,
|
storageGroupInstanceName,
|
||||||
storageGroupName, volumeInstance,
|
storageGroupName, volumeInstance,
|
||||||
volumeInstance['ElementName'], extraSpecs)
|
volumeInstance['ElementName'], extraSpecs)
|
||||||
else:
|
else:
|
||||||
# Not the last volume so remove it from storage group
|
# Not the last volume so remove it from storage group
|
||||||
self._multiple_vols_in_SG(
|
self._multiple_vols_in_SG(
|
||||||
conn, controllerConfigService,
|
conn, controllerConfigService,
|
||||||
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,
|
||||||
@ -2009,9 +2038,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,
|
||||||
@ -2020,18 +2049,21 @@ class VMAXMasking(object):
|
|||||||
volumeName, extraSpecs)
|
volumeName, extraSpecs)
|
||||||
status = True
|
status = True
|
||||||
else:
|
else:
|
||||||
maskingViewInstance = conn.GetInstance(
|
mv_count = len(mvInstanceNames)
|
||||||
mvInstanceName, LocalOnly=False)
|
for mvInstanceName in mvInstanceNames:
|
||||||
maskingViewName = maskingViewInstance['ElementName']
|
maskingViewInstance = conn.GetInstance(
|
||||||
|
mvInstanceName, LocalOnly=False)
|
||||||
|
maskingViewName = maskingViewInstance['ElementName']
|
||||||
|
|
||||||
def do_delete_mv_ig_and_sg():
|
def do_delete_mv_ig_and_sg():
|
||||||
return self._delete_mv_ig_and_sg(
|
return self._delete_mv_ig_and_sg(
|
||||||
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(
|
||||||
@ -2069,7 +2101,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
|
||||||
@ -2081,6 +2113,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)
|
||||||
@ -2108,16 +2141,20 @@ class VMAXMasking(object):
|
|||||||
storageSystemInstanceName['Name'],
|
storageSystemInstanceName['Name'],
|
||||||
storageGroupInstanceName, extraSpecs)
|
storageGroupInstanceName, extraSpecs)
|
||||||
|
|
||||||
self._remove_last_vol_and_delete_sg(
|
if mv_count == 1:
|
||||||
conn, controllerConfigService, storageGroupInstanceName,
|
if self._is_volume_in_storage_group(
|
||||||
storageGroupName, volumeInstance.path, volumeName,
|
conn, storageGroupInstanceName,
|
||||||
extraSpecs)
|
volumeInstance, storageGroupName):
|
||||||
|
self._remove_last_vol_and_delete_sg(
|
||||||
|
conn, controllerConfigService, storageGroupInstanceName,
|
||||||
|
storageGroupName, volumeInstance.path, volumeName,
|
||||||
|
extraSpecs)
|
||||||
|
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Volume %(volumeName)s successfully removed from SG and "
|
"Volume %(volumeName)s successfully removed from SG and "
|
||||||
"Storage Group %(storageGroupName)s successfully deleted. ",
|
"Storage Group %(storageGroupName)s successfully deleted. ",
|
||||||
{'volumeName': volumeName,
|
{'volumeName': volumeName,
|
||||||
'storageGroupName': storageGroupName})
|
'storageGroupName': storageGroupName})
|
||||||
|
|
||||||
def _return_back_to_default_sg(
|
def _return_back_to_default_sg(
|
||||||
self, conn, controllerConfigService, volumeInstance, volumeName,
|
self, conn, controllerConfigService, volumeInstance, volumeName,
|
||||||
@ -2472,10 +2509,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