Adds Nova utils classes as is

Adds Utils classes in oslo_windows.utils.
Adds utilsfactory module in oslo_windows.
Adds Utils classes unit tests in oslo_windows.tests.utils.
Adds utilsfactory module unit tests in oslo_windows.tests.
This commit is contained in:
Claudiu Belu 2015-08-05 20:18:42 +03:00
parent 2dd79e9e22
commit 9433b9b6fb
38 changed files with 6245 additions and 0 deletions

View File

View File

@ -0,0 +1,57 @@
# Copyright 2014 Cloudbase Solutions SRL
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Unit tests for the Hyper-V utils factory.
"""
import mock
from oslo_config import cfg
from nova import test
from nova.virt.hyperv import hostutils
from nova.virt.hyperv import utilsfactory
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import vmutilsv2
CONF = cfg.CONF
class TestHyperVUtilsFactory(test.NoDBTestCase):
def test_get_vmutils_force_v1_and_min_version(self):
self._test_returned_class(None, True, True)
def test_get_vmutils_v2(self):
self._test_returned_class(vmutilsv2.VMUtilsV2, False, True)
def test_get_vmutils_v2_r2(self):
self._test_returned_class(vmutils.VMUtils, False, False)
def test_get_vmutils_force_v1_and_not_min_version(self):
self._test_returned_class(vmutils.VMUtils, True, False)
def _test_returned_class(self, expected_class, force_v1, os_supports_v2):
CONF.set_override('force_hyperv_utils_v1', force_v1, 'hyperv')
with mock.patch.object(
hostutils.HostUtils,
'check_min_windows_version') as mock_check_min_windows_version:
mock_check_min_windows_version.return_value = os_supports_v2
if os_supports_v2 and force_v1:
self.assertRaises(vmutils.HyperVException,
utilsfactory.get_vmutils)
else:
actual_class = type(utilsfactory.get_vmutils())
self.assertEqual(actual_class, expected_class)

View File

View File

@ -0,0 +1,188 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import basevolumeutils
def _exception_thrower():
raise Exception("Testing exception handling.")
class BaseVolumeUtilsTestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V BaseVolumeUtils class."""
_FAKE_COMPUTER_NAME = "fake_computer_name"
_FAKE_DOMAIN_NAME = "fake_domain_name"
_FAKE_INITIATOR_NAME = "fake_initiator_name"
_FAKE_INITIATOR_IQN_NAME = "iqn.1991-05.com.microsoft:fake_computer_name"
_FAKE_DISK_PATH = 'fake_path DeviceID="123\\\\2"'
_FAKE_MOUNT_DEVICE = '/dev/fake/mount'
_FAKE_DEVICE_NAME = '/dev/fake/path'
_FAKE_SWAP = {'device_name': _FAKE_DISK_PATH}
def setUp(self):
self._volutils = basevolumeutils.BaseVolumeUtils()
self._volutils._conn_wmi = mock.MagicMock()
self._volutils._conn_cimv2 = mock.MagicMock()
super(BaseVolumeUtilsTestCase, self).setUp()
def test_get_iscsi_initiator_ok(self):
self._check_get_iscsi_initiator(
mock.MagicMock(return_value=mock.sentinel.FAKE_KEY),
self._FAKE_INITIATOR_NAME)
def test_get_iscsi_initiator_exception(self):
initiator_name = "%(iqn)s.%(domain)s" % {
'iqn': self._FAKE_INITIATOR_IQN_NAME,
'domain': self._FAKE_DOMAIN_NAME
}
self._check_get_iscsi_initiator(_exception_thrower, initiator_name)
def _check_get_iscsi_initiator(self, winreg_method, expected):
mock_computer = mock.MagicMock()
mock_computer.name = self._FAKE_COMPUTER_NAME
mock_computer.Domain = self._FAKE_DOMAIN_NAME
self._volutils._conn_cimv2.Win32_ComputerSystem.return_value = [
mock_computer]
with mock.patch.object(basevolumeutils,
'_winreg', create=True) as mock_winreg:
mock_winreg.OpenKey = winreg_method
mock_winreg.QueryValueEx = mock.MagicMock(return_value=[expected])
initiator_name = self._volutils.get_iscsi_initiator()
self.assertEqual(expected, initiator_name)
@mock.patch.object(basevolumeutils, 'driver')
def test_volume_in_mapping(self, mock_driver):
mock_driver.block_device_info_get_mapping.return_value = [
{'mount_device': self._FAKE_MOUNT_DEVICE}]
mock_driver.block_device_info_get_swap = mock.MagicMock(
return_value=self._FAKE_SWAP)
mock_driver.block_device_info_get_ephemerals = mock.MagicMock(
return_value=[{'device_name': self._FAKE_DEVICE_NAME}])
mock_driver.swap_is_usable = mock.MagicMock(return_value=True)
self.assertTrue(self._volutils.volume_in_mapping(
self._FAKE_MOUNT_DEVICE, mock.sentinel.FAKE_BLOCK_DEVICE_INFO))
def test_get_drive_number_from_disk_path(self):
fake_disk_path = (
'\\\\WIN-I5BTVHOIFGK\\root\\virtualization\\v2:Msvm_DiskDrive.'
'CreationClassName="Msvm_DiskDrive",DeviceID="Microsoft:353B3BE8-'
'310C-4cf4-839E-4E1B14616136\\\\1",SystemCreationClassName='
'"Msvm_ComputerSystem",SystemName="WIN-I5BTVHOIFGK"')
expected_disk_number = 1
ret_val = self._volutils._get_drive_number_from_disk_path(
fake_disk_path)
self.assertEqual(expected_disk_number, ret_val)
def test_get_drive_number_not_found(self):
fake_disk_path = 'fake_disk_path'
ret_val = self._volutils._get_drive_number_from_disk_path(
fake_disk_path)
self.assertFalse(ret_val)
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
"_get_drive_number_from_disk_path")
def test_get_session_id_from_mounted_disk(self, mock_get_session_id):
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
mock_initiator_session = self._create_initiator_session()
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
mock_ses_class.return_value = [mock_initiator_session]
session_id = self._volutils.get_session_id_from_mounted_disk(
self._FAKE_DISK_PATH)
self.assertEqual(mock.sentinel.FAKE_SESSION_ID, session_id)
def test_get_devices_for_target(self):
init_session = self._create_initiator_session()
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
mock_ses_class.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):
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
mock_ses_class.return_value = []
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
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
mock_ses_class.return_value = [init_session]
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()
# Only disk devices are being counted.
disk_device = mock.Mock(DeviceType=self._volutils._FILE_DEVICE_DISK)
init_session.Devices.append(disk_device)
fake_get_devices.return_value = init_session.Devices
lun_count = self._volutils.get_target_lun_count(
mock.sentinel.FAKE_IQN)
self.assertEqual(1, 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):
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
init_sess = self._create_initiator_session()
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
mock_ses_class.return_value = [init_sess]
(target_name, scsi_lun) = self._volutils.get_target_from_disk_path(
self._FAKE_DISK_PATH)
self.assertEqual(mock.sentinel.FAKE_TARGET_NAME, target_name)
self.assertEqual(mock.sentinel.FAKE_LUN, scsi_lun)
def _create_initiator_session(self):
device = mock.MagicMock()
device.ScsiLun = mock.sentinel.FAKE_LUN
device.DeviceNumber = mock.sentinel.FAKE_DEVICE_NUMBER
device.TargetName = mock.sentinel.FAKE_TARGET_NAME
init_session = mock.MagicMock()
init_session.Devices = [device]
init_session.SessionId = mock.sentinel.FAKE_SESSION_ID
return init_session

View File

@ -0,0 +1,141 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import constants
from nova.virt.hyperv import hostutils
class FakeCPUSpec(object):
"""Fake CPU Spec for unit tests."""
Architecture = mock.sentinel.cpu_arch
Name = mock.sentinel.cpu_name
Manufacturer = mock.sentinel.cpu_man
NumberOfCores = mock.sentinel.cpu_cores
NumberOfLogicalProcessors = mock.sentinel.cpu_procs
class HostUtilsTestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V hostutils class."""
_FAKE_MEMORY_TOTAL = 1024
_FAKE_MEMORY_FREE = 512
_FAKE_DISK_SIZE = 1024
_FAKE_DISK_FREE = 512
_FAKE_VERSION_GOOD = '6.2.0'
_FAKE_VERSION_BAD = '6.1.9'
def setUp(self):
self._hostutils = hostutils.HostUtils()
self._hostutils._conn_cimv2 = mock.MagicMock()
super(HostUtilsTestCase, self).setUp()
@mock.patch('nova.virt.hyperv.hostutils.ctypes')
def test_get_host_tick_count64(self, mock_ctypes):
tick_count64 = "100"
mock_ctypes.windll.kernel32.GetTickCount64.return_value = tick_count64
response = self._hostutils.get_host_tick_count64()
self.assertEqual(tick_count64, response)
def test_get_cpus_info(self):
cpu = mock.MagicMock(spec=FakeCPUSpec)
self._hostutils._conn_cimv2.query.return_value = [cpu]
cpu_list = self._hostutils.get_cpus_info()
self.assertEqual([cpu._mock_children], cpu_list)
def test_get_memory_info(self):
memory = mock.MagicMock()
type(memory).TotalVisibleMemorySize = mock.PropertyMock(
return_value=self._FAKE_MEMORY_TOTAL)
type(memory).FreePhysicalMemory = mock.PropertyMock(
return_value=self._FAKE_MEMORY_FREE)
self._hostutils._conn_cimv2.query.return_value = [memory]
total_memory, free_memory = self._hostutils.get_memory_info()
self.assertEqual(self._FAKE_MEMORY_TOTAL, total_memory)
self.assertEqual(self._FAKE_MEMORY_FREE, free_memory)
def test_get_volume_info(self):
disk = mock.MagicMock()
type(disk).Size = mock.PropertyMock(return_value=self._FAKE_DISK_SIZE)
type(disk).FreeSpace = mock.PropertyMock(
return_value=self._FAKE_DISK_FREE)
self._hostutils._conn_cimv2.query.return_value = [disk]
(total_memory, free_memory) = self._hostutils.get_volume_info(
mock.sentinel.FAKE_DRIVE)
self.assertEqual(self._FAKE_DISK_SIZE, total_memory)
self.assertEqual(self._FAKE_DISK_FREE, free_memory)
def test_check_min_windows_version_true(self):
self._test_check_min_windows_version(self._FAKE_VERSION_GOOD, True)
def test_check_min_windows_version_false(self):
self._test_check_min_windows_version(self._FAKE_VERSION_BAD, False)
def _test_check_min_windows_version(self, version, expected):
os = mock.MagicMock()
os.Version = version
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [os]
self.assertEqual(expected,
self._hostutils.check_min_windows_version(6, 2))
def _test_host_power_action(self, action):
fake_win32 = mock.MagicMock()
fake_win32.Win32Shutdown = mock.MagicMock()
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [
fake_win32]
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
self._hostutils.host_power_action(action)
fake_win32.Win32Shutdown.assert_called_with(
self._hostutils._HOST_FORCED_SHUTDOWN)
elif action == constants.HOST_POWER_ACTION_REBOOT:
self._hostutils.host_power_action(action)
fake_win32.Win32Shutdown.assert_called_with(
self._hostutils._HOST_FORCED_REBOOT)
else:
self.assertRaises(NotImplementedError,
self._hostutils.host_power_action, action)
def test_host_shutdown(self):
self._test_host_power_action(constants.HOST_POWER_ACTION_SHUTDOWN)
def test_host_reboot(self):
self._test_host_power_action(constants.HOST_POWER_ACTION_REBOOT)
def test_host_startup(self):
self._test_host_power_action(constants.HOST_POWER_ACTION_STARTUP)
def test_get_supported_vm_types_2012_r2(self):
with mock.patch.object(self._hostutils,
'check_min_windows_version') as mock_check_win:
mock_check_win.return_value = True
result = self._hostutils.get_supported_vm_types()
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1,
constants.IMAGE_PROP_VM_GEN_2], result)
def test_get_supported_vm_types(self):
with mock.patch.object(self._hostutils,
'check_min_windows_version') as mock_check_win:
mock_check_win.return_value = False
result = self._hostutils.get_supported_vm_types()
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1], result)

View File

@ -0,0 +1,30 @@
# Copyright 2015 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import hostutilsv2
class HostUtilsV2TestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V hostutilsv2 class."""
def setUp(self):
self._hostutils = hostutilsv2.HostUtilsV2()
self._hostutils._conn_cimv2 = mock.MagicMock()
self._hostutils._conn_virt = mock.MagicMock()
super(HostUtilsV2TestCase, self).setUp()

View File

@ -0,0 +1,61 @@
# Copyright 2014 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.import mock
import mock
import os
from nova import test
from nova.virt.hyperv import ioutils
class IOThreadTestCase(test.NoDBTestCase):
_FAKE_SRC = r'fake_source_file'
_FAKE_DEST = r'fake_dest_file'
_FAKE_MAX_BYTES = 1
def setUp(self):
self._iothread = ioutils.IOThread(
self._FAKE_SRC, self._FAKE_DEST, self._FAKE_MAX_BYTES)
super(IOThreadTestCase, self).setUp()
@mock.patch('__builtin__.open')
@mock.patch('os.rename')
@mock.patch('os.path.exists')
@mock.patch('os.remove')
def test_copy(self, fake_remove, fake_exists, fake_rename, fake_open):
fake_data = 'a'
fake_src = mock.Mock()
fake_dest = mock.Mock()
fake_src.read.return_value = fake_data
fake_dest.tell.return_value = 0
fake_exists.return_value = True
mock_context_manager = mock.MagicMock()
fake_open.return_value = mock_context_manager
mock_context_manager.__enter__.side_effect = [fake_src, fake_dest]
self._iothread._stopped.isSet = mock.Mock(side_effect=[False, True])
self._iothread._copy()
fake_dest.seek.assert_called_once_with(0, os.SEEK_END)
fake_dest.write.assert_called_once_with(fake_data)
fake_dest.close.assert_called_once_with()
fake_rename.assert_called_once_with(
self._iothread._dest, self._iothread._dest_archive)
fake_remove.assert_called_once_with(
self._iothread._dest_archive)
self.assertEqual(3, fake_open.call_count)

View File

@ -0,0 +1,274 @@
# Copyright 2014 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import livemigrationutils
class LiveMigrationUtilsTestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V LiveMigrationUtils class."""
_FAKE_RET_VAL = 0
_RESOURCE_TYPE_VHD = 31
_RESOURCE_TYPE_DISK = 17
_RESOURCE_SUB_TYPE_VHD = 'Microsoft:Hyper-V:Virtual Hard Disk'
_RESOURCE_SUB_TYPE_DISK = 'Microsoft:Hyper-V:Physical Disk Drive'
def setUp(self):
self.liveutils = livemigrationutils.LiveMigrationUtils()
self.liveutils._vmutils = mock.MagicMock()
self.liveutils._volutils = mock.MagicMock()
self._conn = mock.MagicMock()
self.liveutils._get_conn_v2 = mock.MagicMock(return_value=self._conn)
super(LiveMigrationUtilsTestCase, self).setUp()
def test_check_live_migration_config(self):
mock_migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
vsmssd = mock.MagicMock()
vsmssd.EnableVirtualSystemMigration = True
mock_migr_svc.associators.return_value = [vsmssd]
mock_migr_svc.MigrationServiceListenerIPAdressList.return_value = [
mock.sentinel.FAKE_HOST]
self.liveutils.check_live_migration_config()
self.assertTrue(mock_migr_svc.associators.called)
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
'_destroy_planned_vm')
def test_check_existing_planned_vm_found(self, mock_destroy_planned_vm):
mock_vm = mock.MagicMock()
mock_v2 = mock.MagicMock()
mock_v2.Msvm_PlannedComputerSystem.return_value = [mock_vm]
self.liveutils._check_existing_planned_vm(mock_v2, mock_vm)
mock_destroy_planned_vm.assert_called_once_with(mock_v2, mock_vm)
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
'_destroy_planned_vm')
def test_check_existing_planned_vm_none(self, mock_destroy_planned_vm):
mock_v2 = mock.MagicMock()
mock_v2.Msvm_PlannedComputerSystem.return_value = []
self.liveutils._check_existing_planned_vm(mock_v2, mock.MagicMock())
self.assertFalse(mock_destroy_planned_vm.called)
def test_create_remote_planned_vm(self):
mock_vsmsd = self._conn.query()[0]
mock_vm = mock.MagicMock()
mock_v2 = mock.MagicMock()
mock_v2.Msvm_PlannedComputerSystem.return_value = [mock_vm]
migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
migr_svc.MigrateVirtualSystemToHost.return_value = (
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
resulted_vm = self.liveutils._create_remote_planned_vm(
self._conn, mock_v2, mock_vm, [mock.sentinel.FAKE_REMOTE_IP_ADDR],
mock.sentinel.FAKE_HOST)
self.assertEqual(mock_vm, resulted_vm)
migr_svc.MigrateVirtualSystemToHost.assert_called_once_with(
ComputerSystem=mock_vm.path_.return_value,
DestinationHost=mock.sentinel.FAKE_HOST,
MigrationSettingData=mock_vsmsd.GetText_.return_value)
def test_get_physical_disk_paths(self):
ide_path = {mock.sentinel.IDE_PATH: mock.sentinel.IDE_HOST_RESOURCE}
scsi_path = {mock.sentinel.SCSI_PATH: mock.sentinel.SCSI_HOST_RESOURCE}
ide_ctrl = self.liveutils._vmutils.get_vm_ide_controller.return_value
scsi_ctrl = self.liveutils._vmutils.get_vm_scsi_controller.return_value
mock_get_controller_paths = (
self.liveutils._vmutils.get_controller_volume_paths)
mock_get_controller_paths.side_effect = [ide_path, scsi_path]
result = self.liveutils._get_physical_disk_paths(mock.sentinel.VM_NAME)
expected = dict(ide_path)
expected.update(scsi_path)
self.assertDictContainsSubset(expected, result)
calls = [mock.call(ide_ctrl), mock.call(scsi_ctrl)]
mock_get_controller_paths.assert_has_calls(calls)
def test_get_physical_disk_paths_no_ide(self):
scsi_path = {mock.sentinel.SCSI_PATH: mock.sentinel.SCSI_HOST_RESOURCE}
scsi_ctrl = self.liveutils._vmutils.get_vm_scsi_controller.return_value
mock_get_controller_paths = (
self.liveutils._vmutils.get_controller_volume_paths)
self.liveutils._vmutils.get_vm_ide_controller.return_value = None
mock_get_controller_paths.return_value = scsi_path
result = self.liveutils._get_physical_disk_paths(mock.sentinel.VM_NAME)
self.assertEqual(scsi_path, result)
mock_get_controller_paths.assert_called_once_with(scsi_ctrl)
@mock.patch.object(livemigrationutils.volumeutilsv2, 'VolumeUtilsV2')
def test_get_remote_disk_data(self, mock_vol_utils_class):
mock_vol_utils_remote = mock_vol_utils_class.return_value
mock_vm_utils = mock.MagicMock()
disk_paths = {
mock.sentinel.FAKE_RASD_PATH: mock.sentinel.FAKE_DISK_PATH}
self.liveutils._volutils.get_target_from_disk_path.return_value = (
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
mock_vol_utils_remote.get_device_number_for_target.return_value = (
mock.sentinel.FAKE_DEV_NUM)
mock_vm_utils.get_mounted_disk_by_drive_number.return_value = (
mock.sentinel.FAKE_DISK_PATH)
disk_paths = self.liveutils._get_remote_disk_data(
mock_vm_utils, disk_paths, mock.sentinel.FAKE_HOST)
self.liveutils._volutils.get_target_from_disk_path.assert_called_with(
mock.sentinel.FAKE_DISK_PATH)
mock_vol_utils_remote.get_device_number_for_target.assert_called_with(
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
mock_vm_utils.get_mounted_disk_by_drive_number.assert_called_once_with(
mock.sentinel.FAKE_DEV_NUM)
self.assertEqual(
{mock.sentinel.FAKE_RASD_PATH: mock.sentinel.FAKE_DISK_PATH},
disk_paths)
def test_update_planned_vm_disk_resources(self):
mock_vm_utils = mock.MagicMock()
self._prepare_vm_mocks(self._RESOURCE_TYPE_DISK,
self._RESOURCE_SUB_TYPE_DISK)
mock_vm = self._conn.Msvm_ComputerSystem.return_value[0]
sasd = mock_vm.associators()[0].associators()[0]
mock_vsmsvc = self._conn.Msvm_VirtualSystemManagementService()[0]
self.liveutils._update_planned_vm_disk_resources(
mock_vm_utils, self._conn, mock_vm, mock.sentinel.FAKE_VM_NAME,
{sasd.path.return_value.RelPath: mock.sentinel.FAKE_RASD_PATH})
mock_vsmsvc.ModifyResourceSettings.assert_called_once_with(
ResourceSettings=[sasd.GetText_.return_value])
def test_get_vhd_setting_data(self):
self._prepare_vm_mocks(self._RESOURCE_TYPE_VHD,
self._RESOURCE_SUB_TYPE_VHD)
mock_vm = self._conn.Msvm_ComputerSystem.return_value[0]
mock_sasd = mock_vm.associators()[0].associators()[0]
vhd_sds = self.liveutils._get_vhd_setting_data(mock_vm)
self.assertEqual([mock_sasd.GetText_.return_value], vhd_sds)
def test_live_migrate_vm_helper(self):
mock_conn_local = mock.MagicMock()
mock_vm = mock.MagicMock()
mock_vsmsd = mock_conn_local.query()[0]
mock_vsmsvc = mock_conn_local.Msvm_VirtualSystemMigrationService()[0]
mock_vsmsvc.MigrateVirtualSystemToHost.return_value = (
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
self.liveutils._live_migrate_vm(
mock_conn_local, mock_vm, None,
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
mock.sentinel.FAKE_RASD_PATH, mock.sentinel.FAKE_HOST)
mock_vsmsvc.MigrateVirtualSystemToHost.assert_called_once_with(
ComputerSystem=mock_vm.path_.return_value,
DestinationHost=mock.sentinel.FAKE_HOST,
MigrationSettingData=mock_vsmsd.GetText_.return_value,
NewResourceSettingData=mock.sentinel.FAKE_RASD_PATH)
@mock.patch.object(livemigrationutils, 'vmutilsv2')
def test_live_migrate_vm(self, mock_vm_utils):
mock_vm_utils_remote = mock_vm_utils.VMUtilsV2.return_value
mock_vm = self._get_vm()
mock_migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
mock_migr_svc.MigrationServiceListenerIPAddressList = [
mock.sentinel.FAKE_REMOTE_IP_ADDR]
# patches, call and assertions.
with mock.patch.multiple(
self.liveutils,
_destroy_planned_vm=mock.DEFAULT,
_get_physical_disk_paths=mock.DEFAULT,
_get_remote_disk_data=mock.DEFAULT,
_create_remote_planned_vm=mock.DEFAULT,
_update_planned_vm_disk_resources=mock.DEFAULT,
_get_vhd_setting_data=mock.DEFAULT,
_live_migrate_vm=mock.DEFAULT):
disk_paths = {
mock.sentinel.FAKE_IDE_PATH: mock.sentinel.FAKE_SASD_RESOURCE}
self.liveutils._get_physical_disk_paths.return_value = disk_paths
mock_disk_paths = [mock.sentinel.FAKE_DISK_PATH]
self.liveutils._get_remote_disk_data.return_value = (
mock_disk_paths)
self.liveutils._create_remote_planned_vm.return_value = mock_vm
self.liveutils.live_migrate_vm(mock.sentinel.FAKE_VM_NAME,
mock.sentinel.FAKE_HOST)
self.liveutils._get_remote_disk_data.assert_called_once_with(
mock_vm_utils_remote, disk_paths, mock.sentinel.FAKE_HOST)
self.liveutils._create_remote_planned_vm.assert_called_once_with(
self._conn, self._conn, mock_vm,
[mock.sentinel.FAKE_REMOTE_IP_ADDR], mock.sentinel.FAKE_HOST)
mocked_method = self.liveutils._update_planned_vm_disk_resources
mocked_method.assert_called_once_with(
mock_vm_utils_remote, self._conn, mock_vm,
mock.sentinel.FAKE_VM_NAME, mock_disk_paths)
self.liveutils._live_migrate_vm.assert_called_once_with(
self._conn, mock_vm, mock_vm,
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
self.liveutils._get_vhd_setting_data.return_value,
mock.sentinel.FAKE_HOST)
def _prepare_vm_mocks(self, resource_type, resource_sub_type):
mock_vm_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
vm = self._get_vm()
self._conn.Msvm_PlannedComputerSystem.return_value = [vm]
mock_vm_svc.DestroySystem.return_value = (mock.sentinel.FAKE_JOB_PATH,
self._FAKE_RET_VAL)
mock_vm_svc.ModifyResourceSettings.return_value = (
None, mock.sentinel.FAKE_JOB_PATH, self._FAKE_RET_VAL)
sasd = mock.MagicMock()
other_sasd = mock.MagicMock()
sasd.ResourceType = resource_type
sasd.ResourceSubType = resource_sub_type
sasd.HostResource = [mock.sentinel.FAKE_SASD_RESOURCE]
sasd.path.return_value.RelPath = mock.sentinel.FAKE_DISK_PATH
vm_settings = mock.MagicMock()
vm.associators.return_value = [vm_settings]
vm_settings.associators.return_value = [sasd, other_sasd]
def _get_vm(self):
mock_vm = mock.MagicMock()
self._conn.Msvm_ComputerSystem.return_value = [mock_vm]
mock_vm.path_.return_value = mock.sentinel.FAKE_VM_PATH
return mock_vm

View File

@ -0,0 +1,82 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import networkutils
from nova.virt.hyperv import vmutils
class NetworkUtilsTestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V NetworkUtils class."""
_FAKE_PORT = {'Name': mock.sentinel.FAKE_PORT_NAME}
_FAKE_RET_VALUE = 0
_MSVM_VIRTUAL_SWITCH = 'Msvm_VirtualSwitch'
def setUp(self):
self._networkutils = networkutils.NetworkUtils()
self._networkutils._conn = mock.MagicMock()
super(NetworkUtilsTestCase, self).setUp()
def test_get_external_vswitch(self):
mock_vswitch = mock.MagicMock()
mock_vswitch.path_.return_value = mock.sentinel.FAKE_VSWITCH_PATH
getattr(self._networkutils._conn,
self._MSVM_VIRTUAL_SWITCH).return_value = [mock_vswitch]
switch_path = self._networkutils.get_external_vswitch(
mock.sentinel.FAKE_VSWITCH_NAME)
self.assertEqual(mock.sentinel.FAKE_VSWITCH_PATH, switch_path)
def test_get_external_vswitch_not_found(self):
self._networkutils._conn.Msvm_VirtualEthernetSwitch.return_value = []
self.assertRaises(vmutils.HyperVException,
self._networkutils.get_external_vswitch,
mock.sentinel.FAKE_VSWITCH_NAME)
def test_get_external_vswitch_no_name(self):
mock_vswitch = mock.MagicMock()
mock_vswitch.path_.return_value = mock.sentinel.FAKE_VSWITCH_PATH
mock_ext_port = self._networkutils._conn.Msvm_ExternalEthernetPort()[0]
self._prepare_external_port(mock_vswitch, mock_ext_port)
switch_path = self._networkutils.get_external_vswitch(None)
self.assertEqual(mock.sentinel.FAKE_VSWITCH_PATH, switch_path)
def _prepare_external_port(self, mock_vswitch, mock_ext_port):
mock_lep = mock_ext_port.associators()[0]
mock_lep.associators.return_value = [mock_vswitch]
def test_create_vswitch_port(self):
svc = self._networkutils._conn.Msvm_VirtualSwitchManagementService()[0]
svc.CreateSwitchPort.return_value = (
self._FAKE_PORT, self._FAKE_RET_VALUE)
port = self._networkutils.create_vswitch_port(
mock.sentinel.FAKE_VSWITCH_PATH, mock.sentinel.FAKE_PORT_NAME)
svc.CreateSwitchPort.assert_called_once_with(
Name=mock.ANY, FriendlyName=mock.sentinel.FAKE_PORT_NAME,
ScopeOfResidence="", VirtualSwitch=mock.sentinel.FAKE_VSWITCH_PATH)
self.assertEqual(self._FAKE_PORT, port)
def test_vswitch_port_needed(self):
self.assertTrue(self._networkutils.vswitch_port_needed())

