Merge "Hyper-V: removes *Utils modules and unit tests"
This commit is contained in:
@@ -1,188 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,141 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,30 +0,0 @@
|
|||||||
# 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()
|
|
@@ -1,61 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,301 +0,0 @@
|
|||||||
# 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 exception
|
|
||||||
from nova import test
|
|
||||||
from nova.virt.hyperv import livemigrationutils
|
|
||||||
from nova.virt.hyperv import vmutils
|
|
||||||
|
|
||||||
|
|
||||||
class LiveMigrationUtilsTestCase(test.NoDBTestCase):
|
|
||||||
"""Unit tests for the Hyper-V LiveMigrationUtils class."""
|
|
||||||
|
|
||||||
_FAKE_VM_NAME = 'fake_vm_name'
|
|
||||||
_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)
|
|
||||||
|
|
||||||
def test_get_vm(self):
|
|
||||||
expected_vm = mock.MagicMock()
|
|
||||||
mock_conn_v2 = mock.MagicMock()
|
|
||||||
mock_conn_v2.Msvm_ComputerSystem.return_value = [expected_vm]
|
|
||||||
|
|
||||||
found_vm = self.liveutils._get_vm(mock_conn_v2, self._FAKE_VM_NAME)
|
|
||||||
|
|
||||||
self.assertEqual(expected_vm, found_vm)
|
|
||||||
|
|
||||||
def test_get_vm_duplicate(self):
|
|
||||||
mock_vm = mock.MagicMock()
|
|
||||||
mock_conn_v2 = mock.MagicMock()
|
|
||||||
mock_conn_v2.Msvm_ComputerSystem.return_value = [mock_vm, mock_vm]
|
|
||||||
|
|
||||||
self.assertRaises(vmutils.HyperVException, self.liveutils._get_vm,
|
|
||||||
mock_conn_v2, self._FAKE_VM_NAME)
|
|
||||||
|
|
||||||
def test_get_vm_not_found(self):
|
|
||||||
mock_conn_v2 = mock.MagicMock()
|
|
||||||
mock_conn_v2.Msvm_ComputerSystem.return_value = []
|
|
||||||
|
|
||||||
self.assertRaises(exception.InstanceNotFound, self.liveutils._get_vm,
|
|
||||||
mock_conn_v2, self._FAKE_VM_NAME)
|
|
||||||
|
|
||||||
@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
|
|
@@ -1,82 +0,0 @@
|
|||||||
# 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())
|
|
@@ -1,45 +0,0 @@
|
|||||||
# 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())
|
|
@@ -1,28 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,37 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,56 +0,0 @@
|
|||||||
# 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 utilsfactory
|
|
||||||
from nova.virt.hyperv import volumeutils
|
|
||||||
from nova.virt.hyperv import volumeutilsv2
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class TestHyperVUtilsFactory(test.NoDBTestCase):
|
|
||||||
def test_get_volumeutils_v2(self):
|
|
||||||
self._test_returned_class(expected_class=volumeutilsv2.VolumeUtilsV2,
|
|
||||||
os_supports_v2=True)
|
|
||||||
|
|
||||||
def test_get_volumeutils_v1(self):
|
|
||||||
self._test_returned_class(expected_class=volumeutils.VolumeUtils)
|
|
||||||
|
|
||||||
def test_get_volumeutils_force_v1_and_not_min_version(self):
|
|
||||||
self._test_returned_class(expected_class=volumeutils.VolumeUtils,
|
|
||||||
force_v1=True)
|
|
||||||
|
|
||||||
@mock.patch.object(utilsfactory, 'CONF')
|
|
||||||
def _test_returned_class(self, mock_CONF, expected_class, force_v1=False,
|
|
||||||
os_supports_v2=False):
|
|
||||||
# NOTE(claudiub): temporary change, in order for unit tests to pass.
|
|
||||||
# force_hyperv_utils_v1 CONF flag does not exist anymore.
|
|
||||||
# utilsfactory and its test cases will be removed next commit.
|
|
||||||
mock_CONF.hyperv.force_volumeutils_v1 = force_v1
|
|
||||||
with mock.patch.object(
|
|
||||||
utilsfactory.utils,
|
|
||||||
'check_min_windows_version') as mock_check_min_windows_version:
|
|
||||||
mock_check_min_windows_version.return_value = os_supports_v2
|
|
||||||
|
|
||||||
actual_class = type(utilsfactory.get_volumeutils())
|
|
||||||
self.assertEqual(actual_class, expected_class)
|
|
@@ -1,288 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,244 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,918 +0,0 @@
|
|||||||
# 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.InstanceNotFound,
|
|
||||||
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):
|
|
||||||
with mock.patch.object(self._vmutils,
|
|
||||||
'_get_disk_resource_address') as mock_get_addr:
|
|
||||||
mock_get_addr.return_value = 3
|
|
||||||
mock_get_attached_disks.return_value = [mock.sentinel.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):
|
|
||||||
mock_get_address = mock.Mock()
|
|
||||||
mock_get_address.side_effect = range(
|
|
||||||
constants.SCSI_CONTROLLER_SLOTS_NUMBER)
|
|
||||||
|
|
||||||
mock_get_attached_disks = mock.Mock()
|
|
||||||
mock_get_attached_disks.return_value = (
|
|
||||||
[mock.sentinel.drive] * constants.SCSI_CONTROLLER_SLOTS_NUMBER)
|
|
||||||
|
|
||||||
with mock.patch.multiple(self._vmutils,
|
|
||||||
get_attached_disks=mock_get_attached_disks,
|
|
||||||
_get_disk_resource_address=mock_get_address):
|
|
||||||
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)
|
|
||||||
|
|
||||||
@mock.patch.object(vmutils.VMUtils, '_get_nic_data_by_name')
|
|
||||||
def test_destroy_nic(self, mock_get_nic_data_by_name):
|
|
||||||
self._lookup_vm()
|
|
||||||
fake_nic_data = mock_get_nic_data_by_name.return_value
|
|
||||||
with mock.patch.object(self._vmutils,
|
|
||||||
'_remove_virt_resource') as mock_rem_virt_res:
|
|
||||||
self._vmutils.destroy_nic(self._FAKE_VM_NAME,
|
|
||||||
mock.sentinel.FAKE_NIC_NAME)
|
|
||||||
mock_rem_virt_res.assert_called_once_with(fake_nic_data,
|
|
||||||
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_killed(self):
|
|
||||||
mockjob = self._prepare_wait_for_job(constants.JOB_STATE_KILLED)
|
|
||||||
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, is_physical):
|
|
||||||
mock_disk_1 = mock.MagicMock()
|
|
||||||
mock_disk_2 = mock.MagicMock()
|
|
||||||
conn_attr = (self._vmutils._PHYS_DISK_CONNECTION_ATTR if is_physical
|
|
||||||
else self._vmutils._VIRT_DISK_CONNECTION_ATTR)
|
|
||||||
setattr(mock_disk_2, conn_attr, [self._FAKE_MOUNTED_DISK_PATH])
|
|
||||||
self._vmutils._conn.query.return_value = [mock_disk_1, mock_disk_2]
|
|
||||||
|
|
||||||
mounted_disk = self._vmutils._get_mounted_disk_resource_from_path(
|
|
||||||
self._FAKE_MOUNTED_DISK_PATH, is_physical)
|
|
||||||
|
|
||||||
self.assertEqual(mock_disk_2, mounted_disk)
|
|
||||||
|
|
||||||
def test_get_physical_mounted_disk_resource_from_path(self):
|
|
||||||
self._test_get_mounted_disk_resource_from_path(is_physical=True)
|
|
||||||
|
|
||||||
def test_get_virtual_mounted_disk_resource_from_path(self):
|
|
||||||
self._test_get_mounted_disk_resource_from_path(is_physical=False)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
mock_vmsettings[0].associators.assert_called_once_with(
|
|
||||||
wmi_result_class=self._vmutils._SERIAL_PORT_SETTING_DATA_CLASS)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def test_get_vm_generation_gen1(self):
|
|
||||||
ret = self._vmutils.get_vm_generation(mock.sentinel.FAKE_VM_NAME)
|
|
||||||
self.assertEqual(constants.VM_GEN_1, ret)
|
|
||||||
|
|
||||||
def test_stop_vm_jobs(self):
|
|
||||||
mock_vm = self._lookup_vm()
|
|
||||||
|
|
||||||
mock_job1 = mock.MagicMock(Cancellable=True)
|
|
||||||
mock_job2 = mock.MagicMock(Cancellable=True)
|
|
||||||
mock_job3 = mock.MagicMock(Cancellable=True)
|
|
||||||
|
|
||||||
mock_job1.JobState = 2
|
|
||||||
mock_job2.JobState = 3
|
|
||||||
mock_job3.JobState = constants.JOB_STATE_KILLED
|
|
||||||
|
|
||||||
mock_vm_jobs = [mock_job1, mock_job2, mock_job3]
|
|
||||||
|
|
||||||
mock_vm.associators.return_value = mock_vm_jobs
|
|
||||||
|
|
||||||
self._vmutils.stop_vm_jobs(mock.sentinel.FAKE_VM_NAME)
|
|
||||||
|
|
||||||
mock_job1.RequestStateChange.assert_called_once_with(
|
|
||||||
self._vmutils._KILL_JOB_STATE_CHANGE_REQUEST)
|
|
||||||
mock_job2.RequestStateChange.assert_called_once_with(
|
|
||||||
self._vmutils._KILL_JOB_STATE_CHANGE_REQUEST)
|
|
||||||
self.assertFalse(mock_job3.RequestStateChange.called)
|
|
||||||
|
|
||||||
def test_is_job_completed_true(self):
|
|
||||||
job = mock.MagicMock(JobState=constants.JOB_STATE_COMPLETED)
|
|
||||||
|
|
||||||
self.assertTrue(self._vmutils._is_job_completed(job))
|
|
||||||
|
|
||||||
def test_is_job_completed_false(self):
|
|
||||||
job = mock.MagicMock(JobState=constants.WMI_JOB_STATE_RUNNING)
|
|
||||||
|
|
||||||
self.assertFalse(self._vmutils._is_job_completed(job))
|
|
@@ -1,294 +0,0 @@
|
|||||||
# 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()
|
|
||||||
|
|
||||||
@mock.patch('nova.virt.hyperv.hostutils.HostUtils'
|
|
||||||
'.check_min_windows_version')
|
|
||||||
@mock.patch.object(vmutilsv2, 'sys')
|
|
||||||
def test_serial_port_setting_data_win_version_10(self, mock_sys,
|
|
||||||
mock_check_version):
|
|
||||||
mock_sys.platform = 'win32'
|
|
||||||
mock_check_version.return_value = True
|
|
||||||
_vmutils = vmutilsv2.VMUtilsV2()
|
|
||||||
|
|
||||||
self.assertEqual("Msvm_SerialPortSettingData",
|
|
||||||
_vmutils._SERIAL_PORT_SETTING_DATA_CLASS)
|
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_vm_setting_data')
|
|
||||||
def _test_get_vm_generation(self, vm_gen, mock_get_vm_setting_data):
|
|
||||||
self._lookup_vm()
|
|
||||||
vm_gen_string = "Microsoft:Hyper-V:SubType:" + str(vm_gen)
|
|
||||||
mock_vssd = mock.MagicMock(VirtualSystemSubType=vm_gen_string)
|
|
||||||
mock_get_vm_setting_data.return_value = mock_vssd
|
|
||||||
|
|
||||||
ret = self._vmutils.get_vm_generation(mock.sentinel.FAKE_VM_NAME)
|
|
||||||
|
|
||||||
self.assertEqual(vm_gen, ret)
|
|
||||||
|
|
||||||
def test_get_vm_generation_gen1(self):
|
|
||||||
self._test_get_vm_generation(constants.VM_GEN_1)
|
|
||||||
|
|
||||||
def test_get_vm_generation_gen2(self):
|
|
||||||
self._test_get_vm_generation(constants.VM_GEN_2)
|
|
@@ -1,164 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,164 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,149 +0,0 @@
|
|||||||
#
|
|
||||||
# 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)
|
|
@@ -60,15 +60,6 @@ PROCESSOR_FEATURE = {
|
|||||||
17: 'xsave',
|
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_IDE = "IDE"
|
||||||
CTRL_TYPE_SCSI = "SCSI"
|
CTRL_TYPE_SCSI = "SCSI"
|
||||||
|
|
||||||
@@ -85,11 +76,6 @@ DISK_FORMAT_MAP = {
|
|||||||
DISK_FORMAT_VHD = "VHD"
|
DISK_FORMAT_VHD = "VHD"
|
||||||
DISK_FORMAT_VHDX = "VHDX"
|
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_SHUTDOWN = "shutdown"
|
||||||
HOST_POWER_ACTION_REBOOT = "reboot"
|
HOST_POWER_ACTION_REBOOT = "reboot"
|
||||||
HOST_POWER_ACTION_STARTUP = "startup"
|
HOST_POWER_ACTION_STARTUP = "startup"
|
||||||
@@ -99,8 +85,3 @@ IMAGE_PROP_VM_GEN_2 = "hyperv-gen2"
|
|||||||
|
|
||||||
VM_GEN_1 = 1
|
VM_GEN_1 = 1
|
||||||
VM_GEN_2 = 2
|
VM_GEN_2 = 2
|
||||||
|
|
||||||
JOB_STATE_COMPLETED = 7
|
|
||||||
JOB_STATE_TERMINATED = 8
|
|
||||||
JOB_STATE_KILLED = 9
|
|
||||||
JOB_STATE_COMPLETED_WITH_WARNINGS = 32768
|
|
||||||
|
@@ -1,120 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,34 +0,0 @@
|
|||||||
# 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')
|
|
@@ -1,73 +0,0 @@
|
|||||||
# 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()
|
|
@@ -1,252 +0,0 @@
|
|||||||
# 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.InstanceNotFound(_('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)
|
|
@@ -1,68 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,63 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,80 +0,0 @@
|
|||||||
# 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.virt.hyperv import hostutils
|
|
||||||
from nova.virt.hyperv import hostutilsv2
|
|
||||||
from nova.virt.hyperv import livemigrationutils
|
|
||||||
from nova.virt.hyperv import networkutilsv2
|
|
||||||
from nova.virt.hyperv import pathutils
|
|
||||||
from nova.virt.hyperv import rdpconsoleutilsv2
|
|
||||||
from nova.virt.hyperv import vhdutilsv2
|
|
||||||
from nova.virt.hyperv import vmutilsv2
|
|
||||||
from nova.virt.hyperv import volumeutils
|
|
||||||
from nova.virt.hyperv import volumeutilsv2
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
CONF.import_group('hyperv', 'os_win.utilsfactory')
|
|
||||||
|
|
||||||
utils = hostutils.HostUtils()
|
|
||||||
|
|
||||||
|
|
||||||
def _get_class(v1_class, v2_class, force_v1_flag=False):
|
|
||||||
# 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_vmutils(host='.'):
|
|
||||||
return vmutilsv2.VMUtilsV2(host)
|
|
||||||
|
|
||||||
|
|
||||||
def get_vhdutils():
|
|
||||||
return vhdutilsv2.VHDUtilsV2()
|
|
||||||
|
|
||||||
|
|
||||||
def get_networkutils():
|
|
||||||
return networkutilsv2.NetworkUtilsV2()
|
|
||||||
|
|
||||||
|
|
||||||
def get_hostutils():
|
|
||||||
return 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 rdpconsoleutilsv2.RDPConsoleUtilsV2()
|
|
@@ -1,212 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,247 +0,0 @@
|
|||||||
# 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
|
|
@@ -1,870 +0,0 @@
|
|||||||
# 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'
|
|
||||||
_SERIAL_PORT_SETTING_DATA_CLASS = _RESOURCE_ALLOC_SETTING_DATA_CLASS
|
|
||||||
_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
|
|
||||||
|
|
||||||
_PHYS_DISK_CONNECTION_ATTR = "HostResource"
|
|
||||||
_VIRT_DISK_CONNECTION_ATTR = "Connection"
|
|
||||||
|
|
||||||
_CONCRETE_JOB_CLASS = "Msvm_ConcreteJob"
|
|
||||||
|
|
||||||
_KILL_JOB_STATE_CHANGE_REQUEST = 5
|
|
||||||
|
|
||||||
_completed_job_states = [constants.JOB_STATE_COMPLETED,
|
|
||||||
constants.JOB_STATE_TERMINATED,
|
|
||||||
constants.JOB_STATE_KILLED,
|
|
||||||
constants.JOB_STATE_COMPLETED_WITH_WARNINGS]
|
|
||||||
|
|
||||||
_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.InstanceNotFound(_('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 destroy_nic(self, vm_name, nic_name):
|
|
||||||
"""Destroys the NIC with the given nic_name from the given VM.
|
|
||||||
|
|
||||||
:param vm_name: The name of the VM which has the NIC to be destroyed.
|
|
||||||
:param nic_name: The NIC's ElementName.
|
|
||||||
"""
|
|
||||||
nic_data = self._get_nic_data_by_name(nic_name)
|
|
||||||
|
|
||||||
vm = self._lookup_vm_check(vm_name)
|
|
||||||
self._remove_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.JOB_STATE_KILLED:
|
|
||||||
LOG.debug("WMI job killed with status %s.", job.JobState)
|
|
||||||
return job
|
|
||||||
|
|
||||||
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
|
|
||||||
conn_attr = self._PHYS_DISK_CONNECTION_ATTR
|
|
||||||
else:
|
|
||||||
class_name = self._STORAGE_ALLOC_SETTING_DATA_CLASS
|
|
||||||
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
|
|
||||||
conn_attr = self._VIRT_DISK_CONNECTION_ATTR
|
|
||||||
|
|
||||||
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:
|
|
||||||
conn = getattr(disk_resource, conn_attr, None)
|
|
||||||
if conn and conn[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(self._get_disk_resource_address(disk))
|
|
||||||
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._SERIAL_PORT_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)
|
|
||||||
|
|
||||||
def get_vm_generation(self, vm_name):
|
|
||||||
return constants.VM_GEN_1
|
|
||||||
|
|
||||||
def stop_vm_jobs(self, vm_name):
|
|
||||||
vm = self._lookup_vm_check(vm_name)
|
|
||||||
vm_jobs = vm.associators(wmi_result_class=self._CONCRETE_JOB_CLASS)
|
|
||||||
|
|
||||||
for job in vm_jobs:
|
|
||||||
if job and job.Cancellable and not self._is_job_completed(job):
|
|
||||||
|
|
||||||
job.RequestStateChange(self._KILL_JOB_STATE_CHANGE_REQUEST)
|
|
||||||
|
|
||||||
return vm_jobs
|
|
||||||
|
|
||||||
def _is_job_completed(self, job):
|
|
||||||
return job.JobState in self._completed_job_states
|
|
@@ -1,346 +0,0 @@
|
|||||||
# 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 hostutils
|
|
||||||
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_SUBTYPE = 'VirtualSystemSubType'
|
|
||||||
_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'
|
|
||||||
|
|
||||||
_VIRT_DISK_CONNECTION_ATTR = "HostResource"
|
|
||||||
|
|
||||||
_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='.'):
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
# A separate WMI class for VM serial ports has been introduced
|
|
||||||
# in Windows 10 / Windows Server 2016
|
|
||||||
if hostutils.HostUtils().check_min_windows_version(10, 0):
|
|
||||||
self._SERIAL_PORT_SETTING_DATA_CLASS = (
|
|
||||||
"Msvm_SerialPortSettingData")
|
|
||||||
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]
|
|
||||||
|
|
||||||
def get_vm_generation(self, vm_name):
|
|
||||||
vm = self._lookup_vm_check(vm_name)
|
|
||||||
vssd = self._get_vm_setting_data(vm)
|
|
||||||
if hasattr(vssd, self._VIRTUAL_SYSTEM_SUBTYPE):
|
|
||||||
# expected format: 'Microsoft:Hyper-V:SubType:2'
|
|
||||||
return int(vssd.VirtualSystemSubType.split(':')[-1])
|
|
||||||
return constants.VM_GEN_1
|
|
@@ -1,122 +0,0 @@
|
|||||||
# 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)
|
|
@@ -1,132 +0,0 @@
|
|||||||
# 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)
|
|
@@ -241,29 +241,16 @@ nova.tests.unit.virt.disk.mount.test_nbd.NbdTestCase
|
|||||||
nova.tests.unit.virt.hyperv.test_driver.HyperVDriverTestCase
|
nova.tests.unit.virt.hyperv.test_driver.HyperVDriverTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_eventhandler.EventHandlerTestCase
|
nova.tests.unit.virt.hyperv.test_eventhandler.EventHandlerTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_hostops.HostOpsTestCase
|
nova.tests.unit.virt.hyperv.test_hostops.HostOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_hostutils.HostUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_ioutils.IOThreadTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_livemigrationops.LiveMigrationOpsTestCase
|
nova.tests.unit.virt.hyperv.test_livemigrationops.LiveMigrationOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_livemigrationutils.LiveMigrationUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_migrationops.MigrationOpsTestCase
|
nova.tests.unit.virt.hyperv.test_migrationops.MigrationOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_networkutils.NetworkUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_networkutilsv2.NetworkUtilsV2TestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_pathutils.PathUtilsTestCase
|
nova.tests.unit.virt.hyperv.test_pathutils.PathUtilsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_rdpconsoleops.RDPConsoleOpsTestCase
|
nova.tests.unit.virt.hyperv.test_rdpconsoleops.RDPConsoleOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_rdpconsoleutilsv2.RDPConsoleUtilsV2TestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_snapshotops.SnapshotOpsTestCase
|
nova.tests.unit.virt.hyperv.test_snapshotops.SnapshotOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_utilsfactory.TestHyperVUtilsFactory
|
|
||||||
nova.tests.unit.virt.hyperv.test_vhdutils.VHDUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_vhdutilsv2.VHDUtilsV2TestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_vif.HyperVNovaNetworkVIFDriverTestCase
|
nova.tests.unit.virt.hyperv.test_vif.HyperVNovaNetworkVIFDriverTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_vmops.VMOpsTestCase
|
nova.tests.unit.virt.hyperv.test_vmops.VMOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_vmutils.VMUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_vmutilsv2.VMUtilsV2TestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_volumeops.ISCSIVolumeDriverTestCase
|
nova.tests.unit.virt.hyperv.test_volumeops.ISCSIVolumeDriverTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_volumeops.SMBFSVolumeDriverTestCase
|
nova.tests.unit.virt.hyperv.test_volumeops.SMBFSVolumeDriverTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_volumeops.VolumeOpsTestCase
|
nova.tests.unit.virt.hyperv.test_volumeops.VolumeOpsTestCase
|
||||||
nova.tests.unit.virt.hyperv.test_volumeutils.VolumeUtilsTestCase
|
|
||||||
nova.tests.unit.virt.hyperv.test_volumeutilsv2.VolumeUtilsV2TestCase
|
|
||||||
nova.tests.unit.virt.ironic.test_driver.IronicDriverTestCase
|
nova.tests.unit.virt.ironic.test_driver.IronicDriverTestCase
|
||||||
nova.tests.unit.virt.ironic.test_patcher.IronicDriverFieldsTestCase
|
nova.tests.unit.virt.ironic.test_patcher.IronicDriverFieldsTestCase
|
||||||
nova.tests.unit.virt.libvirt.storage.test_lvm.LvmTestCase
|
nova.tests.unit.virt.libvirt.storage.test_lvm.LvmTestCase
|
||||||
|
Reference in New Issue
Block a user