Merge "VMAX driver - Detaching volumes if part of two or more MVs"

This commit is contained in:
Jenkins 2017-05-25 18:25:05 +00:00 committed by Gerrit Code Review
commit da08bc0f6e
4 changed files with 195 additions and 21 deletions

View File

@ -16,6 +16,7 @@
import ast
import os
import shutil
import sys
import tempfile
import unittest
import uuid
@ -3431,6 +3432,9 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
self.driver.delete_volume,
self.data.failed_delete_vol)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
common.VMAXCommon,
'_is_same_host',
@ -3451,7 +3455,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'ISCSINoFAST'})
def test_already_mapped_no_fast_success(
self, _mock_volume_type, mock_wrap_group, mock_wrap_device,
mock_is_same_host):
mock_is_same_host, mock_rec):
self.driver.common._get_correct_port_group = mock.Mock(
return_value=self.data.port_group)
self.driver.initialize_connection(self.data.test_volume,
@ -3481,6 +3485,9 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
self.driver.initialize_connection(self.data.test_volume,
self.data.connector)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
common.VMAXCommon,
'_get_port_group_from_source',
@ -3518,7 +3525,8 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
mock_device,
mock_same_host,
mock_sg_from_mv,
mock_pg_from_mv):
mock_pg_from_mv,
mock_rec):
extraSpecs = self.data.extra_specs
rollback_dict = self.driver.common._populate_masking_dict(
self.data.test_volume, self.data.connector, extraSpecs)
@ -3528,6 +3536,9 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
self.driver.initialize_connection(self.data.test_volume,
self.data.connector)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
masking.VMAXMasking,
'_get_initiator_group_from_masking_view',
@ -3550,7 +3561,7 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'ISCSINoFAST'})
def test_map_existing_masking_view_no_fast_success(
self, _mock_volume_type, mock_wrap_group, mock_storage_group,
mock_initiator_group, mock_ig_from_mv):
mock_initiator_group, mock_ig_from_mv, mock_rec):
self.driver.initialize_connection(self.data.test_volume,
self.data.connector)
@ -4447,6 +4458,9 @@ class VMAXISCSIDriverFastTestCase(test.TestCase):
self.driver.delete_volume,
self.data.failed_delete_vol)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
common.VMAXCommon,
'_is_same_host',
@ -4467,7 +4481,7 @@ class VMAXISCSIDriverFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'ISCSIFAST'})
def test_already_mapped_fast_success(
self, _mock_volume_type, mock_wrap_group, mock_wrap_device,
mock_is_same_host):
mock_is_same_host, mock_rec):
self.driver.common._get_correct_port_group = mock.Mock(
return_value=self.data.port_group)
self.driver.initialize_connection(self.data.test_volume,
@ -5068,6 +5082,9 @@ class VMAXFCDriverNoFastTestCase(test.TestCase):
self.driver.delete_volume,
self.data.failed_delete_vol)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
common.VMAXCommon,
'_is_same_host',
@ -5082,7 +5099,8 @@ class VMAXFCDriverNoFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'FCNoFAST',
'FASTPOLICY': 'FC_GOLD1'})
def test_map_lookup_service_no_fast_success(
self, _mock_volume_type, mock_maskingview, mock_is_same_host):
self, _mock_volume_type, mock_maskingview, mock_is_same_host,
mock_rec):
self.data.test_volume['volume_name'] = "vmax-1234567"
common = self.driver.common
common.get_target_wwns_from_masking_view = mock.Mock(
@ -5597,6 +5615,9 @@ class VMAXFCDriverFastTestCase(test.TestCase):
self.driver.delete_volume,
self.data.failed_delete_vol)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
common.VMAXCommon,
'_is_same_host',
@ -5611,7 +5632,7 @@ class VMAXFCDriverFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'FCFAST',
'FASTPOLICY': 'FC_GOLD1'})
def test_map_fast_success(self, _mock_volume_type, mock_maskingview,
mock_is_same_host):
mock_is_same_host, mock_rec):
common = self.driver.common
common.get_target_wwns_list = mock.Mock(
return_value=VMAXCommonData.target_wwns)
@ -6660,6 +6681,9 @@ class EMCV3DriverTestCase(test.TestCase):
self.data.test_ctxt, self.data.test_CG,
add_volumes, remove_volumes)
@mock.patch.object(
utils.VMAXUtils,
'insert_live_migration_record')
@mock.patch.object(
utils.VMAXUtils,
'get_volume_element_name',
@ -6678,7 +6702,7 @@ class EMCV3DriverTestCase(test.TestCase):
return_value={'volume_backend_name': 'V3_BE'})
def test_map_v3_success(
self, _mock_volume_type, mock_maskingview, mock_is_same_host,
mock_element_name):
mock_element_name, mock_rec):
common = self.driver.common
common.get_target_wwns_list = mock.Mock(
return_value=VMAXCommonData.target_wwns)
@ -8985,6 +9009,59 @@ class VMAXUtilsTest(test.TestCase):
self.assertRaises(exception.VolumeBackendAPIException,
utils.get_array_and_device_id, volume, external_ref)
@mock.patch('builtins.open' if sys.version_info >= (3,)
else '__builtin__.open')
def test_insert_live_migration_record(self, mock_open):
volume = {'id': '12345678-87654321'}
tempdir = tempfile.mkdtemp()
utils.LIVE_MIGRATION_FILE = (
tempdir + '/livemigrationarray')
lm_file_name = ("%(prefix)s-%(volid)s"
% {'prefix': utils.LIVE_MIGRATION_FILE,
'volid': volume['id'][:8]})
self.driver.utils.insert_live_migration_record(volume)
mock_open.assert_called_once_with(lm_file_name, "w")
self.driver.utils.delete_live_migration_record(volume)
shutil.rmtree(tempdir)
def test_delete_live_migration_record(self):
volume = {'id': '12345678-87654321'}
tempdir = tempfile.mkdtemp()
utils.LIVE_MIGRATION_FILE = (
tempdir + '/livemigrationarray')
lm_file_name = ("%(prefix)s-%(volid)s"
% {'prefix': utils.LIVE_MIGRATION_FILE,
'volid': volume['id'][:8]})
m = mock.mock_open()
with mock.patch('{}.open'.format(__name__), m, create=True):
with open(lm_file_name, "w") as f:
f.write('live migration details')
self.driver.utils.insert_live_migration_record(volume)
self.driver.utils.delete_live_migration_record(volume)
m.assert_called_once_with(lm_file_name, "w")
shutil.rmtree(tempdir)
def test_get_live_migration_record(self):
volume = {'id': '12345678-87654321'}
tempdir = tempfile.mkdtemp()
utils.LIVE_MIGRATION_FILE = (
tempdir + '/livemigrationarray')
lm_file_name = ("%(prefix)s-%(volid)s"
% {'prefix': utils.LIVE_MIGRATION_FILE,
'volid': volume['id'][:8]})
self.driver.utils.insert_live_migration_record(volume)
record = self.driver.utils.get_live_migration_record(volume)
self.assertEqual(volume['id'], record[0])
os.remove(lm_file_name)
shutil.rmtree(tempdir)
def test_get_live_migration_file_name(self):
volume = {'id': '12345678-87654321'}
lm_live_migration = self.driver.utils.get_live_migration_file_name(
volume)
self.assertIn('/livemigrationarray-12345678', lm_live_migration)
self.assertIn('/tmp/', lm_live_migration)
class VMAXCommonTest(test.TestCase):
def setUp(self):