View File

@ -0,0 +1,45 @@
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova.tests.unit.virt.hyperv import test_networkutils
from nova.virt.hyperv import networkutilsv2
class NetworkUtilsV2TestCase(test_networkutils.NetworkUtilsTestCase):
"""Unit tests for the Hyper-V NetworkUtilsV2 class."""
_MSVM_VIRTUAL_SWITCH = 'Msvm_VirtualEthernetSwitch'
def setUp(self):
super(NetworkUtilsV2TestCase, self).setUp()
self._networkutils = networkutilsv2.NetworkUtilsV2()
self._networkutils._conn = mock.MagicMock()
def _prepare_external_port(self, mock_vswitch, mock_ext_port):
mock_lep = mock_ext_port.associators()[0]
mock_lep1 = mock_lep.associators()[0]
mock_esw = mock_lep1.associators()[0]
mock_esw.associators.return_value = [mock_vswitch]
def test_create_vswitch_port(self):
self.assertRaises(
NotImplementedError,
self._networkutils.create_vswitch_port,
mock.sentinel.FAKE_VSWITCH_PATH,
mock.sentinel.FAKE_PORT_NAME)
def test_vswitch_port_needed(self):
self.assertFalse(self._networkutils.vswitch_port_needed())

View File

@ -0,0 +1,170 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import mock
from nova.tests.unit.virt.hyperv import test_base
from nova.virt.hyperv import constants
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import vmutils
class PathUtilsTestCase(test_base.HyperVBaseTestCase):
"""Unit tests for the Hyper-V PathUtils class."""
def setUp(self):
super(PathUtilsTestCase, self).setUp()
self.fake_instance_dir = os.path.join('C:', 'fake_instance_dir')
self.fake_instance_name = 'fake_instance_name'
self._pathutils = pathutils.PathUtils()
@mock.patch.object(pathutils.PathUtils, 'rename')
@mock.patch.object(os.path, 'isfile')
@mock.patch.object(os, 'listdir')
def test_move_folder_files(self, mock_listdir, mock_isfile, mock_rename):
src_dir = 'src'
dest_dir = 'dest'
fname = 'tmp_file.txt'
subdir = 'tmp_folder'
src_fname = os.path.join(src_dir, fname)
dest_fname = os.path.join(dest_dir, fname)
# making sure src_subdir is not moved.
mock_listdir.return_value = [fname, subdir]
mock_isfile.side_effect = [True, False]
self._pathutils.move_folder_files(src_dir, dest_dir)
mock_rename.assert_called_once_with(src_fname, dest_fname)
def _mock_lookup_configdrive_path(self, ext):
self._pathutils.get_instance_dir = mock.MagicMock(
return_value=self.fake_instance_dir)
def mock_exists(*args, **kwargs):
path = args[0]
return True if path[(path.rfind('.') + 1):] == ext else False
self._pathutils.exists = mock_exists
configdrive_path = self._pathutils.lookup_configdrive_path(
self.fake_instance_name)
return configdrive_path
def test_lookup_configdrive_path(self):
for format_ext in constants.DISK_FORMAT_MAP:
configdrive_path = self._mock_lookup_configdrive_path(format_ext)
fake_path = os.path.join(self.fake_instance_dir,
'configdrive.' + format_ext)
self.assertEqual(configdrive_path, fake_path)
def test_lookup_configdrive_path_non_exist(self):
self._pathutils.get_instance_dir = mock.MagicMock(
return_value=self.fake_instance_dir)
self._pathutils.exists = mock.MagicMock(return_value=False)
configdrive_path = self._pathutils.lookup_configdrive_path(
self.fake_instance_name)
self.assertIsNone(configdrive_path)
@mock.patch.object(pathutils.PathUtils, 'unmount_smb_share')
@mock.patch('os.path.exists')
def _test_check_smb_mapping(self, mock_exists, mock_unmount_smb_share,
existing_mappings=True, share_available=False):
mock_exists.return_value = share_available
fake_mappings = (
[mock.sentinel.smb_mapping] if existing_mappings else [])
self._pathutils._smb_conn.Msft_SmbMapping.return_value = (
fake_mappings)
ret_val = self._pathutils.check_smb_mapping(
mock.sentinel.share_path)
self.assertEqual(existing_mappings and share_available, ret_val)
if existing_mappings and not share_available:
mock_unmount_smb_share.assert_called_once_with(
mock.sentinel.share_path, force=True)
def test_check_mapping(self):
self._test_check_smb_mapping()
def test_remake_unavailable_mapping(self):
self._test_check_smb_mapping(existing_mappings=True,
share_available=False)
def test_available_mapping(self):
self._test_check_smb_mapping(existing_mappings=True,
share_available=True)
def test_mount_smb_share(self):
fake_create = self._pathutils._smb_conn.Msft_SmbMapping.Create
self._pathutils.mount_smb_share(mock.sentinel.share_path,
mock.sentinel.username,
mock.sentinel.password)
fake_create.assert_called_once_with(
RemotePath=mock.sentinel.share_path,
UserName=mock.sentinel.username,
Password=mock.sentinel.password)
def _test_unmount_smb_share(self, force=False):
fake_mapping = mock.Mock()
smb_mapping_class = self._pathutils._smb_conn.Msft_SmbMapping
smb_mapping_class.return_value = [fake_mapping]
self._pathutils.unmount_smb_share(mock.sentinel.share_path,
force)
smb_mapping_class.assert_called_once_with(
RemotePath=mock.sentinel.share_path)
fake_mapping.Remove.assert_called_once_with(Force=force)
def test_soft_unmount_smb_share(self):
self._test_unmount_smb_share()
def test_force_unmount_smb_share(self):
self._test_unmount_smb_share(force=True)
@mock.patch('shutil.rmtree')
def test_rmtree(self, mock_rmtree):
class WindowsError(Exception):
def __init__(self, winerror=None):
self.winerror = winerror
mock_rmtree.side_effect = [WindowsError(
pathutils.ERROR_DIR_IS_NOT_EMPTY), True]
fake_windows_error = WindowsError
with mock.patch('__builtin__.WindowsError',
fake_windows_error, create=True):
self._pathutils.rmtree(mock.sentinel.FAKE_PATH)
mock_rmtree.assert_has_calls([mock.call(mock.sentinel.FAKE_PATH),
mock.call(mock.sentinel.FAKE_PATH)])
@mock.patch('os.path.join')
def test_get_instances_sub_dir(self, fake_path_join):
class WindowsError(Exception):
def __init__(self, winerror=None):
self.winerror = winerror
fake_dir_name = "fake_dir_name"
fake_windows_error = WindowsError
self._pathutils._check_create_dir = mock.MagicMock(
side_effect=WindowsError(pathutils.ERROR_INVALID_NAME))
with mock.patch('__builtin__.WindowsError',
fake_windows_error, create=True):
self.assertRaises(vmutils.HyperVException,
self._pathutils._get_instances_sub_dir,
fake_dir_name)

View File

@ -0,0 +1,28 @@
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova import test
from nova.virt.hyperv import rdpconsoleutils
class RDPConsoleUtilsTestCase(test.NoDBTestCase):
def setUp(self):
self._rdpconsoleutils = rdpconsoleutils.RDPConsoleUtils()
super(RDPConsoleUtilsTestCase, self).setUp()
def test_get_rdp_console_port(self):
listener_port = self._rdpconsoleutils.get_rdp_console_port()
self.assertEqual(self._rdpconsoleutils._DEFAULT_HYPERV_RDP_PORT,
listener_port)

View File

@ -0,0 +1,37 @@
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import test
from nova.virt.hyperv import rdpconsoleutilsv2
class RDPConsoleUtilsV2TestCase(test.NoDBTestCase):
_FAKE_RDP_PORT = 1000
def setUp(self):
self._rdpconsoleutils = rdpconsoleutilsv2.RDPConsoleUtilsV2()
self._rdpconsoleutils._conn = mock.MagicMock()
super(RDPConsoleUtilsV2TestCase, self).setUp()
def test_get_rdp_console_port(self):
conn = self._rdpconsoleutils._conn
mock_rdp_setting_data = conn.Msvm_TerminalServiceSettingData()[0]
mock_rdp_setting_data.ListenerPort = self._FAKE_RDP_PORT
listener_port = self._rdpconsoleutils.get_rdp_console_port()
self.assertEqual(self._FAKE_RDP_PORT, listener_port)

View File

@ -0,0 +1,288 @@
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_utils import units
from nova import test
from nova.virt.hyperv import constants
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vmutils
class VHDUtilsBaseTestCase(test.NoDBTestCase):
"Base Class unit test classes of Hyper-V VHD Utils classes."
_FAKE_VHD_PATH = "C:\\fake_path.vhdx"
_FAKE_PARENT_PATH = "C:\\fake_parent_path.vhdx"
_FAKE_FORMAT = 3
_FAKE_TYPE = 3
_FAKE_MAX_INTERNAL_SIZE = units.Gi
_FAKE_DYNAMIC_BLK_SIZE = 2097152
_FAKE_BAD_TYPE = 5
_FAKE_JOB_PATH = 'fake_job_path'
_FAKE_RET_VAL = 0
_FAKE_VHD_INFO_XML = (
"""<INSTANCE CLASSNAME="Msvm_VirtualHardDiskSettingData">
<PROPERTY NAME="BlockSize" TYPE="uint32">
<VALUE>33554432</VALUE>
</PROPERTY>
<PROPERTY NAME="Caption" TYPE="string">
<VALUE>Virtual Hard Disk Setting Data</VALUE>
</PROPERTY>
<PROPERTY NAME="Description" TYPE="string">
<VALUE>Setting Data for a Virtual Hard Disk.</VALUE>
</PROPERTY>
<PROPERTY NAME="ElementName" TYPE="string">
<VALUE>fake_path.vhdx</VALUE>
</PROPERTY>
<PROPERTY NAME="Format" TYPE="uint16">
<VALUE>%(format)s</VALUE>
</PROPERTY>
<PROPERTY NAME="InstanceID" TYPE="string">
<VALUE>52794B89-AC06-4349-AC57-486CAAD52F69</VALUE>
</PROPERTY>
<PROPERTY NAME="LogicalSectorSize" TYPE="uint32">
<VALUE>4096</VALUE>
</PROPERTY>
<PROPERTY NAME="MaxInternalSize" TYPE="uint64">
<VALUE>%(max_internal_size)s</VALUE>
</PROPERTY>
<PROPERTY NAME="ParentPath" TYPE="string">
<VALUE>%(parent_path)s</VALUE>
</PROPERTY>
<PROPERTY NAME="Path" TYPE="string">
<VALUE>%(path)s</VALUE>
</PROPERTY>
<PROPERTY NAME="PhysicalSectorSize" TYPE="uint32">
<VALUE>4096</VALUE>
</PROPERTY>
<PROPERTY NAME="Type" TYPE="uint16">
<VALUE>%(type)s</VALUE>
</PROPERTY>
</INSTANCE>""" % {'path': _FAKE_VHD_PATH,
'parent_path': _FAKE_PARENT_PATH,
'format': _FAKE_FORMAT,
'max_internal_size': _FAKE_MAX_INTERNAL_SIZE,
'type': _FAKE_TYPE})
class VHDUtilsTestCase(VHDUtilsBaseTestCase):
"""Unit tests for the Hyper-V VHDUtils class."""
def setUp(self):
super(VHDUtilsTestCase, self).setUp()
self._vhdutils = vhdutils.VHDUtils()
self._vhdutils._conn = mock.MagicMock()
self._vhdutils._vmutils = mock.MagicMock()
self._fake_vhd_info = {
'ParentPath': self._FAKE_PARENT_PATH,
'MaxInternalSize': self._FAKE_MAX_INTERNAL_SIZE,
'Type': self._FAKE_TYPE}
def test_validate_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.ValidateVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.validate_vhd(self._FAKE_VHD_PATH)
mock_img_svc.ValidateVirtualHardDisk.assert_called_once_with(
Path=self._FAKE_VHD_PATH)
def test_get_vhd_info(self):
self._mock_get_vhd_info()
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
self.assertEqual(self._fake_vhd_info, vhd_info)
def _mock_get_vhd_info(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.GetVirtualHardDiskInfo.return_value = (
self._FAKE_VHD_INFO_XML, self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
def test_create_dynamic_vhd(self):
self._vhdutils.get_vhd_info = mock.MagicMock(
return_value={'Format': self._FAKE_FORMAT})
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.CreateDynamicVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.create_dynamic_vhd(self._FAKE_VHD_PATH,
self._FAKE_MAX_INTERNAL_SIZE,
constants.DISK_FORMAT_VHD)
mock_img_svc.CreateDynamicVirtualHardDisk.assert_called_once_with(
Path=self._FAKE_VHD_PATH,
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
def test_reconnect_parent_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.ReconnectParentVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.reconnect_parent_vhd(self._FAKE_VHD_PATH,
self._FAKE_PARENT_PATH)
mock_img_svc.ReconnectParentVirtualHardDisk.assert_called_once_with(
ChildPath=self._FAKE_VHD_PATH,
ParentPath=self._FAKE_PARENT_PATH,
Force=True)
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
def test_merge_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.MergeVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.merge_vhd(self._FAKE_VHD_PATH, self._FAKE_VHD_PATH)
mock_img_svc.MergeVirtualHardDisk.assert_called_once_with(
SourcePath=self._FAKE_VHD_PATH,
DestinationPath=self._FAKE_VHD_PATH)
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
def test_resize_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.ExpandVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.get_internal_vhd_size_by_file_size = mock.MagicMock(
return_value=self._FAKE_MAX_INTERNAL_SIZE)
self._vhdutils.resize_vhd(self._FAKE_VHD_PATH,
self._FAKE_MAX_INTERNAL_SIZE)
mock_img_svc.ExpandVirtualHardDisk.assert_called_once_with(
Path=self._FAKE_VHD_PATH,
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
def _mocked_get_internal_vhd_size(self, root_vhd_size, vhd_type):
mock_get_vhd_info = mock.MagicMock(return_value={'Type': vhd_type})
mock_get_blk_size = mock.MagicMock(
return_value=self._FAKE_DYNAMIC_BLK_SIZE)
with mock.patch.multiple(self._vhdutils,
get_vhd_info=mock_get_vhd_info,
_get_vhd_dynamic_blk_size=mock_get_blk_size):
return self._vhdutils.get_internal_vhd_size_by_file_size(
None, root_vhd_size)
def test_create_differencing_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.CreateDifferencingVirtualHardDisk.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.create_differencing_vhd(self._FAKE_VHD_PATH,
self._FAKE_PARENT_PATH)
mock_img_svc.CreateDifferencingVirtualHardDisk.assert_called_once_with(
Path=self._FAKE_VHD_PATH,
ParentPath=self._FAKE_PARENT_PATH)
def test_get_internal_vhd_size_by_file_size_fixed(self):
root_vhd_size = 1 * 1024 ** 3
real_size = self._mocked_get_internal_vhd_size(
root_vhd_size, constants.VHD_TYPE_FIXED)
expected_vhd_size = 1 * 1024 ** 3 - 512
self.assertEqual(expected_vhd_size, real_size)
def test_get_internal_vhd_size_by_file_size_dynamic(self):
root_vhd_size = 20 * 1024 ** 3
real_size = self._mocked_get_internal_vhd_size(
root_vhd_size, constants.VHD_TYPE_DYNAMIC)
expected_vhd_size = 20 * 1024 ** 3 - 43008
self.assertEqual(expected_vhd_size, real_size)
def test_get_internal_vhd_size_by_file_size_differencing(self):
# For differencing images, the internal size of the parent vhd
# is returned
vhdutil = vhdutils.VHDUtils()
root_vhd_size = 20 * 1024 ** 3
vhdutil.get_vhd_info = mock.MagicMock()
vhdutil.get_vhd_parent_path = mock.MagicMock()
vhdutil.get_vhd_parent_path.return_value = self._FAKE_VHD_PATH
vhdutil.get_vhd_info.side_effect = [
{'Type': 4}, {'Type': constants.VHD_TYPE_DYNAMIC}]
vhdutil._get_vhd_dynamic_blk_size = mock.MagicMock()
vhdutil._get_vhd_dynamic_blk_size.return_value = 2097152
real_size = vhdutil.get_internal_vhd_size_by_file_size(None,
root_vhd_size)
expected_vhd_size = 20 * 1024 ** 3 - 43008
self.assertEqual(expected_vhd_size, real_size)
def test_get_vhd_format_vhdx(self):
with mock.patch('nova.virt.hyperv.vhdutils.open',
mock.mock_open(read_data=vhdutils.VHDX_SIGNATURE),
create=True):
format = self._vhdutils.get_vhd_format(self._FAKE_VHD_PATH)
self.assertEqual(constants.DISK_FORMAT_VHDX, format)
def test_get_vhd_format_vhd(self):
with mock.patch('nova.virt.hyperv.vhdutils.open',
mock.mock_open(),
create=True) as mock_open:
f = mock_open.return_value
f.tell.return_value = 1024
readdata = ['notthesig', vhdutils.VHD_SIGNATURE]
def read(*args):
for content in readdata:
yield content
f.read.side_effect = read()
format = self._vhdutils.get_vhd_format(self._FAKE_VHD_PATH)
self.assertEqual(constants.DISK_FORMAT_VHD, format)
def test_get_vhd_format_invalid_format(self):
with mock.patch('nova.virt.hyperv.vhdutils.open',
mock.mock_open(read_data='invalid'),
create=True) as mock_open:
f = mock_open.return_value
f.tell.return_value = 1024
self.assertRaises(vmutils.HyperVException,
self._vhdutils.get_vhd_format,
self._FAKE_VHD_PATH)
def test_get_vhd_format_zero_length_file(self):
with mock.patch('nova.virt.hyperv.vhdutils.open',
mock.mock_open(read_data=''),
create=True) as mock_open:
f = mock_open.return_value
f.tell.return_value = 0
self.assertRaises(vmutils.HyperVException,
self._vhdutils.get_vhd_format,
self._FAKE_VHD_PATH)
f.seek.assert_called_once_with(0, 2)
def test_get_supported_vhd_format(self):
fmt = self._vhdutils.get_best_supported_vhd_format()
self.assertEqual(constants.DISK_FORMAT_VHD, fmt)

View File

@ -0,0 +1,244 @@
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova.tests.unit.virt.hyperv import test_vhdutils
from nova.virt.hyperv import constants
from nova.virt.hyperv import vhdutilsv2
from nova.virt.hyperv import vmutils
class VHDUtilsV2TestCase(test_vhdutils.VHDUtilsBaseTestCase):
"""Unit tests for the Hyper-V VHDUtilsV2 class."""
_FAKE_BLOCK_SIZE = 33554432
_FAKE_LOG_SIZE = 1048576
_FAKE_LOGICAL_SECTOR_SIZE = 4096
_FAKE_METADATA_SIZE = 1048576
_FAKE_PHYSICAL_SECTOR_SIZE = 4096
def setUp(self):
super(VHDUtilsV2TestCase, self).setUp()
self._vhdutils = vhdutilsv2.VHDUtilsV2()
self._vhdutils._conn = mock.MagicMock()
self._vhdutils._vmutils = mock.MagicMock()
self._fake_file_handle = mock.MagicMock()
self._fake_vhd_info = {
'Path': self._FAKE_VHD_PATH,
'ParentPath': self._FAKE_PARENT_PATH,
'Format': self._FAKE_FORMAT,
'MaxInternalSize': self._FAKE_MAX_INTERNAL_SIZE,
'Type': self._FAKE_TYPE,
'BlockSize': self._FAKE_BLOCK_SIZE,
'LogicalSectorSize': self._FAKE_LOGICAL_SECTOR_SIZE,
'PhysicalSectorSize': self._FAKE_PHYSICAL_SECTOR_SIZE}
def _mock_get_vhd_info(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.GetVirtualHardDiskSettingData.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, self._FAKE_VHD_INFO_XML)
def test_get_vhd_info(self):
self._mock_get_vhd_info()
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
self.assertEqual(self._FAKE_VHD_PATH, vhd_info['Path'])
self.assertEqual(self._FAKE_PARENT_PATH, vhd_info['ParentPath'])
self.assertEqual(self._FAKE_FORMAT, vhd_info['Format'])
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE,
vhd_info['MaxInternalSize'])
self.assertEqual(self._FAKE_TYPE, vhd_info['Type'])
def test_get_vhd_info_no_parent(self):
fake_vhd_xml_no_parent = self._FAKE_VHD_INFO_XML.replace(
self._FAKE_PARENT_PATH, "")
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.GetVirtualHardDiskSettingData.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, fake_vhd_xml_no_parent)
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
self.assertEqual(self._FAKE_VHD_PATH, vhd_info['Path'])
self.assertIsNone(vhd_info['ParentPath'])
self.assertEqual(self._FAKE_FORMAT, vhd_info['Format'])
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE,
vhd_info['MaxInternalSize'])
self.assertEqual(self._FAKE_TYPE, vhd_info['Type'])
def test_create_dynamic_vhd(self):
self._vhdutils.get_vhd_info = mock.MagicMock(
return_value={'Format': self._FAKE_FORMAT})
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.CreateVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
self._FAKE_RET_VAL)
self._vhdutils.create_dynamic_vhd(self._FAKE_VHD_PATH,
self._FAKE_MAX_INTERNAL_SIZE,
constants.DISK_FORMAT_VHDX)
self.assertTrue(mock_img_svc.CreateVirtualHardDisk.called)
def test_create_differencing_vhd(self):
self._vhdutils.get_vhd_info = mock.MagicMock(
return_value={'ParentPath': self._FAKE_PARENT_PATH,
'Format': self._FAKE_FORMAT})
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.CreateVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
self._FAKE_RET_VAL)
self._vhdutils.create_differencing_vhd(self._FAKE_VHD_PATH,
self._FAKE_PARENT_PATH)
self.assertTrue(mock_img_svc.CreateVirtualHardDisk.called)
def test_reconnect_parent_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
fake_new_parent_path = 'fake_new_parent_path'
self._vhdutils._get_vhd_info_xml = mock.MagicMock(
return_value=self._FAKE_VHD_INFO_XML)
mock_img_svc.SetVirtualHardDiskSettingData.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vhdutils.reconnect_parent_vhd(self._FAKE_VHD_PATH,
fake_new_parent_path)
expected_virt_disk_data = self._FAKE_VHD_INFO_XML.replace(
self._FAKE_PARENT_PATH, fake_new_parent_path)
mock_img_svc.SetVirtualHardDiskSettingData.assert_called_once_with(
VirtualDiskSettingData=expected_virt_disk_data)
def test_reconnect_parent_vhd_exception(self):
# Test that reconnect_parent_vhd raises an exception if the
# vhd info XML does not contain the ParentPath property.
fake_vhd_info_xml = self._FAKE_VHD_INFO_XML.replace('ParentPath',
'FakeParentPath')
self._vhdutils._get_vhd_info_xml = mock.Mock(
return_value=fake_vhd_info_xml)
self.assertRaises(vmutils.HyperVException,
self._vhdutils.reconnect_parent_vhd,
self._FAKE_VHD_PATH,
mock.sentinel.new_parent_path)
def test_resize_vhd(self):
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
mock_img_svc.ResizeVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
self._FAKE_RET_VAL)
self._vhdutils.get_internal_vhd_size_by_file_size = mock.MagicMock(
return_value=self._FAKE_MAX_INTERNAL_SIZE)
self._vhdutils.resize_vhd(self._FAKE_VHD_PATH,
self._FAKE_MAX_INTERNAL_SIZE)
mock_img_svc.ResizeVirtualHardDisk.assert_called_once_with(
Path=self._FAKE_VHD_PATH,
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
self.mock_get = self._vhdutils.get_internal_vhd_size_by_file_size
self.mock_get.assert_called_once_with(self._FAKE_VHD_PATH,
self._FAKE_MAX_INTERNAL_SIZE)
def _test_get_vhdx_internal_size(self, vhd_type):
self._vhdutils.get_vhd_info = mock.MagicMock()
self._vhdutils.get_vhd_parent_path = mock.Mock(
return_value=self._FAKE_PARENT_PATH)
if vhd_type == 4:
self._vhdutils.get_vhd_info.side_effect = [
{'Type': vhd_type}, self._fake_vhd_info]
else:
self._vhdutils.get_vhd_info.return_value = self._fake_vhd_info
@mock.patch('nova.virt.hyperv.vhdutils.VHDUtils.get_vhd_format')
def test_get_vhdx_internal_size(self, mock_get_vhd_format):
mock_get_vhd_format.return_value = constants.DISK_FORMAT_VHDX
self._mock_get_vhd_info()
self._vhdutils._get_vhdx_log_size = mock.MagicMock(
return_value=self._FAKE_LOG_SIZE)
self._vhdutils._get_vhdx_metadata_size_and_offset = mock.MagicMock(
return_value=(self._FAKE_METADATA_SIZE, 1024))
self._vhdutils._get_vhdx_block_size = mock.MagicMock(
return_value=self._FAKE_BLOCK_SIZE)
file_mock = mock.MagicMock()
with mock.patch('__builtin__.open', file_mock):
internal_size = (
self._vhdutils.get_internal_vhd_size_by_file_size(
self._FAKE_VHD_PATH, self._FAKE_MAX_INTERNAL_SIZE))
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE - self._FAKE_BLOCK_SIZE,
internal_size)
def test_get_vhdx_internal_size_dynamic(self):
self._test_get_vhdx_internal_size(3)
def test_get_vhdx_internal_size_differencing(self):
self._test_get_vhdx_internal_size(4)
def test_get_vhdx_current_header(self):
VHDX_HEADER_OFFSETS = [64 * 1024, 128 * 1024]
fake_sequence_numbers = ['\x01\x00\x00\x00\x00\x00\x00\x00',
'\x02\x00\x00\x00\x00\x00\x00\x00']
self._fake_file_handle.read = mock.MagicMock(
side_effect=fake_sequence_numbers)
offset = self._vhdutils._get_vhdx_current_header_offset(
self._fake_file_handle)
self.assertEqual(offset, VHDX_HEADER_OFFSETS[1])
def test_get_vhdx_metadata_size(self):
fake_metadata_offset = '\x01\x00\x00\x00\x00\x00\x00\x00'
fake_metadata_size = '\x01\x00\x00\x00'
self._fake_file_handle.read = mock.MagicMock(
side_effect=[fake_metadata_offset, fake_metadata_size])
metadata_size, metadata_offset = (
self._vhdutils._get_vhdx_metadata_size_and_offset(
self._fake_file_handle))
self.assertEqual(metadata_size, 1)
self.assertEqual(metadata_offset, 1)
def test_get_block_size(self):
self._vhdutils._get_vhdx_metadata_size_and_offset = mock.MagicMock(
return_value=(self._FAKE_METADATA_SIZE, 1024))
fake_block_size = '\x01\x00\x00\x00'
self._fake_file_handle.read = mock.MagicMock(
return_value=fake_block_size)
block_size = self._vhdutils._get_vhdx_block_size(
self._fake_file_handle)
self.assertEqual(block_size, 1)
def test_get_log_size(self):
fake_current_header_offset = 64 * 1024
self._vhdutils._get_vhdx_current_header_offset = mock.MagicMock(
return_value=fake_current_header_offset)
fake_log_size = '\x01\x00\x00\x00'
self._fake_file_handle.read = mock.MagicMock(
return_value=fake_log_size)
log_size = self._vhdutils._get_vhdx_log_size(self._fake_file_handle)
self.assertEqual(log_size, 1)
def test_get_supported_vhd_format(self):
fmt = self._vhdutils.get_best_supported_vhd_format()
self.assertEqual(constants.DISK_FORMAT_VHDX, fmt)

