Merge "Hyper-V: Skip logging out in-use targets"
This commit is contained in:
@@ -96,14 +96,41 @@ class BaseVolumeUtilsTestCase(test.NoDBTestCase):
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_SESSION_ID, session_id)
|
||||
|
||||
def test_get_device_number_for_target(self):
|
||||
def test_get_devices_for_target(self):
|
||||
init_session = self._create_initiator_session()
|
||||
self._volutils._conn_wmi.query.return_value = [init_session]
|
||||
devices = self._volutils._get_devices_for_target(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(init_session.Devices, devices)
|
||||
|
||||
def test_get_devices_for_target_not_found(self):
|
||||
self._volutils._conn_wmi.query.return_value = None
|
||||
devices = self._volutils._get_devices_for_target(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(0, len(devices))
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_device_number_for_target(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
fake_get_devices.return_value = init_session.Devices
|
||||
device_number = self._volutils.get_device_number_for_target(
|
||||
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_DEVICE_NUMBER, device_number)
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_target_lun_count(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
fake_get_devices.return_value = [init_session]
|
||||
lun_count = self._volutils.get_target_lun_count(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(len(init_session.Devices), lun_count)
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
"_get_drive_number_from_disk_path")
|
||||
def test_get_target_from_disk_path(self, mock_get_session_id):
|
||||
|
||||
@@ -229,6 +229,8 @@ class HyperVAPIBaseTestCase(test.NoDBTestCase):
|
||||
'get_device_number_for_target')
|
||||
self._mox.StubOutWithMock(basevolumeutils.BaseVolumeUtils,
|
||||
'get_target_from_disk_path')
|
||||
self._mox.StubOutWithMock(basevolumeutils.BaseVolumeUtils,
|
||||
'get_target_lun_count')
|
||||
|
||||
self._mox.StubOutWithMock(volumeutils.VolumeUtils,
|
||||
'login_storage_target')
|
||||
@@ -639,6 +641,10 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
self._mox.VerifyAll()
|
||||
|
||||
def _setup_destroy_mocks(self, destroy_disks=True):
|
||||
fake_volume_drives = ['fake_volume_drive']
|
||||
fake_target_iqn = 'fake_target_iqn'
|
||||
fake_target_lun = 'fake_target_lun'
|
||||
|
||||
m = vmutils.VMUtils.vm_exists(mox.Func(self._check_instance_name))
|
||||
m.AndReturn(True)
|
||||
|
||||
@@ -648,10 +654,16 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
self._setup_delete_vm_log_mocks()
|
||||
|
||||
m = vmutils.VMUtils.get_vm_storage_paths(func)
|
||||
m.AndReturn(([], []))
|
||||
m.AndReturn(([], fake_volume_drives))
|
||||
|
||||
vmutils.VMUtils.destroy_vm(func)
|
||||
|
||||
m = self._conn._volumeops.get_target_from_disk_path(
|
||||
fake_volume_drives[0])
|
||||
m.AndReturn((fake_target_iqn, fake_target_lun))
|
||||
|
||||
self._mock_logout_storage_target(fake_target_iqn)
|
||||
|
||||
if destroy_disks:
|
||||
m = fake.PathUtils.get_instance_dir(mox.IsA(str),
|
||||
create_dir=False,
|
||||
@@ -678,11 +690,16 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
def test_live_migration_with_volumes(self):
|
||||
self._test_live_migration(with_volumes=True)
|
||||
|
||||
def test_live_migration_with_multiple_luns_per_target(self):
|
||||
self._test_live_migration(with_volumes=True,
|
||||
other_luns_available=True)
|
||||
|
||||
def test_live_migration_with_target_failure(self):
|
||||
self._test_live_migration(test_failure=True)
|
||||
|
||||
def _test_live_migration(self, test_failure=False,
|
||||
with_volumes=False,
|
||||
other_luns_available=False,
|
||||
unsupported_os=False):
|
||||
dest_server = 'fake_server'
|
||||
|
||||
@@ -700,7 +717,7 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
|
||||
if with_volumes:
|
||||
fake_target_iqn = 'fake_target_iqn'
|
||||
fake_target_lun = 1
|
||||
fake_target_lun_count = 1
|
||||
|
||||
if not unsupported_os:
|
||||
m = fake.PathUtils.get_vm_console_log_paths(mox.IsA(str))
|
||||
@@ -725,10 +742,12 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
m.AndRaise(vmutils.HyperVException('Simulated failure'))
|
||||
|
||||
if with_volumes:
|
||||
m.AndReturn([(fake_target_iqn, fake_target_lun)])
|
||||
volumeutils.VolumeUtils.logout_storage_target(fake_target_iqn)
|
||||
m.AndReturn({fake_target_iqn: fake_target_lun_count})
|
||||
|
||||
self._mock_logout_storage_target(fake_target_iqn,
|
||||
other_luns_available)
|
||||
else:
|
||||
m.AndReturn([])
|
||||
m.AndReturn({})
|
||||
|
||||
self._mox.ReplayAll()
|
||||
try:
|
||||
@@ -1349,7 +1368,7 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
fake_mounted_disk,
|
||||
fake_device_number)
|
||||
|
||||
volumeutils.VolumeUtils.logout_storage_target(target_iqn)
|
||||
self._mock_logout_storage_target(target_iqn)
|
||||
|
||||
def test_attach_volume_logout(self):
|
||||
instance_data = self._get_instance_data()
|
||||
@@ -1386,7 +1405,8 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
self.assertRaises(vmutils.HyperVException, self._conn.attach_volume,
|
||||
None, connection_info, instance_data, mount_point)
|
||||
|
||||
def _mock_detach_volume(self, target_iqn, target_lun):
|
||||
def _mock_detach_volume(self, target_iqn, target_lun,
|
||||
other_luns_available=False):
|
||||
fake_mounted_disk = "fake_mounted_disk"
|
||||
fake_device_number = 0
|
||||
m = volumeutils.VolumeUtils.get_device_number_for_target(target_iqn,
|
||||
@@ -1399,9 +1419,18 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
|
||||
vmutils.VMUtils.detach_vm_disk(mox.IsA(str), fake_mounted_disk)
|
||||
|
||||
volumeutils.VolumeUtils.logout_storage_target(mox.IsA(str))
|
||||
self._mock_logout_storage_target(target_iqn, other_luns_available)
|
||||
|
||||
def test_detach_volume(self):
|
||||
def _mock_logout_storage_target(self, target_iqn,
|
||||
other_luns_available=False):
|
||||
|
||||
m = volumeutils.VolumeUtils.get_target_lun_count(target_iqn)
|
||||
m.AndReturn(1 + int(other_luns_available))
|
||||
|
||||
if not other_luns_available:
|
||||
volumeutils.VolumeUtils.logout_storage_target(target_iqn)
|
||||
|
||||
def _test_detach_volume(self, other_luns_available=False):
|
||||
instance_data = self._get_instance_data()
|
||||
self.assertIn('name', instance_data)
|
||||
|
||||
@@ -1414,12 +1443,18 @@ class HyperVAPITestCase(HyperVAPIBaseTestCase):
|
||||
|
||||
mount_point = '/dev/sdc'
|
||||
|
||||
self._mock_detach_volume(target_iqn, target_lun)
|
||||
|
||||
self._mock_detach_volume(target_iqn, target_lun, other_luns_available)
|
||||
self._mox.ReplayAll()
|
||||
self._conn.detach_volume(connection_info, instance_data, mount_point)
|
||||
self._mox.VerifyAll()
|
||||
|
||||
def test_detach_volume(self):
|
||||
self._test_detach_volume()
|
||||
|
||||
def test_detach_volume_multiple_luns_per_target(self):
|
||||
# The iSCSI target should not be disconnected in this case.
|
||||
self._test_detach_volume(other_luns_available=True)
|
||||
|
||||
def test_boot_from_volume(self):
|
||||
block_device_info = db_fakes.get_fake_block_device_info(
|
||||
self._volume_target_portal, self._volume_id)
|
||||
|
||||
@@ -118,23 +118,27 @@ class BaseVolumeUtils(object):
|
||||
if device_number == drive_number:
|
||||
return initiator_session.SessionId
|
||||
|
||||
def get_device_number_for_target(self, target_iqn, target_lun):
|
||||
def _get_devices_for_target(self, target_iqn):
|
||||
initiator_sessions = self._conn_wmi.query("SELECT * FROM "
|
||||
"MSiSCSIInitiator_Session"
|
||||
"Class WHERE TargetName='%s'"
|
||||
% target_iqn)
|
||||
if not initiator_sessions:
|
||||
return None
|
||||
return []
|
||||
|
||||
devices = initiator_sessions[0].Devices
|
||||
return initiator_sessions[0].Devices
|
||||
|
||||
if not devices:
|
||||
return None
|
||||
def get_device_number_for_target(self, target_iqn, target_lun):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
|
||||
for device in devices:
|
||||
if device.ScsiLun == target_lun:
|
||||
return device.DeviceNumber
|
||||
|
||||
def get_target_lun_count(self, target_iqn):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
return len(devices)
|
||||
|
||||
def get_target_from_disk_path(self, disk_path):
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
|
||||
drive_number = self._get_drive_number_from_disk_path(disk_path)
|
||||
|
||||
@@ -68,8 +68,9 @@ class LiveMigrationOps(object):
|
||||
self._vmops.copy_vm_console_logs(instance_name, dest)
|
||||
iscsi_targets = self._livemigrutils.live_migrate_vm(instance_name,
|
||||
dest)
|
||||
for (target_iqn, target_lun) in iscsi_targets:
|
||||
self._volumeops.logout_storage_target(target_iqn)
|
||||
for target_iqn, target_luns_count in iscsi_targets.items():
|
||||
self._volumeops.logout_storage_target(target_iqn,
|
||||
target_luns_count)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.debug("Calling live migration recover_method "
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
@@ -120,18 +121,23 @@ class LiveMigrationUtils(object):
|
||||
volutils_remote = volumeutilsv2.VolumeUtilsV2(dest_host)
|
||||
|
||||
disk_paths_remote = {}
|
||||
iscsi_targets = []
|
||||
iscsi_targets = collections.defaultdict(int)
|
||||
for (rasd_rel_path, disk_path) in disk_paths.items():
|
||||
(target_iqn,
|
||||
target_lun) = self._volutils.get_target_from_disk_path(disk_path)
|
||||
iscsi_targets.append((target_iqn, target_lun))
|
||||
target = self._volutils.get_target_from_disk_path(disk_path)
|
||||
if target:
|
||||
(target_iqn, target_lun) = target
|
||||
|
||||
dev_num = volutils_remote.get_device_number_for_target(target_iqn,
|
||||
target_lun)
|
||||
disk_path_remote = vmutils_remote.get_mounted_disk_by_drive_number(
|
||||
dev_num)
|
||||
iscsi_targets[target_iqn] += 1
|
||||
|
||||
disk_paths_remote[rasd_rel_path] = disk_path_remote
|
||||
dev_num = volutils_remote.get_device_number_for_target(
|
||||
target_iqn, target_lun)
|
||||
disk_path_remote = (
|
||||
vmutils_remote.get_mounted_disk_by_drive_number(dev_num))
|
||||
|
||||
disk_paths_remote[rasd_rel_path] = disk_path_remote
|
||||
else:
|
||||
LOG.debug("Could not retrieve iSCSI target "
|
||||
"from disk path: %s", disk_path)
|
||||
|
||||
return (disk_paths_remote, iscsi_targets)
|
||||
|
||||
@@ -223,7 +229,7 @@ class LiveMigrationUtils(object):
|
||||
rmt_ip_addr_list = self._get_remote_ip_address_list(conn_v2_remote,
|
||||
dest_host)
|
||||
|
||||
iscsi_targets = []
|
||||
iscsi_targets = {}
|
||||
planned_vm = None
|
||||
disk_paths = self._get_physical_disk_paths(vm_name)
|
||||
if disk_paths:
|
||||
|
||||
@@ -390,10 +390,6 @@ class VMOps(object):
|
||||
except KeyError:
|
||||
raise exception.InvalidDiskFormat(disk_format=configdrive_ext)
|
||||
|
||||
def _disconnect_volumes(self, volume_drives):
|
||||
for volume_drive in volume_drives:
|
||||
self._volumeops.disconnect_volume(volume_drive)
|
||||
|
||||
def _delete_disk_files(self, instance_name):
|
||||
self._pathutils.get_instance_dir(instance_name,
|
||||
create_dir=False,
|
||||
@@ -413,7 +409,7 @@ class VMOps(object):
|
||||
(disk_files, volume_drives) = storage
|
||||
|
||||
self._vmutils.destroy_vm(instance_name)
|
||||
self._disconnect_volumes(volume_drives)
|
||||
self._volumeops.disconnect_volumes(volume_drives)
|
||||
else:
|
||||
LOG.debug("Instance not found", instance=instance)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"""
|
||||
Management class for Storage-related functions (attach, detach, etc).
|
||||
"""
|
||||
import collections
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
@@ -152,7 +153,7 @@ class VolumeOps(object):
|
||||
LOG.error(_LE('Unable to attach volume to instance %s'),
|
||||
instance_name)
|
||||
if target_iqn:
|
||||
self._volutils.logout_storage_target(target_iqn)
|
||||
self.logout_storage_target(target_iqn)
|
||||
|
||||
def _get_free_controller_slot(self, scsi_controller_path):
|
||||
attached_disks = self._vmutils.get_attached_disks(scsi_controller_path)
|
||||
@@ -168,9 +169,16 @@ class VolumeOps(object):
|
||||
for vol in mapping:
|
||||
self.detach_volume(vol['connection_info'], instance_name)
|
||||
|
||||
def logout_storage_target(self, target_iqn):
|
||||
LOG.debug("Logging off storage target %s", target_iqn)
|
||||
self._volutils.logout_storage_target(target_iqn)
|
||||
def logout_storage_target(self, target_iqn, disconnected_luns_count=1):
|
||||
total_available_luns = self._volutils.get_target_lun_count(
|
||||
target_iqn)
|
||||
|
||||
if total_available_luns == disconnected_luns_count:
|
||||
LOG.debug("Logging off storage target %s", target_iqn)
|
||||
self._volutils.logout_storage_target(target_iqn)
|
||||
else:
|
||||
LOG.debug("Skipping disconnecting target %s as there "
|
||||
"are LUNs still being used.", target_iqn)
|
||||
|
||||
def detach_volume(self, connection_info, instance_name):
|
||||
"""Detach a volume to the SCSI controller."""
|
||||
@@ -241,12 +249,20 @@ class VolumeOps(object):
|
||||
'for target_iqn: %s') % target_iqn)
|
||||
return mounted_disk_path
|
||||
|
||||
def disconnect_volume(self, physical_drive_path):
|
||||
# Get the session_id of the ISCSI connection
|
||||
session_id = self._volutils.get_session_id_from_mounted_disk(
|
||||
physical_drive_path)
|
||||
# Logging out the target
|
||||
self._volutils.execute_log_out(session_id)
|
||||
def disconnect_volumes(self, volume_drives):
|
||||
targets = collections.defaultdict(int)
|
||||
for volume_drive in volume_drives:
|
||||
target = self._volutils.get_target_from_disk_path(
|
||||
volume_drive)
|
||||
if target:
|
||||
target_iqn = target[0]
|
||||
targets[target_iqn] += 1
|
||||
else:
|
||||
LOG.debug("Could not retrieve iSCSI target from disk path: ",
|
||||
volume_drive)
|
||||
|
||||
for target_iqn in targets:
|
||||
self.logout_storage_target(target_iqn, targets[target_iqn])
|
||||
|
||||
def get_target_from_disk_path(self, physical_drive_path):
|
||||
return self._volutils.get_target_from_disk_path(physical_drive_path)
|
||||
|
||||
Reference in New Issue
Block a user