View File

@ -527,7 +527,12 @@ class VMAXCommon(object):
vol_instance = self._find_lun(volume)
storage_system = vol_instance['SystemName']
if self._is_volume_multiple_masking_views(vol_instance):
livemigrationrecord = self.utils.get_live_migration_record(volume)
if livemigrationrecord:
self.utils.delete_live_migration_record(volume)
if livemigrationrecord and self._is_volume_multiple_masking_views(
vol_instance):
return
configservice = self.utils.find_controller_configuration_service(
@ -613,12 +618,14 @@ class VMAXCommon(object):
"The device number is %(deviceNumber)s.",
{'volume': volumeName,
'deviceNumber': deviceNumber})
self.utils.insert_live_migration_record(volume)
# Special case, we still need to get the iscsi ip address.
portGroupName = (
self._get_correct_port_group(
deviceInfoDict, maskingViewDict['storageSystemName']))
else:
if isLiveMigration:
self.utils.insert_live_migration_record(volume)
maskingViewDict['storageGroupInstanceName'] = (
self._get_storage_group_from_source(sourceInfoDict))
maskingViewDict['portGroupInstanceName'] = (
@ -676,6 +683,9 @@ class VMAXCommon(object):
(rollbackDict['isV3'] is not None)):
(self.masking._check_if_rollback_action_for_masking_required(
self.conn, rollbackDict))
livemigrationrecord = self.utils.get_live_migration_record(volume)
if livemigrationrecord:
self.utils.delete_live_migration_record(volume)
exception_message = (_("Error Attaching volume %(vol)s.")
% {'vol': volumeName})
raise exception.VolumeBackendAPIException(
@ -1945,14 +1955,16 @@ class VMAXCommon(object):
"""
maskedvols = []
data = {}
isLiveMigration = False
source_data = {}
foundController = None
foundNumDeviceNumber = None
foundMaskingViewName = None
volumeName = volume['name']
volumeInstance = self._find_lun(volume)
storageSystemName = volumeInstance['SystemName']
isLiveMigration = False
source_data = {}
if not volumeInstance:
return data, isLiveMigration, source_data
unitnames = self.conn.ReferenceNames(
volumeInstance.path,
@ -1965,7 +1977,15 @@ class VMAXCommon(object):
if index > -1:
unitinstance = self.conn.GetInstance(unitname,
LocalOnly=False)
numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
if unitinstance['DeviceNumber']:
numDeviceNumber = int(unitinstance['DeviceNumber'], 16)
else:
LOG.debug(
"Device number not found for volume "
"%(volumeName)s %(volumeInstance)s.",
{'volumeName': volumeName,
'volumeInstance': volumeInstance.path})
break
foundNumDeviceNumber = numDeviceNumber
foundController = controller
controllerInstance = self.conn.GetInstance(controller,

View File

@ -2110,10 +2110,16 @@ class VMAXMasking(object):
self._last_volume_delete_masking_view(
conn, controllerConfigService, mvInstanceName,
maskingViewName, extraSpecs)
self._last_volume_delete_initiator_group(
conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, host)
initiatorGroupInstance = conn.GetInstance(initiatorGroupInstanceName)
if initiatorGroupInstance:
initiatorGroupName = initiatorGroupInstance['ElementName']
@coordination.synchronized('emc-ig-{initiatorGroupName}')
def inner_do_delete_initiator_group(initiatorGroupName):
self._last_volume_delete_initiator_group(
conn, controllerConfigService,
initiatorGroupInstanceName, extraSpecs, host)
inner_do_delete_initiator_group(initiatorGroupName)
if not isV3:
isTieringPolicySupported, tierPolicyServiceInstanceName = (
self._get_tiering_info(conn, storageSystemInstanceName,
@ -2672,15 +2678,19 @@ class VMAXMasking(object):
:param hardwareIdManagementService - hardware id management service
:param hardwareIdPath - The path of the initiator object
"""
ret = conn.InvokeMethod('DeleteStorageHardwareID',
hardwareIdManagementService,
HardwareID = hardwareIdPath)
ret = -1
try:
ret = conn.InvokeMethod('DeleteStorageHardwareID',
hardwareIdManagementService,
HardwareID = hardwareIdPath)
except Exception:
pass
if ret == 0:
LOG.debug("Deletion of initiator path %(hardwareIdPath)s "
"is successful.", {'hardwareIdPath': hardwareIdPath})
else:
LOG.warning("Deletion of initiator path %(hardwareIdPath)s "
"is failed.", {'hardwareIdPath': hardwareIdPath})
LOG.debug("Deletion of initiator path %(hardwareIdPath)s "
"failed.", {'hardwareIdPath': hardwareIdPath})
def _delete_initiators_from_initiator_group(self, conn,
controllerConfigService,
@ -2745,7 +2755,6 @@ class VMAXMasking(object):
"OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': host,
'protocol': protocol}))
if initiatorGroupName == defaultInitiatorGroupName:
maskingViewInstanceNames = (
self.get_masking_views_by_initiator_group(

View File

@ -16,12 +16,15 @@
import ast
import datetime
import hashlib
import os
import random
import re
import tempfile
import time
from xml.dom import minidom
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_service import loopingcall
from oslo_utils import units
import six
@ -53,7 +56,7 @@ EMC_ROOT = 'root/emc'
CONCATENATED = 'concatenated'
CINDER_EMC_CONFIG_FILE_PREFIX = '/etc/cinder/cinder_emc_config_'
CINDER_EMC_CONFIG_FILE_POSTFIX = '.xml'
LIVE_MIGRATION_FILE = '/etc/cinder/livemigrationarray'
LIVE_MIGRATION_FILE = tempfile.gettempdir() + '/livemigrationarray'
ISCSI = 'iscsi'
FC = 'fc'
JOB_RETRIES = 60
@ -2978,3 +2981,68 @@ class VMAXUtils(object):
default_dict[INTERVAL] = INTERVAL_10_SEC
default_dict[RETRIES] = JOB_RETRIES
return default_dict
def insert_live_migration_record(self, volume):
"""Insert a record of live migration destination into a temporary file
:param volume: the volume dictionary
"""
lm_file_name = self.get_live_migration_file_name(volume)
live_migration_details = self.get_live_migration_record(volume)
if live_migration_details:
return
else:
live_migration_details = {volume['id']: [volume['id']]}
try:
with open(lm_file_name, "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
"""
lm_file_name = self.get_live_migration_file_name(volume)
live_migration_details = self.get_live_migration_record(volume)
if live_migration_details:
if volume['id'] in live_migration_details:
os.remove(lm_file_name)
def get_live_migration_record(self, volume):
"""get record of live migration destination from a temporary file
:param volume: the volume dictionary
:returns: returns a single record
"""
returned_record = None
lm_file_name = self.get_live_migration_file_name(volume)
if os.path.isfile(lm_file_name):
with open(lm_file_name, "rb") as f:
live_migration_details = jsonutils.load(f)
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_live_migration_file_name(self, volume):
"""get name of temporary live migration file
:param volume: the volume dictionary
:returns: returns file name
"""
lm_file_name = ("%(prefix)s-%(volid)s"
% {'prefix': LIVE_MIGRATION_FILE,
'volid': volume['id'][:8]})
return lm_file_name