View File

@ -0,0 +1,850 @@
# Copyright 2014 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from six.moves import range
from nova import exception
from nova import test
from nova.virt.hyperv import constants
from nova.virt.hyperv import vmutils
class VMUtilsTestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V VMUtils class."""
_FAKE_VM_NAME = 'fake_vm'
_FAKE_MEMORY_MB = 2
_FAKE_VCPUS_NUM = 4
_FAKE_JOB_PATH = 'fake_job_path'
_FAKE_RET_VAL = 0
_FAKE_RET_VAL_BAD = -1
_FAKE_PATH = "fake_path"
_FAKE_CTRL_PATH = 'fake_ctrl_path'
_FAKE_CTRL_ADDR = 0
_FAKE_DRIVE_ADDR = 0
_FAKE_MOUNTED_DISK_PATH = 'fake_mounted_disk_path'
_FAKE_VM_PATH = "fake_vm_path"
_FAKE_VHD_PATH = "fake_vhd_path"
_FAKE_DVD_PATH = "fake_dvd_path"
_FAKE_VOLUME_DRIVE_PATH = "fake_volume_drive_path"
_FAKE_VM_UUID = "04e79212-39bc-4065-933c-50f6d48a57f6"
_FAKE_INSTANCE = {"name": _FAKE_VM_NAME,
"uuid": _FAKE_VM_UUID}
_FAKE_SNAPSHOT_PATH = "fake_snapshot_path"
_FAKE_RES_DATA = "fake_res_data"
_FAKE_HOST_RESOURCE = "fake_host_resource"
_FAKE_CLASS = "FakeClass"
_FAKE_RES_PATH = "fake_res_path"
_FAKE_RES_NAME = 'fake_res_name'
_FAKE_ADDRESS = "fake_address"
_FAKE_JOB_STATUS_DONE = 7
_FAKE_JOB_STATUS_BAD = -1
_FAKE_JOB_DESCRIPTION = "fake_job_description"
_FAKE_ERROR = "fake_error"
_FAKE_ELAPSED_TIME = 0
_CONCRETE_JOB = "Msvm_ConcreteJob"
_FAKE_DYNAMIC_MEMORY_RATIO = 1.0
_FAKE_SUMMARY_INFO = {'NumberOfProcessors': 4,
'EnabledState': 2,
'MemoryUsage': 2,
'UpTime': 1}
_DEFINE_SYSTEM = 'DefineVirtualSystem'
_DESTROY_SYSTEM = 'DestroyVirtualSystem'
_DESTROY_SNAPSHOT = 'RemoveVirtualSystemSnapshot'
_ADD_RESOURCE = 'AddVirtualSystemResources'
_REMOVE_RESOURCE = 'RemoveVirtualSystemResources'
_SETTING_TYPE = 'SettingType'
_VM_GEN = constants.VM_GEN_1
_VIRTUAL_SYSTEM_TYPE_REALIZED = 3
def setUp(self):
self._vmutils = vmutils.VMUtils()
self._vmutils._conn = mock.MagicMock()
super(VMUtilsTestCase, self).setUp()
def test_enable_vm_metrics_collection(self):
self.assertRaises(NotImplementedError,
self._vmutils.enable_vm_metrics_collection,
self._FAKE_VM_NAME)
def test_get_vm_summary_info(self):
self._lookup_vm()
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
mock_summary = mock.MagicMock()
mock_svc.GetSummaryInformation.return_value = (self._FAKE_RET_VAL,
[mock_summary])
for (key, val) in self._FAKE_SUMMARY_INFO.items():
setattr(mock_summary, key, val)
summary = self._vmutils.get_vm_summary_info(self._FAKE_VM_NAME)
self.assertEqual(self._FAKE_SUMMARY_INFO, summary)
def _lookup_vm(self):
mock_vm = mock.MagicMock()
self._vmutils._lookup_vm_check = mock.MagicMock(
return_value=mock_vm)
mock_vm.path_.return_value = self._FAKE_VM_PATH
return mock_vm
def test_lookup_vm_ok(self):
mock_vm = mock.MagicMock()
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mock_vm]
vm = self._vmutils._lookup_vm_check(self._FAKE_VM_NAME)
self.assertEqual(mock_vm, vm)
def test_lookup_vm_multiple(self):
mockvm = mock.MagicMock()
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mockvm, mockvm]
self.assertRaises(vmutils.HyperVException,
self._vmutils._lookup_vm_check,
self._FAKE_VM_NAME)
def test_lookup_vm_none(self):
self._vmutils._conn.Msvm_ComputerSystem.return_value = []
self.assertRaises(exception.NotFound,
self._vmutils._lookup_vm_check,
self._FAKE_VM_NAME)
def test_set_vm_memory_static(self):
self._test_set_vm_memory_dynamic(1.0)
def test_set_vm_memory_dynamic(self):
self._test_set_vm_memory_dynamic(2.0)
def _test_set_vm_memory_dynamic(self, dynamic_memory_ratio):
mock_vm = self._lookup_vm()
mock_s = self._vmutils._conn.Msvm_VirtualSystemSettingData()[0]
mock_s.SystemType = 3
mock_vmsetting = mock.MagicMock()
mock_vmsetting.associators.return_value = [mock_s]
self._vmutils._modify_virt_resource = mock.MagicMock()
self._vmutils._set_vm_memory(mock_vm, mock_vmsetting,
self._FAKE_MEMORY_MB,
dynamic_memory_ratio)
self._vmutils._modify_virt_resource.assert_called_with(
mock_s, self._FAKE_VM_PATH)
if dynamic_memory_ratio > 1:
self.assertTrue(mock_s.DynamicMemoryEnabled)
else:
self.assertFalse(mock_s.DynamicMemoryEnabled)
def test_soft_shutdown_vm(self):
mock_vm = self._lookup_vm()
mock_shutdown = mock.MagicMock()
mock_shutdown.InitiateShutdown.return_value = (self._FAKE_RET_VAL, )
mock_vm.associators.return_value = [mock_shutdown]
with mock.patch.object(self._vmutils, 'check_ret_val') as mock_check:
self._vmutils.soft_shutdown_vm(self._FAKE_VM_NAME)
mock_shutdown.InitiateShutdown.assert_called_once_with(
Force=False, Reason=mock.ANY)
mock_check.assert_called_once_with(self._FAKE_RET_VAL, None)
def test_soft_shutdown_vm_no_component(self):
mock_vm = self._lookup_vm()
mock_vm.associators.return_value = []
with mock.patch.object(self._vmutils, 'check_ret_val') as mock_check:
self._vmutils.soft_shutdown_vm(self._FAKE_VM_NAME)
self.assertFalse(mock_check.called)
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_disks')
def test_get_vm_storage_paths(self, mock_get_vm_disks):
self._lookup_vm()
mock_rasds = self._create_mock_disks()
mock_get_vm_disks.return_value = ([mock_rasds[0]], [mock_rasds[1]])
storage = self._vmutils.get_vm_storage_paths(self._FAKE_VM_NAME)
(disk_files, volume_drives) = storage
self.assertEqual([self._FAKE_VHD_PATH], disk_files)
self.assertEqual([self._FAKE_VOLUME_DRIVE_PATH], volume_drives)
def test_get_vm_disks(self):
mock_vm = self._lookup_vm()
mock_vmsettings = [mock.MagicMock()]
mock_vm.associators.return_value = mock_vmsettings
mock_rasds = self._create_mock_disks()
mock_vmsettings[0].associators.return_value = mock_rasds
(disks, volumes) = self._vmutils._get_vm_disks(mock_vm)
mock_vm.associators.assert_called_with(
wmi_result_class=self._vmutils._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
mock_vmsettings[0].associators.assert_called_with(
wmi_result_class=self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS)
self.assertEqual([mock_rasds[0]], disks)
self.assertEqual([mock_rasds[1]], volumes)
def _create_mock_disks(self):
mock_rasd1 = mock.MagicMock()
mock_rasd1.ResourceSubType = self._vmutils._HARD_DISK_RES_SUB_TYPE
mock_rasd1.HostResource = [self._FAKE_VHD_PATH]
mock_rasd1.Connection = [self._FAKE_VHD_PATH]
mock_rasd1.Parent = self._FAKE_CTRL_PATH
mock_rasd1.Address = self._FAKE_ADDRESS
mock_rasd1.HostResource = [self._FAKE_VHD_PATH]
mock_rasd2 = mock.MagicMock()
mock_rasd2.ResourceSubType = self._vmutils._PHYS_DISK_RES_SUB_TYPE
mock_rasd2.HostResource = [self._FAKE_VOLUME_DRIVE_PATH]
return [mock_rasd1, mock_rasd2]
@mock.patch.object(vmutils.VMUtils, '_set_vm_vcpus')
@mock.patch.object(vmutils.VMUtils, '_set_vm_memory')
@mock.patch.object(vmutils.VMUtils, '_get_wmi_obj')
def test_create_vm(self, mock_get_wmi_obj, mock_set_mem, mock_set_vcpus):
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
getattr(mock_svc, self._DEFINE_SYSTEM).return_value = (
None, self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
mock_vm = mock_get_wmi_obj.return_value
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mock_vm]
mock_s = mock.MagicMock()
setattr(mock_s,
self._SETTING_TYPE,
self._VIRTUAL_SYSTEM_TYPE_REALIZED)
mock_vm.associators.return_value = [mock_s]
self._vmutils.create_vm(self._FAKE_VM_NAME, self._FAKE_MEMORY_MB,
self._FAKE_VCPUS_NUM, False,
self._FAKE_DYNAMIC_MEMORY_RATIO,
self._VM_GEN,
mock.sentinel.instance_path)
self.assertTrue(getattr(mock_svc, self._DEFINE_SYSTEM).called)
mock_set_mem.assert_called_with(mock_vm, mock_s, self._FAKE_MEMORY_MB,
self._FAKE_DYNAMIC_MEMORY_RATIO)
mock_set_vcpus.assert_called_with(mock_vm, mock_s,
self._FAKE_VCPUS_NUM,
False)
def test_get_vm_scsi_controller(self):
self._prepare_get_vm_controller(self._vmutils._SCSI_CTRL_RES_SUB_TYPE)
path = self._vmutils.get_vm_scsi_controller(self._FAKE_VM_NAME)
self.assertEqual(self._FAKE_RES_PATH, path)
@mock.patch("nova.virt.hyperv.vmutils.VMUtils.get_attached_disks")
def test_get_free_controller_slot(self, mock_get_attached_disks):
mock_disk = mock.MagicMock()
mock_disk.AddressOnParent = 3
mock_get_attached_disks.return_value = [mock_disk]
response = self._vmutils.get_free_controller_slot(
self._FAKE_CTRL_PATH)
mock_get_attached_disks.assert_called_once_with(
self._FAKE_CTRL_PATH)
self.assertEqual(response, 0)
def test_get_free_controller_slot_exception(self):
fake_drive = mock.MagicMock()
type(fake_drive).AddressOnParent = mock.PropertyMock(
side_effect=range(constants.SCSI_CONTROLLER_SLOTS_NUMBER))
with mock.patch.object(self._vmutils,
'get_attached_disks') as fake_get_attached_disks:
fake_get_attached_disks.return_value = (
[fake_drive] * constants.SCSI_CONTROLLER_SLOTS_NUMBER)
self.assertRaises(vmutils.HyperVException,
self._vmutils.get_free_controller_slot,
mock.sentinel.scsi_controller_path)
def test_get_vm_ide_controller(self):
self._prepare_get_vm_controller(self._vmutils._IDE_CTRL_RES_SUB_TYPE)
path = self._vmutils.get_vm_ide_controller(self._FAKE_VM_NAME,
self._FAKE_ADDRESS)
self.assertEqual(self._FAKE_RES_PATH, path)
def test_get_vm_ide_controller_none(self):
self._prepare_get_vm_controller(self._vmutils._IDE_CTRL_RES_SUB_TYPE)
path = self._vmutils.get_vm_ide_controller(
mock.sentinel.FAKE_VM_NAME, mock.sentinel.FAKE_NOT_FOUND_ADDR)
self.assertNotEqual(self._FAKE_RES_PATH, path)
def _prepare_get_vm_controller(self, resource_sub_type):
mock_vm = self._lookup_vm()
mock_vm_settings = mock.MagicMock()
mock_rasds = mock.MagicMock()
mock_rasds.path_.return_value = self._FAKE_RES_PATH
mock_rasds.ResourceSubType = resource_sub_type
mock_rasds.Address = self._FAKE_ADDRESS
mock_vm_settings.associators.return_value = [mock_rasds]
mock_vm.associators.return_value = [mock_vm_settings]
def _prepare_resources(self, mock_path, mock_subtype, mock_vm_settings):
mock_rasds = mock_vm_settings.associators.return_value[0]
mock_rasds.path_.return_value = mock_path
mock_rasds.ResourceSubType = mock_subtype
return mock_rasds
@mock.patch("nova.virt.hyperv.vmutils.VMUtils.get_free_controller_slot")
@mock.patch("nova.virt.hyperv.vmutils.VMUtils._get_vm_scsi_controller")
def test_attach_scsi_drive(self, mock_get_vm_scsi_controller,
mock_get_free_controller_slot):
mock_vm = self._lookup_vm()
mock_get_vm_scsi_controller.return_value = self._FAKE_CTRL_PATH
mock_get_free_controller_slot.return_value = self._FAKE_DRIVE_ADDR
with mock.patch.object(self._vmutils,
'attach_drive') as mock_attach_drive:
self._vmutils.attach_scsi_drive(mock_vm, self._FAKE_PATH,
constants.DISK)
mock_get_vm_scsi_controller.assert_called_once_with(mock_vm)
mock_get_free_controller_slot.assert_called_once_with(
self._FAKE_CTRL_PATH)
mock_attach_drive.assert_called_once_with(
mock_vm, self._FAKE_PATH, self._FAKE_CTRL_PATH,
self._FAKE_DRIVE_ADDR, constants.DISK)
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
@mock.patch.object(vmutils.VMUtils, '_get_vm_ide_controller')
def test_attach_ide_drive(self, mock_get_ide_ctrl, mock_get_new_rsd):
mock_vm = self._lookup_vm()
mock_rsd = mock_get_new_rsd.return_value
with mock.patch.object(self._vmutils,
'_add_virt_resource') as mock_add_virt_res:
self._vmutils.attach_ide_drive(self._FAKE_VM_NAME,
self._FAKE_CTRL_PATH,
self._FAKE_CTRL_ADDR,
self._FAKE_DRIVE_ADDR)
mock_add_virt_res.assert_called_with(mock_rsd,
mock_vm.path_.return_value)
mock_get_ide_ctrl.assert_called_with(mock_vm, self._FAKE_CTRL_ADDR)
self.assertTrue(mock_get_new_rsd.called)
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
def test_create_scsi_controller(self, mock_get_new_rsd):
mock_vm = self._lookup_vm()
with mock.patch.object(self._vmutils,
'_add_virt_resource') as mock_add_virt_res:
self._vmutils.create_scsi_controller(self._FAKE_VM_NAME)
mock_add_virt_res.assert_called_with(mock_get_new_rsd.return_value,
mock_vm.path_.return_value)
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
def test_attach_volume_to_controller(self, mock_get_new_rsd):
mock_vm = self._lookup_vm()
with mock.patch.object(self._vmutils,
'_add_virt_resource') as mock_add_virt_res:
self._vmutils.attach_volume_to_controller(
self._FAKE_VM_NAME, self._FAKE_CTRL_PATH, self._FAKE_CTRL_ADDR,
self._FAKE_MOUNTED_DISK_PATH)
mock_add_virt_res.assert_called_with(mock_get_new_rsd.return_value,
mock_vm.path_.return_value)
@mock.patch.object(vmutils.VMUtils, '_modify_virt_resource')
@mock.patch.object(vmutils.VMUtils, '_get_nic_data_by_name')
def test_set_nic_connection(self, mock_get_nic_conn, mock_modify_virt_res):
self._lookup_vm()
mock_nic = mock_get_nic_conn.return_value
self._vmutils.set_nic_connection(self._FAKE_VM_NAME, None, None)
mock_modify_virt_res.assert_called_with(mock_nic, self._FAKE_VM_PATH)
@mock.patch.object(vmutils.VMUtils, '_get_new_setting_data')
def test_create_nic(self, mock_get_new_virt_res):
self._lookup_vm()
mock_nic = mock_get_new_virt_res.return_value
with mock.patch.object(self._vmutils,
'_add_virt_resource') as mock_add_virt_res:
self._vmutils.create_nic(
self._FAKE_VM_NAME, self._FAKE_RES_NAME, self._FAKE_ADDRESS)
mock_add_virt_res.assert_called_with(mock_nic, self._FAKE_VM_PATH)
def test_set_vm_state(self):
mock_vm = self._lookup_vm()
mock_vm.RequestStateChange.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vmutils.set_vm_state(self._FAKE_VM_NAME,
constants.HYPERV_VM_STATE_ENABLED)
mock_vm.RequestStateChange.assert_called_with(
constants.HYPERV_VM_STATE_ENABLED)
def test_destroy_vm(self):
self._lookup_vm()
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
getattr(mock_svc, self._DESTROY_SYSTEM).return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vmutils.destroy_vm(self._FAKE_VM_NAME)
getattr(mock_svc, self._DESTROY_SYSTEM).assert_called_with(
self._FAKE_VM_PATH)
@mock.patch.object(vmutils.VMUtils, '_wait_for_job')
def test_check_ret_val_ok(self, mock_wait_for_job):
self._vmutils.check_ret_val(constants.WMI_JOB_STATUS_STARTED,
self._FAKE_JOB_PATH)
mock_wait_for_job.assert_called_once_with(self._FAKE_JOB_PATH)
def test_check_ret_val_exception(self):
self.assertRaises(vmutils.HyperVException,
self._vmutils.check_ret_val,
self._FAKE_RET_VAL_BAD,
self._FAKE_JOB_PATH)
def test_wait_for_job_done(self):
mockjob = self._prepare_wait_for_job(constants.WMI_JOB_STATE_COMPLETED)
job = self._vmutils._wait_for_job(self._FAKE_JOB_PATH)
self.assertEqual(mockjob, job)
def test_wait_for_job_exception_concrete_job(self):
mock_job = self._prepare_wait_for_job()
mock_job.path.return_value.Class = self._CONCRETE_JOB
self.assertRaises(vmutils.HyperVException,
self._vmutils._wait_for_job,
self._FAKE_JOB_PATH)
def test_wait_for_job_exception_with_error(self):
mock_job = self._prepare_wait_for_job()
mock_job.GetError.return_value = (self._FAKE_ERROR, self._FAKE_RET_VAL)
self.assertRaises(vmutils.HyperVException,
self._vmutils._wait_for_job,
self._FAKE_JOB_PATH)
def test_wait_for_job_exception_no_error(self):
mock_job = self._prepare_wait_for_job()
mock_job.GetError.return_value = (None, None)
self.assertRaises(vmutils.HyperVException,
self._vmutils._wait_for_job,
self._FAKE_JOB_PATH)
def _prepare_wait_for_job(self, state=_FAKE_JOB_STATUS_BAD):
mock_job = mock.MagicMock()
mock_job.JobState = state
mock_job.Description = self._FAKE_JOB_DESCRIPTION
mock_job.ElapsedTime = self._FAKE_ELAPSED_TIME
self._vmutils._get_wmi_obj = mock.MagicMock(return_value=mock_job)
return mock_job
def test_add_virt_resource(self):
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
getattr(mock_svc, self._ADD_RESOURCE).return_value = (
self._FAKE_JOB_PATH, mock.MagicMock(), self._FAKE_RET_VAL)
mock_res_setting_data = mock.MagicMock()
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
self._vmutils._add_virt_resource(mock_res_setting_data,
self._FAKE_VM_PATH)
self._assert_add_resources(mock_svc)
def test_modify_virt_resource(self):
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
mock_svc.ModifyVirtualSystemResources.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
mock_res_setting_data = mock.MagicMock()
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
self._vmutils._modify_virt_resource(mock_res_setting_data,
self._FAKE_VM_PATH)
mock_svc.ModifyVirtualSystemResources.assert_called_with(
ResourceSettingData=[self._FAKE_RES_DATA],
ComputerSystem=self._FAKE_VM_PATH)
def test_remove_virt_resource(self):
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
getattr(mock_svc, self._REMOVE_RESOURCE).return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
mock_res_setting_data = mock.MagicMock()
mock_res_setting_data.path_.return_value = self._FAKE_RES_PATH
self._vmutils._remove_virt_resource(mock_res_setting_data,
self._FAKE_VM_PATH)
self._assert_remove_resources(mock_svc)
def test_set_disk_host_resource(self):
self._lookup_vm()
mock_rasds = self._create_mock_disks()
self._vmutils._get_vm_disks = mock.MagicMock(
return_value=([mock_rasds[0]], [mock_rasds[1]]))
self._vmutils._modify_virt_resource = mock.MagicMock()
self._vmutils._get_disk_resource_address = mock.MagicMock(
return_value=self._FAKE_ADDRESS)
self._vmutils.set_disk_host_resource(
self._FAKE_VM_NAME,
self._FAKE_CTRL_PATH,
self._FAKE_ADDRESS,
mock.sentinel.fake_new_mounted_disk_path)
self._vmutils._get_disk_resource_address.assert_called_with(
mock_rasds[0])
self._vmutils._modify_virt_resource.assert_called_with(
mock_rasds[0], self._FAKE_VM_PATH)
self.assertEqual(
mock.sentinel.fake_new_mounted_disk_path,
mock_rasds[0].HostResource[0])
@mock.patch.object(vmutils, 'wmi', create=True)
@mock.patch.object(vmutils.VMUtils, 'check_ret_val')
def test_take_vm_snapshot(self, mock_check_ret_val, mock_wmi):
self._lookup_vm()
mock_svc = self._get_snapshot_service()
mock_svc.CreateVirtualSystemSnapshot.return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, mock.MagicMock())
self._vmutils.take_vm_snapshot(self._FAKE_VM_NAME)
mock_svc.CreateVirtualSystemSnapshot.assert_called_with(
self._FAKE_VM_PATH)
mock_check_ret_val.assert_called_once_with(self._FAKE_RET_VAL,
self._FAKE_JOB_PATH)
def test_remove_vm_snapshot(self):
mock_svc = self._get_snapshot_service()
getattr(mock_svc, self._DESTROY_SNAPSHOT).return_value = (
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
self._vmutils.remove_vm_snapshot(self._FAKE_SNAPSHOT_PATH)
getattr(mock_svc, self._DESTROY_SNAPSHOT).assert_called_with(
self._FAKE_SNAPSHOT_PATH)
def test_detach_vm_disk(self):
self._lookup_vm()
mock_disk = self._prepare_mock_disk()
with mock.patch.object(self._vmutils,
'_remove_virt_resource') as mock_rm_virt_res:
self._vmutils.detach_vm_disk(self._FAKE_VM_NAME,
self._FAKE_HOST_RESOURCE)
mock_rm_virt_res.assert_called_with(mock_disk, self._FAKE_VM_PATH)
def test_get_mounted_disk_resource_from_path(self):
mock_disk_1 = mock.MagicMock()
mock_disk_2 = mock.MagicMock()
mock_disk_2.HostResource = [self._FAKE_MOUNTED_DISK_PATH]
self._vmutils._conn.query.return_value = [mock_disk_1, mock_disk_2]
physical_disk = self._vmutils._get_mounted_disk_resource_from_path(
self._FAKE_MOUNTED_DISK_PATH, True)
self.assertEqual(mock_disk_2, physical_disk)
def test_get_controller_volume_paths(self):
self._prepare_mock_disk()
mock_disks = {self._FAKE_RES_PATH: self._FAKE_HOST_RESOURCE}
disks = self._vmutils.get_controller_volume_paths(self._FAKE_RES_PATH)
self.assertEqual(mock_disks, disks)
def _prepare_mock_disk(self):
mock_disk = mock.MagicMock()
mock_disk.HostResource = [self._FAKE_HOST_RESOURCE]
mock_disk.path.return_value.RelPath = self._FAKE_RES_PATH
mock_disk.ResourceSubType = self._vmutils._HARD_DISK_RES_SUB_TYPE
self._vmutils._conn.query.return_value = [mock_disk]
return mock_disk
def _get_snapshot_service(self):
return self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
def _assert_add_resources(self, mock_svc):
getattr(mock_svc, self._ADD_RESOURCE).assert_called_with(
[self._FAKE_RES_DATA], self._FAKE_VM_PATH)
def _assert_remove_resources(self, mock_svc):
getattr(mock_svc, self._REMOVE_RESOURCE).assert_called_with(
[self._FAKE_RES_PATH], self._FAKE_VM_PATH)
def test_get_active_instances(self):
fake_vm = mock.MagicMock()
type(fake_vm).ElementName = mock.PropertyMock(
side_effect=['active_vm', 'inactive_vm'])
type(fake_vm).EnabledState = mock.PropertyMock(
side_effect=[constants.HYPERV_VM_STATE_ENABLED,
constants.HYPERV_VM_STATE_DISABLED])
self._vmutils.list_instances = mock.MagicMock(
return_value=[mock.sentinel.fake_vm_name] * 2)
self._vmutils._lookup_vm = mock.MagicMock(side_effect=[fake_vm] * 2)
active_instances = self._vmutils.get_active_instances()
self.assertEqual(['active_vm'], active_instances)
def _test_get_vm_serial_port_connection(self, new_connection=None):
old_serial_connection = 'old_serial_connection'
mock_vm = self._lookup_vm()
mock_vmsettings = [mock.MagicMock()]
mock_vm.associators.return_value = mock_vmsettings
fake_serial_port = mock.MagicMock()
fake_serial_port.ResourceSubType = (
self._vmutils._SERIAL_PORT_RES_SUB_TYPE)
fake_serial_port.Connection = [old_serial_connection]
mock_rasds = [fake_serial_port]
mock_vmsettings[0].associators.return_value = mock_rasds
self._vmutils._modify_virt_resource = mock.MagicMock()
fake_modify = self._vmutils._modify_virt_resource
ret_val = self._vmutils.get_vm_serial_port_connection(
self._FAKE_VM_NAME, update_connection=new_connection)
if new_connection:
self.assertEqual(new_connection, ret_val)
fake_modify.assert_called_once_with(fake_serial_port,
mock_vm.path_())
else:
self.assertEqual(old_serial_connection, ret_val)
def test_set_vm_serial_port_connection(self):
self._test_get_vm_serial_port_connection('new_serial_connection')
def test_get_vm_serial_port_connection(self):
self._test_get_vm_serial_port_connection()
def test_list_instance_notes(self):
vs = mock.MagicMock()
attrs = {'ElementName': 'fake_name',
'Notes': '4f54fb69-d3a2-45b7-bb9b-b6e6b3d893b3'}
vs.configure_mock(**attrs)
vs2 = mock.MagicMock(ElementName='fake_name2', Notes=None)
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs,
vs2]
response = self._vmutils.list_instance_notes()
self.assertEqual([(attrs['ElementName'], [attrs['Notes']])], response)
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
['ElementName', 'Notes'],
SettingType=self._vmutils._VIRTUAL_SYSTEM_CURRENT_SETTINGS)
@mock.patch('nova.virt.hyperv.vmutils.VMUtils.check_ret_val')
def test_modify_virtual_system(self, mock_check_ret_val):
mock_vs_man_svc = mock.MagicMock()
mock_vmsetting = mock.MagicMock()
fake_path = 'fake path'
fake_job_path = 'fake job path'
fake_ret_val = 'fake return value'
mock_vs_man_svc.ModifyVirtualSystem.return_value = (0, fake_job_path,
fake_ret_val)
self._vmutils._modify_virtual_system(vs_man_svc=mock_vs_man_svc,
vm_path=fake_path,
vmsetting=mock_vmsetting)
mock_vs_man_svc.ModifyVirtualSystem.assert_called_once_with(
ComputerSystem=fake_path,
SystemSettingData=mock_vmsetting.GetText_(1))
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
@mock.patch('nova.virt.hyperv.vmutils.VMUtils.check_ret_val')
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_wmi_obj')
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._modify_virtual_system')
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_setting_data')
def test_create_vm_obj(self, mock_get_vm_setting_data,
mock_modify_virtual_system,
mock_get_wmi_obj, mock_check_ret_val):
mock_vs_man_svc = mock.MagicMock()
mock_vs_gs_data = mock.MagicMock()
fake_vm_path = 'fake vm path'
fake_job_path = 'fake job path'
fake_ret_val = 'fake return value'
_conn = self._vmutils._conn.Msvm_VirtualSystemGlobalSettingData
_conn.new.return_value = mock_vs_gs_data
mock_vs_man_svc.DefineVirtualSystem.return_value = (fake_vm_path,
fake_job_path,
fake_ret_val)
response = self._vmutils._create_vm_obj(
vs_man_svc=mock_vs_man_svc,
vm_name='fake vm', vm_gen='fake vm gen',
notes='fake notes', dynamic_memory_ratio=1.0,
instance_path=mock.sentinel.instance_path)
_conn.new.assert_called_once_with()
self.assertEqual(mock_vs_gs_data.ElementName, 'fake vm')
mock_vs_man_svc.DefineVirtualSystem.assert_called_once_with(
[], None, mock_vs_gs_data.GetText_(1))
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
self.assertEqual(mock.sentinel.instance_path,
mock_vs_gs_data.ExternalDataRoot)
self.assertEqual(mock.sentinel.instance_path,
mock_vs_gs_data.SnapshotDataRoot)
mock_get_wmi_obj.assert_called_with(fake_vm_path)
mock_get_vm_setting_data.assert_called_once_with(mock_get_wmi_obj())
mock_modify_virtual_system.assert_called_once_with(
mock_vs_man_svc, fake_vm_path, mock_get_vm_setting_data())
self.assertEqual(mock_get_vm_setting_data().Notes,
'\n'.join('fake notes'))
self.assertEqual(response, mock_get_wmi_obj())
def test_list_instances(self):
vs = mock.MagicMock()
attrs = {'ElementName': 'fake_name'}
vs.configure_mock(**attrs)
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs]
response = self._vmutils.list_instances()
self.assertEqual([(attrs['ElementName'])], response)
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
['ElementName'],
SettingType=self._vmutils._VIRTUAL_SYSTEM_CURRENT_SETTINGS)
@mock.patch.object(vmutils.VMUtils, "_clone_wmi_obj")
def _test_check_clone_wmi_obj(self, mock_clone_wmi_obj, clone_objects):
mock_obj = mock.MagicMock()
self._vmutils._clone_wmi_objs = clone_objects
response = self._vmutils._check_clone_wmi_obj(class_name="fakeClass",
obj=mock_obj)
if not clone_objects:
self.assertEqual(mock_obj, response)
else:
mock_clone_wmi_obj.assert_called_once_with("fakeClass", mock_obj)
self.assertEqual(mock_clone_wmi_obj.return_value, response)
def test_check_clone_wmi_obj_true(self):
self._test_check_clone_wmi_obj(clone_objects=True)
def test_check_clone_wmi_obj_false(self):
self._test_check_clone_wmi_obj(clone_objects=False)
def test_clone_wmi_obj(self):
mock_obj = mock.MagicMock()
mock_value = mock.MagicMock()
mock_value.Value = mock.sentinel.fake_value
mock_obj._properties = [mock.sentinel.property]
mock_obj.Properties_.Item.return_value = mock_value
response = self._vmutils._clone_wmi_obj(
class_name="FakeClass", obj=mock_obj)
compare = self._vmutils._conn.FakeClass.new()
self.assertEqual(mock.sentinel.fake_value,
compare.Properties_.Item().Value)
self.assertEqual(compare, response)
def test_get_attached_disks(self):
mock_scsi_ctrl_path = mock.MagicMock()
expected_query = ("SELECT * FROM %(class_name)s "
"WHERE (ResourceSubType='%(res_sub_type)s' OR "
"ResourceSubType='%(res_sub_type_virt)s')"
" AND Parent='%(parent)s'" %
{"class_name":
self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS,
"res_sub_type":
self._vmutils._PHYS_DISK_RES_SUB_TYPE,
"res_sub_type_virt":
self._vmutils._DISK_DRIVE_RES_SUB_TYPE,
"parent":
mock_scsi_ctrl_path.replace("'", "''")})
expected_disks = self._vmutils._conn.query.return_value
ret_disks = self._vmutils.get_attached_disks(mock_scsi_ctrl_path)
self._vmutils._conn.query.assert_called_once_with(expected_query)
self.assertEqual(expected_disks, ret_disks)
def _get_fake_instance_notes(self):
return self._FAKE_VM_UUID
def test_instance_notes(self):
self._lookup_vm()
mock_vm_settings = mock.Mock()
mock_vm_settings.Notes = self._get_fake_instance_notes()
self._vmutils._get_vm_setting_data = mock.Mock(
return_value=mock_vm_settings)
notes = self._vmutils._get_instance_notes(mock.sentinel.vm_name)
self.assertEqual(notes[0], self._FAKE_VM_UUID)
def test_get_event_wql_query(self):
cls = self._vmutils._COMPUTER_SYSTEM_CLASS
field = self._vmutils._VM_ENABLED_STATE_PROP
timeframe = 10
filtered_states = [constants.HYPERV_VM_STATE_ENABLED,
constants.HYPERV_VM_STATE_DISABLED]
expected_checks = ' OR '.join(
["TargetInstance.%s = '%s'" % (field, state)
for state in filtered_states])
expected_query = (
"SELECT %(field)s, TargetInstance "
"FROM __InstanceModificationEvent "
"WITHIN %(timeframe)s "
"WHERE TargetInstance ISA '%(class)s' "
"AND TargetInstance.%(field)s != "
"PreviousInstance.%(field)s "
"AND (%(checks)s)" %
{'class': cls,
'field': field,
'timeframe': timeframe,
'checks': expected_checks})
query = self._vmutils._get_event_wql_query(
cls=cls, field=field, timeframe=timeframe,
filtered_states=filtered_states)
self.assertEqual(expected_query, query)
def test_get_vm_power_state_change_listener(self):
with mock.patch.object(self._vmutils,
'_get_event_wql_query') as mock_get_query:
listener = self._vmutils.get_vm_power_state_change_listener(
mock.sentinel.timeframe,
mock.sentinel.filtered_states)
mock_get_query.assert_called_once_with(
cls=self._vmutils._COMPUTER_SYSTEM_CLASS,
field=self._vmutils._VM_ENABLED_STATE_PROP,
timeframe=mock.sentinel.timeframe,
filtered_states=mock.sentinel.filtered_states)
watcher = self._vmutils._conn.Msvm_ComputerSystem.watch_for
watcher.assert_called_once_with(
raw_wql=mock_get_query.return_value,
fields=[self._vmutils._VM_ENABLED_STATE_PROP])
self.assertEqual(watcher.return_value, listener)

View File

@ -0,0 +1,265 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova.tests.unit.virt.hyperv import test_vmutils
from nova.virt.hyperv import constants
from nova.virt.hyperv import vmutilsv2
class VMUtilsV2TestCase(test_vmutils.VMUtilsTestCase):
"""Unit tests for the Hyper-V VMUtilsV2 class."""
_DEFINE_SYSTEM = 'DefineSystem'
_DESTROY_SYSTEM = 'DestroySystem'
_DESTROY_SNAPSHOT = 'DestroySnapshot'
_ADD_RESOURCE = 'AddResourceSettings'
_REMOVE_RESOURCE = 'RemoveResourceSettings'
_SETTING_TYPE = 'VirtualSystemType'
_VM_GEN = constants.VM_GEN_2
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
def setUp(self):
super(VMUtilsV2TestCase, self).setUp()
self._vmutils = vmutilsv2.VMUtilsV2()
self._vmutils._conn = mock.MagicMock()
def test_create_vm(self):
super(VMUtilsV2TestCase, self).test_create_vm()
mock_vssd = self._vmutils._conn.Msvm_VirtualSystemSettingData.new()
self.assertEqual(self._vmutils._VIRTUAL_SYSTEM_SUBTYPE_GEN2,
mock_vssd.VirtualSystemSubType)
self.assertFalse(mock_vssd.SecureBootEnabled)
def test_modify_virt_resource(self):
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
mock_svc.ModifyResourceSettings.return_value = (self._FAKE_JOB_PATH,
mock.MagicMock(),
self._FAKE_RET_VAL)
mock_res_setting_data = mock.MagicMock()
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
self._vmutils._modify_virt_resource(mock_res_setting_data,
self._FAKE_VM_PATH)
mock_svc.ModifyResourceSettings.assert_called_with(
ResourceSettings=[self._FAKE_RES_DATA])
@mock.patch.object(vmutilsv2, 'wmi', create=True)
@mock.patch.object(vmutilsv2.VMUtilsV2, 'check_ret_val')
def test_take_vm_snapshot(self, mock_check_ret_val, mock_wmi):
self._lookup_vm()
mock_svc = self._get_snapshot_service()
mock_svc.CreateSnapshot.return_value = (self._FAKE_JOB_PATH,
mock.MagicMock(),
self._FAKE_RET_VAL)
self._vmutils.take_vm_snapshot(self._FAKE_VM_NAME)
mock_svc.CreateSnapshot.assert_called_with(
AffectedSystem=self._FAKE_VM_PATH,
SnapshotType=self._vmutils._SNAPSHOT_FULL)
mock_check_ret_val.assert_called_once_with(self._FAKE_RET_VAL,
self._FAKE_JOB_PATH)
@mock.patch.object(vmutilsv2.VMUtilsV2, '_add_virt_resource')
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_new_setting_data')
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_nic_data_by_name')
def test_set_nic_connection(self, mock_get_nic_data, mock_get_new_sd,
mock_add_virt_res):
self._lookup_vm()
fake_eth_port = mock_get_new_sd.return_value
self._vmutils.set_nic_connection(self._FAKE_VM_NAME, None, None)
mock_add_virt_res.assert_called_with(fake_eth_port, self._FAKE_VM_PATH)
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_disks')
def test_enable_vm_metrics_collection(self, mock_get_vm_disks):
self._lookup_vm()
mock_svc = self._vmutils._conn.Msvm_MetricService()[0]
metric_def = mock.MagicMock()
mock_disk = mock.MagicMock()
mock_disk.path_.return_value = self._FAKE_RES_PATH
mock_get_vm_disks.return_value = ([mock_disk], [mock_disk])
fake_metric_def_paths = ['fake_0', 'fake_0', None]
fake_metric_resource_paths = [self._FAKE_VM_PATH,
self._FAKE_VM_PATH,
self._FAKE_RES_PATH]
metric_def.path_.side_effect = fake_metric_def_paths
self._vmutils._conn.CIM_BaseMetricDefinition.return_value = [
metric_def]
self._vmutils.enable_vm_metrics_collection(self._FAKE_VM_NAME)
calls = [mock.call(Name=def_name)
for def_name in [self._vmutils._METRIC_AGGR_CPU_AVG,
self._vmutils._METRIC_AGGR_MEMORY_AVG]]
self._vmutils._conn.CIM_BaseMetricDefinition.assert_has_calls(calls)
calls = []
for i in range(len(fake_metric_def_paths)):
calls.append(mock.call(
Subject=fake_metric_resource_paths[i],
Definition=fake_metric_def_paths[i],
MetricCollectionEnabled=self._vmutils._METRIC_ENABLED))
mock_svc.ControlMetrics.assert_has_calls(calls, any_order=True)
def _get_snapshot_service(self):
return self._vmutils._conn.Msvm_VirtualSystemSnapshotService()[0]
def _assert_add_resources(self, mock_svc):
getattr(mock_svc, self._ADD_RESOURCE).assert_called_with(
self._FAKE_VM_PATH, [self._FAKE_RES_DATA])
def _assert_remove_resources(self, mock_svc):
getattr(mock_svc, self._REMOVE_RESOURCE).assert_called_with(
[self._FAKE_RES_PATH])
def test_list_instance_notes(self):
vs = mock.MagicMock()
attrs = {'ElementName': 'fake_name',
'Notes': ['4f54fb69-d3a2-45b7-bb9b-b6e6b3d893b3']}
vs.configure_mock(**attrs)
vs2 = mock.MagicMock(ElementName='fake_name2', Notes=None)
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs,
vs2]
response = self._vmutils.list_instance_notes()
self.assertEqual([(attrs['ElementName'], attrs['Notes'])], response)
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
['ElementName', 'Notes'],
VirtualSystemType=self._vmutils._VIRTUAL_SYSTEM_TYPE_REALIZED)
def _get_fake_instance_notes(self):
return [self._FAKE_VM_UUID]
@mock.patch('nova.virt.hyperv.vmutilsv2.VMUtilsV2.check_ret_val')
@mock.patch('nova.virt.hyperv.vmutilsv2.VMUtilsV2._get_wmi_obj')
def _test_create_vm_obj(self, mock_get_wmi_obj, mock_check_ret_val,
vm_path, dynamic_memory_ratio=1.0):
mock_vs_man_svc = mock.MagicMock()
mock_vs_data = mock.MagicMock()
mock_job = mock.MagicMock()
fake_job_path = 'fake job path'
fake_ret_val = 'fake return value'
fake_vm_name = 'fake_vm_name'
_conn = self._vmutils._conn.Msvm_VirtualSystemSettingData
mock_check_ret_val.return_value = mock_job
_conn.new.return_value = mock_vs_data
mock_vs_man_svc.DefineSystem.return_value = (fake_job_path,
vm_path,
fake_ret_val)
mock_job.associators.return_value = ['fake vm path']
response = self._vmutils._create_vm_obj(
vs_man_svc=mock_vs_man_svc,
vm_name=fake_vm_name,
vm_gen='fake vm gen',
notes='fake notes',
dynamic_memory_ratio=dynamic_memory_ratio,
instance_path=mock.sentinel.instance_path)
if not vm_path:
mock_job.associators.assert_called_once_with(
self._vmutils._AFFECTED_JOB_ELEMENT_CLASS)
_conn.new.assert_called_once_with()
self.assertEqual(mock_vs_data.ElementName, fake_vm_name)
mock_vs_man_svc.DefineSystem.assert_called_once_with(
ResourceSettings=[], ReferenceConfiguration=None,
SystemSettings=mock_vs_data.GetText_(1))
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
if dynamic_memory_ratio > 1:
self.assertFalse(mock_vs_data.VirtualNumaEnabled)
mock_get_wmi_obj.assert_called_with('fake vm path')
self.assertEqual(mock_vs_data.Notes, 'fake notes')
self.assertEqual(mock.sentinel.instance_path,
mock_vs_data.ConfigurationDataRoot)
self.assertEqual(mock.sentinel.instance_path, mock_vs_data.LogDataRoot)
self.assertEqual(mock.sentinel.instance_path,
mock_vs_data.SnapshotDataRoot)
self.assertEqual(mock.sentinel.instance_path,
mock_vs_data.SuspendDataRoot)
self.assertEqual(mock.sentinel.instance_path,
mock_vs_data.SwapFileDataRoot)
self.assertEqual(response, mock_get_wmi_obj())
def test_create_vm_obj(self):
self._test_create_vm_obj(vm_path='fake vm path')
def test_create_vm_obj_no_vm_path(self):
self._test_create_vm_obj(vm_path=None)
def test_create_vm_obj_dynamic_memory(self):
self._test_create_vm_obj(vm_path=None, dynamic_memory_ratio=1.1)
def test_list_instances(self):
vs = mock.MagicMock()
attrs = {'ElementName': 'fake_name'}
vs.configure_mock(**attrs)
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs]
response = self._vmutils.list_instances()
self.assertEqual([(attrs['ElementName'])], response)
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
['ElementName'],
VirtualSystemType=self._vmutils._VIRTUAL_SYSTEM_TYPE_REALIZED)
def test_get_attached_disks(self):
mock_scsi_ctrl_path = mock.MagicMock()
expected_query = ("SELECT * FROM %(class_name)s "
"WHERE (ResourceSubType='%(res_sub_type)s' OR "
"ResourceSubType='%(res_sub_type_virt)s' OR "
"ResourceSubType='%(res_sub_type_dvd)s') AND "
"Parent = '%(parent)s'" %
{"class_name":
self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS,
"res_sub_type":
self._vmutils._PHYS_DISK_RES_SUB_TYPE,
"res_sub_type_virt":
self._vmutils._DISK_DRIVE_RES_SUB_TYPE,
"res_sub_type_dvd":
self._vmutils._DVD_DRIVE_RES_SUB_TYPE,
"parent": mock_scsi_ctrl_path.replace("'", "''")})
expected_disks = self._vmutils._conn.query.return_value
ret_disks = self._vmutils.get_attached_disks(mock_scsi_ctrl_path)
self._vmutils._conn.query.assert_called_once_with(expected_query)
self.assertEqual(expected_disks, ret_disks)
def test_get_vm_dvd_disk_paths(self):
mock_vm = self._lookup_vm()
mock_sasd1 = mock.MagicMock(
ResourceSubType=self._vmutils._DVD_DISK_RES_SUB_TYPE,
HostResource=[mock.sentinel.FAKE_DVD_PATH1])
mock_settings = mock.MagicMock()
mock_settings.associators.return_value = [mock_sasd1]
mock_vm.associators.return_value = [mock_settings]
ret_val = self._vmutils.get_vm_dvd_disk_paths(self._FAKE_VM_NAME)
self.assertEqual(mock.sentinel.FAKE_DVD_PATH1, ret_val[0])

View File

@ -0,0 +1,164 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_config import cfg
from nova.tests.unit.virt.hyperv import test_basevolumeutils
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import volumeutils
CONF = cfg.CONF
CONF.import_opt('volume_attach_retry_count', 'nova.virt.hyperv.volumeops',
'hyperv')
class VolumeUtilsTestCase(test_basevolumeutils.BaseVolumeUtilsTestCase):
"""Unit tests for the Hyper-V VolumeUtils class."""
_FAKE_PORTAL_ADDR = '10.1.1.1'
_FAKE_PORTAL_PORT = '3260'
_FAKE_LUN = 0
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
_FAKE_STDOUT_VALUE = 'The operation completed successfully'
def setUp(self):
super(VolumeUtilsTestCase, self).setUp()
self._volutils = volumeutils.VolumeUtils()
self._volutils._conn_wmi = mock.MagicMock()
self._volutils._conn_cimv2 = mock.MagicMock()
self.flags(volume_attach_retry_count=4, group='hyperv')
self.flags(volume_attach_retry_interval=0, group='hyperv')
def _test_login_target_portal(self, portal_connected):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
self._volutils.execute = mock.MagicMock()
if portal_connected:
exec_output = 'Address and Socket: %s %s' % (
self._FAKE_PORTAL_ADDR, self._FAKE_PORTAL_PORT)
else:
exec_output = ''
self._volutils.execute.return_value = exec_output
self._volutils._login_target_portal(fake_portal)
call_list = self._volutils.execute.call_args_list
all_call_args = [arg for call in call_list for arg in call[0]]
if portal_connected:
self.assertIn('RefreshTargetPortal', all_call_args)
else:
self.assertIn('AddTargetPortal', all_call_args)
def test_login_connected_portal(self):
self._test_login_target_portal(True)
def test_login_new_portal(self):
self._test_login_target_portal(False)
def _test_login_target(self, target_connected=False, raise_exception=False,
use_chap=False):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
self._volutils.execute = mock.MagicMock()
self._volutils._login_target_portal = mock.MagicMock()
if use_chap:
username, password = (mock.sentinel.username,
mock.sentinel.password)
else:
username, password = None, None
if target_connected:
self._volutils.execute.return_value = self._FAKE_TARGET
elif raise_exception:
self._volutils.execute.return_value = ''
else:
self._volutils.execute.side_effect = (
['', '', '', self._FAKE_TARGET, ''])
if raise_exception:
self.assertRaises(vmutils.HyperVException,
self._volutils.login_storage_target,
self._FAKE_LUN, self._FAKE_TARGET,
fake_portal, username, password)
else:
self._volutils.login_storage_target(self._FAKE_LUN,
self._FAKE_TARGET,
fake_portal,
username, password)
if target_connected:
call_list = self._volutils.execute.call_args_list
all_call_args = [arg for call in call_list for arg in call[0]]
self.assertNotIn('qlogintarget', all_call_args)
else:
self._volutils.execute.assert_any_call(
'iscsicli.exe', 'qlogintarget',
self._FAKE_TARGET, username, password)
def test_login_connected_target(self):
self._test_login_target(target_connected=True)
def test_login_disconncted_target(self):
self._test_login_target()
def test_login_target_exception(self):
self._test_login_target(raise_exception=True)
def test_login_target_using_chap(self):
self._test_login_target(use_chap=True)
def _test_execute_wrapper(self, raise_exception):
fake_cmd = ('iscsicli.exe', 'ListTargetPortals')
if raise_exception:
output = 'fake error'
else:
output = 'The operation completed successfully'
with mock.patch('nova.utils.execute') as fake_execute:
fake_execute.return_value = (output, None)
if raise_exception:
self.assertRaises(vmutils.HyperVException,
self._volutils.execute,
*fake_cmd)
else:
ret_val = self._volutils.execute(*fake_cmd)
self.assertEqual(output, ret_val)
def test_execute_raise_exception(self):
self._test_execute_wrapper(True)
def test_execute_exception(self):
self._test_execute_wrapper(False)
@mock.patch.object(volumeutils, 'utils')
def test_logout_storage_target(self, mock_utils):
mock_utils.execute.return_value = (self._FAKE_STDOUT_VALUE,
mock.sentinel.FAKE_STDERR_VALUE)
session = mock.MagicMock()
session.SessionId = mock.sentinel.FAKE_SESSION_ID
self._volutils._conn_wmi.query.return_value = [session]
self._volutils.logout_storage_target(mock.sentinel.FAKE_IQN)
mock_utils.execute.assert_called_once_with(
'iscsicli.exe', 'logouttarget', mock.sentinel.FAKE_SESSION_ID)

View File

@ -0,0 +1,164 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_config import cfg
from nova import test
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import volumeutilsv2
CONF = cfg.CONF
CONF.import_opt('volume_attach_retry_count', 'nova.virt.hyperv.volumeops',
'hyperv')
class VolumeUtilsV2TestCase(test.NoDBTestCase):
"""Unit tests for the Hyper-V VolumeUtilsV2 class."""
_FAKE_PORTAL_ADDR = '10.1.1.1'
_FAKE_PORTAL_PORT = '3260'
_FAKE_LUN = 0
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
def setUp(self):
super(VolumeUtilsV2TestCase, self).setUp()
self._volutilsv2 = volumeutilsv2.VolumeUtilsV2()
self._volutilsv2._conn_storage = mock.MagicMock()
self._volutilsv2._conn_wmi = mock.MagicMock()
self.flags(volume_attach_retry_count=4, group='hyperv')
self.flags(volume_attach_retry_interval=0, group='hyperv')
def _test_login_target_portal(self, portal_connected):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
fake_portal_object = mock.MagicMock()
_query = self._volutilsv2._conn_storage.query
self._volutilsv2._conn_storage.MSFT_iSCSITargetPortal = (
fake_portal_object)
if portal_connected:
_query.return_value = [fake_portal_object]
else:
_query.return_value = None
self._volutilsv2._login_target_portal(fake_portal)
if portal_connected:
fake_portal_object.Update.assert_called_once_with()
else:
fake_portal_object.New.assert_called_once_with(
TargetPortalAddress=self._FAKE_PORTAL_ADDR,
TargetPortalPortNumber=self._FAKE_PORTAL_PORT)
def test_login_connected_portal(self):
self._test_login_target_portal(True)
def test_login_new_portal(self):
self._test_login_target_portal(False)
def _test_login_target(self, target_connected=False, raise_exception=False,
use_chap=False):
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
self._FAKE_PORTAL_PORT)
fake_target_object = mock.MagicMock()
if target_connected:
fake_target_object.IsConnected = True
elif not raise_exception:
type(fake_target_object).IsConnected = mock.PropertyMock(
side_effect=[False, True])
else:
fake_target_object.IsConnected = False
_query = self._volutilsv2._conn_storage.query
_query.return_value = [fake_target_object]
self._volutilsv2._conn_storage.MSFT_iSCSITarget = (
fake_target_object)
if use_chap:
username, password = (mock.sentinel.username,
mock.sentinel.password)
auth = {
'AuthenticationType': self._volutilsv2._CHAP_AUTH_TYPE,
'ChapUsername': username,
'ChapSecret': password,
}
else:
username, password = None, None
auth = {}
if raise_exception:
self.assertRaises(vmutils.HyperVException,
self._volutilsv2.login_storage_target,
self._FAKE_LUN, self._FAKE_TARGET, fake_portal)
else:
self._volutilsv2.login_storage_target(self._FAKE_LUN,
self._FAKE_TARGET,
fake_portal,
username, password)
if target_connected:
fake_target_object.Update.assert_called_with()
else:
fake_target_object.Connect.assert_called_once_with(
IsPersistent=True, NodeAddress=self._FAKE_TARGET, **auth)
def test_login_connected_target(self):
self._test_login_target(target_connected=True)
def test_login_disconncted_target(self):
self._test_login_target()
def test_login_target_exception(self):
self._test_login_target(raise_exception=True)
def test_login_target_using_chap(self):
self._test_login_target(use_chap=True)
def test_logout_storage_target(self):
mock_msft_target = self._volutilsv2._conn_storage.MSFT_iSCSITarget
mock_msft_session = self._volutilsv2._conn_storage.MSFT_iSCSISession
mock_target = mock.MagicMock()
mock_target.IsConnected = True
mock_msft_target.return_value = [mock_target]
mock_session = mock.MagicMock()
mock_session.IsPersistent = True
mock_msft_session.return_value = [mock_session]
self._volutilsv2.logout_storage_target(self._FAKE_TARGET)
mock_msft_target.assert_called_once_with(NodeAddress=self._FAKE_TARGET)
mock_msft_session.assert_called_once_with(
TargetNodeAddress=self._FAKE_TARGET)
mock_session.Unregister.assert_called_once_with()
mock_target.Disconnect.assert_called_once_with()
@mock.patch.object(volumeutilsv2.VolumeUtilsV2, 'logout_storage_target')
def test_execute_log_out(self, mock_logout_target):
sess_class = self._volutilsv2._conn_wmi.MSiSCSIInitiator_SessionClass
mock_session = mock.MagicMock()
sess_class.return_value = [mock_session]
self._volutilsv2.execute_log_out(mock.sentinel.FAKE_SESSION_ID)
sess_class.assert_called_once_with(
SessionId=mock.sentinel.FAKE_SESSION_ID)
mock_logout_target.assert_called_once_with(mock_session.TargetName)

View File

View File

@ -0,0 +1,149 @@
#
# Copyright 2012 Pedro Navarro Perez
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Helper methods for operations related to the management of volumes,
and storage repositories
"""
import abc
import re
import sys
if sys.platform == 'win32':
import _winreg
import wmi
from oslo_log import log as logging
from nova import block_device
from nova.i18n import _LI
from nova.virt import driver
LOG = logging.getLogger(__name__)
class BaseVolumeUtils(object):
_FILE_DEVICE_DISK = 7
def __init__(self, host='.'):
if sys.platform == 'win32':
self._conn_wmi = wmi.WMI(moniker='//%s/root/wmi' % host)
self._conn_cimv2 = wmi.WMI(moniker='//%s/root/cimv2' % host)
self._drive_number_regex = re.compile(r'DeviceID=\"[^,]*\\(\d+)\"')
@abc.abstractmethod
def login_storage_target(self, target_lun, target_iqn, target_portal):
pass
@abc.abstractmethod
def logout_storage_target(self, target_iqn):
pass
@abc.abstractmethod
def execute_log_out(self, session_id):
pass
def get_iscsi_initiator(self):
"""Get iscsi initiator name for this machine."""
computer_system = self._conn_cimv2.Win32_ComputerSystem()[0]
hostname = computer_system.name
keypath = ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
"iSCSI\\Discovery")
try:
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keypath, 0,
_winreg.KEY_ALL_ACCESS)
temp = _winreg.QueryValueEx(key, 'DefaultInitiatorName')
initiator_name = str(temp[0])
_winreg.CloseKey(key)
except Exception:
LOG.info(_LI("The ISCSI initiator name can't be found. "
"Choosing the default one"))
initiator_name = "iqn.1991-05.com.microsoft:" + hostname.lower()
if computer_system.PartofDomain:
initiator_name += '.' + computer_system.Domain.lower()
return initiator_name
def volume_in_mapping(self, mount_device, block_device_info):
block_device_list = [block_device.strip_dev(vol['mount_device'])
for vol in
driver.block_device_info_get_mapping(
block_device_info)]
swap = driver.block_device_info_get_swap(block_device_info)
if driver.swap_is_usable(swap):
block_device_list.append(
block_device.strip_dev(swap['device_name']))
block_device_list += [block_device.strip_dev(
ephemeral['device_name'])
for ephemeral in
driver.block_device_info_get_ephemerals(block_device_info)]
LOG.debug("block_device_list %s", block_device_list)
return block_device.strip_dev(mount_device) in block_device_list
def _get_drive_number_from_disk_path(self, disk_path):
drive_number = self._drive_number_regex.findall(disk_path)
if drive_number:
return int(drive_number[0])
def get_session_id_from_mounted_disk(self, physical_drive_path):
drive_number = self._get_drive_number_from_disk_path(
physical_drive_path)
if not drive_number:
return None
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
for initiator_session in initiator_sessions:
devices = initiator_session.Devices
for device in devices:
device_number = device.DeviceNumber
if device_number == drive_number:
return initiator_session.SessionId
def _get_devices_for_target(self, target_iqn):
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
TargetName=target_iqn)
if not initiator_sessions:
return []
return initiator_sessions[0].Devices
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)
disk_devices = [device for device in devices
if device.DeviceType == self._FILE_DEVICE_DISK]
return len(disk_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)
if not drive_number:
return None
for initiator_session in initiator_sessions:
devices = initiator_session.Devices
for device in devices:
if device.DeviceNumber == drive_number:
return (device.TargetName, device.ScsiLun)

View File

@ -0,0 +1,102 @@
# Copyright 2012 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Constants used in ops classes
"""
from nova.compute import arch
from nova.compute import power_state
HYPERV_VM_STATE_OTHER = 1
HYPERV_VM_STATE_ENABLED = 2
HYPERV_VM_STATE_DISABLED = 3
HYPERV_VM_STATE_SHUTTING_DOWN = 4
HYPERV_VM_STATE_REBOOT = 10
HYPERV_VM_STATE_PAUSED = 32768
HYPERV_VM_STATE_SUSPENDED = 32769
HYPERV_POWER_STATE = {
HYPERV_VM_STATE_DISABLED: power_state.SHUTDOWN,
HYPERV_VM_STATE_SHUTTING_DOWN: power_state.SHUTDOWN,
HYPERV_VM_STATE_ENABLED: power_state.RUNNING,
HYPERV_VM_STATE_PAUSED: power_state.PAUSED,
HYPERV_VM_STATE_SUSPENDED: power_state.SUSPENDED
}
WMI_WIN32_PROCESSOR_ARCHITECTURE = {
0: arch.I686,
1: arch.MIPS,
2: arch.ALPHA,
3: arch.PPC,
5: arch.ARMV7,
6: arch.IA64,
9: arch.X86_64,
}
PROCESSOR_FEATURE = {
7: '3dnow',
3: 'mmx',
12: 'nx',
9: 'pae',
8: 'rdtsc',
20: 'slat',
13: 'sse3',
21: 'vmx',
6: 'sse',
10: 'sse2',
17: 'xsave',
}
WMI_JOB_STATUS_STARTED = 4096
WMI_JOB_STATE_RUNNING = 4
WMI_JOB_STATE_COMPLETED = 7
VM_SUMMARY_NUM_PROCS = 4
VM_SUMMARY_ENABLED_STATE = 100
VM_SUMMARY_MEMORY_USAGE = 103
VM_SUMMARY_UPTIME = 105
CTRL_TYPE_IDE = "IDE"
CTRL_TYPE_SCSI = "SCSI"
DISK = "VHD"
DISK_FORMAT = DISK
DVD = "DVD"
DVD_FORMAT = "ISO"
DISK_FORMAT_MAP = {
DISK_FORMAT.lower(): DISK,
DVD_FORMAT.lower(): DVD
}
DISK_FORMAT_VHD = "VHD"
DISK_FORMAT_VHDX = "VHDX"
VHD_TYPE_FIXED = 2
VHD_TYPE_DYNAMIC = 3
SCSI_CONTROLLER_SLOTS_NUMBER = 64
HOST_POWER_ACTION_SHUTDOWN = "shutdown"
HOST_POWER_ACTION_REBOOT = "reboot"
HOST_POWER_ACTION_STARTUP = "startup"
IMAGE_PROP_VM_GEN = "hw_machine_type"
IMAGE_PROP_VM_GEN_1 = "hyperv-gen1"
IMAGE_PROP_VM_GEN_2 = "hyperv-gen2"
VM_GEN_1 = 1
VM_GEN_2 = 2

View File

@ -0,0 +1,120 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ctypes
import socket
import sys
if sys.platform == 'win32':
import wmi
from nova.i18n import _
from nova.virt.hyperv import constants
class HostUtils(object):
_HOST_FORCED_REBOOT = 6
_HOST_FORCED_SHUTDOWN = 12
_DEFAULT_VM_GENERATION = constants.IMAGE_PROP_VM_GEN_1
def __init__(self):
if sys.platform == 'win32':
self._conn_cimv2 = wmi.WMI(privileges=["Shutdown"])
self._init_wmi_virt_conn()
def _init_wmi_virt_conn(self):
self._conn_virt = None
def get_cpus_info(self):
cpus = self._conn_cimv2.query("SELECT * FROM Win32_Processor "
"WHERE ProcessorType = 3")
cpus_list = []
for cpu in cpus:
cpu_info = {'Architecture': cpu.Architecture,
'Name': cpu.Name,
'Manufacturer': cpu.Manufacturer,
'NumberOfCores': cpu.NumberOfCores,
'NumberOfLogicalProcessors':
cpu.NumberOfLogicalProcessors}
cpus_list.append(cpu_info)
return cpus_list
def is_cpu_feature_present(self, feature_key):
return ctypes.windll.kernel32.IsProcessorFeaturePresent(feature_key)
def get_memory_info(self):
"""Returns a tuple with total visible memory and free physical memory
expressed in kB.
"""
mem_info = self._conn_cimv2.query("SELECT TotalVisibleMemorySize, "
"FreePhysicalMemory "
"FROM win32_operatingsystem")[0]
return (long(mem_info.TotalVisibleMemorySize),
long(mem_info.FreePhysicalMemory))
def get_volume_info(self, drive):
"""Returns a tuple with total size and free space
expressed in bytes.
"""
logical_disk = self._conn_cimv2.query("SELECT Size, FreeSpace "
"FROM win32_logicaldisk "
"WHERE DeviceID='%s'"
% drive)[0]
return (long(logical_disk.Size), long(logical_disk.FreeSpace))
def check_min_windows_version(self, major, minor, build=0):
version_str = self.get_windows_version()
return map(int, version_str.split('.')) >= [major, minor, build]
def get_windows_version(self):
return self._conn_cimv2.Win32_OperatingSystem()[0].Version
def get_local_ips(self):
addr_info = socket.getaddrinfo(socket.gethostname(), None, 0, 0, 0)
# Returns IPv4 and IPv6 addresses, ordered by protocol family
addr_info.sort()
return [a[4][0] for a in addr_info]
def get_host_tick_count64(self):
return ctypes.windll.kernel32.GetTickCount64()
def host_power_action(self, action):
win32_os = self._conn_cimv2.Win32_OperatingSystem()[0]
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
win32_os.Win32Shutdown(self._HOST_FORCED_SHUTDOWN)
elif action == constants.HOST_POWER_ACTION_REBOOT:
win32_os.Win32Shutdown(self._HOST_FORCED_REBOOT)
else:
raise NotImplementedError(
_("Host %(action)s is not supported by the Hyper-V driver") %
{"action": action})
def get_supported_vm_types(self):
"""Get the supported Hyper-V VM generations.
Hyper-V Generation 2 VMs are supported in Windows 8.1,
Windows Server / Hyper-V Server 2012 R2 or newer.
:returns: array of supported VM generations (ex. ['hyperv-gen1'])
"""
if self.check_min_windows_version(6, 3):
return [constants.IMAGE_PROP_VM_GEN_1,
constants.IMAGE_PROP_VM_GEN_2]
else:
return [constants.IMAGE_PROP_VM_GEN_1]
def get_default_vm_generation(self):
return self._DEFAULT_VM_GENERATION

View File

@ -0,0 +1,34 @@
# Copyright 2015 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
if sys.platform == 'win32':
import wmi
from nova.virt.hyperv import hostutils
class HostUtilsV2(hostutils.HostUtils):
FEATURE_RDS_VIRTUALIZATION = 322
def __init__(self):
super(HostUtilsV2, self).__init__()
self._init_wmi_virt_conn()
def _init_wmi_virt_conn(self):
if sys.platform == 'win32':
self._conn_virt = wmi.WMI(moniker='//./root/virtualization/v2')

View File

@ -0,0 +1,73 @@
# Copyright 2014 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import errno
import os
from eventlet import patcher
from oslo_log import log as logging
from nova.i18n import _LE
LOG = logging.getLogger(__name__)
native_threading = patcher.original('threading')
class IOThread(native_threading.Thread):
def __init__(self, src, dest, max_bytes):
super(IOThread, self).__init__()
self.setDaemon(True)
self._src = src
self._dest = dest
self._dest_archive = dest + '.1'
self._max_bytes = max_bytes
self._stopped = native_threading.Event()
def run(self):
try:
self._copy()
except IOError as err:
self._stopped.set()
# Invalid argument error means that the vm console pipe was closed,
# probably the vm was stopped. The worker can stop it's execution.
if err.errno != errno.EINVAL:
LOG.error(_LE("Error writing vm console log file from "
"serial console pipe. Error: %s") % err)
def _copy(self):
with open(self._src, 'rb') as src:
with open(self._dest, 'ab', 0) as dest:
dest.seek(0, os.SEEK_END)
log_size = dest.tell()
while (not self._stopped.isSet()):
# Read one byte at a time to avoid blocking.
data = src.read(1)
dest.write(data)
log_size += len(data)
if (log_size >= self._max_bytes):
dest.close()
if os.path.exists(self._dest_archive):
os.remove(self._dest_archive)
os.rename(self._dest, self._dest_archive)
dest = open(self._dest, 'ab', 0)
log_size = 0
def join(self):
self._stopped.set()
super(IOThread, self).join()
def is_active(self):
return not self._stopped.isSet()

View File

@ -0,0 +1,252 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
if sys.platform == 'win32':
import wmi
from oslo_log import log as logging
from nova import exception
from nova.i18n import _, _LE
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import vmutilsv2
from nova.virt.hyperv import volumeutilsv2
LOG = logging.getLogger(__name__)
class LiveMigrationUtils(object):
def __init__(self):
self._vmutils = vmutilsv2.VMUtilsV2()
self._volutils = volumeutilsv2.VolumeUtilsV2()
def _get_conn_v2(self, host='localhost'):
try:
return wmi.WMI(moniker='//%s/root/virtualization/v2' % host)
except wmi.x_wmi as ex:
LOG.exception(_LE('Get version 2 connection error'))
if ex.com_error.hresult == -2147217394:
msg = (_('Live migration is not supported on target host "%s"')
% host)
elif ex.com_error.hresult == -2147023174:
msg = (_('Target live migration host "%s" is unreachable')
% host)
else:
msg = _('Live migration failed: %s') % ex.message
raise vmutils.HyperVException(msg)
def check_live_migration_config(self):
conn_v2 = self._get_conn_v2()
migration_svc = conn_v2.Msvm_VirtualSystemMigrationService()[0]
vsmssds = migration_svc.associators(
wmi_association_class='Msvm_ElementSettingData',
wmi_result_class='Msvm_VirtualSystemMigrationServiceSettingData')
vsmssd = vsmssds[0]
if not vsmssd.EnableVirtualSystemMigration:
raise vmutils.HyperVException(
_('Live migration is not enabled on this host'))
if not migration_svc.MigrationServiceListenerIPAddressList:
raise vmutils.HyperVException(
_('Live migration networks are not configured on this host'))
def _get_vm(self, conn_v2, vm_name):
vms = conn_v2.Msvm_ComputerSystem(ElementName=vm_name)
n = len(vms)
if not n:
raise exception.NotFound(_('VM not found: %s') % vm_name)
elif n > 1:
raise vmutils.HyperVException(_('Duplicate VM name found: %s')
% vm_name)
return vms[0]
def _destroy_planned_vm(self, conn_v2_remote, planned_vm):
LOG.debug("Destroying existing remote planned VM: %s",
planned_vm.ElementName)
vs_man_svc = conn_v2_remote.Msvm_VirtualSystemManagementService()[0]
(job_path, ret_val) = vs_man_svc.DestroySystem(planned_vm.path_())
self._vmutils.check_ret_val(ret_val, job_path)
def _check_existing_planned_vm(self, conn_v2_remote, vm):
# Make sure that there's not yet a remote planned VM on the target
# host for this VM
planned_vms = conn_v2_remote.Msvm_PlannedComputerSystem(Name=vm.Name)
if planned_vms:
self._destroy_planned_vm(conn_v2_remote, planned_vms[0])
def _create_remote_planned_vm(self, conn_v2_local, conn_v2_remote,
vm, rmt_ip_addr_list, dest_host):
# Staged
vsmsd = conn_v2_local.query("select * from "
"Msvm_VirtualSystemMigrationSettingData "
"where MigrationType = 32770")[0]
vsmsd.DestinationIPAddressList = rmt_ip_addr_list
migration_setting_data = vsmsd.GetText_(1)
LOG.debug("Creating remote planned VM for VM: %s",
vm.ElementName)
migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
ComputerSystem=vm.path_(),
DestinationHost=dest_host,
MigrationSettingData=migration_setting_data)
self._vmutils.check_ret_val(ret_val, job_path)
return conn_v2_remote.Msvm_PlannedComputerSystem(Name=vm.Name)[0]
def _get_physical_disk_paths(self, vm_name):
ide_ctrl_path = self._vmutils.get_vm_ide_controller(vm_name, 0)
if ide_ctrl_path:
ide_paths = self._vmutils.get_controller_volume_paths(
ide_ctrl_path)
else:
ide_paths = {}
scsi_ctrl_path = self._vmutils.get_vm_scsi_controller(vm_name)
scsi_paths = self._vmutils.get_controller_volume_paths(scsi_ctrl_path)
return dict(ide_paths.items() + scsi_paths.items())
def _get_remote_disk_data(self, vmutils_remote, disk_paths, dest_host):
volutils_remote = volumeutilsv2.VolumeUtilsV2(dest_host)
disk_paths_remote = {}
for (rasd_rel_path, disk_path) in disk_paths.items():
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))
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
def _update_planned_vm_disk_resources(self, vmutils_remote, conn_v2_remote,
planned_vm, vm_name,
disk_paths_remote):
vm_settings = planned_vm.associators(
wmi_association_class='Msvm_SettingsDefineState',
wmi_result_class='Msvm_VirtualSystemSettingData')[0]
updated_resource_setting_data = []
sasds = vm_settings.associators(
wmi_association_class='Msvm_VirtualSystemSettingDataComponent')
for sasd in sasds:
if (sasd.ResourceType == 17 and sasd.ResourceSubType ==
"Microsoft:Hyper-V:Physical Disk Drive" and
sasd.HostResource):
# Replace the local disk target with the correct remote one
old_disk_path = sasd.HostResource[0]
new_disk_path = disk_paths_remote.pop(sasd.path().RelPath)
LOG.debug("Replacing host resource "
"%(old_disk_path)s with "
"%(new_disk_path)s on planned VM %(vm_name)s",
{'old_disk_path': old_disk_path,
'new_disk_path': new_disk_path,
'vm_name': vm_name})
sasd.HostResource = [new_disk_path]
updated_resource_setting_data.append(sasd.GetText_(1))
LOG.debug("Updating remote planned VM disk paths for VM: %s",
vm_name)
vsmsvc = conn_v2_remote.Msvm_VirtualSystemManagementService()[0]
(res_settings, job_path, ret_val) = vsmsvc.ModifyResourceSettings(
ResourceSettings=updated_resource_setting_data)
vmutils_remote.check_ret_val(ret_val, job_path)
def _get_vhd_setting_data(self, vm):
vm_settings = vm.associators(
wmi_association_class='Msvm_SettingsDefineState',
wmi_result_class='Msvm_VirtualSystemSettingData')[0]
new_resource_setting_data = []
sasds = vm_settings.associators(
wmi_association_class='Msvm_VirtualSystemSettingDataComponent',
wmi_result_class='Msvm_StorageAllocationSettingData')
for sasd in sasds:
if (sasd.ResourceType == 31 and sasd.ResourceSubType ==
"Microsoft:Hyper-V:Virtual Hard Disk"):
new_resource_setting_data.append(sasd.GetText_(1))
return new_resource_setting_data
def _live_migrate_vm(self, conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
new_resource_setting_data, dest_host):
# VirtualSystemAndStorage
vsmsd = conn_v2_local.query("select * from "
"Msvm_VirtualSystemMigrationSettingData "
"where MigrationType = 32771")[0]
vsmsd.DestinationIPAddressList = rmt_ip_addr_list
if planned_vm:
vsmsd.DestinationPlannedVirtualSystemId = planned_vm.Name
migration_setting_data = vsmsd.GetText_(1)
migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
LOG.debug("Starting live migration for VM: %s", vm.ElementName)
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
ComputerSystem=vm.path_(),
DestinationHost=dest_host,
MigrationSettingData=migration_setting_data,
NewResourceSettingData=new_resource_setting_data)
self._vmutils.check_ret_val(ret_val, job_path)
def _get_remote_ip_address_list(self, conn_v2_remote, dest_host):
LOG.debug("Getting live migration networks for remote host: %s",
dest_host)
migr_svc_rmt = conn_v2_remote.Msvm_VirtualSystemMigrationService()[0]
return migr_svc_rmt.MigrationServiceListenerIPAddressList
def live_migrate_vm(self, vm_name, dest_host):
self.check_live_migration_config()
conn_v2_local = self._get_conn_v2()
conn_v2_remote = self._get_conn_v2(dest_host)
vm = self._get_vm(conn_v2_local, vm_name)
self._check_existing_planned_vm(conn_v2_remote, vm)
rmt_ip_addr_list = self._get_remote_ip_address_list(conn_v2_remote,
dest_host)
planned_vm = None
disk_paths = self._get_physical_disk_paths(vm_name)
if disk_paths:
vmutils_remote = vmutilsv2.VMUtilsV2(dest_host)
disk_paths_remote = self._get_remote_disk_data(vmutils_remote,
disk_paths,
dest_host)
planned_vm = self._create_remote_planned_vm(conn_v2_local,
conn_v2_remote,
vm, rmt_ip_addr_list,
dest_host)
self._update_planned_vm_disk_resources(vmutils_remote,
conn_v2_remote, planned_vm,
vm_name, disk_paths_remote)
new_resource_setting_data = self._get_vhd_setting_data(vm)
self._live_migrate_vm(conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
new_resource_setting_data, dest_host)

View File

@ -0,0 +1,68 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for network related operations.
"""
import sys
import uuid
if sys.platform == 'win32':
import wmi
from nova.i18n import _
from nova.virt.hyperv import vmutils
class NetworkUtils(object):
def __init__(self):
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization')
def get_external_vswitch(self, vswitch_name):
if vswitch_name:
vswitches = self._conn.Msvm_VirtualSwitch(ElementName=vswitch_name)
else:
# Find the vswitch that is connected to the first physical nic.
ext_port = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]
port = ext_port.associators(wmi_result_class='Msvm_SwitchPort')[0]
vswitches = port.associators(wmi_result_class='Msvm_VirtualSwitch')
if not len(vswitches):
raise vmutils.HyperVException(_('vswitch "%s" not found')
% vswitch_name)
return vswitches[0].path_()
def create_vswitch_port(self, vswitch_path, port_name):
switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
# Create a port on the vswitch.
(new_port, ret_val) = switch_svc.CreateSwitchPort(
Name=str(uuid.uuid4()),
FriendlyName=port_name,
ScopeOfResidence="",
VirtualSwitch=vswitch_path)
if ret_val != 0:
raise vmutils.HyperVException(_("Failed to create vswitch port "
"%(port_name)s on switch "
"%(vswitch_path)s") %
{'port_name': port_name,
'vswitch_path': vswitch_path})
return new_port
def vswitch_port_needed(self):
# NOTE(alexpilotti): In WMI V2 the vswitch_path is set in the VM
# setting data without the need for a vswitch port.
return True

View File

@ -0,0 +1,63 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for network related operations.
Based on the "root/virtualization/v2" namespace available starting with
Hyper-V Server / Windows Server 2012.
"""
import sys
if sys.platform == 'win32':
import wmi
from nova.i18n import _
from nova.virt.hyperv import networkutils
from nova.virt.hyperv import vmutils
class NetworkUtilsV2(networkutils.NetworkUtils):
def __init__(self):
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
def get_external_vswitch(self, vswitch_name):
if vswitch_name:
vswitches = self._conn.Msvm_VirtualEthernetSwitch(
ElementName=vswitch_name)
if not len(vswitches):
raise vmutils.HyperVException(_('vswitch "%s" not found')
% vswitch_name)
else:
# Find the vswitch that is connected to the first physical nic.
ext_port = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]
lep = ext_port.associators(wmi_result_class='Msvm_LANEndpoint')[0]
lep1 = lep.associators(wmi_result_class='Msvm_LANEndpoint')[0]
esw = lep1.associators(
wmi_result_class='Msvm_EthernetSwitchPort')[0]
vswitches = esw.associators(
wmi_result_class='Msvm_VirtualEthernetSwitch')
if not len(vswitches):
raise vmutils.HyperVException(_('No external vswitch found'))
return vswitches[0].path_()
def create_vswitch_port(self, vswitch_path, port_name):
raise NotImplementedError()
def vswitch_port_needed(self):
return False

View File

@ -0,0 +1,271 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import shutil
import sys
import time
if sys.platform == 'win32':
import wmi
from oslo_config import cfg
from oslo_log import log as logging
from nova.i18n import _
from nova import utils
from nova.virt.hyperv import constants
from nova.virt.hyperv import vmutils
LOG = logging.getLogger(__name__)
hyperv_opts = [
cfg.StrOpt('instances_path_share',
default="",
help='The name of a Windows share name mapped to the '
'"instances_path" dir and used by the resize feature '
'to copy files to the target host. If left blank, an '
'administrative share will be used, looking for the same '
'"instances_path" used locally'),
]
CONF = cfg.CONF
CONF.register_opts(hyperv_opts, 'hyperv')
CONF.import_opt('instances_path', 'nova.compute.manager')
ERROR_INVALID_NAME = 123
ERROR_DIR_IS_NOT_EMPTY = 145
class PathUtils(object):
def __init__(self):
self._smb_conn = wmi.WMI(moniker=r"root\Microsoft\Windows\SMB")
def open(self, path, mode):
"""Wrapper on __builtin__.open used to simplify unit testing."""
import __builtin__
return __builtin__.open(path, mode)
def exists(self, path):
return os.path.exists(path)
def makedirs(self, path):
os.makedirs(path)
def remove(self, path):
os.remove(path)
def rename(self, src, dest):
os.rename(src, dest)
def copyfile(self, src, dest):
self.copy(src, dest)
def copy(self, src, dest):
# With large files this is 2x-3x faster than shutil.copy(src, dest),
# especially when copying to a UNC target.
# shutil.copyfileobj(...) with a proper buffer is better than
# shutil.copy(...) but still 20% slower than a shell copy.
# It can be replaced with Win32 API calls to avoid the process
# spawning overhead.
LOG.debug('Copying file from %s to %s', src, dest)
output, ret = utils.execute('cmd.exe', '/C', 'copy', '/Y', src, dest)
if ret:
raise IOError(_('The file copy from %(src)s to %(dest)s failed')
% {'src': src, 'dest': dest})
def move_folder_files(self, src_dir, dest_dir):
"""Moves the files of the given src_dir to dest_dir.
It will ignore any nested folders.
:param src_dir: Given folder from which to move files.
:param dest_dir: Folder to which to move files.
"""
for fname in os.listdir(src_dir):
src = os.path.join(src_dir, fname)
# ignore subdirs.
if os.path.isfile(src):
self.rename(src, os.path.join(dest_dir, fname))
def rmtree(self, path):
# This will be removed once support for Windows Server 2008R2 is
# stopped
for i in range(5):
try:
shutil.rmtree(path)
return
except WindowsError as e:
if e.winerror == ERROR_DIR_IS_NOT_EMPTY:
time.sleep(1)
else:
raise e
def get_instances_dir(self, remote_server=None):
local_instance_path = os.path.normpath(CONF.instances_path)
if remote_server:
if CONF.hyperv.instances_path_share:
path = CONF.hyperv.instances_path_share
else:
# Use an administrative share
path = local_instance_path.replace(':', '$')
return ('\\\\%(remote_server)s\\%(path)s' %
{'remote_server': remote_server, 'path': path})
else:
return local_instance_path
def _check_create_dir(self, path):
if not self.exists(path):
LOG.debug('Creating directory: %s', path)
self.makedirs(path)
def _check_remove_dir(self, path):
if self.exists(path):
LOG.debug('Removing directory: %s', path)
self.rmtree(path)
def _get_instances_sub_dir(self, dir_name, remote_server=None,
create_dir=True, remove_dir=False):
instances_path = self.get_instances_dir(remote_server)
path = os.path.join(instances_path, dir_name)
try:
if remove_dir:
self._check_remove_dir(path)
if create_dir:
self._check_create_dir(path)
return path
except WindowsError as ex:
if ex.winerror == ERROR_INVALID_NAME:
raise vmutils.HyperVException(_(
"Cannot access \"%(instances_path)s\", make sure the "
"path exists and that you have the proper permissions. "
"In particular Nova-Compute must not be executed with the "
"builtin SYSTEM account or other accounts unable to "
"authenticate on a remote host.") %
{'instances_path': instances_path})
raise
def get_instance_migr_revert_dir(self, instance_name, create_dir=False,
remove_dir=False):
dir_name = '%s_revert' % instance_name
return self._get_instances_sub_dir(dir_name, None, create_dir,
remove_dir)
def get_instance_dir(self, instance_name, remote_server=None,
create_dir=True, remove_dir=False):
return self._get_instances_sub_dir(instance_name, remote_server,
create_dir, remove_dir)
def _lookup_vhd_path(self, instance_name, vhd_path_func):
vhd_path = None
for format_ext in ['vhd', 'vhdx']:
test_path = vhd_path_func(instance_name, format_ext)
if self.exists(test_path):
vhd_path = test_path
break
return vhd_path
def lookup_root_vhd_path(self, instance_name):
return self._lookup_vhd_path(instance_name, self.get_root_vhd_path)
def lookup_configdrive_path(self, instance_name):
configdrive_path = None
for format_ext in constants.DISK_FORMAT_MAP:
test_path = self.get_configdrive_path(instance_name, format_ext)
if self.exists(test_path):
configdrive_path = test_path
break
return configdrive_path
def lookup_ephemeral_vhd_path(self, instance_name):
return self._lookup_vhd_path(instance_name,
self.get_ephemeral_vhd_path)
def get_root_vhd_path(self, instance_name, format_ext):
instance_path = self.get_instance_dir(instance_name)
return os.path.join(instance_path, 'root.' + format_ext.lower())
def get_configdrive_path(self, instance_name, format_ext,
remote_server=None):
instance_path = self.get_instance_dir(instance_name, remote_server)
return os.path.join(instance_path, 'configdrive.' + format_ext.lower())
def get_ephemeral_vhd_path(self, instance_name, format_ext):
instance_path = self.get_instance_dir(instance_name)
return os.path.join(instance_path, 'ephemeral.' + format_ext.lower())
def get_base_vhd_dir(self):
return self._get_instances_sub_dir('_base')
def get_export_dir(self, instance_name):
dir_name = os.path.join('export', instance_name)
return self._get_instances_sub_dir(dir_name, create_dir=True,
remove_dir=True)
def get_vm_console_log_paths(self, vm_name, remote_server=None):
instance_dir = self.get_instance_dir(vm_name,
remote_server)
console_log_path = os.path.join(instance_dir, 'console.log')
return console_log_path, console_log_path + '.1'
def check_smb_mapping(self, smbfs_share):
mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share)
if not mappings:
return False
if os.path.exists(smbfs_share):
LOG.debug('Share already mounted: %s', smbfs_share)
return True
else:
LOG.debug('Share exists but is unavailable: %s ', smbfs_share)
self.unmount_smb_share(smbfs_share, force=True)
return False
def mount_smb_share(self, smbfs_share, username=None, password=None):
try:
LOG.debug('Mounting share: %s', smbfs_share)
self._smb_conn.Msft_SmbMapping.Create(RemotePath=smbfs_share,
UserName=username,
Password=password)
except wmi.x_wmi as exc:
err_msg = (_(
'Unable to mount SMBFS share: %(smbfs_share)s '
'WMI exception: %(wmi_exc)s'), {'smbfs_share': smbfs_share,
'wmi_exc': exc})
raise vmutils.HyperVException(err_msg)
def unmount_smb_share(self, smbfs_share, force=False):
mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share)
if not mappings:
LOG.debug('Share %s is not mounted. Skipping unmount.',
smbfs_share)
for mapping in mappings:
# Due to a bug in the WMI module, getting the output of
# methods returning None will raise an AttributeError
try:
mapping.Remove(Force=force)
except AttributeError:
pass
except wmi.x_wmi:
# If this fails, a 'Generic Failure' exception is raised.
# This happens even if we unforcefully unmount an in-use
# share, for which reason we'll simply ignore it in this
# case.
if force:
raise vmutils.HyperVException(
_("Could not unmount share: %s"), smbfs_share)

View File

@ -0,0 +1,21 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class RDPConsoleUtils(object):
_DEFAULT_HYPERV_RDP_PORT = 2179
def get_rdp_console_port(self):
return self._DEFAULT_HYPERV_RDP_PORT

View File

@ -0,0 +1,31 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
from nova.virt.hyperv import rdpconsoleutils
if sys.platform == 'win32':
import wmi
class RDPConsoleUtilsV2(rdpconsoleutils.RDPConsoleUtils):
def __init__(self):
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
def get_rdp_console_port(self):
rdp_setting_data = self._conn.Msvm_TerminalServiceSettingData()[0]
return rdp_setting_data.ListenerPort

View File

@ -0,0 +1,212 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for VHD related operations.
Official VHD format specs can be retrieved at:
http://technet.microsoft.com/en-us/library/bb676673.aspx
See "Download the Specifications Without Registering"
Official VHDX format specs can be retrieved at:
http://www.microsoft.com/en-us/download/details.aspx?id=34750
"""
import struct
import sys
if sys.platform == 'win32':
import wmi
from xml.etree import ElementTree
from nova.i18n import _
from nova.virt.hyperv import constants
from nova.virt.hyperv import vmutils
VHD_HEADER_SIZE_FIX = 512
VHD_BAT_ENTRY_SIZE = 4
VHD_DYNAMIC_DISK_HEADER_SIZE = 1024
VHD_HEADER_SIZE_DYNAMIC = 512
VHD_FOOTER_SIZE_DYNAMIC = 512
VHD_BLK_SIZE_OFFSET = 544
VHD_SIGNATURE = 'conectix'
VHDX_SIGNATURE = 'vhdxfile'
class VHDUtils(object):
def __init__(self):
self._vmutils = vmutils.VMUtils()
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization')
def validate_vhd(self, vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.ValidateVirtualHardDisk(
Path=vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
def create_dynamic_vhd(self, path, max_internal_size, format):
if format != constants.DISK_FORMAT_VHD:
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
format)
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.CreateDynamicVirtualHardDisk(
Path=path, MaxInternalSize=max_internal_size)
self._vmutils.check_ret_val(ret_val, job_path)
def create_differencing_vhd(self, path, parent_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.CreateDifferencingVirtualHardDisk(
Path=path, ParentPath=parent_path)
self._vmutils.check_ret_val(ret_val, job_path)
def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.ReconnectParentVirtualHardDisk(
ChildPath=child_vhd_path,
ParentPath=parent_vhd_path,
Force=True)
self._vmutils.check_ret_val(ret_val, job_path)
def merge_vhd(self, src_vhd_path, dest_vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.MergeVirtualHardDisk(
SourcePath=src_vhd_path,
DestinationPath=dest_vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
def _get_resize_method(self):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
return image_man_svc.ExpandVirtualHardDisk
def resize_vhd(self, vhd_path, new_max_size, is_file_max_size=True):
if is_file_max_size:
new_internal_max_size = self.get_internal_vhd_size_by_file_size(
vhd_path, new_max_size)
else:
new_internal_max_size = new_max_size
resize = self._get_resize_method()
(job_path, ret_val) = resize(
Path=vhd_path, MaxInternalSize=new_internal_max_size)
self._vmutils.check_ret_val(ret_val, job_path)
def get_internal_vhd_size_by_file_size(self, vhd_path, new_vhd_file_size):
"""Fixed VHD size = Data Block size + 512 bytes
| Dynamic_VHD_size = Dynamic Disk Header
| + Copy of hard disk footer
| + Hard Disk Footer
| + Data Block
| + BAT
| Dynamic Disk header fields
| Copy of hard disk footer (512 bytes)
| Dynamic Disk Header (1024 bytes)
| BAT (Block Allocation table)
| Data Block 1
| Data Block 2
| Data Block n
| Hard Disk Footer (512 bytes)
| Default block size is 2M
| BAT entry size is 4byte
"""
base_vhd_info = self.get_vhd_info(vhd_path)
vhd_type = base_vhd_info['Type']
if vhd_type == constants.VHD_TYPE_FIXED:
vhd_header_size = VHD_HEADER_SIZE_FIX
return new_vhd_file_size - vhd_header_size
elif vhd_type == constants.VHD_TYPE_DYNAMIC:
bs = self._get_vhd_dynamic_blk_size(vhd_path)
bes = VHD_BAT_ENTRY_SIZE
ddhs = VHD_DYNAMIC_DISK_HEADER_SIZE
hs = VHD_HEADER_SIZE_DYNAMIC
fs = VHD_FOOTER_SIZE_DYNAMIC
max_internal_size = (new_vhd_file_size -
(hs + ddhs + fs)) * bs / (bes + bs)
return max_internal_size
else:
vhd_parent = self.get_vhd_parent_path(vhd_path)
return self.get_internal_vhd_size_by_file_size(vhd_parent,
new_vhd_file_size)
def _get_vhd_dynamic_blk_size(self, vhd_path):
blk_size_offset = VHD_BLK_SIZE_OFFSET
try:
with open(vhd_path, "rb") as f:
f.seek(blk_size_offset)
version = f.read(4)
except IOError:
raise vmutils.HyperVException(_("Unable to obtain block size from"
" VHD %(vhd_path)s") %
{"vhd_path": vhd_path})
return struct.unpack('>i', version)[0]
def get_vhd_parent_path(self, vhd_path):
return self.get_vhd_info(vhd_path).get("ParentPath")
def get_vhd_info(self, vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(vhd_info,
job_path,
ret_val) = image_man_svc.GetVirtualHardDiskInfo(vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
vhd_info_dict = {}
et = ElementTree.fromstring(vhd_info)
for item in et.findall("PROPERTY"):
name = item.attrib["NAME"]
value_text = item.find("VALUE").text
if name == "ParentPath":
vhd_info_dict[name] = value_text
elif name in ["FileSize", "MaxInternalSize"]:
vhd_info_dict[name] = long(value_text)
elif name in ["InSavedState", "InUse"]:
vhd_info_dict[name] = bool(value_text)
elif name == "Type":
vhd_info_dict[name] = int(value_text)
return vhd_info_dict
def get_vhd_format(self, path):
with open(path, 'rb') as f:
# Read header
if f.read(8) == VHDX_SIGNATURE:
return constants.DISK_FORMAT_VHDX
# Read footer
f.seek(0, 2)
file_size = f.tell()
if file_size >= 512:
f.seek(-512, 2)
if f.read(8) == VHD_SIGNATURE:
return constants.DISK_FORMAT_VHD
raise vmutils.HyperVException(_('Unsupported virtual disk format'))
def get_best_supported_vhd_format(self):
return constants.DISK_FORMAT_VHD

View File

@ -0,0 +1,247 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for VHD related operations.
Based on the "root/virtualization/v2" namespace available starting with
Hyper-V Server / Windows Server 2012.
"""
import struct
import sys
if sys.platform == 'win32':
import wmi
from xml.etree import ElementTree
from oslo_utils import units
from nova.i18n import _
from nova.virt.hyperv import constants
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import vmutilsv2
VHDX_BAT_ENTRY_SIZE = 8
VHDX_HEADER_OFFSETS = [64 * units.Ki, 128 * units.Ki]
VHDX_HEADER_SECTION_SIZE = units.Mi
VHDX_LOG_LENGTH_OFFSET = 68
VHDX_METADATA_SIZE_OFFSET = 64
VHDX_REGION_TABLE_OFFSET = 192 * units.Ki
VHDX_BS_METADATA_ENTRY_OFFSET = 48
class VHDUtilsV2(vhdutils.VHDUtils):
_VHD_TYPE_DYNAMIC = 3
_VHD_TYPE_DIFFERENCING = 4
_vhd_format_map = {
constants.DISK_FORMAT_VHD: 2,
constants.DISK_FORMAT_VHDX: 3,
}
def __init__(self):
self._vmutils = vmutilsv2.VMUtilsV2()
if sys.platform == 'win32':
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
def create_dynamic_vhd(self, path, max_internal_size, format):
vhd_format = self._vhd_format_map.get(format)
if not vhd_format:
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
format)
self._create_vhd(self._VHD_TYPE_DYNAMIC, vhd_format, path,
max_internal_size=max_internal_size)
def create_differencing_vhd(self, path, parent_path):
# Although this method can take a size argument in case of VHDX
# images, avoid it as the underlying Win32 is currently not
# resizing the disk properly. This can be reconsidered once the
# Win32 issue is fixed.
parent_vhd_info = self.get_vhd_info(parent_path)
self._create_vhd(self._VHD_TYPE_DIFFERENCING,
parent_vhd_info["Format"],
path, parent_path=parent_path)
def _create_vhd(self, vhd_type, format, path, max_internal_size=None,
parent_path=None):
vhd_info = self._conn.Msvm_VirtualHardDiskSettingData.new()
vhd_info.Type = vhd_type
vhd_info.Format = format
vhd_info.Path = path
vhd_info.ParentPath = parent_path
if max_internal_size:
vhd_info.MaxInternalSize = max_internal_size
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
(job_path, ret_val) = image_man_svc.CreateVirtualHardDisk(
VirtualDiskSettingData=vhd_info.GetText_(1))
self._vmutils.check_ret_val(ret_val, job_path)
def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, child_vhd_path)
et = ElementTree.fromstring(vhd_info_xml)
item = et.find(".//PROPERTY[@NAME='ParentPath']/VALUE")
if item is not None:
item.text = parent_vhd_path
else:
msg = (_("Failed to reconnect image %(child_vhd_path)s to "
"parent %(parent_vhd_path)s. The child image has no "
"parent path property.") %
{'child_vhd_path': child_vhd_path,
'parent_vhd_path': parent_vhd_path})
raise vmutils.HyperVException(msg)
vhd_info_xml = ElementTree.tostring(et)
(job_path, ret_val) = image_man_svc.SetVirtualHardDiskSettingData(
VirtualDiskSettingData=vhd_info_xml)
self._vmutils.check_ret_val(ret_val, job_path)
def _get_resize_method(self):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
return image_man_svc.ResizeVirtualHardDisk
def get_internal_vhd_size_by_file_size(self, vhd_path,
new_vhd_file_size):
"""Get internal size of a VHD according to new VHD file size.
VHDX Size = Header (1MB) + Log + Metadata Region + BAT + Payload Blocks
The chunk size is the maximum number of bytes described by a SB
block.
Chunk size = 2^{23} * LogicalSectorSize
:param str vhd_path: VHD file path
:param new_vhd_file_size: Size of the new VHD file.
:return: Internal VHD size according to new VHD file size.
"""
vhd_format = self.get_vhd_format(vhd_path)
if vhd_format == constants.DISK_FORMAT_VHD:
return super(VHDUtilsV2,
self).get_internal_vhd_size_by_file_size(
vhd_path, new_vhd_file_size)
else:
vhd_info = self.get_vhd_info(vhd_path)
vhd_type = vhd_info['Type']
if vhd_type == self._VHD_TYPE_DIFFERENCING:
vhd_parent = self.get_vhd_parent_path(vhd_path)
return self.get_internal_vhd_size_by_file_size(vhd_parent,
new_vhd_file_size)
else:
try:
with open(vhd_path, 'rb') as f:
hs = VHDX_HEADER_SECTION_SIZE
bes = VHDX_BAT_ENTRY_SIZE
lss = vhd_info['LogicalSectorSize']
bs = self._get_vhdx_block_size(f)
ls = self._get_vhdx_log_size(f)
ms = self._get_vhdx_metadata_size_and_offset(f)[0]
chunk_ratio = (1 << 23) * lss / bs
size = new_vhd_file_size
max_internal_size = (bs * chunk_ratio * (size - hs -
ls - ms - bes - bes / chunk_ratio) / (bs *
chunk_ratio + bes * chunk_ratio + bes))
return max_internal_size - (max_internal_size % bs)
except IOError as ex:
raise vmutils.HyperVException(_("Unable to obtain "
"internal size from VHDX: "
"%(vhd_path)s. Exception: "
"%(ex)s") %
{"vhd_path": vhd_path,
"ex": ex})
def _get_vhdx_current_header_offset(self, vhdx_file):
sequence_numbers = []
for offset in VHDX_HEADER_OFFSETS:
vhdx_file.seek(offset + 8)
sequence_numbers.append(struct.unpack('<Q',
vhdx_file.read(8))[0])
current_header = sequence_numbers.index(max(sequence_numbers))
return VHDX_HEADER_OFFSETS[current_header]
def _get_vhdx_log_size(self, vhdx_file):
current_header_offset = self._get_vhdx_current_header_offset(vhdx_file)
offset = current_header_offset + VHDX_LOG_LENGTH_OFFSET
vhdx_file.seek(offset)
log_size = struct.unpack('<I', vhdx_file.read(4))[0]
return log_size
def _get_vhdx_metadata_size_and_offset(self, vhdx_file):
offset = VHDX_METADATA_SIZE_OFFSET + VHDX_REGION_TABLE_OFFSET
vhdx_file.seek(offset)
metadata_offset = struct.unpack('<Q', vhdx_file.read(8))[0]
metadata_size = struct.unpack('<I', vhdx_file.read(4))[0]
return metadata_size, metadata_offset
def _get_vhdx_block_size(self, vhdx_file):
metadata_offset = self._get_vhdx_metadata_size_and_offset(vhdx_file)[1]
offset = metadata_offset + VHDX_BS_METADATA_ENTRY_OFFSET
vhdx_file.seek(offset)
file_parameter_offset = struct.unpack('<I', vhdx_file.read(4))[0]
vhdx_file.seek(file_parameter_offset + metadata_offset)
block_size = struct.unpack('<I', vhdx_file.read(4))[0]
return block_size
def _get_vhd_info_xml(self, image_man_svc, vhd_path):
(job_path,
ret_val,
vhd_info_xml) = image_man_svc.GetVirtualHardDiskSettingData(vhd_path)
self._vmutils.check_ret_val(ret_val, job_path)
return vhd_info_xml.encode('utf8', 'xmlcharrefreplace')
def get_vhd_info(self, vhd_path):
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, vhd_path)
vhd_info_dict = {}
et = ElementTree.fromstring(vhd_info_xml)
for item in et.findall("PROPERTY"):
name = item.attrib["NAME"]
value_item = item.find("VALUE")
if value_item is None:
value_text = None
else:
value_text = value_item.text
if name in ["Path", "ParentPath"]:
vhd_info_dict[name] = value_text
elif name in ["BlockSize", "LogicalSectorSize",
"PhysicalSectorSize", "MaxInternalSize"]:
vhd_info_dict[name] = long(value_text)
elif name in ["Type", "Format"]:
vhd_info_dict[name] = int(value_text)
return vhd_info_dict
def get_best_supported_vhd_format(self):
return constants.DISK_FORMAT_VHDX

View File

@ -0,0 +1,822 @@
# Copyright (c) 2010 Cloud.com, Inc
# Copyright 2012 Cloudbase Solutions Srl / Pedro Navarro Perez
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for VM related operations on Hyper-V.
"""
import sys
import time
import uuid
if sys.platform == 'win32':
import wmi
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import uuidutils
import six
from six.moves import range
from nova import exception
from nova.i18n import _, _LW
from nova.virt.hyperv import constants
from nova.virt.hyperv import hostutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
# TODO(alexpilotti): Move the exceptions to a separate module
# TODO(alexpilotti): Add more domain exceptions
class HyperVException(exception.NovaException):
def __init__(self, message=None):
super(HyperVException, self).__init__(message)
# TODO(alexpilotti): Add a storage exception base class
class VHDResizeException(HyperVException):
def __init__(self, message=None):
super(HyperVException, self).__init__(message)
class HyperVAuthorizationException(HyperVException):
def __init__(self, message=None):
super(HyperVException, self).__init__(message)
class UnsupportedConfigDriveFormatException(HyperVException):
def __init__(self, message=None):
super(HyperVException, self).__init__(message)
class VMUtils(object):
# These constants can be overridden by inherited classes
_PHYS_DISK_RES_SUB_TYPE = 'Microsoft Physical Disk Drive'
_DISK_DRIVE_RES_SUB_TYPE = 'Microsoft Synthetic Disk Drive'
_DVD_DRIVE_RES_SUB_TYPE = 'Microsoft Synthetic DVD Drive'
_HARD_DISK_RES_SUB_TYPE = 'Microsoft Virtual Hard Disk'
_DVD_DISK_RES_SUB_TYPE = 'Microsoft Virtual CD/DVD Disk'
_IDE_CTRL_RES_SUB_TYPE = 'Microsoft Emulated IDE Controller'
_SCSI_CTRL_RES_SUB_TYPE = 'Microsoft Synthetic SCSI Controller'
_SERIAL_PORT_RES_SUB_TYPE = 'Microsoft Serial Port'
_SETTINGS_DEFINE_STATE_CLASS = 'Msvm_SettingsDefineState'
_VIRTUAL_SYSTEM_SETTING_DATA_CLASS = 'Msvm_VirtualSystemSettingData'
_RESOURCE_ALLOC_SETTING_DATA_CLASS = 'Msvm_ResourceAllocationSettingData'
_PROCESSOR_SETTING_DATA_CLASS = 'Msvm_ProcessorSettingData'
_MEMORY_SETTING_DATA_CLASS = 'Msvm_MemorySettingData'
_STORAGE_ALLOC_SETTING_DATA_CLASS = _RESOURCE_ALLOC_SETTING_DATA_CLASS
_SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS = \
'Msvm_SyntheticEthernetPortSettingData'
_AFFECTED_JOB_ELEMENT_CLASS = "Msvm_AffectedJobElement"
_COMPUTER_SYSTEM_CLASS = "Msvm_ComputerSystem"
_VM_ENABLED_STATE_PROP = "EnabledState"
_SHUTDOWN_COMPONENT = "Msvm_ShutdownComponent"
_VIRTUAL_SYSTEM_CURRENT_SETTINGS = 3
_AUTOMATIC_STARTUP_ACTION_NONE = 0
_vm_power_states_map = {constants.HYPERV_VM_STATE_ENABLED: 2,
constants.HYPERV_VM_STATE_DISABLED: 3,
constants.HYPERV_VM_STATE_SHUTTING_DOWN: 4,
constants.HYPERV_VM_STATE_REBOOT: 10,
constants.HYPERV_VM_STATE_PAUSED: 32768,
constants.HYPERV_VM_STATE_SUSPENDED: 32769}
def __init__(self, host='.'):
self._enabled_states_map = {v: k for k, v in
six.iteritems(self._vm_power_states_map)}
if sys.platform == 'win32':
self._init_hyperv_wmi_conn(host)
self._conn_cimv2 = wmi.WMI(moniker='//%s/root/cimv2' % host)
# On version of Hyper-V prior to 2012 trying to directly set properties
# in default setting data WMI objects results in an exception
self._clone_wmi_objs = False
if sys.platform == 'win32':
hostutls = hostutils.HostUtils()
self._clone_wmi_objs = not hostutls.check_min_windows_version(6, 2)
def _init_hyperv_wmi_conn(self, host):
self._conn = wmi.WMI(moniker='//%s/root/virtualization' % host)
def list_instance_notes(self):
instance_notes = []
for vs in self._conn.Msvm_VirtualSystemSettingData(
['ElementName', 'Notes'],
SettingType=self._VIRTUAL_SYSTEM_CURRENT_SETTINGS):
if vs.Notes is not None:
instance_notes.append(
(vs.ElementName, [v for v in vs.Notes.split('\n') if v]))
return instance_notes
def list_instances(self):
"""Return the names of all the instances known to Hyper-V."""
return [v.ElementName for v in
self._conn.Msvm_VirtualSystemSettingData(
['ElementName'],
SettingType=self._VIRTUAL_SYSTEM_CURRENT_SETTINGS)]
def get_vm_summary_info(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
vmsettings = vm.associators(
wmi_association_class=self._SETTINGS_DEFINE_STATE_CLASS,
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
settings_paths = [v.path_() for v in vmsettings]
# See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
(ret_val, summary_info) = vs_man_svc.GetSummaryInformation(
[constants.VM_SUMMARY_NUM_PROCS,
constants.VM_SUMMARY_ENABLED_STATE,
constants.VM_SUMMARY_MEMORY_USAGE,
constants.VM_SUMMARY_UPTIME],
settings_paths)
if ret_val:
raise HyperVException(_('Cannot get VM summary data for: %s')
% vm_name)
si = summary_info[0]
memory_usage = None
if si.MemoryUsage is not None:
memory_usage = long(si.MemoryUsage)
up_time = None
if si.UpTime is not None:
up_time = long(si.UpTime)
# Nova requires a valid state to be returned. Hyper-V has more
# states than Nova, typically intermediate ones and since there is
# no direct mapping for those, ENABLED is the only reasonable option
# considering that in all the non mappable states the instance
# is running.
enabled_state = self._enabled_states_map.get(si.EnabledState,
constants.HYPERV_VM_STATE_ENABLED)
summary_info_dict = {'NumberOfProcessors': si.NumberOfProcessors,
'EnabledState': enabled_state,
'MemoryUsage': memory_usage,
'UpTime': up_time}
return summary_info_dict
def _lookup_vm_check(self, vm_name):
vm = self._lookup_vm(vm_name)
if not vm:
raise exception.NotFound(_('VM not found: %s') % vm_name)
return vm
def _lookup_vm(self, vm_name):
vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
n = len(vms)
if n == 0:
return None
elif n > 1:
raise HyperVException(_('Duplicate VM name found: %s') % vm_name)
else:
return vms[0]
def vm_exists(self, vm_name):
return self._lookup_vm(vm_name) is not None
def get_vm_id(self, vm_name):
vm = self._lookup_vm_check(vm_name)
return vm.Name
def _get_vm_setting_data(self, vm):
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
# Avoid snapshots
return [s for s in vmsettings if s.SettingType == 3][0]
def _set_vm_memory(self, vm, vmsetting, memory_mb, dynamic_memory_ratio):
mem_settings = vmsetting.associators(
wmi_result_class=self._MEMORY_SETTING_DATA_CLASS)[0]
max_mem = long(memory_mb)
mem_settings.Limit = max_mem
if dynamic_memory_ratio > 1:
mem_settings.DynamicMemoryEnabled = True
# Must be a multiple of 2
reserved_mem = min(
long(max_mem / dynamic_memory_ratio) >> 1 << 1,
max_mem)
else:
mem_settings.DynamicMemoryEnabled = False
reserved_mem = max_mem
mem_settings.Reservation = reserved_mem
# Start with the minimum memory
mem_settings.VirtualQuantity = reserved_mem
self._modify_virt_resource(mem_settings, vm.path_())
def _set_vm_vcpus(self, vm, vmsetting, vcpus_num, limit_cpu_features):
procsetting = vmsetting.associators(
wmi_result_class=self._PROCESSOR_SETTING_DATA_CLASS)[0]
vcpus = long(vcpus_num)
procsetting.VirtualQuantity = vcpus
procsetting.Reservation = vcpus
procsetting.Limit = 100000 # static assignment to 100%
procsetting.LimitProcessorFeatures = limit_cpu_features
self._modify_virt_resource(procsetting, vm.path_())
def update_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features,
dynamic_memory_ratio):
vm = self._lookup_vm_check(vm_name)
vmsetting = self._get_vm_setting_data(vm)
self._set_vm_memory(vm, vmsetting, memory_mb, dynamic_memory_ratio)
self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features)
def check_admin_permissions(self):
if not self._conn.Msvm_VirtualSystemManagementService():
msg = _("The Windows account running nova-compute on this Hyper-V"
" host doesn't have the required permissions to create or"
" operate the virtual machine.")
raise HyperVAuthorizationException(msg)
def create_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features,
dynamic_memory_ratio, vm_gen, instance_path, notes=None):
"""Creates a VM."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
LOG.debug('Creating VM %s', vm_name)
vm = self._create_vm_obj(vs_man_svc, vm_name, vm_gen, notes,
dynamic_memory_ratio, instance_path)
vmsetting = self._get_vm_setting_data(vm)
LOG.debug('Setting memory for vm %s', vm_name)
self._set_vm_memory(vm, vmsetting, memory_mb, dynamic_memory_ratio)
LOG.debug('Set vCPUs for vm %s', vm_name)
self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features)
def _create_vm_obj(self, vs_man_svc, vm_name, vm_gen, notes,
dynamic_memory_ratio, instance_path):
vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new()
vs_gs_data.ElementName = vm_name
# Don't start automatically on host boot
vs_gs_data.AutomaticStartupAction = self._AUTOMATIC_STARTUP_ACTION_NONE
vs_gs_data.ExternalDataRoot = instance_path
vs_gs_data.SnapshotDataRoot = instance_path
(vm_path,
job_path,
ret_val) = vs_man_svc.DefineVirtualSystem([], None,
vs_gs_data.GetText_(1))
self.check_ret_val(ret_val, job_path)
vm = self._get_wmi_obj(vm_path)
if notes:
vmsetting = self._get_vm_setting_data(vm)
vmsetting.Notes = '\n'.join(notes)
self._modify_virtual_system(vs_man_svc, vm_path, vmsetting)
return self._get_wmi_obj(vm_path)
def _modify_virtual_system(self, vs_man_svc, vm_path, vmsetting):
(job_path, ret_val) = vs_man_svc.ModifyVirtualSystem(
ComputerSystem=vm_path,
SystemSettingData=vmsetting.GetText_(1))[1:]
self.check_ret_val(ret_val, job_path)
def get_vm_scsi_controller(self, vm_name):
vm = self._lookup_vm_check(vm_name)
return self._get_vm_scsi_controller(vm)
def _get_vm_scsi_controller(self, vm):
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
rasds = vmsettings[0].associators(
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
res = [r for r in rasds
if r.ResourceSubType == self._SCSI_CTRL_RES_SUB_TYPE][0]
return res.path_()
def _get_vm_ide_controller(self, vm, ctrller_addr):
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
rasds = vmsettings[0].associators(
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
ide_ctrls = [r for r in rasds
if r.ResourceSubType == self._IDE_CTRL_RES_SUB_TYPE
and r.Address == str(ctrller_addr)]
return ide_ctrls[0].path_() if ide_ctrls else None
def get_vm_ide_controller(self, vm_name, ctrller_addr):
vm = self._lookup_vm_check(vm_name)
return self._get_vm_ide_controller(vm, ctrller_addr)
def get_attached_disks(self, scsi_controller_path):
volumes = self._conn.query(
self._get_attached_disks_query_string(scsi_controller_path))
return volumes
def _get_attached_disks_query_string(self, scsi_controller_path):
return ("SELECT * FROM %(class_name)s WHERE ("
"ResourceSubType='%(res_sub_type)s' OR "
"ResourceSubType='%(res_sub_type_virt)s') AND "
"Parent='%(parent)s'" % {
'class_name': self._RESOURCE_ALLOC_SETTING_DATA_CLASS,
'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE,
'res_sub_type_virt': self._DISK_DRIVE_RES_SUB_TYPE,
'parent': scsi_controller_path.replace("'", "''")})
def _get_new_setting_data(self, class_name):
obj = self._conn.query("SELECT * FROM %s WHERE InstanceID "
"LIKE '%%\\Default'" % class_name)[0]
return self._check_clone_wmi_obj(class_name, obj)
def _get_new_resource_setting_data(self, resource_sub_type,
class_name=None):
if class_name is None:
class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS
obj = self._conn.query("SELECT * FROM %(class_name)s "
"WHERE ResourceSubType = "
"'%(res_sub_type)s' AND "
"InstanceID LIKE '%%\\Default'" %
{"class_name": class_name,
"res_sub_type": resource_sub_type})[0]
return self._check_clone_wmi_obj(class_name, obj)
def _check_clone_wmi_obj(self, class_name, obj):
if self._clone_wmi_objs:
return self._clone_wmi_obj(class_name, obj)
else:
return obj
def _clone_wmi_obj(self, class_name, obj):
wmi_class = getattr(self._conn, class_name)
new_obj = wmi_class.new()
# Copy the properties from the original.
for prop in obj._properties:
value = obj.Properties_.Item(prop).Value
new_obj.Properties_.Item(prop).Value = value
return new_obj
def attach_scsi_drive(self, vm_name, path, drive_type=constants.DISK):
vm = self._lookup_vm_check(vm_name)
ctrller_path = self._get_vm_scsi_controller(vm)
drive_addr = self.get_free_controller_slot(ctrller_path)
self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type)
def attach_ide_drive(self, vm_name, path, ctrller_addr, drive_addr,
drive_type=constants.DISK):
vm = self._lookup_vm_check(vm_name)
ctrller_path = self._get_vm_ide_controller(vm, ctrller_addr)
self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type)
def attach_drive(self, vm_name, path, ctrller_path, drive_addr,
drive_type=constants.DISK):
"""Create a drive and attach it to the vm."""
vm = self._lookup_vm_check(vm_name)
if drive_type == constants.DISK:
res_sub_type = self._DISK_DRIVE_RES_SUB_TYPE
elif drive_type == constants.DVD:
res_sub_type = self._DVD_DRIVE_RES_SUB_TYPE
drive = self._get_new_resource_setting_data(res_sub_type)
# Set the ctrller as parent.
drive.Parent = ctrller_path
drive.Address = drive_addr
# Add the cloned disk drive object to the vm.
new_resources = self._add_virt_resource(drive, vm.path_())
drive_path = new_resources[0]
if drive_type == constants.DISK:
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
elif drive_type == constants.DVD:
res_sub_type = self._DVD_DISK_RES_SUB_TYPE
res = self._get_new_resource_setting_data(res_sub_type)
# Set the new drive as the parent.
res.Parent = drive_path
res.Connection = [path]
# Add the new vhd object as a virtual hard disk to the vm.
self._add_virt_resource(res, vm.path_())
def create_scsi_controller(self, vm_name):
"""Create an iscsi controller ready to mount volumes."""
vm = self._lookup_vm_check(vm_name)
scsicontrl = self._get_new_resource_setting_data(
self._SCSI_CTRL_RES_SUB_TYPE)
scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
self._add_virt_resource(scsicontrl, vm.path_())
def attach_volume_to_controller(self, vm_name, controller_path, address,
mounted_disk_path):
"""Attach a volume to a controller."""
vm = self._lookup_vm_check(vm_name)
diskdrive = self._get_new_resource_setting_data(
self._PHYS_DISK_RES_SUB_TYPE)
diskdrive.Address = address
diskdrive.Parent = controller_path
diskdrive.HostResource = [mounted_disk_path]
self._add_virt_resource(diskdrive, vm.path_())
def _get_disk_resource_address(self, disk_resource):
return disk_resource.Address
def set_disk_host_resource(self, vm_name, controller_path, address,
mounted_disk_path):
disk_found = False
vm = self._lookup_vm_check(vm_name)
(disk_resources, volume_resources) = self._get_vm_disks(vm)
for disk_resource in disk_resources + volume_resources:
if (disk_resource.Parent == controller_path and
self._get_disk_resource_address(disk_resource) ==
str(address)):
if (disk_resource.HostResource and
disk_resource.HostResource[0] != mounted_disk_path):
LOG.debug('Updating disk host resource "%(old)s" to '
'"%(new)s"' %
{'old': disk_resource.HostResource[0],
'new': mounted_disk_path})
disk_resource.HostResource = [mounted_disk_path]
self._modify_virt_resource(disk_resource, vm.path_())
disk_found = True
break
if not disk_found:
LOG.warning(_LW('Disk not found on controller '
'"%(controller_path)s" with '
'address "%(address)s"'),
{'controller_path': controller_path,
'address': address})
def set_nic_connection(self, vm_name, nic_name, vswitch_conn_data):
nic_data = self._get_nic_data_by_name(nic_name)
nic_data.Connection = [vswitch_conn_data]
vm = self._lookup_vm_check(vm_name)
self._modify_virt_resource(nic_data, vm.path_())
def _get_nic_data_by_name(self, name):
return self._conn.Msvm_SyntheticEthernetPortSettingData(
ElementName=name)[0]
def create_nic(self, vm_name, nic_name, mac_address):
"""Create a (synthetic) nic and attach it to the vm."""
# Create a new nic
new_nic_data = self._get_new_setting_data(
self._SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS)
# Configure the nic
new_nic_data.ElementName = nic_name
new_nic_data.Address = mac_address.replace(':', '')
new_nic_data.StaticMacAddress = 'True'
new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
# Add the new nic to the vm
vm = self._lookup_vm_check(vm_name)
self._add_virt_resource(new_nic_data, vm.path_())
def soft_shutdown_vm(self, vm_name):
vm = self._lookup_vm_check(vm_name)
shutdown_component = vm.associators(
wmi_result_class=self._SHUTDOWN_COMPONENT)
if not shutdown_component:
# If no shutdown_component is found, it means the VM is already
# in a shutdown state.
return
reason = 'Soft shutdown requested by OpenStack Nova.'
(ret_val, ) = shutdown_component[0].InitiateShutdown(Force=False,
Reason=reason)
self.check_ret_val(ret_val, None)
def set_vm_state(self, vm_name, req_state):
"""Set the desired state of the VM."""
vm = self._lookup_vm_check(vm_name)
(job_path,
ret_val) = vm.RequestStateChange(self._vm_power_states_map[req_state])
# Invalid state for current operation (32775) typically means that
# the VM is already in the state requested
self.check_ret_val(ret_val, job_path, [0, 32775])
LOG.debug("Successfully changed vm state of %(vm_name)s "
"to %(req_state)s",
{'vm_name': vm_name, 'req_state': req_state})
def _get_disk_resource_disk_path(self, disk_resource):
return disk_resource.Connection
def get_vm_storage_paths(self, vm_name):
vm = self._lookup_vm_check(vm_name)
(disk_resources, volume_resources) = self._get_vm_disks(vm)
volume_drives = []
for volume_resource in volume_resources:
drive_path = volume_resource.HostResource[0]
volume_drives.append(drive_path)
disk_files = []
for disk_resource in disk_resources:
disk_files.extend(
[c for c in self._get_disk_resource_disk_path(disk_resource)])
return (disk_files, volume_drives)
def _get_vm_disks(self, vm):
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
rasds = vmsettings[0].associators(
wmi_result_class=self._STORAGE_ALLOC_SETTING_DATA_CLASS)
disk_resources = [r for r in rasds if
r.ResourceSubType in
[self._HARD_DISK_RES_SUB_TYPE,
self._DVD_DISK_RES_SUB_TYPE]]
if (self._RESOURCE_ALLOC_SETTING_DATA_CLASS !=
self._STORAGE_ALLOC_SETTING_DATA_CLASS):
rasds = vmsettings[0].associators(
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
volume_resources = [r for r in rasds if
r.ResourceSubType == self._PHYS_DISK_RES_SUB_TYPE]
return (disk_resources, volume_resources)
def destroy_vm(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
# Remove the VM. Does not destroy disks.
(job_path, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_())
self.check_ret_val(ret_val, job_path)
def check_ret_val(self, ret_val, job_path, success_values=[0]):
if ret_val == constants.WMI_JOB_STATUS_STARTED:
return self._wait_for_job(job_path)
elif ret_val not in success_values:
raise HyperVException(_('Operation failed with return value: %s')
% ret_val)
def _wait_for_job(self, job_path):
"""Poll WMI job state and wait for completion."""
job = self._get_wmi_obj(job_path)
while job.JobState == constants.WMI_JOB_STATE_RUNNING:
time.sleep(0.1)
job = self._get_wmi_obj(job_path)
if job.JobState != constants.WMI_JOB_STATE_COMPLETED:
job_state = job.JobState
if job.path().Class == "Msvm_ConcreteJob":
err_sum_desc = job.ErrorSummaryDescription
err_desc = job.ErrorDescription
err_code = job.ErrorCode
raise HyperVException(_("WMI job failed with status "
"%(job_state)d. Error details: "
"%(err_sum_desc)s - %(err_desc)s - "
"Error code: %(err_code)d") %
{'job_state': job_state,
'err_sum_desc': err_sum_desc,
'err_desc': err_desc,
'err_code': err_code})
else:
(error, ret_val) = job.GetError()
if not ret_val and error:
raise HyperVException(_("WMI job failed with status "
"%(job_state)d. Error details: "
"%(error)s") %
{'job_state': job_state,
'error': error})
else:
raise HyperVException(_("WMI job failed with status "
"%d. No error "
"description available") %
job_state)
desc = job.Description
elap = job.ElapsedTime
LOG.debug("WMI job succeeded: %(desc)s, Elapsed=%(elap)s",
{'desc': desc, 'elap': elap})
return job
def _get_wmi_obj(self, path):
return wmi.WMI(moniker=path.replace('\\', '/'))
def _add_virt_resource(self, res_setting_data, vm_path):
"""Adds a new resource to the VM."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
res_xml = [res_setting_data.GetText_(1)]
(job_path,
new_resources,
ret_val) = vs_man_svc.AddVirtualSystemResources(res_xml, vm_path)
self.check_ret_val(ret_val, job_path)
return new_resources
def _modify_virt_resource(self, res_setting_data, vm_path):
"""Updates a VM resource."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
(job_path, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
ResourceSettingData=[res_setting_data.GetText_(1)],
ComputerSystem=vm_path)
self.check_ret_val(ret_val, job_path)
def _remove_virt_resource(self, res_setting_data, vm_path):
"""Removes a VM resource."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
res_path = [res_setting_data.path_()]
(job_path, ret_val) = vs_man_svc.RemoveVirtualSystemResources(res_path,
vm_path)
self.check_ret_val(ret_val, job_path)
def take_vm_snapshot(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
(job_path, ret_val,
snp_setting_data) = vs_man_svc.CreateVirtualSystemSnapshot(vm.path_())
self.check_ret_val(ret_val, job_path)
job_wmi_path = job_path.replace('\\', '/')
job = wmi.WMI(moniker=job_wmi_path)
snp_setting_data = job.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
return snp_setting_data.path_()
def remove_vm_snapshot(self, snapshot_path):
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
(job_path, ret_val) = vs_man_svc.RemoveVirtualSystemSnapshot(
snapshot_path)
self.check_ret_val(ret_val, job_path)
def detach_vm_disk(self, vm_name, disk_path, is_physical=True):
vm = self._lookup_vm_check(vm_name)
disk_resource = self._get_mounted_disk_resource_from_path(disk_path,
is_physical)
if disk_resource:
parent = self._conn.query("SELECT * FROM "
"Msvm_ResourceAllocationSettingData "
"WHERE __PATH = '%s'" %
disk_resource.Parent)[0]
self._remove_virt_resource(disk_resource, vm.path_())
if not is_physical:
self._remove_virt_resource(parent, vm.path_())
def _get_mounted_disk_resource_from_path(self, disk_path, is_physical):
if is_physical:
class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS
res_sub_type = self._PHYS_DISK_RES_SUB_TYPE
else:
class_name = self._STORAGE_ALLOC_SETTING_DATA_CLASS
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
disk_resources = self._conn.query("SELECT * FROM %(class_name)s "
"WHERE ResourceSubType = "
"'%(res_sub_type)s'" %
{"class_name": class_name,
"res_sub_type": res_sub_type})
for disk_resource in disk_resources:
if disk_resource.HostResource:
if disk_resource.HostResource[0].lower() == disk_path.lower():
return disk_resource
def get_mounted_disk_by_drive_number(self, device_number):
mounted_disks = self._conn.query("SELECT * FROM Msvm_DiskDrive "
"WHERE DriveNumber=" +
str(device_number))
if len(mounted_disks):
return mounted_disks[0].path_()
def get_controller_volume_paths(self, controller_path):
disks = self._conn.query("SELECT * FROM %(class_name)s "
"WHERE ResourceSubType = '%(res_sub_type)s' "
"AND Parent='%(parent)s'" %
{"class_name":
self._RESOURCE_ALLOC_SETTING_DATA_CLASS,
"res_sub_type":
self._PHYS_DISK_RES_SUB_TYPE,
"parent":
controller_path})
disk_data = {}
for disk in disks:
if disk.HostResource:
disk_data[disk.path().RelPath] = disk.HostResource[0]
return disk_data
def get_free_controller_slot(self, scsi_controller_path):
attached_disks = self.get_attached_disks(scsi_controller_path)
used_slots = [int(disk.AddressOnParent) for disk in attached_disks]
for slot in range(constants.SCSI_CONTROLLER_SLOTS_NUMBER):
if slot not in used_slots:
return slot
raise HyperVException(_("Exceeded the maximum number of slots"))
def enable_vm_metrics_collection(self, vm_name):
raise NotImplementedError(_("Metrics collection is not supported on "
"this version of Hyper-V"))
def get_vm_serial_port_connection(self, vm_name, update_connection=None):
vm = self._lookup_vm_check(vm_name)
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
rasds = vmsettings[0].associators(
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
serial_port = (
[r for r in rasds if
r.ResourceSubType == self._SERIAL_PORT_RES_SUB_TYPE][0])
if update_connection:
serial_port.Connection = [update_connection]
self._modify_virt_resource(serial_port, vm.path_())
if len(serial_port.Connection) > 0:
return serial_port.Connection[0]
def get_active_instances(self):
"""Return the names of all the active instances known to Hyper-V."""
vm_names = self.list_instances()
vms = [self._lookup_vm(vm_name) for vm_name in vm_names]
active_vm_names = [v.ElementName for v in vms
if v.EnabledState == constants.HYPERV_VM_STATE_ENABLED]
return active_vm_names
def get_vm_power_state_change_listener(self, timeframe, filtered_states):
field = self._VM_ENABLED_STATE_PROP
query = self._get_event_wql_query(cls=self._COMPUTER_SYSTEM_CLASS,
field=field,
timeframe=timeframe,
filtered_states=filtered_states)
return self._conn.Msvm_ComputerSystem.watch_for(raw_wql=query,
fields=[field])
def _get_event_wql_query(self, cls, field,
timeframe, filtered_states=None):
"""Return a WQL query used for polling WMI events.
:param cls: the WMI class polled for events
:param field: the field checked
:param timeframe: check for events that occurred in
the specified timeframe
:param filtered_states: only catch events triggered when a WMI
object transitioned into one of those
states.
"""
query = ("SELECT %(field)s, TargetInstance "
"FROM __InstanceModificationEvent "
"WITHIN %(timeframe)s "
"WHERE TargetInstance ISA '%(class)s' "
"AND TargetInstance.%(field)s != "
"PreviousInstance.%(field)s" %
{'class': cls,
'field': field,
'timeframe': timeframe})
if filtered_states:
checks = ["TargetInstance.%s = '%s'" % (field, state)
for state in filtered_states]
query += " AND (%s)" % " OR ".join(checks)
return query
def _get_instance_notes(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vmsettings = self._get_vm_setting_data(vm)
return [note for note in vmsettings.Notes.split('\n') if note]
def get_instance_uuid(self, vm_name):
instance_notes = self._get_instance_notes(vm_name)
if instance_notes and uuidutils.is_uuid_like(instance_notes[0]):
return instance_notes[0]
def get_vm_power_state(self, vm_enabled_state):
return self._enabled_states_map.get(vm_enabled_state,
constants.HYPERV_VM_STATE_OTHER)

View File

@ -0,0 +1,328 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility class for VM related operations.
Based on the "root/virtualization/v2" namespace available starting with
Hyper-V Server / Windows Server 2012.
"""
import sys
import uuid
if sys.platform == 'win32':
import wmi
from oslo_config import cfg
from oslo_log import log as logging
from nova.virt.hyperv import constants
from nova.virt.hyperv import vmutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class VMUtilsV2(vmutils.VMUtils):
_PHYS_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Physical Disk Drive'
_DISK_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic Disk Drive'
_DVD_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic DVD Drive'
_SCSI_RES_SUBTYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller'
_HARD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual Hard Disk'
_DVD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual CD/DVD Disk'
_IDE_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Emulated IDE Controller'
_SCSI_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller'
_SERIAL_PORT_RES_SUB_TYPE = 'Microsoft:Hyper-V:Serial Port'
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
_VIRTUAL_SYSTEM_SUBTYPE_GEN2 = 'Microsoft:Hyper-V:SubType:2'
_SNAPSHOT_FULL = 2
_METRIC_AGGR_CPU_AVG = 'Aggregated Average CPU Utilization'
_METRIC_AGGR_MEMORY_AVG = 'Aggregated Average Memory Utilization'
_METRIC_ENABLED = 2
_STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData'
_ETHERNET_PORT_ALLOCATION_SETTING_DATA_CLASS = \
'Msvm_EthernetPortAllocationSettingData'
_AUTOMATIC_STARTUP_ACTION_NONE = 2
_vm_power_states_map = {constants.HYPERV_VM_STATE_ENABLED: 2,
constants.HYPERV_VM_STATE_DISABLED: 3,
constants.HYPERV_VM_STATE_SHUTTING_DOWN: 4,
constants.HYPERV_VM_STATE_REBOOT: 11,
constants.HYPERV_VM_STATE_PAUSED: 9,
constants.HYPERV_VM_STATE_SUSPENDED: 6}
def __init__(self, host='.'):
super(VMUtilsV2, self).__init__(host)
def _init_hyperv_wmi_conn(self, host):
self._conn = wmi.WMI(moniker='//%s/root/virtualization/v2' % host)
def list_instance_notes(self):
instance_notes = []
for vs in self._conn.Msvm_VirtualSystemSettingData(
['ElementName', 'Notes'],
VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED):
if vs.Notes is not None:
instance_notes.append(
(vs.ElementName, [v for v in vs.Notes if v]))
return instance_notes
def list_instances(self):
"""Return the names of all the instances known to Hyper-V."""
return [v.ElementName for v in
self._conn.Msvm_VirtualSystemSettingData(
['ElementName'],
VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED)]
def _create_vm_obj(self, vs_man_svc, vm_name, vm_gen, notes,
dynamic_memory_ratio, instance_path):
vs_data = self._conn.Msvm_VirtualSystemSettingData.new()
vs_data.ElementName = vm_name
vs_data.Notes = notes
# Don't start automatically on host boot
vs_data.AutomaticStartupAction = self._AUTOMATIC_STARTUP_ACTION_NONE
# vNUMA and dynamic memory are mutually exclusive
if dynamic_memory_ratio > 1:
vs_data.VirtualNumaEnabled = False
if vm_gen == constants.VM_GEN_2:
vs_data.VirtualSystemSubType = self._VIRTUAL_SYSTEM_SUBTYPE_GEN2
vs_data.SecureBootEnabled = False
# Created VMs must have their *DataRoot paths in the same location as
# the instances' path.
vs_data.ConfigurationDataRoot = instance_path
vs_data.LogDataRoot = instance_path
vs_data.SnapshotDataRoot = instance_path
vs_data.SuspendDataRoot = instance_path
vs_data.SwapFileDataRoot = instance_path
(job_path,
vm_path,
ret_val) = vs_man_svc.DefineSystem(ResourceSettings=[],
ReferenceConfiguration=None,
SystemSettings=vs_data.GetText_(1))
job = self.check_ret_val(ret_val, job_path)
if not vm_path and job:
vm_path = job.associators(self._AFFECTED_JOB_ELEMENT_CLASS)[0]
return self._get_wmi_obj(vm_path)
def _get_vm_setting_data(self, vm):
vmsettings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
# Avoid snapshots
return [s for s in vmsettings if
s.VirtualSystemType == self._VIRTUAL_SYSTEM_TYPE_REALIZED][0]
def _get_attached_disks_query_string(self, scsi_controller_path):
# DVD Drives can be attached to SCSI as well, if the VM Generation is 2
return ("SELECT * FROM Msvm_ResourceAllocationSettingData WHERE ("
"ResourceSubType='%(res_sub_type)s' OR "
"ResourceSubType='%(res_sub_type_virt)s' OR "
"ResourceSubType='%(res_sub_type_dvd)s') AND "
"Parent = '%(parent)s'" % {
'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE,
'res_sub_type_virt': self._DISK_DRIVE_RES_SUB_TYPE,
'res_sub_type_dvd': self._DVD_DRIVE_RES_SUB_TYPE,
'parent': scsi_controller_path.replace("'", "''")})
def attach_drive(self, vm_name, path, ctrller_path, drive_addr,
drive_type=constants.DISK):
"""Create a drive and attach it to the vm."""
vm = self._lookup_vm_check(vm_name)
if drive_type == constants.DISK:
res_sub_type = self._DISK_DRIVE_RES_SUB_TYPE
elif drive_type == constants.DVD:
res_sub_type = self._DVD_DRIVE_RES_SUB_TYPE
drive = self._get_new_resource_setting_data(res_sub_type)
# Set the ctrller as parent.
drive.Parent = ctrller_path
drive.Address = drive_addr
drive.AddressOnParent = drive_addr
# Add the cloned disk drive object to the vm.
new_resources = self._add_virt_resource(drive, vm.path_())
drive_path = new_resources[0]
if drive_type == constants.DISK:
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
elif drive_type == constants.DVD:
res_sub_type = self._DVD_DISK_RES_SUB_TYPE
res = self._get_new_resource_setting_data(
res_sub_type, self._STORAGE_ALLOC_SETTING_DATA_CLASS)
res.Parent = drive_path
res.HostResource = [path]
self._add_virt_resource(res, vm.path_())
def attach_volume_to_controller(self, vm_name, controller_path, address,
mounted_disk_path):
"""Attach a volume to a controller."""
vm = self._lookup_vm_check(vm_name)
diskdrive = self._get_new_resource_setting_data(
self._PHYS_DISK_RES_SUB_TYPE)
diskdrive.AddressOnParent = address
diskdrive.Parent = controller_path
diskdrive.HostResource = [mounted_disk_path]
self._add_virt_resource(diskdrive, vm.path_())
def _get_disk_resource_address(self, disk_resource):
return disk_resource.AddressOnParent
def create_scsi_controller(self, vm_name):
"""Create an iscsi controller ready to mount volumes."""
scsicontrl = self._get_new_resource_setting_data(
self._SCSI_RES_SUBTYPE)
scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
vm = self._lookup_vm_check(vm_name)
self._add_virt_resource(scsicontrl, vm.path_())
def _get_disk_resource_disk_path(self, disk_resource):
return disk_resource.HostResource
def destroy_vm(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
# Remove the VM. It does not destroy any associated virtual disk.
(job_path, ret_val) = vs_man_svc.DestroySystem(vm.path_())
self.check_ret_val(ret_val, job_path)
def _add_virt_resource(self, res_setting_data, vm_path):
"""Adds a new resource to the VM."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
res_xml = [res_setting_data.GetText_(1)]
(job_path,
new_resources,
ret_val) = vs_man_svc.AddResourceSettings(vm_path, res_xml)
self.check_ret_val(ret_val, job_path)
return new_resources
def _modify_virt_resource(self, res_setting_data, vm_path):
"""Updates a VM resource."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
(job_path,
out_res_setting_data,
ret_val) = vs_man_svc.ModifyResourceSettings(
ResourceSettings=[res_setting_data.GetText_(1)])
self.check_ret_val(ret_val, job_path)
def _remove_virt_resource(self, res_setting_data, vm_path):
"""Removes a VM resource."""
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
res_path = [res_setting_data.path_()]
(job_path, ret_val) = vs_man_svc.RemoveResourceSettings(res_path)
self.check_ret_val(ret_val, job_path)
def get_vm_state(self, vm_name):
settings = self.get_vm_summary_info(vm_name)
return settings['EnabledState']
def take_vm_snapshot(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vs_snap_svc = self._conn.Msvm_VirtualSystemSnapshotService()[0]
(job_path, snp_setting_data, ret_val) = vs_snap_svc.CreateSnapshot(
AffectedSystem=vm.path_(),
SnapshotType=self._SNAPSHOT_FULL)
self.check_ret_val(ret_val, job_path)
job_wmi_path = job_path.replace('\\', '/')
job = wmi.WMI(moniker=job_wmi_path)
snp_setting_data = job.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
return snp_setting_data.path_()
def remove_vm_snapshot(self, snapshot_path):
vs_snap_svc = self._conn.Msvm_VirtualSystemSnapshotService()[0]
(job_path, ret_val) = vs_snap_svc.DestroySnapshot(snapshot_path)
self.check_ret_val(ret_val, job_path)
def set_nic_connection(self, vm_name, nic_name, vswitch_conn_data):
nic_data = self._get_nic_data_by_name(nic_name)
eth_port_data = self._get_new_setting_data(
self._ETHERNET_PORT_ALLOCATION_SETTING_DATA_CLASS)
eth_port_data.HostResource = [vswitch_conn_data]
eth_port_data.Parent = nic_data.path_()
vm = self._lookup_vm_check(vm_name)
self._add_virt_resource(eth_port_data, vm.path_())
def enable_vm_metrics_collection(self, vm_name):
metric_names = [self._METRIC_AGGR_CPU_AVG,
self._METRIC_AGGR_MEMORY_AVG]
vm = self._lookup_vm_check(vm_name)
metric_svc = self._conn.Msvm_MetricService()[0]
(disks, volumes) = self._get_vm_disks(vm)
filtered_disks = [d for d in disks if
d.ResourceSubType is not self._DVD_DISK_RES_SUB_TYPE]
# enable metrics for disk.
for disk in filtered_disks:
self._enable_metrics(metric_svc, disk)
for metric_name in metric_names:
metric_def = self._conn.CIM_BaseMetricDefinition(Name=metric_name)
if not metric_def:
LOG.debug("Metric not found: %s", metric_name)
else:
self._enable_metrics(metric_svc, vm, metric_def[0].path_())
def _enable_metrics(self, metric_svc, element, definition_path=None):
metric_svc.ControlMetrics(
Subject=element.path_(),
Definition=definition_path,
MetricCollectionEnabled=self._METRIC_ENABLED)
def get_vm_dvd_disk_paths(self, vm_name):
vm = self._lookup_vm_check(vm_name)
settings = vm.associators(
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
sasds = settings.associators(
wmi_result_class=self._STORAGE_ALLOC_SETTING_DATA_CLASS)
dvd_paths = [sasd.HostResource[0] for sasd in sasds
if sasd.ResourceSubType == self._DVD_DISK_RES_SUB_TYPE]
return dvd_paths
def _get_instance_notes(self, vm_name):
vm = self._lookup_vm_check(vm_name)
vmsettings = self._get_vm_setting_data(vm)
return [note for note in vmsettings.Notes if note]

View File

@ -0,0 +1,122 @@
# Copyright 2012 Pedro Navarro Perez
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Helper methods for operations related to the management of volumes,
and storage repositories
Official Microsoft iSCSI Initiator and iSCSI command line interface
documentation can be retrieved at:
http://www.microsoft.com/en-us/download/details.aspx?id=34750
"""
import re
import time
from oslo_config import cfg
from oslo_log import log as logging
from six.moves import range
from nova.i18n import _
from nova import utils
from nova.virt.hyperv import basevolumeutils
from nova.virt.hyperv import vmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VolumeUtils(basevolumeutils.BaseVolumeUtils):
def __init__(self):
super(VolumeUtils, self).__init__()
def execute(self, *args, **kwargs):
stdout_value, stderr_value = utils.execute(*args, **kwargs)
if stdout_value.find('The operation completed successfully') == -1:
raise vmutils.HyperVException(_('An error has occurred when '
'calling the iscsi initiator: %s')
% stdout_value)
return stdout_value
def _login_target_portal(self, target_portal):
(target_address,
target_port) = utils.parse_server_string(target_portal)
output = self.execute('iscsicli.exe', 'ListTargetPortals')
pattern = r'Address and Socket *: (.*)'
portals = [addr.split() for addr in re.findall(pattern, output)]
LOG.debug("Ensuring connection to portal: %s" % target_portal)
if [target_address, str(target_port)] in portals:
self.execute('iscsicli.exe', 'RefreshTargetPortal',
target_address, target_port)
else:
# Adding target portal to iscsi initiator. Sending targets
self.execute('iscsicli.exe', 'AddTargetPortal',
target_address, target_port,
'*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*',
'*', '*')
def login_storage_target(self, target_lun, target_iqn, target_portal,
auth_username=None, auth_password=None):
"""Ensure that the target is logged in."""
self._login_target_portal(target_portal)
# Listing targets
self.execute('iscsicli.exe', 'ListTargets')
retry_count = CONF.hyperv.volume_attach_retry_count
# If the target is not connected, at least two iterations are needed:
# one for performing the login and another one for checking if the
# target was logged in successfully.
if retry_count < 2:
retry_count = 2
for attempt in range(retry_count):
try:
session_info = self.execute('iscsicli.exe', 'SessionList')
if session_info.find(target_iqn) == -1:
# Sending login
self.execute('iscsicli.exe', 'qlogintarget', target_iqn,
auth_username, auth_password)
else:
return
except vmutils.HyperVException as exc:
LOG.debug("Attempt %(attempt)d to connect to target "
"%(target_iqn)s failed. Retrying. "
"Exceptipn: %(exc)s ",
{'target_iqn': target_iqn,
'exc': exc,
'attempt': attempt})
time.sleep(CONF.hyperv.volume_attach_retry_interval)
raise vmutils.HyperVException(_('Failed to login target %s') %
target_iqn)
def logout_storage_target(self, target_iqn):
"""Logs out storage target through its session id."""
sessions = self._conn_wmi.query("SELECT * FROM "
"MSiSCSIInitiator_SessionClass "
"WHERE TargetName='%s'" % target_iqn)
for session in sessions:
self.execute_log_out(session.SessionId)
def execute_log_out(self, session_id):
"""Executes log out of the session described by its session ID."""
self.execute('iscsicli.exe', 'logouttarget', session_id)

View File

@ -0,0 +1,132 @@
# Copyright 2012 Pedro Navarro Perez
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Helper methods for operations related to the management of volumes
and storage repositories on Windows Server 2012 and above
"""
import sys
import time
if sys.platform == 'win32':
import wmi
from oslo_config import cfg
from oslo_log import log as logging
from six.moves import range
from nova.i18n import _
from nova import utils
from nova.virt.hyperv import basevolumeutils
from nova.virt.hyperv import vmutils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils):
_CHAP_AUTH_TYPE = 'ONEWAYCHAP'
def __init__(self, host='.'):
super(VolumeUtilsV2, self).__init__(host)
storage_namespace = '//%s/root/microsoft/windows/storage' % host
if sys.platform == 'win32':
self._conn_storage = wmi.WMI(moniker=storage_namespace)
def _login_target_portal(self, target_portal):
(target_address,
target_port) = utils.parse_server_string(target_portal)
# Checking if the portal is already connected.
portal = self._conn_storage.query("SELECT * FROM "
"MSFT_iSCSITargetPortal "
"WHERE TargetPortalAddress='%s' "
"AND TargetPortalPortNumber='%s'"
% (target_address, target_port))
if portal:
portal[0].Update()
else:
# Adding target portal to iscsi initiator. Sending targets
portal = self._conn_storage.MSFT_iSCSITargetPortal
portal.New(TargetPortalAddress=target_address,
TargetPortalPortNumber=target_port)
def login_storage_target(self, target_lun, target_iqn, target_portal,
auth_username=None, auth_password=None):
"""Ensure that the target is logged in."""
self._login_target_portal(target_portal)
retry_count = CONF.hyperv.volume_attach_retry_count
# If the target is not connected, at least two iterations are needed:
# one for performing the login and another one for checking if the
# target was logged in successfully.
if retry_count < 2:
retry_count = 2
for attempt in range(retry_count):
target = self._conn_storage.query("SELECT * FROM MSFT_iSCSITarget "
"WHERE NodeAddress='%s' " %
target_iqn)
if target and target[0].IsConnected:
if attempt == 0:
# The target was already connected but an update may be
# required
target[0].Update()
return
try:
target = self._conn_storage.MSFT_iSCSITarget
auth = {}
if auth_username and auth_password:
auth['AuthenticationType'] = self._CHAP_AUTH_TYPE
auth['ChapUsername'] = auth_username
auth['ChapSecret'] = auth_password
target.Connect(NodeAddress=target_iqn,
IsPersistent=True, **auth)
time.sleep(CONF.hyperv.volume_attach_retry_interval)
except wmi.x_wmi as exc:
LOG.debug("Attempt %(attempt)d to connect to target "
"%(target_iqn)s failed. Retrying. "
"WMI exception: %(exc)s " %
{'target_iqn': target_iqn,
'exc': exc,
'attempt': attempt})
raise vmutils.HyperVException(_('Failed to login target %s') %
target_iqn)
def logout_storage_target(self, target_iqn):
"""Logs out storage target through its session id."""
targets = self._conn_storage.MSFT_iSCSITarget(NodeAddress=target_iqn)
if targets:
target = targets[0]
if target.IsConnected:
sessions = self._conn_storage.MSFT_iSCSISession(
TargetNodeAddress=target_iqn)
for session in sessions:
if session.IsPersistent:
session.Unregister()
target.Disconnect()
def execute_log_out(self, session_id):
sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
SessionId=session_id)
if sessions:
self.logout_storage_target(sessions[0].TargetName)

View File

@ -0,0 +1,110 @@
# Copyright 2013 Cloudbase Solutions Srl
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_log import log as logging
from nova.i18n import _
from nova.virt.hyperv import hostutils
from nova.virt.hyperv import hostutilsv2
from nova.virt.hyperv import livemigrationutils
from nova.virt.hyperv import networkutils
from nova.virt.hyperv import networkutilsv2
from nova.virt.hyperv import pathutils
from nova.virt.hyperv import rdpconsoleutils
from nova.virt.hyperv import rdpconsoleutilsv2
from nova.virt.hyperv import vhdutils
from nova.virt.hyperv import vhdutilsv2
from nova.virt.hyperv import vmutils
from nova.virt.hyperv import vmutilsv2
from nova.virt.hyperv import volumeutils
from nova.virt.hyperv import volumeutilsv2
hyper_opts = [
cfg.BoolOpt('force_hyperv_utils_v1',
default=False,
help='Force V1 WMI utility classes'),
cfg.BoolOpt('force_volumeutils_v1',
default=False,
help='Force V1 volume utility class'),
]
CONF = cfg.CONF
CONF.register_opts(hyper_opts, 'hyperv')
LOG = logging.getLogger(__name__)
utils = hostutils.HostUtils()
def _get_class(v1_class, v2_class, force_v1_flag):
# V2 classes are supported starting from Hyper-V Server 2012 and
# Windows Server 2012 (kernel version 6.2)
if not force_v1_flag and utils.check_min_windows_version(6, 2):
cls = v2_class
else:
cls = v1_class
LOG.debug("Loading class: %(module_name)s.%(class_name)s",
{'module_name': cls.__module__, 'class_name': cls.__name__})
return cls
def _get_virt_utils_class(v1_class, v2_class):
# The "root/virtualization" WMI namespace is no longer supported on
# Windows Server / Hyper-V Server 2012 R2 / Windows 8.1
# (kernel version 6.3) or above.
if (CONF.hyperv.force_hyperv_utils_v1 and
utils.check_min_windows_version(6, 3)):
raise vmutils.HyperVException(
_('The "force_hyperv_utils_v1" option cannot be set to "True" '
'on Windows Server / Hyper-V Server 2012 R2 or above as the WMI '
'"root/virtualization" namespace is no longer supported.'))
return _get_class(v1_class, v2_class, CONF.hyperv.force_hyperv_utils_v1)
def get_vmutils(host='.'):
return _get_virt_utils_class(vmutils.VMUtils, vmutilsv2.VMUtilsV2)(host)
def get_vhdutils():
return _get_virt_utils_class(vhdutils.VHDUtils, vhdutilsv2.VHDUtilsV2)()
def get_networkutils():
return _get_virt_utils_class(networkutils.NetworkUtils,
networkutilsv2.NetworkUtilsV2)()
def get_hostutils():
return _get_virt_utils_class(hostutils.HostUtils,
hostutilsv2.HostUtilsV2)()
def get_pathutils():
return pathutils.PathUtils()
def get_volumeutils():
return _get_class(volumeutils.VolumeUtils, volumeutilsv2.VolumeUtilsV2,
CONF.hyperv.force_volumeutils_v1)()
def get_livemigrationutils():
return livemigrationutils.LiveMigrationUtils()
def get_rdpconsoleutils():
return _get_virt_utils_class(rdpconsoleutils.RDPConsoleUtils,
rdpconsoleutilsv2.RDPConsoleUtilsV2)()