Adds Nova utils classes as is
Adds Utils classes in oslo_windows.utils. Adds utilsfactory module in oslo_windows. Adds Utils classes unit tests in oslo_windows.tests.utils. Adds utilsfactory module unit tests in oslo_windows.tests.
This commit is contained in:
parent
2dd79e9e22
commit
9433b9b6fb
0
oslo_windows/tests/__init__.py
Normal file
0
oslo_windows/tests/__init__.py
Normal file
57
oslo_windows/tests/test_utilsfactory.py
Normal file
57
oslo_windows/tests/test_utilsfactory.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Copyright 2014 Cloudbase Solutions SRL
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Unit tests for the Hyper-V utils factory.
|
||||
"""
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import hostutils
|
||||
from nova.virt.hyperv import utilsfactory
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import vmutilsv2
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestHyperVUtilsFactory(test.NoDBTestCase):
|
||||
def test_get_vmutils_force_v1_and_min_version(self):
|
||||
self._test_returned_class(None, True, True)
|
||||
|
||||
def test_get_vmutils_v2(self):
|
||||
self._test_returned_class(vmutilsv2.VMUtilsV2, False, True)
|
||||
|
||||
def test_get_vmutils_v2_r2(self):
|
||||
self._test_returned_class(vmutils.VMUtils, False, False)
|
||||
|
||||
def test_get_vmutils_force_v1_and_not_min_version(self):
|
||||
self._test_returned_class(vmutils.VMUtils, True, False)
|
||||
|
||||
def _test_returned_class(self, expected_class, force_v1, os_supports_v2):
|
||||
CONF.set_override('force_hyperv_utils_v1', force_v1, 'hyperv')
|
||||
with mock.patch.object(
|
||||
hostutils.HostUtils,
|
||||
'check_min_windows_version') as mock_check_min_windows_version:
|
||||
mock_check_min_windows_version.return_value = os_supports_v2
|
||||
|
||||
if os_supports_v2 and force_v1:
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
utilsfactory.get_vmutils)
|
||||
else:
|
||||
actual_class = type(utilsfactory.get_vmutils())
|
||||
self.assertEqual(actual_class, expected_class)
|
0
oslo_windows/tests/utils/__init__.py
Normal file
0
oslo_windows/tests/utils/__init__.py
Normal file
188
oslo_windows/tests/utils/test_basevolumeutils.py
Normal file
188
oslo_windows/tests/utils/test_basevolumeutils.py
Normal file
@ -0,0 +1,188 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import basevolumeutils
|
||||
|
||||
|
||||
def _exception_thrower():
|
||||
raise Exception("Testing exception handling.")
|
||||
|
||||
|
||||
class BaseVolumeUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V BaseVolumeUtils class."""
|
||||
|
||||
_FAKE_COMPUTER_NAME = "fake_computer_name"
|
||||
_FAKE_DOMAIN_NAME = "fake_domain_name"
|
||||
_FAKE_INITIATOR_NAME = "fake_initiator_name"
|
||||
_FAKE_INITIATOR_IQN_NAME = "iqn.1991-05.com.microsoft:fake_computer_name"
|
||||
_FAKE_DISK_PATH = 'fake_path DeviceID="123\\\\2"'
|
||||
_FAKE_MOUNT_DEVICE = '/dev/fake/mount'
|
||||
_FAKE_DEVICE_NAME = '/dev/fake/path'
|
||||
_FAKE_SWAP = {'device_name': _FAKE_DISK_PATH}
|
||||
|
||||
def setUp(self):
|
||||
self._volutils = basevolumeutils.BaseVolumeUtils()
|
||||
self._volutils._conn_wmi = mock.MagicMock()
|
||||
self._volutils._conn_cimv2 = mock.MagicMock()
|
||||
|
||||
super(BaseVolumeUtilsTestCase, self).setUp()
|
||||
|
||||
def test_get_iscsi_initiator_ok(self):
|
||||
self._check_get_iscsi_initiator(
|
||||
mock.MagicMock(return_value=mock.sentinel.FAKE_KEY),
|
||||
self._FAKE_INITIATOR_NAME)
|
||||
|
||||
def test_get_iscsi_initiator_exception(self):
|
||||
initiator_name = "%(iqn)s.%(domain)s" % {
|
||||
'iqn': self._FAKE_INITIATOR_IQN_NAME,
|
||||
'domain': self._FAKE_DOMAIN_NAME
|
||||
}
|
||||
|
||||
self._check_get_iscsi_initiator(_exception_thrower, initiator_name)
|
||||
|
||||
def _check_get_iscsi_initiator(self, winreg_method, expected):
|
||||
mock_computer = mock.MagicMock()
|
||||
mock_computer.name = self._FAKE_COMPUTER_NAME
|
||||
mock_computer.Domain = self._FAKE_DOMAIN_NAME
|
||||
self._volutils._conn_cimv2.Win32_ComputerSystem.return_value = [
|
||||
mock_computer]
|
||||
|
||||
with mock.patch.object(basevolumeutils,
|
||||
'_winreg', create=True) as mock_winreg:
|
||||
mock_winreg.OpenKey = winreg_method
|
||||
mock_winreg.QueryValueEx = mock.MagicMock(return_value=[expected])
|
||||
|
||||
initiator_name = self._volutils.get_iscsi_initiator()
|
||||
self.assertEqual(expected, initiator_name)
|
||||
|
||||
@mock.patch.object(basevolumeutils, 'driver')
|
||||
def test_volume_in_mapping(self, mock_driver):
|
||||
mock_driver.block_device_info_get_mapping.return_value = [
|
||||
{'mount_device': self._FAKE_MOUNT_DEVICE}]
|
||||
mock_driver.block_device_info_get_swap = mock.MagicMock(
|
||||
return_value=self._FAKE_SWAP)
|
||||
mock_driver.block_device_info_get_ephemerals = mock.MagicMock(
|
||||
return_value=[{'device_name': self._FAKE_DEVICE_NAME}])
|
||||
|
||||
mock_driver.swap_is_usable = mock.MagicMock(return_value=True)
|
||||
|
||||
self.assertTrue(self._volutils.volume_in_mapping(
|
||||
self._FAKE_MOUNT_DEVICE, mock.sentinel.FAKE_BLOCK_DEVICE_INFO))
|
||||
|
||||
def test_get_drive_number_from_disk_path(self):
|
||||
fake_disk_path = (
|
||||
'\\\\WIN-I5BTVHOIFGK\\root\\virtualization\\v2:Msvm_DiskDrive.'
|
||||
'CreationClassName="Msvm_DiskDrive",DeviceID="Microsoft:353B3BE8-'
|
||||
'310C-4cf4-839E-4E1B14616136\\\\1",SystemCreationClassName='
|
||||
'"Msvm_ComputerSystem",SystemName="WIN-I5BTVHOIFGK"')
|
||||
expected_disk_number = 1
|
||||
|
||||
ret_val = self._volutils._get_drive_number_from_disk_path(
|
||||
fake_disk_path)
|
||||
|
||||
self.assertEqual(expected_disk_number, ret_val)
|
||||
|
||||
def test_get_drive_number_not_found(self):
|
||||
fake_disk_path = 'fake_disk_path'
|
||||
|
||||
ret_val = self._volutils._get_drive_number_from_disk_path(
|
||||
fake_disk_path)
|
||||
|
||||
self.assertFalse(ret_val)
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
"_get_drive_number_from_disk_path")
|
||||
def test_get_session_id_from_mounted_disk(self, mock_get_session_id):
|
||||
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
mock_initiator_session = self._create_initiator_session()
|
||||
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [mock_initiator_session]
|
||||
|
||||
session_id = self._volutils.get_session_id_from_mounted_disk(
|
||||
self._FAKE_DISK_PATH)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_SESSION_ID, session_id)
|
||||
|
||||
def test_get_devices_for_target(self):
|
||||
init_session = self._create_initiator_session()
|
||||
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_session]
|
||||
devices = self._volutils._get_devices_for_target(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(init_session.Devices, devices)
|
||||
|
||||
def test_get_devices_for_target_not_found(self):
|
||||
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = []
|
||||
devices = self._volutils._get_devices_for_target(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(0, len(devices))
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_device_number_for_target(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
fake_get_devices.return_value = init_session.Devices
|
||||
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_session]
|
||||
device_number = self._volutils.get_device_number_for_target(
|
||||
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_DEVICE_NUMBER, device_number)
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
'_get_devices_for_target')
|
||||
def test_get_target_lun_count(self, fake_get_devices):
|
||||
init_session = self._create_initiator_session()
|
||||
# Only disk devices are being counted.
|
||||
disk_device = mock.Mock(DeviceType=self._volutils._FILE_DEVICE_DISK)
|
||||
init_session.Devices.append(disk_device)
|
||||
fake_get_devices.return_value = init_session.Devices
|
||||
|
||||
lun_count = self._volutils.get_target_lun_count(
|
||||
mock.sentinel.FAKE_IQN)
|
||||
|
||||
self.assertEqual(1, lun_count)
|
||||
|
||||
@mock.patch.object(basevolumeutils.BaseVolumeUtils,
|
||||
"_get_drive_number_from_disk_path")
|
||||
def test_get_target_from_disk_path(self, mock_get_session_id):
|
||||
mock_get_session_id.return_value = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
init_sess = self._create_initiator_session()
|
||||
mock_ses_class = self._volutils._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
mock_ses_class.return_value = [init_sess]
|
||||
|
||||
(target_name, scsi_lun) = self._volutils.get_target_from_disk_path(
|
||||
self._FAKE_DISK_PATH)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_TARGET_NAME, target_name)
|
||||
self.assertEqual(mock.sentinel.FAKE_LUN, scsi_lun)
|
||||
|
||||
def _create_initiator_session(self):
|
||||
device = mock.MagicMock()
|
||||
device.ScsiLun = mock.sentinel.FAKE_LUN
|
||||
device.DeviceNumber = mock.sentinel.FAKE_DEVICE_NUMBER
|
||||
device.TargetName = mock.sentinel.FAKE_TARGET_NAME
|
||||
init_session = mock.MagicMock()
|
||||
init_session.Devices = [device]
|
||||
init_session.SessionId = mock.sentinel.FAKE_SESSION_ID
|
||||
|
||||
return init_session
|
141
oslo_windows/tests/utils/test_hostutils.py
Normal file
141
oslo_windows/tests/utils/test_hostutils.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import hostutils
|
||||
|
||||
|
||||
class FakeCPUSpec(object):
|
||||
"""Fake CPU Spec for unit tests."""
|
||||
|
||||
Architecture = mock.sentinel.cpu_arch
|
||||
Name = mock.sentinel.cpu_name
|
||||
Manufacturer = mock.sentinel.cpu_man
|
||||
NumberOfCores = mock.sentinel.cpu_cores
|
||||
NumberOfLogicalProcessors = mock.sentinel.cpu_procs
|
||||
|
||||
|
||||
class HostUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V hostutils class."""
|
||||
|
||||
_FAKE_MEMORY_TOTAL = 1024
|
||||
_FAKE_MEMORY_FREE = 512
|
||||
_FAKE_DISK_SIZE = 1024
|
||||
_FAKE_DISK_FREE = 512
|
||||
_FAKE_VERSION_GOOD = '6.2.0'
|
||||
_FAKE_VERSION_BAD = '6.1.9'
|
||||
|
||||
def setUp(self):
|
||||
self._hostutils = hostutils.HostUtils()
|
||||
self._hostutils._conn_cimv2 = mock.MagicMock()
|
||||
|
||||
super(HostUtilsTestCase, self).setUp()
|
||||
|
||||
@mock.patch('nova.virt.hyperv.hostutils.ctypes')
|
||||
def test_get_host_tick_count64(self, mock_ctypes):
|
||||
tick_count64 = "100"
|
||||
mock_ctypes.windll.kernel32.GetTickCount64.return_value = tick_count64
|
||||
response = self._hostutils.get_host_tick_count64()
|
||||
self.assertEqual(tick_count64, response)
|
||||
|
||||
def test_get_cpus_info(self):
|
||||
cpu = mock.MagicMock(spec=FakeCPUSpec)
|
||||
self._hostutils._conn_cimv2.query.return_value = [cpu]
|
||||
cpu_list = self._hostutils.get_cpus_info()
|
||||
self.assertEqual([cpu._mock_children], cpu_list)
|
||||
|
||||
def test_get_memory_info(self):
|
||||
memory = mock.MagicMock()
|
||||
type(memory).TotalVisibleMemorySize = mock.PropertyMock(
|
||||
return_value=self._FAKE_MEMORY_TOTAL)
|
||||
type(memory).FreePhysicalMemory = mock.PropertyMock(
|
||||
return_value=self._FAKE_MEMORY_FREE)
|
||||
|
||||
self._hostutils._conn_cimv2.query.return_value = [memory]
|
||||
total_memory, free_memory = self._hostutils.get_memory_info()
|
||||
|
||||
self.assertEqual(self._FAKE_MEMORY_TOTAL, total_memory)
|
||||
self.assertEqual(self._FAKE_MEMORY_FREE, free_memory)
|
||||
|
||||
def test_get_volume_info(self):
|
||||
disk = mock.MagicMock()
|
||||
type(disk).Size = mock.PropertyMock(return_value=self._FAKE_DISK_SIZE)
|
||||
type(disk).FreeSpace = mock.PropertyMock(
|
||||
return_value=self._FAKE_DISK_FREE)
|
||||
|
||||
self._hostutils._conn_cimv2.query.return_value = [disk]
|
||||
(total_memory, free_memory) = self._hostutils.get_volume_info(
|
||||
mock.sentinel.FAKE_DRIVE)
|
||||
|
||||
self.assertEqual(self._FAKE_DISK_SIZE, total_memory)
|
||||
self.assertEqual(self._FAKE_DISK_FREE, free_memory)
|
||||
|
||||
def test_check_min_windows_version_true(self):
|
||||
self._test_check_min_windows_version(self._FAKE_VERSION_GOOD, True)
|
||||
|
||||
def test_check_min_windows_version_false(self):
|
||||
self._test_check_min_windows_version(self._FAKE_VERSION_BAD, False)
|
||||
|
||||
def _test_check_min_windows_version(self, version, expected):
|
||||
os = mock.MagicMock()
|
||||
os.Version = version
|
||||
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [os]
|
||||
self.assertEqual(expected,
|
||||
self._hostutils.check_min_windows_version(6, 2))
|
||||
|
||||
def _test_host_power_action(self, action):
|
||||
fake_win32 = mock.MagicMock()
|
||||
fake_win32.Win32Shutdown = mock.MagicMock()
|
||||
|
||||
self._hostutils._conn_cimv2.Win32_OperatingSystem.return_value = [
|
||||
fake_win32]
|
||||
|
||||
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
|
||||
self._hostutils.host_power_action(action)
|
||||
fake_win32.Win32Shutdown.assert_called_with(
|
||||
self._hostutils._HOST_FORCED_SHUTDOWN)
|
||||
elif action == constants.HOST_POWER_ACTION_REBOOT:
|
||||
self._hostutils.host_power_action(action)
|
||||
fake_win32.Win32Shutdown.assert_called_with(
|
||||
self._hostutils._HOST_FORCED_REBOOT)
|
||||
else:
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._hostutils.host_power_action, action)
|
||||
|
||||
def test_host_shutdown(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_SHUTDOWN)
|
||||
|
||||
def test_host_reboot(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_REBOOT)
|
||||
|
||||
def test_host_startup(self):
|
||||
self._test_host_power_action(constants.HOST_POWER_ACTION_STARTUP)
|
||||
|
||||
def test_get_supported_vm_types_2012_r2(self):
|
||||
with mock.patch.object(self._hostutils,
|
||||
'check_min_windows_version') as mock_check_win:
|
||||
mock_check_win.return_value = True
|
||||
result = self._hostutils.get_supported_vm_types()
|
||||
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1,
|
||||
constants.IMAGE_PROP_VM_GEN_2], result)
|
||||
|
||||
def test_get_supported_vm_types(self):
|
||||
with mock.patch.object(self._hostutils,
|
||||
'check_min_windows_version') as mock_check_win:
|
||||
mock_check_win.return_value = False
|
||||
result = self._hostutils.get_supported_vm_types()
|
||||
self.assertEqual([constants.IMAGE_PROP_VM_GEN_1], result)
|
30
oslo_windows/tests/utils/test_hostutilsv2.py
Normal file
30
oslo_windows/tests/utils/test_hostutilsv2.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import hostutilsv2
|
||||
|
||||
|
||||
class HostUtilsV2TestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V hostutilsv2 class."""
|
||||
|
||||
def setUp(self):
|
||||
self._hostutils = hostutilsv2.HostUtilsV2()
|
||||
self._hostutils._conn_cimv2 = mock.MagicMock()
|
||||
self._hostutils._conn_virt = mock.MagicMock()
|
||||
|
||||
super(HostUtilsV2TestCase, self).setUp()
|
61
oslo_windows/tests/utils/test_ioutils.py
Normal file
61
oslo_windows/tests/utils/test_ioutils.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.import mock
|
||||
|
||||
import mock
|
||||
|
||||
import os
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import ioutils
|
||||
|
||||
|
||||
class IOThreadTestCase(test.NoDBTestCase):
|
||||
_FAKE_SRC = r'fake_source_file'
|
||||
_FAKE_DEST = r'fake_dest_file'
|
||||
_FAKE_MAX_BYTES = 1
|
||||
|
||||
def setUp(self):
|
||||
self._iothread = ioutils.IOThread(
|
||||
self._FAKE_SRC, self._FAKE_DEST, self._FAKE_MAX_BYTES)
|
||||
super(IOThreadTestCase, self).setUp()
|
||||
|
||||
@mock.patch('__builtin__.open')
|
||||
@mock.patch('os.rename')
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('os.remove')
|
||||
def test_copy(self, fake_remove, fake_exists, fake_rename, fake_open):
|
||||
fake_data = 'a'
|
||||
fake_src = mock.Mock()
|
||||
fake_dest = mock.Mock()
|
||||
|
||||
fake_src.read.return_value = fake_data
|
||||
fake_dest.tell.return_value = 0
|
||||
fake_exists.return_value = True
|
||||
|
||||
mock_context_manager = mock.MagicMock()
|
||||
fake_open.return_value = mock_context_manager
|
||||
mock_context_manager.__enter__.side_effect = [fake_src, fake_dest]
|
||||
self._iothread._stopped.isSet = mock.Mock(side_effect=[False, True])
|
||||
|
||||
self._iothread._copy()
|
||||
|
||||
fake_dest.seek.assert_called_once_with(0, os.SEEK_END)
|
||||
fake_dest.write.assert_called_once_with(fake_data)
|
||||
fake_dest.close.assert_called_once_with()
|
||||
fake_rename.assert_called_once_with(
|
||||
self._iothread._dest, self._iothread._dest_archive)
|
||||
fake_remove.assert_called_once_with(
|
||||
self._iothread._dest_archive)
|
||||
self.assertEqual(3, fake_open.call_count)
|
274
oslo_windows/tests/utils/test_livemigrationutils.py
Normal file
274
oslo_windows/tests/utils/test_livemigrationutils.py
Normal file
@ -0,0 +1,274 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import livemigrationutils
|
||||
|
||||
|
||||
class LiveMigrationUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V LiveMigrationUtils class."""
|
||||
|
||||
_FAKE_RET_VAL = 0
|
||||
|
||||
_RESOURCE_TYPE_VHD = 31
|
||||
_RESOURCE_TYPE_DISK = 17
|
||||
_RESOURCE_SUB_TYPE_VHD = 'Microsoft:Hyper-V:Virtual Hard Disk'
|
||||
_RESOURCE_SUB_TYPE_DISK = 'Microsoft:Hyper-V:Physical Disk Drive'
|
||||
|
||||
def setUp(self):
|
||||
self.liveutils = livemigrationutils.LiveMigrationUtils()
|
||||
self.liveutils._vmutils = mock.MagicMock()
|
||||
self.liveutils._volutils = mock.MagicMock()
|
||||
|
||||
self._conn = mock.MagicMock()
|
||||
self.liveutils._get_conn_v2 = mock.MagicMock(return_value=self._conn)
|
||||
|
||||
super(LiveMigrationUtilsTestCase, self).setUp()
|
||||
|
||||
def test_check_live_migration_config(self):
|
||||
mock_migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
|
||||
|
||||
vsmssd = mock.MagicMock()
|
||||
vsmssd.EnableVirtualSystemMigration = True
|
||||
mock_migr_svc.associators.return_value = [vsmssd]
|
||||
mock_migr_svc.MigrationServiceListenerIPAdressList.return_value = [
|
||||
mock.sentinel.FAKE_HOST]
|
||||
|
||||
self.liveutils.check_live_migration_config()
|
||||
self.assertTrue(mock_migr_svc.associators.called)
|
||||
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_destroy_planned_vm')
|
||||
def test_check_existing_planned_vm_found(self, mock_destroy_planned_vm):
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_v2 = mock.MagicMock()
|
||||
mock_v2.Msvm_PlannedComputerSystem.return_value = [mock_vm]
|
||||
self.liveutils._check_existing_planned_vm(mock_v2, mock_vm)
|
||||
|
||||
mock_destroy_planned_vm.assert_called_once_with(mock_v2, mock_vm)
|
||||
|
||||
@mock.patch.object(livemigrationutils.LiveMigrationUtils,
|
||||
'_destroy_planned_vm')
|
||||
def test_check_existing_planned_vm_none(self, mock_destroy_planned_vm):
|
||||
mock_v2 = mock.MagicMock()
|
||||
mock_v2.Msvm_PlannedComputerSystem.return_value = []
|
||||
self.liveutils._check_existing_planned_vm(mock_v2, mock.MagicMock())
|
||||
|
||||
self.assertFalse(mock_destroy_planned_vm.called)
|
||||
|
||||
def test_create_remote_planned_vm(self):
|
||||
mock_vsmsd = self._conn.query()[0]
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_v2 = mock.MagicMock()
|
||||
mock_v2.Msvm_PlannedComputerSystem.return_value = [mock_vm]
|
||||
|
||||
migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
|
||||
migr_svc.MigrateVirtualSystemToHost.return_value = (
|
||||
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
|
||||
|
||||
resulted_vm = self.liveutils._create_remote_planned_vm(
|
||||
self._conn, mock_v2, mock_vm, [mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
mock.sentinel.FAKE_HOST)
|
||||
|
||||
self.assertEqual(mock_vm, resulted_vm)
|
||||
|
||||
migr_svc.MigrateVirtualSystemToHost.assert_called_once_with(
|
||||
ComputerSystem=mock_vm.path_.return_value,
|
||||
DestinationHost=mock.sentinel.FAKE_HOST,
|
||||
MigrationSettingData=mock_vsmsd.GetText_.return_value)
|
||||
|
||||
def test_get_physical_disk_paths(self):
|
||||
ide_path = {mock.sentinel.IDE_PATH: mock.sentinel.IDE_HOST_RESOURCE}
|
||||
scsi_path = {mock.sentinel.SCSI_PATH: mock.sentinel.SCSI_HOST_RESOURCE}
|
||||
ide_ctrl = self.liveutils._vmutils.get_vm_ide_controller.return_value
|
||||
scsi_ctrl = self.liveutils._vmutils.get_vm_scsi_controller.return_value
|
||||
mock_get_controller_paths = (
|
||||
self.liveutils._vmutils.get_controller_volume_paths)
|
||||
|
||||
mock_get_controller_paths.side_effect = [ide_path, scsi_path]
|
||||
|
||||
result = self.liveutils._get_physical_disk_paths(mock.sentinel.VM_NAME)
|
||||
|
||||
expected = dict(ide_path)
|
||||
expected.update(scsi_path)
|
||||
self.assertDictContainsSubset(expected, result)
|
||||
calls = [mock.call(ide_ctrl), mock.call(scsi_ctrl)]
|
||||
mock_get_controller_paths.assert_has_calls(calls)
|
||||
|
||||
def test_get_physical_disk_paths_no_ide(self):
|
||||
scsi_path = {mock.sentinel.SCSI_PATH: mock.sentinel.SCSI_HOST_RESOURCE}
|
||||
scsi_ctrl = self.liveutils._vmutils.get_vm_scsi_controller.return_value
|
||||
mock_get_controller_paths = (
|
||||
self.liveutils._vmutils.get_controller_volume_paths)
|
||||
|
||||
self.liveutils._vmutils.get_vm_ide_controller.return_value = None
|
||||
mock_get_controller_paths.return_value = scsi_path
|
||||
|
||||
result = self.liveutils._get_physical_disk_paths(mock.sentinel.VM_NAME)
|
||||
|
||||
self.assertEqual(scsi_path, result)
|
||||
mock_get_controller_paths.assert_called_once_with(scsi_ctrl)
|
||||
|
||||
@mock.patch.object(livemigrationutils.volumeutilsv2, 'VolumeUtilsV2')
|
||||
def test_get_remote_disk_data(self, mock_vol_utils_class):
|
||||
mock_vol_utils_remote = mock_vol_utils_class.return_value
|
||||
mock_vm_utils = mock.MagicMock()
|
||||
disk_paths = {
|
||||
mock.sentinel.FAKE_RASD_PATH: mock.sentinel.FAKE_DISK_PATH}
|
||||
self.liveutils._volutils.get_target_from_disk_path.return_value = (
|
||||
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
|
||||
mock_vol_utils_remote.get_device_number_for_target.return_value = (
|
||||
mock.sentinel.FAKE_DEV_NUM)
|
||||
mock_vm_utils.get_mounted_disk_by_drive_number.return_value = (
|
||||
mock.sentinel.FAKE_DISK_PATH)
|
||||
|
||||
disk_paths = self.liveutils._get_remote_disk_data(
|
||||
mock_vm_utils, disk_paths, mock.sentinel.FAKE_HOST)
|
||||
|
||||
self.liveutils._volutils.get_target_from_disk_path.assert_called_with(
|
||||
mock.sentinel.FAKE_DISK_PATH)
|
||||
mock_vol_utils_remote.get_device_number_for_target.assert_called_with(
|
||||
mock.sentinel.FAKE_IQN, mock.sentinel.FAKE_LUN)
|
||||
mock_vm_utils.get_mounted_disk_by_drive_number.assert_called_once_with(
|
||||
mock.sentinel.FAKE_DEV_NUM)
|
||||
|
||||
self.assertEqual(
|
||||
{mock.sentinel.FAKE_RASD_PATH: mock.sentinel.FAKE_DISK_PATH},
|
||||
disk_paths)
|
||||
|
||||
def test_update_planned_vm_disk_resources(self):
|
||||
mock_vm_utils = mock.MagicMock()
|
||||
|
||||
self._prepare_vm_mocks(self._RESOURCE_TYPE_DISK,
|
||||
self._RESOURCE_SUB_TYPE_DISK)
|
||||
mock_vm = self._conn.Msvm_ComputerSystem.return_value[0]
|
||||
sasd = mock_vm.associators()[0].associators()[0]
|
||||
|
||||
mock_vsmsvc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
self.liveutils._update_planned_vm_disk_resources(
|
||||
mock_vm_utils, self._conn, mock_vm, mock.sentinel.FAKE_VM_NAME,
|
||||
{sasd.path.return_value.RelPath: mock.sentinel.FAKE_RASD_PATH})
|
||||
|
||||
mock_vsmsvc.ModifyResourceSettings.assert_called_once_with(
|
||||
ResourceSettings=[sasd.GetText_.return_value])
|
||||
|
||||
def test_get_vhd_setting_data(self):
|
||||
self._prepare_vm_mocks(self._RESOURCE_TYPE_VHD,
|
||||
self._RESOURCE_SUB_TYPE_VHD)
|
||||
mock_vm = self._conn.Msvm_ComputerSystem.return_value[0]
|
||||
mock_sasd = mock_vm.associators()[0].associators()[0]
|
||||
|
||||
vhd_sds = self.liveutils._get_vhd_setting_data(mock_vm)
|
||||
self.assertEqual([mock_sasd.GetText_.return_value], vhd_sds)
|
||||
|
||||
def test_live_migrate_vm_helper(self):
|
||||
mock_conn_local = mock.MagicMock()
|
||||
mock_vm = mock.MagicMock()
|
||||
mock_vsmsd = mock_conn_local.query()[0]
|
||||
|
||||
mock_vsmsvc = mock_conn_local.Msvm_VirtualSystemMigrationService()[0]
|
||||
mock_vsmsvc.MigrateVirtualSystemToHost.return_value = (
|
||||
self._FAKE_RET_VAL, mock.sentinel.FAKE_JOB_PATH)
|
||||
|
||||
self.liveutils._live_migrate_vm(
|
||||
mock_conn_local, mock_vm, None,
|
||||
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
mock.sentinel.FAKE_RASD_PATH, mock.sentinel.FAKE_HOST)
|
||||
|
||||
mock_vsmsvc.MigrateVirtualSystemToHost.assert_called_once_with(
|
||||
ComputerSystem=mock_vm.path_.return_value,
|
||||
DestinationHost=mock.sentinel.FAKE_HOST,
|
||||
MigrationSettingData=mock_vsmsd.GetText_.return_value,
|
||||
NewResourceSettingData=mock.sentinel.FAKE_RASD_PATH)
|
||||
|
||||
@mock.patch.object(livemigrationutils, 'vmutilsv2')
|
||||
def test_live_migrate_vm(self, mock_vm_utils):
|
||||
mock_vm_utils_remote = mock_vm_utils.VMUtilsV2.return_value
|
||||
mock_vm = self._get_vm()
|
||||
|
||||
mock_migr_svc = self._conn.Msvm_VirtualSystemMigrationService()[0]
|
||||
mock_migr_svc.MigrationServiceListenerIPAddressList = [
|
||||
mock.sentinel.FAKE_REMOTE_IP_ADDR]
|
||||
|
||||
# patches, call and assertions.
|
||||
with mock.patch.multiple(
|
||||
self.liveutils,
|
||||
_destroy_planned_vm=mock.DEFAULT,
|
||||
_get_physical_disk_paths=mock.DEFAULT,
|
||||
_get_remote_disk_data=mock.DEFAULT,
|
||||
_create_remote_planned_vm=mock.DEFAULT,
|
||||
_update_planned_vm_disk_resources=mock.DEFAULT,
|
||||
_get_vhd_setting_data=mock.DEFAULT,
|
||||
_live_migrate_vm=mock.DEFAULT):
|
||||
|
||||
disk_paths = {
|
||||
mock.sentinel.FAKE_IDE_PATH: mock.sentinel.FAKE_SASD_RESOURCE}
|
||||
self.liveutils._get_physical_disk_paths.return_value = disk_paths
|
||||
|
||||
mock_disk_paths = [mock.sentinel.FAKE_DISK_PATH]
|
||||
self.liveutils._get_remote_disk_data.return_value = (
|
||||
mock_disk_paths)
|
||||
|
||||
self.liveutils._create_remote_planned_vm.return_value = mock_vm
|
||||
|
||||
self.liveutils.live_migrate_vm(mock.sentinel.FAKE_VM_NAME,
|
||||
mock.sentinel.FAKE_HOST)
|
||||
|
||||
self.liveutils._get_remote_disk_data.assert_called_once_with(
|
||||
mock_vm_utils_remote, disk_paths, mock.sentinel.FAKE_HOST)
|
||||
|
||||
self.liveutils._create_remote_planned_vm.assert_called_once_with(
|
||||
self._conn, self._conn, mock_vm,
|
||||
[mock.sentinel.FAKE_REMOTE_IP_ADDR], mock.sentinel.FAKE_HOST)
|
||||
|
||||
mocked_method = self.liveutils._update_planned_vm_disk_resources
|
||||
mocked_method.assert_called_once_with(
|
||||
mock_vm_utils_remote, self._conn, mock_vm,
|
||||
mock.sentinel.FAKE_VM_NAME, mock_disk_paths)
|
||||
|
||||
self.liveutils._live_migrate_vm.assert_called_once_with(
|
||||
self._conn, mock_vm, mock_vm,
|
||||
[mock.sentinel.FAKE_REMOTE_IP_ADDR],
|
||||
self.liveutils._get_vhd_setting_data.return_value,
|
||||
mock.sentinel.FAKE_HOST)
|
||||
|
||||
def _prepare_vm_mocks(self, resource_type, resource_sub_type):
|
||||
mock_vm_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
vm = self._get_vm()
|
||||
self._conn.Msvm_PlannedComputerSystem.return_value = [vm]
|
||||
mock_vm_svc.DestroySystem.return_value = (mock.sentinel.FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
mock_vm_svc.ModifyResourceSettings.return_value = (
|
||||
None, mock.sentinel.FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
sasd = mock.MagicMock()
|
||||
other_sasd = mock.MagicMock()
|
||||
sasd.ResourceType = resource_type
|
||||
sasd.ResourceSubType = resource_sub_type
|
||||
sasd.HostResource = [mock.sentinel.FAKE_SASD_RESOURCE]
|
||||
sasd.path.return_value.RelPath = mock.sentinel.FAKE_DISK_PATH
|
||||
|
||||
vm_settings = mock.MagicMock()
|
||||
vm.associators.return_value = [vm_settings]
|
||||
vm_settings.associators.return_value = [sasd, other_sasd]
|
||||
|
||||
def _get_vm(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
self._conn.Msvm_ComputerSystem.return_value = [mock_vm]
|
||||
mock_vm.path_.return_value = mock.sentinel.FAKE_VM_PATH
|
||||
return mock_vm
|
82
oslo_windows/tests/utils/test_networkutils.py
Normal file
82
oslo_windows/tests/utils/test_networkutils.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import networkutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class NetworkUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V NetworkUtils class."""
|
||||
|
||||
_FAKE_PORT = {'Name': mock.sentinel.FAKE_PORT_NAME}
|
||||
_FAKE_RET_VALUE = 0
|
||||
|
||||
_MSVM_VIRTUAL_SWITCH = 'Msvm_VirtualSwitch'
|
||||
|
||||
def setUp(self):
|
||||
self._networkutils = networkutils.NetworkUtils()
|
||||
self._networkutils._conn = mock.MagicMock()
|
||||
|
||||
super(NetworkUtilsTestCase, self).setUp()
|
||||
|
||||
def test_get_external_vswitch(self):
|
||||
mock_vswitch = mock.MagicMock()
|
||||
mock_vswitch.path_.return_value = mock.sentinel.FAKE_VSWITCH_PATH
|
||||
getattr(self._networkutils._conn,
|
||||
self._MSVM_VIRTUAL_SWITCH).return_value = [mock_vswitch]
|
||||
|
||||
switch_path = self._networkutils.get_external_vswitch(
|
||||
mock.sentinel.FAKE_VSWITCH_NAME)
|
||||
|
||||
self.assertEqual(mock.sentinel.FAKE_VSWITCH_PATH, switch_path)
|
||||
|
||||
def test_get_external_vswitch_not_found(self):
|
||||
self._networkutils._conn.Msvm_VirtualEthernetSwitch.return_value = []
|
||||
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._networkutils.get_external_vswitch,
|
||||
mock.sentinel.FAKE_VSWITCH_NAME)
|
||||
|
||||
def test_get_external_vswitch_no_name(self):
|
||||
mock_vswitch = mock.MagicMock()
|
||||
mock_vswitch.path_.return_value = mock.sentinel.FAKE_VSWITCH_PATH
|
||||
|
||||
mock_ext_port = self._networkutils._conn.Msvm_ExternalEthernetPort()[0]
|
||||
self._prepare_external_port(mock_vswitch, mock_ext_port)
|
||||
|
||||
switch_path = self._networkutils.get_external_vswitch(None)
|
||||
self.assertEqual(mock.sentinel.FAKE_VSWITCH_PATH, switch_path)
|
||||
|
||||
def _prepare_external_port(self, mock_vswitch, mock_ext_port):
|
||||
mock_lep = mock_ext_port.associators()[0]
|
||||
mock_lep.associators.return_value = [mock_vswitch]
|
||||
|
||||
def test_create_vswitch_port(self):
|
||||
svc = self._networkutils._conn.Msvm_VirtualSwitchManagementService()[0]
|
||||
svc.CreateSwitchPort.return_value = (
|
||||
self._FAKE_PORT, self._FAKE_RET_VALUE)
|
||||
|
||||
port = self._networkutils.create_vswitch_port(
|
||||
mock.sentinel.FAKE_VSWITCH_PATH, mock.sentinel.FAKE_PORT_NAME)
|
||||
|
||||
svc.CreateSwitchPort.assert_called_once_with(
|
||||
Name=mock.ANY, FriendlyName=mock.sentinel.FAKE_PORT_NAME,
|
||||
ScopeOfResidence="", VirtualSwitch=mock.sentinel.FAKE_VSWITCH_PATH)
|
||||
self.assertEqual(self._FAKE_PORT, port)
|
||||
|
||||
def test_vswitch_port_needed(self):
|
||||
self.assertTrue(self._networkutils.vswitch_port_needed())
|
45
oslo_windows/tests/utils/test_networkutilsv2.py
Normal file
45
oslo_windows/tests/utils/test_networkutilsv2.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova.tests.unit.virt.hyperv import test_networkutils
|
||||
from nova.virt.hyperv import networkutilsv2
|
||||
|
||||
|
||||
class NetworkUtilsV2TestCase(test_networkutils.NetworkUtilsTestCase):
|
||||
"""Unit tests for the Hyper-V NetworkUtilsV2 class."""
|
||||
|
||||
_MSVM_VIRTUAL_SWITCH = 'Msvm_VirtualEthernetSwitch'
|
||||
|
||||
def setUp(self):
|
||||
super(NetworkUtilsV2TestCase, self).setUp()
|
||||
self._networkutils = networkutilsv2.NetworkUtilsV2()
|
||||
self._networkutils._conn = mock.MagicMock()
|
||||
|
||||
def _prepare_external_port(self, mock_vswitch, mock_ext_port):
|
||||
mock_lep = mock_ext_port.associators()[0]
|
||||
mock_lep1 = mock_lep.associators()[0]
|
||||
mock_esw = mock_lep1.associators()[0]
|
||||
mock_esw.associators.return_value = [mock_vswitch]
|
||||
|
||||
def test_create_vswitch_port(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self._networkutils.create_vswitch_port,
|
||||
mock.sentinel.FAKE_VSWITCH_PATH,
|
||||
mock.sentinel.FAKE_PORT_NAME)
|
||||
|
||||
def test_vswitch_port_needed(self):
|
||||
self.assertFalse(self._networkutils.vswitch_port_needed())
|
170
oslo_windows/tests/utils/test_pathutils.py
Normal file
170
oslo_windows/tests/utils/test_pathutils.py
Normal file
@ -0,0 +1,170 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
import mock
|
||||
|
||||
from nova.tests.unit.virt.hyperv import test_base
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import pathutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class PathUtilsTestCase(test_base.HyperVBaseTestCase):
|
||||
"""Unit tests for the Hyper-V PathUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(PathUtilsTestCase, self).setUp()
|
||||
self.fake_instance_dir = os.path.join('C:', 'fake_instance_dir')
|
||||
self.fake_instance_name = 'fake_instance_name'
|
||||
|
||||
self._pathutils = pathutils.PathUtils()
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'rename')
|
||||
@mock.patch.object(os.path, 'isfile')
|
||||
@mock.patch.object(os, 'listdir')
|
||||
def test_move_folder_files(self, mock_listdir, mock_isfile, mock_rename):
|
||||
src_dir = 'src'
|
||||
dest_dir = 'dest'
|
||||
fname = 'tmp_file.txt'
|
||||
subdir = 'tmp_folder'
|
||||
src_fname = os.path.join(src_dir, fname)
|
||||
dest_fname = os.path.join(dest_dir, fname)
|
||||
|
||||
# making sure src_subdir is not moved.
|
||||
mock_listdir.return_value = [fname, subdir]
|
||||
mock_isfile.side_effect = [True, False]
|
||||
|
||||
self._pathutils.move_folder_files(src_dir, dest_dir)
|
||||
mock_rename.assert_called_once_with(src_fname, dest_fname)
|
||||
|
||||
def _mock_lookup_configdrive_path(self, ext):
|
||||
self._pathutils.get_instance_dir = mock.MagicMock(
|
||||
return_value=self.fake_instance_dir)
|
||||
|
||||
def mock_exists(*args, **kwargs):
|
||||
path = args[0]
|
||||
return True if path[(path.rfind('.') + 1):] == ext else False
|
||||
self._pathutils.exists = mock_exists
|
||||
configdrive_path = self._pathutils.lookup_configdrive_path(
|
||||
self.fake_instance_name)
|
||||
return configdrive_path
|
||||
|
||||
def test_lookup_configdrive_path(self):
|
||||
for format_ext in constants.DISK_FORMAT_MAP:
|
||||
configdrive_path = self._mock_lookup_configdrive_path(format_ext)
|
||||
fake_path = os.path.join(self.fake_instance_dir,
|
||||
'configdrive.' + format_ext)
|
||||
self.assertEqual(configdrive_path, fake_path)
|
||||
|
||||
def test_lookup_configdrive_path_non_exist(self):
|
||||
self._pathutils.get_instance_dir = mock.MagicMock(
|
||||
return_value=self.fake_instance_dir)
|
||||
self._pathutils.exists = mock.MagicMock(return_value=False)
|
||||
configdrive_path = self._pathutils.lookup_configdrive_path(
|
||||
self.fake_instance_name)
|
||||
self.assertIsNone(configdrive_path)
|
||||
|
||||
@mock.patch.object(pathutils.PathUtils, 'unmount_smb_share')
|
||||
@mock.patch('os.path.exists')
|
||||
def _test_check_smb_mapping(self, mock_exists, mock_unmount_smb_share,
|
||||
existing_mappings=True, share_available=False):
|
||||
mock_exists.return_value = share_available
|
||||
|
||||
fake_mappings = (
|
||||
[mock.sentinel.smb_mapping] if existing_mappings else [])
|
||||
|
||||
self._pathutils._smb_conn.Msft_SmbMapping.return_value = (
|
||||
fake_mappings)
|
||||
|
||||
ret_val = self._pathutils.check_smb_mapping(
|
||||
mock.sentinel.share_path)
|
||||
|
||||
self.assertEqual(existing_mappings and share_available, ret_val)
|
||||
if existing_mappings and not share_available:
|
||||
mock_unmount_smb_share.assert_called_once_with(
|
||||
mock.sentinel.share_path, force=True)
|
||||
|
||||
def test_check_mapping(self):
|
||||
self._test_check_smb_mapping()
|
||||
|
||||
def test_remake_unavailable_mapping(self):
|
||||
self._test_check_smb_mapping(existing_mappings=True,
|
||||
share_available=False)
|
||||
|
||||
def test_available_mapping(self):
|
||||
self._test_check_smb_mapping(existing_mappings=True,
|
||||
share_available=True)
|
||||
|
||||
def test_mount_smb_share(self):
|
||||
fake_create = self._pathutils._smb_conn.Msft_SmbMapping.Create
|
||||
self._pathutils.mount_smb_share(mock.sentinel.share_path,
|
||||
mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
fake_create.assert_called_once_with(
|
||||
RemotePath=mock.sentinel.share_path,
|
||||
UserName=mock.sentinel.username,
|
||||
Password=mock.sentinel.password)
|
||||
|
||||
def _test_unmount_smb_share(self, force=False):
|
||||
fake_mapping = mock.Mock()
|
||||
smb_mapping_class = self._pathutils._smb_conn.Msft_SmbMapping
|
||||
smb_mapping_class.return_value = [fake_mapping]
|
||||
|
||||
self._pathutils.unmount_smb_share(mock.sentinel.share_path,
|
||||
force)
|
||||
|
||||
smb_mapping_class.assert_called_once_with(
|
||||
RemotePath=mock.sentinel.share_path)
|
||||
fake_mapping.Remove.assert_called_once_with(Force=force)
|
||||
|
||||
def test_soft_unmount_smb_share(self):
|
||||
self._test_unmount_smb_share()
|
||||
|
||||
def test_force_unmount_smb_share(self):
|
||||
self._test_unmount_smb_share(force=True)
|
||||
|
||||
@mock.patch('shutil.rmtree')
|
||||
def test_rmtree(self, mock_rmtree):
|
||||
class WindowsError(Exception):
|
||||
def __init__(self, winerror=None):
|
||||
self.winerror = winerror
|
||||
|
||||
mock_rmtree.side_effect = [WindowsError(
|
||||
pathutils.ERROR_DIR_IS_NOT_EMPTY), True]
|
||||
fake_windows_error = WindowsError
|
||||
with mock.patch('__builtin__.WindowsError',
|
||||
fake_windows_error, create=True):
|
||||
self._pathutils.rmtree(mock.sentinel.FAKE_PATH)
|
||||
|
||||
mock_rmtree.assert_has_calls([mock.call(mock.sentinel.FAKE_PATH),
|
||||
mock.call(mock.sentinel.FAKE_PATH)])
|
||||
|
||||
@mock.patch('os.path.join')
|
||||
def test_get_instances_sub_dir(self, fake_path_join):
|
||||
|
||||
class WindowsError(Exception):
|
||||
def __init__(self, winerror=None):
|
||||
self.winerror = winerror
|
||||
|
||||
fake_dir_name = "fake_dir_name"
|
||||
fake_windows_error = WindowsError
|
||||
self._pathutils._check_create_dir = mock.MagicMock(
|
||||
side_effect=WindowsError(pathutils.ERROR_INVALID_NAME))
|
||||
with mock.patch('__builtin__.WindowsError',
|
||||
fake_windows_error, create=True):
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._pathutils._get_instances_sub_dir,
|
||||
fake_dir_name)
|
28
oslo_windows/tests/utils/test_rdpconsoleutils.py
Normal file
28
oslo_windows/tests/utils/test_rdpconsoleutils.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import rdpconsoleutils
|
||||
|
||||
|
||||
class RDPConsoleUtilsTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
self._rdpconsoleutils = rdpconsoleutils.RDPConsoleUtils()
|
||||
super(RDPConsoleUtilsTestCase, self).setUp()
|
||||
|
||||
def test_get_rdp_console_port(self):
|
||||
listener_port = self._rdpconsoleutils.get_rdp_console_port()
|
||||
|
||||
self.assertEqual(self._rdpconsoleutils._DEFAULT_HYPERV_RDP_PORT,
|
||||
listener_port)
|
37
oslo_windows/tests/utils/test_rdpconsoleutilsv2.py
Normal file
37
oslo_windows/tests/utils/test_rdpconsoleutilsv2.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import rdpconsoleutilsv2
|
||||
|
||||
|
||||
class RDPConsoleUtilsV2TestCase(test.NoDBTestCase):
|
||||
_FAKE_RDP_PORT = 1000
|
||||
|
||||
def setUp(self):
|
||||
self._rdpconsoleutils = rdpconsoleutilsv2.RDPConsoleUtilsV2()
|
||||
self._rdpconsoleutils._conn = mock.MagicMock()
|
||||
|
||||
super(RDPConsoleUtilsV2TestCase, self).setUp()
|
||||
|
||||
def test_get_rdp_console_port(self):
|
||||
conn = self._rdpconsoleutils._conn
|
||||
mock_rdp_setting_data = conn.Msvm_TerminalServiceSettingData()[0]
|
||||
mock_rdp_setting_data.ListenerPort = self._FAKE_RDP_PORT
|
||||
|
||||
listener_port = self._rdpconsoleutils.get_rdp_console_port()
|
||||
|
||||
self.assertEqual(self._FAKE_RDP_PORT, listener_port)
|
288
oslo_windows/tests/utils/test_vhdutils.py
Normal file
288
oslo_windows/tests/utils/test_vhdutils.py
Normal file
@ -0,0 +1,288 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_utils import units
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vhdutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class VHDUtilsBaseTestCase(test.NoDBTestCase):
|
||||
"Base Class unit test classes of Hyper-V VHD Utils classes."
|
||||
|
||||
_FAKE_VHD_PATH = "C:\\fake_path.vhdx"
|
||||
_FAKE_PARENT_PATH = "C:\\fake_parent_path.vhdx"
|
||||
_FAKE_FORMAT = 3
|
||||
_FAKE_TYPE = 3
|
||||
_FAKE_MAX_INTERNAL_SIZE = units.Gi
|
||||
_FAKE_DYNAMIC_BLK_SIZE = 2097152
|
||||
_FAKE_BAD_TYPE = 5
|
||||
|
||||
_FAKE_JOB_PATH = 'fake_job_path'
|
||||
_FAKE_RET_VAL = 0
|
||||
_FAKE_VHD_INFO_XML = (
|
||||
"""<INSTANCE CLASSNAME="Msvm_VirtualHardDiskSettingData">
|
||||
<PROPERTY NAME="BlockSize" TYPE="uint32">
|
||||
<VALUE>33554432</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="Caption" TYPE="string">
|
||||
<VALUE>Virtual Hard Disk Setting Data</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="Description" TYPE="string">
|
||||
<VALUE>Setting Data for a Virtual Hard Disk.</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="ElementName" TYPE="string">
|
||||
<VALUE>fake_path.vhdx</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="Format" TYPE="uint16">
|
||||
<VALUE>%(format)s</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="InstanceID" TYPE="string">
|
||||
<VALUE>52794B89-AC06-4349-AC57-486CAAD52F69</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="LogicalSectorSize" TYPE="uint32">
|
||||
<VALUE>4096</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="MaxInternalSize" TYPE="uint64">
|
||||
<VALUE>%(max_internal_size)s</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="ParentPath" TYPE="string">
|
||||
<VALUE>%(parent_path)s</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="Path" TYPE="string">
|
||||
<VALUE>%(path)s</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="PhysicalSectorSize" TYPE="uint32">
|
||||
<VALUE>4096</VALUE>
|
||||
</PROPERTY>
|
||||
<PROPERTY NAME="Type" TYPE="uint16">
|
||||
<VALUE>%(type)s</VALUE>
|
||||
</PROPERTY>
|
||||
</INSTANCE>""" % {'path': _FAKE_VHD_PATH,
|
||||
'parent_path': _FAKE_PARENT_PATH,
|
||||
'format': _FAKE_FORMAT,
|
||||
'max_internal_size': _FAKE_MAX_INTERNAL_SIZE,
|
||||
'type': _FAKE_TYPE})
|
||||
|
||||
|
||||
class VHDUtilsTestCase(VHDUtilsBaseTestCase):
|
||||
"""Unit tests for the Hyper-V VHDUtils class."""
|
||||
|
||||
def setUp(self):
|
||||
super(VHDUtilsTestCase, self).setUp()
|
||||
self._vhdutils = vhdutils.VHDUtils()
|
||||
self._vhdutils._conn = mock.MagicMock()
|
||||
self._vhdutils._vmutils = mock.MagicMock()
|
||||
|
||||
self._fake_vhd_info = {
|
||||
'ParentPath': self._FAKE_PARENT_PATH,
|
||||
'MaxInternalSize': self._FAKE_MAX_INTERNAL_SIZE,
|
||||
'Type': self._FAKE_TYPE}
|
||||
|
||||
def test_validate_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.ValidateVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.validate_vhd(self._FAKE_VHD_PATH)
|
||||
mock_img_svc.ValidateVirtualHardDisk.assert_called_once_with(
|
||||
Path=self._FAKE_VHD_PATH)
|
||||
|
||||
def test_get_vhd_info(self):
|
||||
self._mock_get_vhd_info()
|
||||
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
|
||||
self.assertEqual(self._fake_vhd_info, vhd_info)
|
||||
|
||||
def _mock_get_vhd_info(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.GetVirtualHardDiskInfo.return_value = (
|
||||
self._FAKE_VHD_INFO_XML, self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
def test_create_dynamic_vhd(self):
|
||||
self._vhdutils.get_vhd_info = mock.MagicMock(
|
||||
return_value={'Format': self._FAKE_FORMAT})
|
||||
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.CreateDynamicVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.create_dynamic_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_MAX_INTERNAL_SIZE,
|
||||
constants.DISK_FORMAT_VHD)
|
||||
|
||||
mock_img_svc.CreateDynamicVirtualHardDisk.assert_called_once_with(
|
||||
Path=self._FAKE_VHD_PATH,
|
||||
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
|
||||
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
|
||||
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
|
||||
|
||||
def test_reconnect_parent_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.ReconnectParentVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.reconnect_parent_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_PARENT_PATH)
|
||||
mock_img_svc.ReconnectParentVirtualHardDisk.assert_called_once_with(
|
||||
ChildPath=self._FAKE_VHD_PATH,
|
||||
ParentPath=self._FAKE_PARENT_PATH,
|
||||
Force=True)
|
||||
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
|
||||
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
|
||||
|
||||
def test_merge_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.MergeVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.merge_vhd(self._FAKE_VHD_PATH, self._FAKE_VHD_PATH)
|
||||
|
||||
mock_img_svc.MergeVirtualHardDisk.assert_called_once_with(
|
||||
SourcePath=self._FAKE_VHD_PATH,
|
||||
DestinationPath=self._FAKE_VHD_PATH)
|
||||
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
|
||||
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
|
||||
|
||||
def test_resize_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.ExpandVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.get_internal_vhd_size_by_file_size = mock.MagicMock(
|
||||
return_value=self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
self._vhdutils.resize_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
mock_img_svc.ExpandVirtualHardDisk.assert_called_once_with(
|
||||
Path=self._FAKE_VHD_PATH,
|
||||
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
|
||||
self._vhdutils._vmutils.check_ret_val.assert_called_once_with(
|
||||
self._FAKE_RET_VAL, self._FAKE_JOB_PATH)
|
||||
|
||||
def _mocked_get_internal_vhd_size(self, root_vhd_size, vhd_type):
|
||||
mock_get_vhd_info = mock.MagicMock(return_value={'Type': vhd_type})
|
||||
mock_get_blk_size = mock.MagicMock(
|
||||
return_value=self._FAKE_DYNAMIC_BLK_SIZE)
|
||||
with mock.patch.multiple(self._vhdutils,
|
||||
get_vhd_info=mock_get_vhd_info,
|
||||
_get_vhd_dynamic_blk_size=mock_get_blk_size):
|
||||
|
||||
return self._vhdutils.get_internal_vhd_size_by_file_size(
|
||||
None, root_vhd_size)
|
||||
|
||||
def test_create_differencing_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.CreateDifferencingVirtualHardDisk.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.create_differencing_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_PARENT_PATH)
|
||||
|
||||
mock_img_svc.CreateDifferencingVirtualHardDisk.assert_called_once_with(
|
||||
Path=self._FAKE_VHD_PATH,
|
||||
ParentPath=self._FAKE_PARENT_PATH)
|
||||
|
||||
def test_get_internal_vhd_size_by_file_size_fixed(self):
|
||||
root_vhd_size = 1 * 1024 ** 3
|
||||
real_size = self._mocked_get_internal_vhd_size(
|
||||
root_vhd_size, constants.VHD_TYPE_FIXED)
|
||||
|
||||
expected_vhd_size = 1 * 1024 ** 3 - 512
|
||||
self.assertEqual(expected_vhd_size, real_size)
|
||||
|
||||
def test_get_internal_vhd_size_by_file_size_dynamic(self):
|
||||
root_vhd_size = 20 * 1024 ** 3
|
||||
real_size = self._mocked_get_internal_vhd_size(
|
||||
root_vhd_size, constants.VHD_TYPE_DYNAMIC)
|
||||
|
||||
expected_vhd_size = 20 * 1024 ** 3 - 43008
|
||||
self.assertEqual(expected_vhd_size, real_size)
|
||||
|
||||
def test_get_internal_vhd_size_by_file_size_differencing(self):
|
||||
# For differencing images, the internal size of the parent vhd
|
||||
# is returned
|
||||
vhdutil = vhdutils.VHDUtils()
|
||||
root_vhd_size = 20 * 1024 ** 3
|
||||
vhdutil.get_vhd_info = mock.MagicMock()
|
||||
vhdutil.get_vhd_parent_path = mock.MagicMock()
|
||||
vhdutil.get_vhd_parent_path.return_value = self._FAKE_VHD_PATH
|
||||
vhdutil.get_vhd_info.side_effect = [
|
||||
{'Type': 4}, {'Type': constants.VHD_TYPE_DYNAMIC}]
|
||||
|
||||
vhdutil._get_vhd_dynamic_blk_size = mock.MagicMock()
|
||||
vhdutil._get_vhd_dynamic_blk_size.return_value = 2097152
|
||||
|
||||
real_size = vhdutil.get_internal_vhd_size_by_file_size(None,
|
||||
root_vhd_size)
|
||||
expected_vhd_size = 20 * 1024 ** 3 - 43008
|
||||
self.assertEqual(expected_vhd_size, real_size)
|
||||
|
||||
def test_get_vhd_format_vhdx(self):
|
||||
with mock.patch('nova.virt.hyperv.vhdutils.open',
|
||||
mock.mock_open(read_data=vhdutils.VHDX_SIGNATURE),
|
||||
create=True):
|
||||
|
||||
format = self._vhdutils.get_vhd_format(self._FAKE_VHD_PATH)
|
||||
|
||||
self.assertEqual(constants.DISK_FORMAT_VHDX, format)
|
||||
|
||||
def test_get_vhd_format_vhd(self):
|
||||
with mock.patch('nova.virt.hyperv.vhdutils.open',
|
||||
mock.mock_open(),
|
||||
create=True) as mock_open:
|
||||
f = mock_open.return_value
|
||||
f.tell.return_value = 1024
|
||||
readdata = ['notthesig', vhdutils.VHD_SIGNATURE]
|
||||
|
||||
def read(*args):
|
||||
for content in readdata:
|
||||
yield content
|
||||
|
||||
f.read.side_effect = read()
|
||||
|
||||
format = self._vhdutils.get_vhd_format(self._FAKE_VHD_PATH)
|
||||
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, format)
|
||||
|
||||
def test_get_vhd_format_invalid_format(self):
|
||||
with mock.patch('nova.virt.hyperv.vhdutils.open',
|
||||
mock.mock_open(read_data='invalid'),
|
||||
create=True) as mock_open:
|
||||
f = mock_open.return_value
|
||||
f.tell.return_value = 1024
|
||||
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vhdutils.get_vhd_format,
|
||||
self._FAKE_VHD_PATH)
|
||||
|
||||
def test_get_vhd_format_zero_length_file(self):
|
||||
with mock.patch('nova.virt.hyperv.vhdutils.open',
|
||||
mock.mock_open(read_data=''),
|
||||
create=True) as mock_open:
|
||||
f = mock_open.return_value
|
||||
f.tell.return_value = 0
|
||||
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vhdutils.get_vhd_format,
|
||||
self._FAKE_VHD_PATH)
|
||||
|
||||
f.seek.assert_called_once_with(0, 2)
|
||||
|
||||
def test_get_supported_vhd_format(self):
|
||||
fmt = self._vhdutils.get_best_supported_vhd_format()
|
||||
self.assertEqual(constants.DISK_FORMAT_VHD, fmt)
|
244
oslo_windows/tests/utils/test_vhdutilsv2.py
Normal file
244
oslo_windows/tests/utils/test_vhdutilsv2.py
Normal file
@ -0,0 +1,244 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova.tests.unit.virt.hyperv import test_vhdutils
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vhdutilsv2
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class VHDUtilsV2TestCase(test_vhdutils.VHDUtilsBaseTestCase):
|
||||
"""Unit tests for the Hyper-V VHDUtilsV2 class."""
|
||||
|
||||
_FAKE_BLOCK_SIZE = 33554432
|
||||
_FAKE_LOG_SIZE = 1048576
|
||||
_FAKE_LOGICAL_SECTOR_SIZE = 4096
|
||||
_FAKE_METADATA_SIZE = 1048576
|
||||
_FAKE_PHYSICAL_SECTOR_SIZE = 4096
|
||||
|
||||
def setUp(self):
|
||||
super(VHDUtilsV2TestCase, self).setUp()
|
||||
self._vhdutils = vhdutilsv2.VHDUtilsV2()
|
||||
self._vhdutils._conn = mock.MagicMock()
|
||||
self._vhdutils._vmutils = mock.MagicMock()
|
||||
|
||||
self._fake_file_handle = mock.MagicMock()
|
||||
|
||||
self._fake_vhd_info = {
|
||||
'Path': self._FAKE_VHD_PATH,
|
||||
'ParentPath': self._FAKE_PARENT_PATH,
|
||||
'Format': self._FAKE_FORMAT,
|
||||
'MaxInternalSize': self._FAKE_MAX_INTERNAL_SIZE,
|
||||
'Type': self._FAKE_TYPE,
|
||||
'BlockSize': self._FAKE_BLOCK_SIZE,
|
||||
'LogicalSectorSize': self._FAKE_LOGICAL_SECTOR_SIZE,
|
||||
'PhysicalSectorSize': self._FAKE_PHYSICAL_SECTOR_SIZE}
|
||||
|
||||
def _mock_get_vhd_info(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.GetVirtualHardDiskSettingData.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, self._FAKE_VHD_INFO_XML)
|
||||
|
||||
def test_get_vhd_info(self):
|
||||
self._mock_get_vhd_info()
|
||||
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
|
||||
|
||||
self.assertEqual(self._FAKE_VHD_PATH, vhd_info['Path'])
|
||||
self.assertEqual(self._FAKE_PARENT_PATH, vhd_info['ParentPath'])
|
||||
self.assertEqual(self._FAKE_FORMAT, vhd_info['Format'])
|
||||
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE,
|
||||
vhd_info['MaxInternalSize'])
|
||||
self.assertEqual(self._FAKE_TYPE, vhd_info['Type'])
|
||||
|
||||
def test_get_vhd_info_no_parent(self):
|
||||
fake_vhd_xml_no_parent = self._FAKE_VHD_INFO_XML.replace(
|
||||
self._FAKE_PARENT_PATH, "")
|
||||
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.GetVirtualHardDiskSettingData.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, fake_vhd_xml_no_parent)
|
||||
|
||||
vhd_info = self._vhdutils.get_vhd_info(self._FAKE_VHD_PATH)
|
||||
|
||||
self.assertEqual(self._FAKE_VHD_PATH, vhd_info['Path'])
|
||||
self.assertIsNone(vhd_info['ParentPath'])
|
||||
self.assertEqual(self._FAKE_FORMAT, vhd_info['Format'])
|
||||
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE,
|
||||
vhd_info['MaxInternalSize'])
|
||||
self.assertEqual(self._FAKE_TYPE, vhd_info['Type'])
|
||||
|
||||
def test_create_dynamic_vhd(self):
|
||||
self._vhdutils.get_vhd_info = mock.MagicMock(
|
||||
return_value={'Format': self._FAKE_FORMAT})
|
||||
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.CreateVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.create_dynamic_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_MAX_INTERNAL_SIZE,
|
||||
constants.DISK_FORMAT_VHDX)
|
||||
|
||||
self.assertTrue(mock_img_svc.CreateVirtualHardDisk.called)
|
||||
|
||||
def test_create_differencing_vhd(self):
|
||||
self._vhdutils.get_vhd_info = mock.MagicMock(
|
||||
return_value={'ParentPath': self._FAKE_PARENT_PATH,
|
||||
'Format': self._FAKE_FORMAT})
|
||||
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.CreateVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.create_differencing_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_PARENT_PATH)
|
||||
|
||||
self.assertTrue(mock_img_svc.CreateVirtualHardDisk.called)
|
||||
|
||||
def test_reconnect_parent_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
fake_new_parent_path = 'fake_new_parent_path'
|
||||
|
||||
self._vhdutils._get_vhd_info_xml = mock.MagicMock(
|
||||
return_value=self._FAKE_VHD_INFO_XML)
|
||||
|
||||
mock_img_svc.SetVirtualHardDiskSettingData.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vhdutils.reconnect_parent_vhd(self._FAKE_VHD_PATH,
|
||||
fake_new_parent_path)
|
||||
|
||||
expected_virt_disk_data = self._FAKE_VHD_INFO_XML.replace(
|
||||
self._FAKE_PARENT_PATH, fake_new_parent_path)
|
||||
mock_img_svc.SetVirtualHardDiskSettingData.assert_called_once_with(
|
||||
VirtualDiskSettingData=expected_virt_disk_data)
|
||||
|
||||
def test_reconnect_parent_vhd_exception(self):
|
||||
# Test that reconnect_parent_vhd raises an exception if the
|
||||
# vhd info XML does not contain the ParentPath property.
|
||||
fake_vhd_info_xml = self._FAKE_VHD_INFO_XML.replace('ParentPath',
|
||||
'FakeParentPath')
|
||||
self._vhdutils._get_vhd_info_xml = mock.Mock(
|
||||
return_value=fake_vhd_info_xml)
|
||||
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vhdutils.reconnect_parent_vhd,
|
||||
self._FAKE_VHD_PATH,
|
||||
mock.sentinel.new_parent_path)
|
||||
|
||||
def test_resize_vhd(self):
|
||||
mock_img_svc = self._vhdutils._conn.Msvm_ImageManagementService()[0]
|
||||
mock_img_svc.ResizeVirtualHardDisk.return_value = (self._FAKE_JOB_PATH,
|
||||
self._FAKE_RET_VAL)
|
||||
self._vhdutils.get_internal_vhd_size_by_file_size = mock.MagicMock(
|
||||
return_value=self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
self._vhdutils.resize_vhd(self._FAKE_VHD_PATH,
|
||||
self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
mock_img_svc.ResizeVirtualHardDisk.assert_called_once_with(
|
||||
Path=self._FAKE_VHD_PATH,
|
||||
MaxInternalSize=self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
self.mock_get = self._vhdutils.get_internal_vhd_size_by_file_size
|
||||
self.mock_get.assert_called_once_with(self._FAKE_VHD_PATH,
|
||||
self._FAKE_MAX_INTERNAL_SIZE)
|
||||
|
||||
def _test_get_vhdx_internal_size(self, vhd_type):
|
||||
self._vhdutils.get_vhd_info = mock.MagicMock()
|
||||
self._vhdutils.get_vhd_parent_path = mock.Mock(
|
||||
return_value=self._FAKE_PARENT_PATH)
|
||||
|
||||
if vhd_type == 4:
|
||||
self._vhdutils.get_vhd_info.side_effect = [
|
||||
{'Type': vhd_type}, self._fake_vhd_info]
|
||||
else:
|
||||
self._vhdutils.get_vhd_info.return_value = self._fake_vhd_info
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vhdutils.VHDUtils.get_vhd_format')
|
||||
def test_get_vhdx_internal_size(self, mock_get_vhd_format):
|
||||
mock_get_vhd_format.return_value = constants.DISK_FORMAT_VHDX
|
||||
self._mock_get_vhd_info()
|
||||
self._vhdutils._get_vhdx_log_size = mock.MagicMock(
|
||||
return_value=self._FAKE_LOG_SIZE)
|
||||
self._vhdutils._get_vhdx_metadata_size_and_offset = mock.MagicMock(
|
||||
return_value=(self._FAKE_METADATA_SIZE, 1024))
|
||||
self._vhdutils._get_vhdx_block_size = mock.MagicMock(
|
||||
return_value=self._FAKE_BLOCK_SIZE)
|
||||
|
||||
file_mock = mock.MagicMock()
|
||||
with mock.patch('__builtin__.open', file_mock):
|
||||
internal_size = (
|
||||
self._vhdutils.get_internal_vhd_size_by_file_size(
|
||||
self._FAKE_VHD_PATH, self._FAKE_MAX_INTERNAL_SIZE))
|
||||
|
||||
self.assertEqual(self._FAKE_MAX_INTERNAL_SIZE - self._FAKE_BLOCK_SIZE,
|
||||
internal_size)
|
||||
|
||||
def test_get_vhdx_internal_size_dynamic(self):
|
||||
self._test_get_vhdx_internal_size(3)
|
||||
|
||||
def test_get_vhdx_internal_size_differencing(self):
|
||||
self._test_get_vhdx_internal_size(4)
|
||||
|
||||
def test_get_vhdx_current_header(self):
|
||||
VHDX_HEADER_OFFSETS = [64 * 1024, 128 * 1024]
|
||||
fake_sequence_numbers = ['\x01\x00\x00\x00\x00\x00\x00\x00',
|
||||
'\x02\x00\x00\x00\x00\x00\x00\x00']
|
||||
self._fake_file_handle.read = mock.MagicMock(
|
||||
side_effect=fake_sequence_numbers)
|
||||
|
||||
offset = self._vhdutils._get_vhdx_current_header_offset(
|
||||
self._fake_file_handle)
|
||||
self.assertEqual(offset, VHDX_HEADER_OFFSETS[1])
|
||||
|
||||
def test_get_vhdx_metadata_size(self):
|
||||
fake_metadata_offset = '\x01\x00\x00\x00\x00\x00\x00\x00'
|
||||
fake_metadata_size = '\x01\x00\x00\x00'
|
||||
self._fake_file_handle.read = mock.MagicMock(
|
||||
side_effect=[fake_metadata_offset, fake_metadata_size])
|
||||
|
||||
metadata_size, metadata_offset = (
|
||||
self._vhdutils._get_vhdx_metadata_size_and_offset(
|
||||
self._fake_file_handle))
|
||||
self.assertEqual(metadata_size, 1)
|
||||
self.assertEqual(metadata_offset, 1)
|
||||
|
||||
def test_get_block_size(self):
|
||||
self._vhdutils._get_vhdx_metadata_size_and_offset = mock.MagicMock(
|
||||
return_value=(self._FAKE_METADATA_SIZE, 1024))
|
||||
fake_block_size = '\x01\x00\x00\x00'
|
||||
self._fake_file_handle.read = mock.MagicMock(
|
||||
return_value=fake_block_size)
|
||||
|
||||
block_size = self._vhdutils._get_vhdx_block_size(
|
||||
self._fake_file_handle)
|
||||
self.assertEqual(block_size, 1)
|
||||
|
||||
def test_get_log_size(self):
|
||||
fake_current_header_offset = 64 * 1024
|
||||
self._vhdutils._get_vhdx_current_header_offset = mock.MagicMock(
|
||||
return_value=fake_current_header_offset)
|
||||
fake_log_size = '\x01\x00\x00\x00'
|
||||
self._fake_file_handle.read = mock.MagicMock(
|
||||
return_value=fake_log_size)
|
||||
|
||||
log_size = self._vhdutils._get_vhdx_log_size(self._fake_file_handle)
|
||||
self.assertEqual(log_size, 1)
|
||||
|
||||
def test_get_supported_vhd_format(self):
|
||||
fmt = self._vhdutils.get_best_supported_vhd_format()
|
||||
self.assertEqual(constants.DISK_FORMAT_VHDX, fmt)
|
850
oslo_windows/tests/utils/test_vmutils.py
Normal file
850
oslo_windows/tests/utils/test_vmutils.py
Normal file
@ -0,0 +1,850 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from six.moves import range
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class VMUtilsTestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V VMUtils class."""
|
||||
|
||||
_FAKE_VM_NAME = 'fake_vm'
|
||||
_FAKE_MEMORY_MB = 2
|
||||
_FAKE_VCPUS_NUM = 4
|
||||
_FAKE_JOB_PATH = 'fake_job_path'
|
||||
_FAKE_RET_VAL = 0
|
||||
_FAKE_RET_VAL_BAD = -1
|
||||
_FAKE_PATH = "fake_path"
|
||||
_FAKE_CTRL_PATH = 'fake_ctrl_path'
|
||||
_FAKE_CTRL_ADDR = 0
|
||||
_FAKE_DRIVE_ADDR = 0
|
||||
_FAKE_MOUNTED_DISK_PATH = 'fake_mounted_disk_path'
|
||||
_FAKE_VM_PATH = "fake_vm_path"
|
||||
_FAKE_VHD_PATH = "fake_vhd_path"
|
||||
_FAKE_DVD_PATH = "fake_dvd_path"
|
||||
_FAKE_VOLUME_DRIVE_PATH = "fake_volume_drive_path"
|
||||
_FAKE_VM_UUID = "04e79212-39bc-4065-933c-50f6d48a57f6"
|
||||
_FAKE_INSTANCE = {"name": _FAKE_VM_NAME,
|
||||
"uuid": _FAKE_VM_UUID}
|
||||
_FAKE_SNAPSHOT_PATH = "fake_snapshot_path"
|
||||
_FAKE_RES_DATA = "fake_res_data"
|
||||
_FAKE_HOST_RESOURCE = "fake_host_resource"
|
||||
_FAKE_CLASS = "FakeClass"
|
||||
_FAKE_RES_PATH = "fake_res_path"
|
||||
_FAKE_RES_NAME = 'fake_res_name'
|
||||
_FAKE_ADDRESS = "fake_address"
|
||||
_FAKE_JOB_STATUS_DONE = 7
|
||||
_FAKE_JOB_STATUS_BAD = -1
|
||||
_FAKE_JOB_DESCRIPTION = "fake_job_description"
|
||||
_FAKE_ERROR = "fake_error"
|
||||
_FAKE_ELAPSED_TIME = 0
|
||||
_CONCRETE_JOB = "Msvm_ConcreteJob"
|
||||
_FAKE_DYNAMIC_MEMORY_RATIO = 1.0
|
||||
|
||||
_FAKE_SUMMARY_INFO = {'NumberOfProcessors': 4,
|
||||
'EnabledState': 2,
|
||||
'MemoryUsage': 2,
|
||||
'UpTime': 1}
|
||||
|
||||
_DEFINE_SYSTEM = 'DefineVirtualSystem'
|
||||
_DESTROY_SYSTEM = 'DestroyVirtualSystem'
|
||||
_DESTROY_SNAPSHOT = 'RemoveVirtualSystemSnapshot'
|
||||
_ADD_RESOURCE = 'AddVirtualSystemResources'
|
||||
_REMOVE_RESOURCE = 'RemoveVirtualSystemResources'
|
||||
_SETTING_TYPE = 'SettingType'
|
||||
_VM_GEN = constants.VM_GEN_1
|
||||
|
||||
_VIRTUAL_SYSTEM_TYPE_REALIZED = 3
|
||||
|
||||
def setUp(self):
|
||||
self._vmutils = vmutils.VMUtils()
|
||||
self._vmutils._conn = mock.MagicMock()
|
||||
|
||||
super(VMUtilsTestCase, self).setUp()
|
||||
|
||||
def test_enable_vm_metrics_collection(self):
|
||||
self.assertRaises(NotImplementedError,
|
||||
self._vmutils.enable_vm_metrics_collection,
|
||||
self._FAKE_VM_NAME)
|
||||
|
||||
def test_get_vm_summary_info(self):
|
||||
self._lookup_vm()
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
mock_summary = mock.MagicMock()
|
||||
mock_svc.GetSummaryInformation.return_value = (self._FAKE_RET_VAL,
|
||||
[mock_summary])
|
||||
|
||||
for (key, val) in self._FAKE_SUMMARY_INFO.items():
|
||||
setattr(mock_summary, key, val)
|
||||
|
||||
summary = self._vmutils.get_vm_summary_info(self._FAKE_VM_NAME)
|
||||
self.assertEqual(self._FAKE_SUMMARY_INFO, summary)
|
||||
|
||||
def _lookup_vm(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
self._vmutils._lookup_vm_check = mock.MagicMock(
|
||||
return_value=mock_vm)
|
||||
mock_vm.path_.return_value = self._FAKE_VM_PATH
|
||||
return mock_vm
|
||||
|
||||
def test_lookup_vm_ok(self):
|
||||
mock_vm = mock.MagicMock()
|
||||
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mock_vm]
|
||||
vm = self._vmutils._lookup_vm_check(self._FAKE_VM_NAME)
|
||||
self.assertEqual(mock_vm, vm)
|
||||
|
||||
def test_lookup_vm_multiple(self):
|
||||
mockvm = mock.MagicMock()
|
||||
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mockvm, mockvm]
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils._lookup_vm_check,
|
||||
self._FAKE_VM_NAME)
|
||||
|
||||
def test_lookup_vm_none(self):
|
||||
self._vmutils._conn.Msvm_ComputerSystem.return_value = []
|
||||
self.assertRaises(exception.NotFound,
|
||||
self._vmutils._lookup_vm_check,
|
||||
self._FAKE_VM_NAME)
|
||||
|
||||
def test_set_vm_memory_static(self):
|
||||
self._test_set_vm_memory_dynamic(1.0)
|
||||
|
||||
def test_set_vm_memory_dynamic(self):
|
||||
self._test_set_vm_memory_dynamic(2.0)
|
||||
|
||||
def _test_set_vm_memory_dynamic(self, dynamic_memory_ratio):
|
||||
mock_vm = self._lookup_vm()
|
||||
|
||||
mock_s = self._vmutils._conn.Msvm_VirtualSystemSettingData()[0]
|
||||
mock_s.SystemType = 3
|
||||
|
||||
mock_vmsetting = mock.MagicMock()
|
||||
mock_vmsetting.associators.return_value = [mock_s]
|
||||
|
||||
self._vmutils._modify_virt_resource = mock.MagicMock()
|
||||
|
||||
self._vmutils._set_vm_memory(mock_vm, mock_vmsetting,
|
||||
self._FAKE_MEMORY_MB,
|
||||
dynamic_memory_ratio)
|
||||
|
||||
self._vmutils._modify_virt_resource.assert_called_with(
|
||||
mock_s, self._FAKE_VM_PATH)
|
||||
|
||||
if dynamic_memory_ratio > 1:
|
||||
self.assertTrue(mock_s.DynamicMemoryEnabled)
|
||||
else:
|
||||
self.assertFalse(mock_s.DynamicMemoryEnabled)
|
||||
|
||||
def test_soft_shutdown_vm(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_shutdown = mock.MagicMock()
|
||||
mock_shutdown.InitiateShutdown.return_value = (self._FAKE_RET_VAL, )
|
||||
mock_vm.associators.return_value = [mock_shutdown]
|
||||
|
||||
with mock.patch.object(self._vmutils, 'check_ret_val') as mock_check:
|
||||
self._vmutils.soft_shutdown_vm(self._FAKE_VM_NAME)
|
||||
|
||||
mock_shutdown.InitiateShutdown.assert_called_once_with(
|
||||
Force=False, Reason=mock.ANY)
|
||||
mock_check.assert_called_once_with(self._FAKE_RET_VAL, None)
|
||||
|
||||
def test_soft_shutdown_vm_no_component(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vm.associators.return_value = []
|
||||
|
||||
with mock.patch.object(self._vmutils, 'check_ret_val') as mock_check:
|
||||
self._vmutils.soft_shutdown_vm(self._FAKE_VM_NAME)
|
||||
self.assertFalse(mock_check.called)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_disks')
|
||||
def test_get_vm_storage_paths(self, mock_get_vm_disks):
|
||||
self._lookup_vm()
|
||||
mock_rasds = self._create_mock_disks()
|
||||
mock_get_vm_disks.return_value = ([mock_rasds[0]], [mock_rasds[1]])
|
||||
|
||||
storage = self._vmutils.get_vm_storage_paths(self._FAKE_VM_NAME)
|
||||
(disk_files, volume_drives) = storage
|
||||
|
||||
self.assertEqual([self._FAKE_VHD_PATH], disk_files)
|
||||
self.assertEqual([self._FAKE_VOLUME_DRIVE_PATH], volume_drives)
|
||||
|
||||
def test_get_vm_disks(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vmsettings = [mock.MagicMock()]
|
||||
mock_vm.associators.return_value = mock_vmsettings
|
||||
|
||||
mock_rasds = self._create_mock_disks()
|
||||
mock_vmsettings[0].associators.return_value = mock_rasds
|
||||
|
||||
(disks, volumes) = self._vmutils._get_vm_disks(mock_vm)
|
||||
|
||||
mock_vm.associators.assert_called_with(
|
||||
wmi_result_class=self._vmutils._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
mock_vmsettings[0].associators.assert_called_with(
|
||||
wmi_result_class=self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS)
|
||||
self.assertEqual([mock_rasds[0]], disks)
|
||||
self.assertEqual([mock_rasds[1]], volumes)
|
||||
|
||||
def _create_mock_disks(self):
|
||||
mock_rasd1 = mock.MagicMock()
|
||||
mock_rasd1.ResourceSubType = self._vmutils._HARD_DISK_RES_SUB_TYPE
|
||||
mock_rasd1.HostResource = [self._FAKE_VHD_PATH]
|
||||
mock_rasd1.Connection = [self._FAKE_VHD_PATH]
|
||||
mock_rasd1.Parent = self._FAKE_CTRL_PATH
|
||||
mock_rasd1.Address = self._FAKE_ADDRESS
|
||||
mock_rasd1.HostResource = [self._FAKE_VHD_PATH]
|
||||
|
||||
mock_rasd2 = mock.MagicMock()
|
||||
mock_rasd2.ResourceSubType = self._vmutils._PHYS_DISK_RES_SUB_TYPE
|
||||
mock_rasd2.HostResource = [self._FAKE_VOLUME_DRIVE_PATH]
|
||||
|
||||
return [mock_rasd1, mock_rasd2]
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_set_vm_vcpus')
|
||||
@mock.patch.object(vmutils.VMUtils, '_set_vm_memory')
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_wmi_obj')
|
||||
def test_create_vm(self, mock_get_wmi_obj, mock_set_mem, mock_set_vcpus):
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
getattr(mock_svc, self._DEFINE_SYSTEM).return_value = (
|
||||
None, self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
mock_vm = mock_get_wmi_obj.return_value
|
||||
self._vmutils._conn.Msvm_ComputerSystem.return_value = [mock_vm]
|
||||
|
||||
mock_s = mock.MagicMock()
|
||||
setattr(mock_s,
|
||||
self._SETTING_TYPE,
|
||||
self._VIRTUAL_SYSTEM_TYPE_REALIZED)
|
||||
mock_vm.associators.return_value = [mock_s]
|
||||
|
||||
self._vmutils.create_vm(self._FAKE_VM_NAME, self._FAKE_MEMORY_MB,
|
||||
self._FAKE_VCPUS_NUM, False,
|
||||
self._FAKE_DYNAMIC_MEMORY_RATIO,
|
||||
self._VM_GEN,
|
||||
mock.sentinel.instance_path)
|
||||
|
||||
self.assertTrue(getattr(mock_svc, self._DEFINE_SYSTEM).called)
|
||||
mock_set_mem.assert_called_with(mock_vm, mock_s, self._FAKE_MEMORY_MB,
|
||||
self._FAKE_DYNAMIC_MEMORY_RATIO)
|
||||
|
||||
mock_set_vcpus.assert_called_with(mock_vm, mock_s,
|
||||
self._FAKE_VCPUS_NUM,
|
||||
False)
|
||||
|
||||
def test_get_vm_scsi_controller(self):
|
||||
self._prepare_get_vm_controller(self._vmutils._SCSI_CTRL_RES_SUB_TYPE)
|
||||
path = self._vmutils.get_vm_scsi_controller(self._FAKE_VM_NAME)
|
||||
self.assertEqual(self._FAKE_RES_PATH, path)
|
||||
|
||||
@mock.patch("nova.virt.hyperv.vmutils.VMUtils.get_attached_disks")
|
||||
def test_get_free_controller_slot(self, mock_get_attached_disks):
|
||||
mock_disk = mock.MagicMock()
|
||||
mock_disk.AddressOnParent = 3
|
||||
mock_get_attached_disks.return_value = [mock_disk]
|
||||
|
||||
response = self._vmutils.get_free_controller_slot(
|
||||
self._FAKE_CTRL_PATH)
|
||||
|
||||
mock_get_attached_disks.assert_called_once_with(
|
||||
self._FAKE_CTRL_PATH)
|
||||
|
||||
self.assertEqual(response, 0)
|
||||
|
||||
def test_get_free_controller_slot_exception(self):
|
||||
fake_drive = mock.MagicMock()
|
||||
type(fake_drive).AddressOnParent = mock.PropertyMock(
|
||||
side_effect=range(constants.SCSI_CONTROLLER_SLOTS_NUMBER))
|
||||
|
||||
with mock.patch.object(self._vmutils,
|
||||
'get_attached_disks') as fake_get_attached_disks:
|
||||
fake_get_attached_disks.return_value = (
|
||||
[fake_drive] * constants.SCSI_CONTROLLER_SLOTS_NUMBER)
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils.get_free_controller_slot,
|
||||
mock.sentinel.scsi_controller_path)
|
||||
|
||||
def test_get_vm_ide_controller(self):
|
||||
self._prepare_get_vm_controller(self._vmutils._IDE_CTRL_RES_SUB_TYPE)
|
||||
path = self._vmutils.get_vm_ide_controller(self._FAKE_VM_NAME,
|
||||
self._FAKE_ADDRESS)
|
||||
self.assertEqual(self._FAKE_RES_PATH, path)
|
||||
|
||||
def test_get_vm_ide_controller_none(self):
|
||||
self._prepare_get_vm_controller(self._vmutils._IDE_CTRL_RES_SUB_TYPE)
|
||||
path = self._vmutils.get_vm_ide_controller(
|
||||
mock.sentinel.FAKE_VM_NAME, mock.sentinel.FAKE_NOT_FOUND_ADDR)
|
||||
self.assertNotEqual(self._FAKE_RES_PATH, path)
|
||||
|
||||
def _prepare_get_vm_controller(self, resource_sub_type):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vm_settings = mock.MagicMock()
|
||||
mock_rasds = mock.MagicMock()
|
||||
mock_rasds.path_.return_value = self._FAKE_RES_PATH
|
||||
mock_rasds.ResourceSubType = resource_sub_type
|
||||
mock_rasds.Address = self._FAKE_ADDRESS
|
||||
mock_vm_settings.associators.return_value = [mock_rasds]
|
||||
mock_vm.associators.return_value = [mock_vm_settings]
|
||||
|
||||
def _prepare_resources(self, mock_path, mock_subtype, mock_vm_settings):
|
||||
mock_rasds = mock_vm_settings.associators.return_value[0]
|
||||
mock_rasds.path_.return_value = mock_path
|
||||
mock_rasds.ResourceSubType = mock_subtype
|
||||
return mock_rasds
|
||||
|
||||
@mock.patch("nova.virt.hyperv.vmutils.VMUtils.get_free_controller_slot")
|
||||
@mock.patch("nova.virt.hyperv.vmutils.VMUtils._get_vm_scsi_controller")
|
||||
def test_attach_scsi_drive(self, mock_get_vm_scsi_controller,
|
||||
mock_get_free_controller_slot):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_get_vm_scsi_controller.return_value = self._FAKE_CTRL_PATH
|
||||
mock_get_free_controller_slot.return_value = self._FAKE_DRIVE_ADDR
|
||||
|
||||
with mock.patch.object(self._vmutils,
|
||||
'attach_drive') as mock_attach_drive:
|
||||
self._vmutils.attach_scsi_drive(mock_vm, self._FAKE_PATH,
|
||||
constants.DISK)
|
||||
|
||||
mock_get_vm_scsi_controller.assert_called_once_with(mock_vm)
|
||||
mock_get_free_controller_slot.assert_called_once_with(
|
||||
self._FAKE_CTRL_PATH)
|
||||
mock_attach_drive.assert_called_once_with(
|
||||
mock_vm, self._FAKE_PATH, self._FAKE_CTRL_PATH,
|
||||
self._FAKE_DRIVE_ADDR, constants.DISK)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_vm_ide_controller')
|
||||
def test_attach_ide_drive(self, mock_get_ide_ctrl, mock_get_new_rsd):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_rsd = mock_get_new_rsd.return_value
|
||||
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_add_virt_resource') as mock_add_virt_res:
|
||||
self._vmutils.attach_ide_drive(self._FAKE_VM_NAME,
|
||||
self._FAKE_CTRL_PATH,
|
||||
self._FAKE_CTRL_ADDR,
|
||||
self._FAKE_DRIVE_ADDR)
|
||||
|
||||
mock_add_virt_res.assert_called_with(mock_rsd,
|
||||
mock_vm.path_.return_value)
|
||||
|
||||
mock_get_ide_ctrl.assert_called_with(mock_vm, self._FAKE_CTRL_ADDR)
|
||||
self.assertTrue(mock_get_new_rsd.called)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
|
||||
def test_create_scsi_controller(self, mock_get_new_rsd):
|
||||
mock_vm = self._lookup_vm()
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_add_virt_resource') as mock_add_virt_res:
|
||||
self._vmutils.create_scsi_controller(self._FAKE_VM_NAME)
|
||||
|
||||
mock_add_virt_res.assert_called_with(mock_get_new_rsd.return_value,
|
||||
mock_vm.path_.return_value)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_new_resource_setting_data')
|
||||
def test_attach_volume_to_controller(self, mock_get_new_rsd):
|
||||
mock_vm = self._lookup_vm()
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_add_virt_resource') as mock_add_virt_res:
|
||||
self._vmutils.attach_volume_to_controller(
|
||||
self._FAKE_VM_NAME, self._FAKE_CTRL_PATH, self._FAKE_CTRL_ADDR,
|
||||
self._FAKE_MOUNTED_DISK_PATH)
|
||||
|
||||
mock_add_virt_res.assert_called_with(mock_get_new_rsd.return_value,
|
||||
mock_vm.path_.return_value)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_modify_virt_resource')
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_nic_data_by_name')
|
||||
def test_set_nic_connection(self, mock_get_nic_conn, mock_modify_virt_res):
|
||||
self._lookup_vm()
|
||||
mock_nic = mock_get_nic_conn.return_value
|
||||
self._vmutils.set_nic_connection(self._FAKE_VM_NAME, None, None)
|
||||
|
||||
mock_modify_virt_res.assert_called_with(mock_nic, self._FAKE_VM_PATH)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_get_new_setting_data')
|
||||
def test_create_nic(self, mock_get_new_virt_res):
|
||||
self._lookup_vm()
|
||||
mock_nic = mock_get_new_virt_res.return_value
|
||||
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_add_virt_resource') as mock_add_virt_res:
|
||||
self._vmutils.create_nic(
|
||||
self._FAKE_VM_NAME, self._FAKE_RES_NAME, self._FAKE_ADDRESS)
|
||||
|
||||
mock_add_virt_res.assert_called_with(mock_nic, self._FAKE_VM_PATH)
|
||||
|
||||
def test_set_vm_state(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vm.RequestStateChange.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vmutils.set_vm_state(self._FAKE_VM_NAME,
|
||||
constants.HYPERV_VM_STATE_ENABLED)
|
||||
mock_vm.RequestStateChange.assert_called_with(
|
||||
constants.HYPERV_VM_STATE_ENABLED)
|
||||
|
||||
def test_destroy_vm(self):
|
||||
self._lookup_vm()
|
||||
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
getattr(mock_svc, self._DESTROY_SYSTEM).return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vmutils.destroy_vm(self._FAKE_VM_NAME)
|
||||
|
||||
getattr(mock_svc, self._DESTROY_SYSTEM).assert_called_with(
|
||||
self._FAKE_VM_PATH)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, '_wait_for_job')
|
||||
def test_check_ret_val_ok(self, mock_wait_for_job):
|
||||
self._vmutils.check_ret_val(constants.WMI_JOB_STATUS_STARTED,
|
||||
self._FAKE_JOB_PATH)
|
||||
mock_wait_for_job.assert_called_once_with(self._FAKE_JOB_PATH)
|
||||
|
||||
def test_check_ret_val_exception(self):
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils.check_ret_val,
|
||||
self._FAKE_RET_VAL_BAD,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_wait_for_job_done(self):
|
||||
mockjob = self._prepare_wait_for_job(constants.WMI_JOB_STATE_COMPLETED)
|
||||
job = self._vmutils._wait_for_job(self._FAKE_JOB_PATH)
|
||||
self.assertEqual(mockjob, job)
|
||||
|
||||
def test_wait_for_job_exception_concrete_job(self):
|
||||
mock_job = self._prepare_wait_for_job()
|
||||
mock_job.path.return_value.Class = self._CONCRETE_JOB
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils._wait_for_job,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_wait_for_job_exception_with_error(self):
|
||||
mock_job = self._prepare_wait_for_job()
|
||||
mock_job.GetError.return_value = (self._FAKE_ERROR, self._FAKE_RET_VAL)
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils._wait_for_job,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_wait_for_job_exception_no_error(self):
|
||||
mock_job = self._prepare_wait_for_job()
|
||||
mock_job.GetError.return_value = (None, None)
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._vmutils._wait_for_job,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def _prepare_wait_for_job(self, state=_FAKE_JOB_STATUS_BAD):
|
||||
mock_job = mock.MagicMock()
|
||||
mock_job.JobState = state
|
||||
mock_job.Description = self._FAKE_JOB_DESCRIPTION
|
||||
mock_job.ElapsedTime = self._FAKE_ELAPSED_TIME
|
||||
|
||||
self._vmutils._get_wmi_obj = mock.MagicMock(return_value=mock_job)
|
||||
return mock_job
|
||||
|
||||
def test_add_virt_resource(self):
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
getattr(mock_svc, self._ADD_RESOURCE).return_value = (
|
||||
self._FAKE_JOB_PATH, mock.MagicMock(), self._FAKE_RET_VAL)
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
|
||||
|
||||
self._vmutils._add_virt_resource(mock_res_setting_data,
|
||||
self._FAKE_VM_PATH)
|
||||
self._assert_add_resources(mock_svc)
|
||||
|
||||
def test_modify_virt_resource(self):
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
mock_svc.ModifyVirtualSystemResources.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
|
||||
|
||||
self._vmutils._modify_virt_resource(mock_res_setting_data,
|
||||
self._FAKE_VM_PATH)
|
||||
|
||||
mock_svc.ModifyVirtualSystemResources.assert_called_with(
|
||||
ResourceSettingData=[self._FAKE_RES_DATA],
|
||||
ComputerSystem=self._FAKE_VM_PATH)
|
||||
|
||||
def test_remove_virt_resource(self):
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
getattr(mock_svc, self._REMOVE_RESOURCE).return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.path_.return_value = self._FAKE_RES_PATH
|
||||
|
||||
self._vmutils._remove_virt_resource(mock_res_setting_data,
|
||||
self._FAKE_VM_PATH)
|
||||
self._assert_remove_resources(mock_svc)
|
||||
|
||||
def test_set_disk_host_resource(self):
|
||||
self._lookup_vm()
|
||||
mock_rasds = self._create_mock_disks()
|
||||
|
||||
self._vmutils._get_vm_disks = mock.MagicMock(
|
||||
return_value=([mock_rasds[0]], [mock_rasds[1]]))
|
||||
self._vmutils._modify_virt_resource = mock.MagicMock()
|
||||
self._vmutils._get_disk_resource_address = mock.MagicMock(
|
||||
return_value=self._FAKE_ADDRESS)
|
||||
|
||||
self._vmutils.set_disk_host_resource(
|
||||
self._FAKE_VM_NAME,
|
||||
self._FAKE_CTRL_PATH,
|
||||
self._FAKE_ADDRESS,
|
||||
mock.sentinel.fake_new_mounted_disk_path)
|
||||
self._vmutils._get_disk_resource_address.assert_called_with(
|
||||
mock_rasds[0])
|
||||
self._vmutils._modify_virt_resource.assert_called_with(
|
||||
mock_rasds[0], self._FAKE_VM_PATH)
|
||||
self.assertEqual(
|
||||
mock.sentinel.fake_new_mounted_disk_path,
|
||||
mock_rasds[0].HostResource[0])
|
||||
|
||||
@mock.patch.object(vmutils, 'wmi', create=True)
|
||||
@mock.patch.object(vmutils.VMUtils, 'check_ret_val')
|
||||
def test_take_vm_snapshot(self, mock_check_ret_val, mock_wmi):
|
||||
self._lookup_vm()
|
||||
|
||||
mock_svc = self._get_snapshot_service()
|
||||
mock_svc.CreateVirtualSystemSnapshot.return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL, mock.MagicMock())
|
||||
|
||||
self._vmutils.take_vm_snapshot(self._FAKE_VM_NAME)
|
||||
|
||||
mock_svc.CreateVirtualSystemSnapshot.assert_called_with(
|
||||
self._FAKE_VM_PATH)
|
||||
|
||||
mock_check_ret_val.assert_called_once_with(self._FAKE_RET_VAL,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
def test_remove_vm_snapshot(self):
|
||||
mock_svc = self._get_snapshot_service()
|
||||
getattr(mock_svc, self._DESTROY_SNAPSHOT).return_value = (
|
||||
self._FAKE_JOB_PATH, self._FAKE_RET_VAL)
|
||||
|
||||
self._vmutils.remove_vm_snapshot(self._FAKE_SNAPSHOT_PATH)
|
||||
getattr(mock_svc, self._DESTROY_SNAPSHOT).assert_called_with(
|
||||
self._FAKE_SNAPSHOT_PATH)
|
||||
|
||||
def test_detach_vm_disk(self):
|
||||
self._lookup_vm()
|
||||
mock_disk = self._prepare_mock_disk()
|
||||
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_remove_virt_resource') as mock_rm_virt_res:
|
||||
self._vmutils.detach_vm_disk(self._FAKE_VM_NAME,
|
||||
self._FAKE_HOST_RESOURCE)
|
||||
|
||||
mock_rm_virt_res.assert_called_with(mock_disk, self._FAKE_VM_PATH)
|
||||
|
||||
def test_get_mounted_disk_resource_from_path(self):
|
||||
mock_disk_1 = mock.MagicMock()
|
||||
mock_disk_2 = mock.MagicMock()
|
||||
mock_disk_2.HostResource = [self._FAKE_MOUNTED_DISK_PATH]
|
||||
self._vmutils._conn.query.return_value = [mock_disk_1, mock_disk_2]
|
||||
|
||||
physical_disk = self._vmutils._get_mounted_disk_resource_from_path(
|
||||
self._FAKE_MOUNTED_DISK_PATH, True)
|
||||
|
||||
self.assertEqual(mock_disk_2, physical_disk)
|
||||
|
||||
def test_get_controller_volume_paths(self):
|
||||
self._prepare_mock_disk()
|
||||
mock_disks = {self._FAKE_RES_PATH: self._FAKE_HOST_RESOURCE}
|
||||
disks = self._vmutils.get_controller_volume_paths(self._FAKE_RES_PATH)
|
||||
self.assertEqual(mock_disks, disks)
|
||||
|
||||
def _prepare_mock_disk(self):
|
||||
mock_disk = mock.MagicMock()
|
||||
mock_disk.HostResource = [self._FAKE_HOST_RESOURCE]
|
||||
mock_disk.path.return_value.RelPath = self._FAKE_RES_PATH
|
||||
mock_disk.ResourceSubType = self._vmutils._HARD_DISK_RES_SUB_TYPE
|
||||
self._vmutils._conn.query.return_value = [mock_disk]
|
||||
|
||||
return mock_disk
|
||||
|
||||
def _get_snapshot_service(self):
|
||||
return self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
def _assert_add_resources(self, mock_svc):
|
||||
getattr(mock_svc, self._ADD_RESOURCE).assert_called_with(
|
||||
[self._FAKE_RES_DATA], self._FAKE_VM_PATH)
|
||||
|
||||
def _assert_remove_resources(self, mock_svc):
|
||||
getattr(mock_svc, self._REMOVE_RESOURCE).assert_called_with(
|
||||
[self._FAKE_RES_PATH], self._FAKE_VM_PATH)
|
||||
|
||||
def test_get_active_instances(self):
|
||||
fake_vm = mock.MagicMock()
|
||||
|
||||
type(fake_vm).ElementName = mock.PropertyMock(
|
||||
side_effect=['active_vm', 'inactive_vm'])
|
||||
type(fake_vm).EnabledState = mock.PropertyMock(
|
||||
side_effect=[constants.HYPERV_VM_STATE_ENABLED,
|
||||
constants.HYPERV_VM_STATE_DISABLED])
|
||||
self._vmutils.list_instances = mock.MagicMock(
|
||||
return_value=[mock.sentinel.fake_vm_name] * 2)
|
||||
self._vmutils._lookup_vm = mock.MagicMock(side_effect=[fake_vm] * 2)
|
||||
active_instances = self._vmutils.get_active_instances()
|
||||
|
||||
self.assertEqual(['active_vm'], active_instances)
|
||||
|
||||
def _test_get_vm_serial_port_connection(self, new_connection=None):
|
||||
old_serial_connection = 'old_serial_connection'
|
||||
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_vmsettings = [mock.MagicMock()]
|
||||
mock_vm.associators.return_value = mock_vmsettings
|
||||
|
||||
fake_serial_port = mock.MagicMock()
|
||||
|
||||
fake_serial_port.ResourceSubType = (
|
||||
self._vmutils._SERIAL_PORT_RES_SUB_TYPE)
|
||||
fake_serial_port.Connection = [old_serial_connection]
|
||||
mock_rasds = [fake_serial_port]
|
||||
mock_vmsettings[0].associators.return_value = mock_rasds
|
||||
self._vmutils._modify_virt_resource = mock.MagicMock()
|
||||
fake_modify = self._vmutils._modify_virt_resource
|
||||
|
||||
ret_val = self._vmutils.get_vm_serial_port_connection(
|
||||
self._FAKE_VM_NAME, update_connection=new_connection)
|
||||
|
||||
if new_connection:
|
||||
self.assertEqual(new_connection, ret_val)
|
||||
fake_modify.assert_called_once_with(fake_serial_port,
|
||||
mock_vm.path_())
|
||||
else:
|
||||
self.assertEqual(old_serial_connection, ret_val)
|
||||
|
||||
def test_set_vm_serial_port_connection(self):
|
||||
self._test_get_vm_serial_port_connection('new_serial_connection')
|
||||
|
||||
def test_get_vm_serial_port_connection(self):
|
||||
self._test_get_vm_serial_port_connection()
|
||||
|
||||
def test_list_instance_notes(self):
|
||||
vs = mock.MagicMock()
|
||||
attrs = {'ElementName': 'fake_name',
|
||||
'Notes': '4f54fb69-d3a2-45b7-bb9b-b6e6b3d893b3'}
|
||||
vs.configure_mock(**attrs)
|
||||
vs2 = mock.MagicMock(ElementName='fake_name2', Notes=None)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs,
|
||||
vs2]
|
||||
response = self._vmutils.list_instance_notes()
|
||||
|
||||
self.assertEqual([(attrs['ElementName'], [attrs['Notes']])], response)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
|
||||
['ElementName', 'Notes'],
|
||||
SettingType=self._vmutils._VIRTUAL_SYSTEM_CURRENT_SETTINGS)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils.check_ret_val')
|
||||
def test_modify_virtual_system(self, mock_check_ret_val):
|
||||
mock_vs_man_svc = mock.MagicMock()
|
||||
mock_vmsetting = mock.MagicMock()
|
||||
fake_path = 'fake path'
|
||||
fake_job_path = 'fake job path'
|
||||
fake_ret_val = 'fake return value'
|
||||
|
||||
mock_vs_man_svc.ModifyVirtualSystem.return_value = (0, fake_job_path,
|
||||
fake_ret_val)
|
||||
|
||||
self._vmutils._modify_virtual_system(vs_man_svc=mock_vs_man_svc,
|
||||
vm_path=fake_path,
|
||||
vmsetting=mock_vmsetting)
|
||||
|
||||
mock_vs_man_svc.ModifyVirtualSystem.assert_called_once_with(
|
||||
ComputerSystem=fake_path,
|
||||
SystemSettingData=mock_vmsetting.GetText_(1))
|
||||
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils.check_ret_val')
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_wmi_obj')
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._modify_virtual_system')
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_setting_data')
|
||||
def test_create_vm_obj(self, mock_get_vm_setting_data,
|
||||
mock_modify_virtual_system,
|
||||
mock_get_wmi_obj, mock_check_ret_val):
|
||||
mock_vs_man_svc = mock.MagicMock()
|
||||
mock_vs_gs_data = mock.MagicMock()
|
||||
fake_vm_path = 'fake vm path'
|
||||
fake_job_path = 'fake job path'
|
||||
fake_ret_val = 'fake return value'
|
||||
_conn = self._vmutils._conn.Msvm_VirtualSystemGlobalSettingData
|
||||
|
||||
_conn.new.return_value = mock_vs_gs_data
|
||||
mock_vs_man_svc.DefineVirtualSystem.return_value = (fake_vm_path,
|
||||
fake_job_path,
|
||||
fake_ret_val)
|
||||
|
||||
response = self._vmutils._create_vm_obj(
|
||||
vs_man_svc=mock_vs_man_svc,
|
||||
vm_name='fake vm', vm_gen='fake vm gen',
|
||||
notes='fake notes', dynamic_memory_ratio=1.0,
|
||||
instance_path=mock.sentinel.instance_path)
|
||||
|
||||
_conn.new.assert_called_once_with()
|
||||
self.assertEqual(mock_vs_gs_data.ElementName, 'fake vm')
|
||||
mock_vs_man_svc.DefineVirtualSystem.assert_called_once_with(
|
||||
[], None, mock_vs_gs_data.GetText_(1))
|
||||
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_gs_data.ExternalDataRoot)
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_gs_data.SnapshotDataRoot)
|
||||
|
||||
mock_get_wmi_obj.assert_called_with(fake_vm_path)
|
||||
mock_get_vm_setting_data.assert_called_once_with(mock_get_wmi_obj())
|
||||
mock_modify_virtual_system.assert_called_once_with(
|
||||
mock_vs_man_svc, fake_vm_path, mock_get_vm_setting_data())
|
||||
|
||||
self.assertEqual(mock_get_vm_setting_data().Notes,
|
||||
'\n'.join('fake notes'))
|
||||
self.assertEqual(response, mock_get_wmi_obj())
|
||||
|
||||
def test_list_instances(self):
|
||||
vs = mock.MagicMock()
|
||||
attrs = {'ElementName': 'fake_name'}
|
||||
vs.configure_mock(**attrs)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs]
|
||||
response = self._vmutils.list_instances()
|
||||
|
||||
self.assertEqual([(attrs['ElementName'])], response)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
|
||||
['ElementName'],
|
||||
SettingType=self._vmutils._VIRTUAL_SYSTEM_CURRENT_SETTINGS)
|
||||
|
||||
@mock.patch.object(vmutils.VMUtils, "_clone_wmi_obj")
|
||||
def _test_check_clone_wmi_obj(self, mock_clone_wmi_obj, clone_objects):
|
||||
mock_obj = mock.MagicMock()
|
||||
self._vmutils._clone_wmi_objs = clone_objects
|
||||
|
||||
response = self._vmutils._check_clone_wmi_obj(class_name="fakeClass",
|
||||
obj=mock_obj)
|
||||
if not clone_objects:
|
||||
self.assertEqual(mock_obj, response)
|
||||
else:
|
||||
mock_clone_wmi_obj.assert_called_once_with("fakeClass", mock_obj)
|
||||
self.assertEqual(mock_clone_wmi_obj.return_value, response)
|
||||
|
||||
def test_check_clone_wmi_obj_true(self):
|
||||
self._test_check_clone_wmi_obj(clone_objects=True)
|
||||
|
||||
def test_check_clone_wmi_obj_false(self):
|
||||
self._test_check_clone_wmi_obj(clone_objects=False)
|
||||
|
||||
def test_clone_wmi_obj(self):
|
||||
mock_obj = mock.MagicMock()
|
||||
mock_value = mock.MagicMock()
|
||||
mock_value.Value = mock.sentinel.fake_value
|
||||
mock_obj._properties = [mock.sentinel.property]
|
||||
mock_obj.Properties_.Item.return_value = mock_value
|
||||
|
||||
response = self._vmutils._clone_wmi_obj(
|
||||
class_name="FakeClass", obj=mock_obj)
|
||||
|
||||
compare = self._vmutils._conn.FakeClass.new()
|
||||
self.assertEqual(mock.sentinel.fake_value,
|
||||
compare.Properties_.Item().Value)
|
||||
self.assertEqual(compare, response)
|
||||
|
||||
def test_get_attached_disks(self):
|
||||
mock_scsi_ctrl_path = mock.MagicMock()
|
||||
expected_query = ("SELECT * FROM %(class_name)s "
|
||||
"WHERE (ResourceSubType='%(res_sub_type)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_virt)s')"
|
||||
" AND Parent='%(parent)s'" %
|
||||
{"class_name":
|
||||
self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS,
|
||||
"res_sub_type":
|
||||
self._vmutils._PHYS_DISK_RES_SUB_TYPE,
|
||||
"res_sub_type_virt":
|
||||
self._vmutils._DISK_DRIVE_RES_SUB_TYPE,
|
||||
"parent":
|
||||
mock_scsi_ctrl_path.replace("'", "''")})
|
||||
expected_disks = self._vmutils._conn.query.return_value
|
||||
|
||||
ret_disks = self._vmutils.get_attached_disks(mock_scsi_ctrl_path)
|
||||
|
||||
self._vmutils._conn.query.assert_called_once_with(expected_query)
|
||||
self.assertEqual(expected_disks, ret_disks)
|
||||
|
||||
def _get_fake_instance_notes(self):
|
||||
return self._FAKE_VM_UUID
|
||||
|
||||
def test_instance_notes(self):
|
||||
self._lookup_vm()
|
||||
mock_vm_settings = mock.Mock()
|
||||
mock_vm_settings.Notes = self._get_fake_instance_notes()
|
||||
self._vmutils._get_vm_setting_data = mock.Mock(
|
||||
return_value=mock_vm_settings)
|
||||
|
||||
notes = self._vmutils._get_instance_notes(mock.sentinel.vm_name)
|
||||
|
||||
self.assertEqual(notes[0], self._FAKE_VM_UUID)
|
||||
|
||||
def test_get_event_wql_query(self):
|
||||
cls = self._vmutils._COMPUTER_SYSTEM_CLASS
|
||||
field = self._vmutils._VM_ENABLED_STATE_PROP
|
||||
timeframe = 10
|
||||
filtered_states = [constants.HYPERV_VM_STATE_ENABLED,
|
||||
constants.HYPERV_VM_STATE_DISABLED]
|
||||
|
||||
expected_checks = ' OR '.join(
|
||||
["TargetInstance.%s = '%s'" % (field, state)
|
||||
for state in filtered_states])
|
||||
expected_query = (
|
||||
"SELECT %(field)s, TargetInstance "
|
||||
"FROM __InstanceModificationEvent "
|
||||
"WITHIN %(timeframe)s "
|
||||
"WHERE TargetInstance ISA '%(class)s' "
|
||||
"AND TargetInstance.%(field)s != "
|
||||
"PreviousInstance.%(field)s "
|
||||
"AND (%(checks)s)" %
|
||||
{'class': cls,
|
||||
'field': field,
|
||||
'timeframe': timeframe,
|
||||
'checks': expected_checks})
|
||||
|
||||
query = self._vmutils._get_event_wql_query(
|
||||
cls=cls, field=field, timeframe=timeframe,
|
||||
filtered_states=filtered_states)
|
||||
self.assertEqual(expected_query, query)
|
||||
|
||||
def test_get_vm_power_state_change_listener(self):
|
||||
with mock.patch.object(self._vmutils,
|
||||
'_get_event_wql_query') as mock_get_query:
|
||||
listener = self._vmutils.get_vm_power_state_change_listener(
|
||||
mock.sentinel.timeframe,
|
||||
mock.sentinel.filtered_states)
|
||||
|
||||
mock_get_query.assert_called_once_with(
|
||||
cls=self._vmutils._COMPUTER_SYSTEM_CLASS,
|
||||
field=self._vmutils._VM_ENABLED_STATE_PROP,
|
||||
timeframe=mock.sentinel.timeframe,
|
||||
filtered_states=mock.sentinel.filtered_states)
|
||||
watcher = self._vmutils._conn.Msvm_ComputerSystem.watch_for
|
||||
watcher.assert_called_once_with(
|
||||
raw_wql=mock_get_query.return_value,
|
||||
fields=[self._vmutils._VM_ENABLED_STATE_PROP])
|
||||
|
||||
self.assertEqual(watcher.return_value, listener)
|
265
oslo_windows/tests/utils/test_vmutilsv2.py
Normal file
265
oslo_windows/tests/utils/test_vmutilsv2.py
Normal file
@ -0,0 +1,265 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from nova.tests.unit.virt.hyperv import test_vmutils
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vmutilsv2
|
||||
|
||||
|
||||
class VMUtilsV2TestCase(test_vmutils.VMUtilsTestCase):
|
||||
"""Unit tests for the Hyper-V VMUtilsV2 class."""
|
||||
|
||||
_DEFINE_SYSTEM = 'DefineSystem'
|
||||
_DESTROY_SYSTEM = 'DestroySystem'
|
||||
_DESTROY_SNAPSHOT = 'DestroySnapshot'
|
||||
|
||||
_ADD_RESOURCE = 'AddResourceSettings'
|
||||
_REMOVE_RESOURCE = 'RemoveResourceSettings'
|
||||
_SETTING_TYPE = 'VirtualSystemType'
|
||||
_VM_GEN = constants.VM_GEN_2
|
||||
|
||||
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
|
||||
|
||||
def setUp(self):
|
||||
super(VMUtilsV2TestCase, self).setUp()
|
||||
self._vmutils = vmutilsv2.VMUtilsV2()
|
||||
self._vmutils._conn = mock.MagicMock()
|
||||
|
||||
def test_create_vm(self):
|
||||
super(VMUtilsV2TestCase, self).test_create_vm()
|
||||
mock_vssd = self._vmutils._conn.Msvm_VirtualSystemSettingData.new()
|
||||
self.assertEqual(self._vmutils._VIRTUAL_SYSTEM_SUBTYPE_GEN2,
|
||||
mock_vssd.VirtualSystemSubType)
|
||||
self.assertFalse(mock_vssd.SecureBootEnabled)
|
||||
|
||||
def test_modify_virt_resource(self):
|
||||
mock_svc = self._vmutils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
mock_svc.ModifyResourceSettings.return_value = (self._FAKE_JOB_PATH,
|
||||
mock.MagicMock(),
|
||||
self._FAKE_RET_VAL)
|
||||
mock_res_setting_data = mock.MagicMock()
|
||||
mock_res_setting_data.GetText_.return_value = self._FAKE_RES_DATA
|
||||
|
||||
self._vmutils._modify_virt_resource(mock_res_setting_data,
|
||||
self._FAKE_VM_PATH)
|
||||
|
||||
mock_svc.ModifyResourceSettings.assert_called_with(
|
||||
ResourceSettings=[self._FAKE_RES_DATA])
|
||||
|
||||
@mock.patch.object(vmutilsv2, 'wmi', create=True)
|
||||
@mock.patch.object(vmutilsv2.VMUtilsV2, 'check_ret_val')
|
||||
def test_take_vm_snapshot(self, mock_check_ret_val, mock_wmi):
|
||||
self._lookup_vm()
|
||||
|
||||
mock_svc = self._get_snapshot_service()
|
||||
mock_svc.CreateSnapshot.return_value = (self._FAKE_JOB_PATH,
|
||||
mock.MagicMock(),
|
||||
self._FAKE_RET_VAL)
|
||||
|
||||
self._vmutils.take_vm_snapshot(self._FAKE_VM_NAME)
|
||||
|
||||
mock_svc.CreateSnapshot.assert_called_with(
|
||||
AffectedSystem=self._FAKE_VM_PATH,
|
||||
SnapshotType=self._vmutils._SNAPSHOT_FULL)
|
||||
|
||||
mock_check_ret_val.assert_called_once_with(self._FAKE_RET_VAL,
|
||||
self._FAKE_JOB_PATH)
|
||||
|
||||
@mock.patch.object(vmutilsv2.VMUtilsV2, '_add_virt_resource')
|
||||
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_new_setting_data')
|
||||
@mock.patch.object(vmutilsv2.VMUtilsV2, '_get_nic_data_by_name')
|
||||
def test_set_nic_connection(self, mock_get_nic_data, mock_get_new_sd,
|
||||
mock_add_virt_res):
|
||||
self._lookup_vm()
|
||||
fake_eth_port = mock_get_new_sd.return_value
|
||||
|
||||
self._vmutils.set_nic_connection(self._FAKE_VM_NAME, None, None)
|
||||
mock_add_virt_res.assert_called_with(fake_eth_port, self._FAKE_VM_PATH)
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmutils.VMUtils._get_vm_disks')
|
||||
def test_enable_vm_metrics_collection(self, mock_get_vm_disks):
|
||||
self._lookup_vm()
|
||||
mock_svc = self._vmutils._conn.Msvm_MetricService()[0]
|
||||
|
||||
metric_def = mock.MagicMock()
|
||||
mock_disk = mock.MagicMock()
|
||||
mock_disk.path_.return_value = self._FAKE_RES_PATH
|
||||
mock_get_vm_disks.return_value = ([mock_disk], [mock_disk])
|
||||
|
||||
fake_metric_def_paths = ['fake_0', 'fake_0', None]
|
||||
fake_metric_resource_paths = [self._FAKE_VM_PATH,
|
||||
self._FAKE_VM_PATH,
|
||||
self._FAKE_RES_PATH]
|
||||
|
||||
metric_def.path_.side_effect = fake_metric_def_paths
|
||||
self._vmutils._conn.CIM_BaseMetricDefinition.return_value = [
|
||||
metric_def]
|
||||
|
||||
self._vmutils.enable_vm_metrics_collection(self._FAKE_VM_NAME)
|
||||
|
||||
calls = [mock.call(Name=def_name)
|
||||
for def_name in [self._vmutils._METRIC_AGGR_CPU_AVG,
|
||||
self._vmutils._METRIC_AGGR_MEMORY_AVG]]
|
||||
self._vmutils._conn.CIM_BaseMetricDefinition.assert_has_calls(calls)
|
||||
|
||||
calls = []
|
||||
for i in range(len(fake_metric_def_paths)):
|
||||
calls.append(mock.call(
|
||||
Subject=fake_metric_resource_paths[i],
|
||||
Definition=fake_metric_def_paths[i],
|
||||
MetricCollectionEnabled=self._vmutils._METRIC_ENABLED))
|
||||
|
||||
mock_svc.ControlMetrics.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def _get_snapshot_service(self):
|
||||
return self._vmutils._conn.Msvm_VirtualSystemSnapshotService()[0]
|
||||
|
||||
def _assert_add_resources(self, mock_svc):
|
||||
getattr(mock_svc, self._ADD_RESOURCE).assert_called_with(
|
||||
self._FAKE_VM_PATH, [self._FAKE_RES_DATA])
|
||||
|
||||
def _assert_remove_resources(self, mock_svc):
|
||||
getattr(mock_svc, self._REMOVE_RESOURCE).assert_called_with(
|
||||
[self._FAKE_RES_PATH])
|
||||
|
||||
def test_list_instance_notes(self):
|
||||
vs = mock.MagicMock()
|
||||
attrs = {'ElementName': 'fake_name',
|
||||
'Notes': ['4f54fb69-d3a2-45b7-bb9b-b6e6b3d893b3']}
|
||||
vs.configure_mock(**attrs)
|
||||
vs2 = mock.MagicMock(ElementName='fake_name2', Notes=None)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs,
|
||||
vs2]
|
||||
response = self._vmutils.list_instance_notes()
|
||||
|
||||
self.assertEqual([(attrs['ElementName'], attrs['Notes'])], response)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
|
||||
['ElementName', 'Notes'],
|
||||
VirtualSystemType=self._vmutils._VIRTUAL_SYSTEM_TYPE_REALIZED)
|
||||
|
||||
def _get_fake_instance_notes(self):
|
||||
return [self._FAKE_VM_UUID]
|
||||
|
||||
@mock.patch('nova.virt.hyperv.vmutilsv2.VMUtilsV2.check_ret_val')
|
||||
@mock.patch('nova.virt.hyperv.vmutilsv2.VMUtilsV2._get_wmi_obj')
|
||||
def _test_create_vm_obj(self, mock_get_wmi_obj, mock_check_ret_val,
|
||||
vm_path, dynamic_memory_ratio=1.0):
|
||||
mock_vs_man_svc = mock.MagicMock()
|
||||
mock_vs_data = mock.MagicMock()
|
||||
mock_job = mock.MagicMock()
|
||||
fake_job_path = 'fake job path'
|
||||
fake_ret_val = 'fake return value'
|
||||
fake_vm_name = 'fake_vm_name'
|
||||
_conn = self._vmutils._conn.Msvm_VirtualSystemSettingData
|
||||
|
||||
mock_check_ret_val.return_value = mock_job
|
||||
_conn.new.return_value = mock_vs_data
|
||||
mock_vs_man_svc.DefineSystem.return_value = (fake_job_path,
|
||||
vm_path,
|
||||
fake_ret_val)
|
||||
mock_job.associators.return_value = ['fake vm path']
|
||||
|
||||
response = self._vmutils._create_vm_obj(
|
||||
vs_man_svc=mock_vs_man_svc,
|
||||
vm_name=fake_vm_name,
|
||||
vm_gen='fake vm gen',
|
||||
notes='fake notes',
|
||||
dynamic_memory_ratio=dynamic_memory_ratio,
|
||||
instance_path=mock.sentinel.instance_path)
|
||||
|
||||
if not vm_path:
|
||||
mock_job.associators.assert_called_once_with(
|
||||
self._vmutils._AFFECTED_JOB_ELEMENT_CLASS)
|
||||
|
||||
_conn.new.assert_called_once_with()
|
||||
self.assertEqual(mock_vs_data.ElementName, fake_vm_name)
|
||||
mock_vs_man_svc.DefineSystem.assert_called_once_with(
|
||||
ResourceSettings=[], ReferenceConfiguration=None,
|
||||
SystemSettings=mock_vs_data.GetText_(1))
|
||||
mock_check_ret_val.assert_called_once_with(fake_ret_val, fake_job_path)
|
||||
|
||||
if dynamic_memory_ratio > 1:
|
||||
self.assertFalse(mock_vs_data.VirtualNumaEnabled)
|
||||
|
||||
mock_get_wmi_obj.assert_called_with('fake vm path')
|
||||
|
||||
self.assertEqual(mock_vs_data.Notes, 'fake notes')
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_data.ConfigurationDataRoot)
|
||||
self.assertEqual(mock.sentinel.instance_path, mock_vs_data.LogDataRoot)
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_data.SnapshotDataRoot)
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_data.SuspendDataRoot)
|
||||
self.assertEqual(mock.sentinel.instance_path,
|
||||
mock_vs_data.SwapFileDataRoot)
|
||||
self.assertEqual(response, mock_get_wmi_obj())
|
||||
|
||||
def test_create_vm_obj(self):
|
||||
self._test_create_vm_obj(vm_path='fake vm path')
|
||||
|
||||
def test_create_vm_obj_no_vm_path(self):
|
||||
self._test_create_vm_obj(vm_path=None)
|
||||
|
||||
def test_create_vm_obj_dynamic_memory(self):
|
||||
self._test_create_vm_obj(vm_path=None, dynamic_memory_ratio=1.1)
|
||||
|
||||
def test_list_instances(self):
|
||||
vs = mock.MagicMock()
|
||||
attrs = {'ElementName': 'fake_name'}
|
||||
vs.configure_mock(**attrs)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.return_value = [vs]
|
||||
response = self._vmutils.list_instances()
|
||||
|
||||
self.assertEqual([(attrs['ElementName'])], response)
|
||||
self._vmutils._conn.Msvm_VirtualSystemSettingData.assert_called_with(
|
||||
['ElementName'],
|
||||
VirtualSystemType=self._vmutils._VIRTUAL_SYSTEM_TYPE_REALIZED)
|
||||
|
||||
def test_get_attached_disks(self):
|
||||
mock_scsi_ctrl_path = mock.MagicMock()
|
||||
expected_query = ("SELECT * FROM %(class_name)s "
|
||||
"WHERE (ResourceSubType='%(res_sub_type)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_virt)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_dvd)s') AND "
|
||||
"Parent = '%(parent)s'" %
|
||||
{"class_name":
|
||||
self._vmutils._RESOURCE_ALLOC_SETTING_DATA_CLASS,
|
||||
"res_sub_type":
|
||||
self._vmutils._PHYS_DISK_RES_SUB_TYPE,
|
||||
"res_sub_type_virt":
|
||||
self._vmutils._DISK_DRIVE_RES_SUB_TYPE,
|
||||
"res_sub_type_dvd":
|
||||
self._vmutils._DVD_DRIVE_RES_SUB_TYPE,
|
||||
"parent": mock_scsi_ctrl_path.replace("'", "''")})
|
||||
expected_disks = self._vmutils._conn.query.return_value
|
||||
|
||||
ret_disks = self._vmutils.get_attached_disks(mock_scsi_ctrl_path)
|
||||
|
||||
self._vmutils._conn.query.assert_called_once_with(expected_query)
|
||||
self.assertEqual(expected_disks, ret_disks)
|
||||
|
||||
def test_get_vm_dvd_disk_paths(self):
|
||||
mock_vm = self._lookup_vm()
|
||||
mock_sasd1 = mock.MagicMock(
|
||||
ResourceSubType=self._vmutils._DVD_DISK_RES_SUB_TYPE,
|
||||
HostResource=[mock.sentinel.FAKE_DVD_PATH1])
|
||||
mock_settings = mock.MagicMock()
|
||||
mock_settings.associators.return_value = [mock_sasd1]
|
||||
mock_vm.associators.return_value = [mock_settings]
|
||||
|
||||
ret_val = self._vmutils.get_vm_dvd_disk_paths(self._FAKE_VM_NAME)
|
||||
self.assertEqual(mock.sentinel.FAKE_DVD_PATH1, ret_val[0])
|
164
oslo_windows/tests/utils/test_volumeutils.py
Normal file
164
oslo_windows/tests/utils/test_volumeutils.py
Normal file
@ -0,0 +1,164 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from nova.tests.unit.virt.hyperv import test_basevolumeutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import volumeutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('volume_attach_retry_count', 'nova.virt.hyperv.volumeops',
|
||||
'hyperv')
|
||||
|
||||
|
||||
class VolumeUtilsTestCase(test_basevolumeutils.BaseVolumeUtilsTestCase):
|
||||
"""Unit tests for the Hyper-V VolumeUtils class."""
|
||||
|
||||
_FAKE_PORTAL_ADDR = '10.1.1.1'
|
||||
_FAKE_PORTAL_PORT = '3260'
|
||||
_FAKE_LUN = 0
|
||||
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
|
||||
|
||||
_FAKE_STDOUT_VALUE = 'The operation completed successfully'
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeUtilsTestCase, self).setUp()
|
||||
self._volutils = volumeutils.VolumeUtils()
|
||||
self._volutils._conn_wmi = mock.MagicMock()
|
||||
self._volutils._conn_cimv2 = mock.MagicMock()
|
||||
self.flags(volume_attach_retry_count=4, group='hyperv')
|
||||
self.flags(volume_attach_retry_interval=0, group='hyperv')
|
||||
|
||||
def _test_login_target_portal(self, portal_connected):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
|
||||
self._volutils.execute = mock.MagicMock()
|
||||
if portal_connected:
|
||||
exec_output = 'Address and Socket: %s %s' % (
|
||||
self._FAKE_PORTAL_ADDR, self._FAKE_PORTAL_PORT)
|
||||
else:
|
||||
exec_output = ''
|
||||
|
||||
self._volutils.execute.return_value = exec_output
|
||||
|
||||
self._volutils._login_target_portal(fake_portal)
|
||||
|
||||
call_list = self._volutils.execute.call_args_list
|
||||
all_call_args = [arg for call in call_list for arg in call[0]]
|
||||
|
||||
if portal_connected:
|
||||
self.assertIn('RefreshTargetPortal', all_call_args)
|
||||
else:
|
||||
self.assertIn('AddTargetPortal', all_call_args)
|
||||
|
||||
def test_login_connected_portal(self):
|
||||
self._test_login_target_portal(True)
|
||||
|
||||
def test_login_new_portal(self):
|
||||
self._test_login_target_portal(False)
|
||||
|
||||
def _test_login_target(self, target_connected=False, raise_exception=False,
|
||||
use_chap=False):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
self._volutils.execute = mock.MagicMock()
|
||||
self._volutils._login_target_portal = mock.MagicMock()
|
||||
|
||||
if use_chap:
|
||||
username, password = (mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
else:
|
||||
username, password = None, None
|
||||
|
||||
if target_connected:
|
||||
self._volutils.execute.return_value = self._FAKE_TARGET
|
||||
elif raise_exception:
|
||||
self._volutils.execute.return_value = ''
|
||||
else:
|
||||
self._volutils.execute.side_effect = (
|
||||
['', '', '', self._FAKE_TARGET, ''])
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._volutils.login_storage_target,
|
||||
self._FAKE_LUN, self._FAKE_TARGET,
|
||||
fake_portal, username, password)
|
||||
else:
|
||||
self._volutils.login_storage_target(self._FAKE_LUN,
|
||||
self._FAKE_TARGET,
|
||||
fake_portal,
|
||||
username, password)
|
||||
|
||||
if target_connected:
|
||||
call_list = self._volutils.execute.call_args_list
|
||||
all_call_args = [arg for call in call_list for arg in call[0]]
|
||||
self.assertNotIn('qlogintarget', all_call_args)
|
||||
else:
|
||||
self._volutils.execute.assert_any_call(
|
||||
'iscsicli.exe', 'qlogintarget',
|
||||
self._FAKE_TARGET, username, password)
|
||||
|
||||
def test_login_connected_target(self):
|
||||
self._test_login_target(target_connected=True)
|
||||
|
||||
def test_login_disconncted_target(self):
|
||||
self._test_login_target()
|
||||
|
||||
def test_login_target_exception(self):
|
||||
self._test_login_target(raise_exception=True)
|
||||
|
||||
def test_login_target_using_chap(self):
|
||||
self._test_login_target(use_chap=True)
|
||||
|
||||
def _test_execute_wrapper(self, raise_exception):
|
||||
fake_cmd = ('iscsicli.exe', 'ListTargetPortals')
|
||||
|
||||
if raise_exception:
|
||||
output = 'fake error'
|
||||
else:
|
||||
output = 'The operation completed successfully'
|
||||
|
||||
with mock.patch('nova.utils.execute') as fake_execute:
|
||||
fake_execute.return_value = (output, None)
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._volutils.execute,
|
||||
*fake_cmd)
|
||||
else:
|
||||
ret_val = self._volutils.execute(*fake_cmd)
|
||||
self.assertEqual(output, ret_val)
|
||||
|
||||
def test_execute_raise_exception(self):
|
||||
self._test_execute_wrapper(True)
|
||||
|
||||
def test_execute_exception(self):
|
||||
self._test_execute_wrapper(False)
|
||||
|
||||
@mock.patch.object(volumeutils, 'utils')
|
||||
def test_logout_storage_target(self, mock_utils):
|
||||
mock_utils.execute.return_value = (self._FAKE_STDOUT_VALUE,
|
||||
mock.sentinel.FAKE_STDERR_VALUE)
|
||||
session = mock.MagicMock()
|
||||
session.SessionId = mock.sentinel.FAKE_SESSION_ID
|
||||
self._volutils._conn_wmi.query.return_value = [session]
|
||||
|
||||
self._volutils.logout_storage_target(mock.sentinel.FAKE_IQN)
|
||||
mock_utils.execute.assert_called_once_with(
|
||||
'iscsicli.exe', 'logouttarget', mock.sentinel.FAKE_SESSION_ID)
|
164
oslo_windows/tests/utils/test_volumeutilsv2.py
Normal file
164
oslo_windows/tests/utils/test_volumeutilsv2.py
Normal file
@ -0,0 +1,164 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from nova import test
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import volumeutilsv2
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('volume_attach_retry_count', 'nova.virt.hyperv.volumeops',
|
||||
'hyperv')
|
||||
|
||||
|
||||
class VolumeUtilsV2TestCase(test.NoDBTestCase):
|
||||
"""Unit tests for the Hyper-V VolumeUtilsV2 class."""
|
||||
|
||||
_FAKE_PORTAL_ADDR = '10.1.1.1'
|
||||
_FAKE_PORTAL_PORT = '3260'
|
||||
_FAKE_LUN = 0
|
||||
_FAKE_TARGET = 'iqn.2010-10.org.openstack:fake_target'
|
||||
|
||||
def setUp(self):
|
||||
super(VolumeUtilsV2TestCase, self).setUp()
|
||||
self._volutilsv2 = volumeutilsv2.VolumeUtilsV2()
|
||||
self._volutilsv2._conn_storage = mock.MagicMock()
|
||||
self._volutilsv2._conn_wmi = mock.MagicMock()
|
||||
self.flags(volume_attach_retry_count=4, group='hyperv')
|
||||
self.flags(volume_attach_retry_interval=0, group='hyperv')
|
||||
|
||||
def _test_login_target_portal(self, portal_connected):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
fake_portal_object = mock.MagicMock()
|
||||
_query = self._volutilsv2._conn_storage.query
|
||||
self._volutilsv2._conn_storage.MSFT_iSCSITargetPortal = (
|
||||
fake_portal_object)
|
||||
|
||||
if portal_connected:
|
||||
_query.return_value = [fake_portal_object]
|
||||
else:
|
||||
_query.return_value = None
|
||||
|
||||
self._volutilsv2._login_target_portal(fake_portal)
|
||||
|
||||
if portal_connected:
|
||||
fake_portal_object.Update.assert_called_once_with()
|
||||
else:
|
||||
fake_portal_object.New.assert_called_once_with(
|
||||
TargetPortalAddress=self._FAKE_PORTAL_ADDR,
|
||||
TargetPortalPortNumber=self._FAKE_PORTAL_PORT)
|
||||
|
||||
def test_login_connected_portal(self):
|
||||
self._test_login_target_portal(True)
|
||||
|
||||
def test_login_new_portal(self):
|
||||
self._test_login_target_portal(False)
|
||||
|
||||
def _test_login_target(self, target_connected=False, raise_exception=False,
|
||||
use_chap=False):
|
||||
fake_portal = '%s:%s' % (self._FAKE_PORTAL_ADDR,
|
||||
self._FAKE_PORTAL_PORT)
|
||||
|
||||
fake_target_object = mock.MagicMock()
|
||||
|
||||
if target_connected:
|
||||
fake_target_object.IsConnected = True
|
||||
elif not raise_exception:
|
||||
type(fake_target_object).IsConnected = mock.PropertyMock(
|
||||
side_effect=[False, True])
|
||||
else:
|
||||
fake_target_object.IsConnected = False
|
||||
|
||||
_query = self._volutilsv2._conn_storage.query
|
||||
_query.return_value = [fake_target_object]
|
||||
|
||||
self._volutilsv2._conn_storage.MSFT_iSCSITarget = (
|
||||
fake_target_object)
|
||||
|
||||
if use_chap:
|
||||
username, password = (mock.sentinel.username,
|
||||
mock.sentinel.password)
|
||||
auth = {
|
||||
'AuthenticationType': self._volutilsv2._CHAP_AUTH_TYPE,
|
||||
'ChapUsername': username,
|
||||
'ChapSecret': password,
|
||||
}
|
||||
else:
|
||||
username, password = None, None
|
||||
auth = {}
|
||||
|
||||
if raise_exception:
|
||||
self.assertRaises(vmutils.HyperVException,
|
||||
self._volutilsv2.login_storage_target,
|
||||
self._FAKE_LUN, self._FAKE_TARGET, fake_portal)
|
||||
else:
|
||||
self._volutilsv2.login_storage_target(self._FAKE_LUN,
|
||||
self._FAKE_TARGET,
|
||||
fake_portal,
|
||||
username, password)
|
||||
|
||||
if target_connected:
|
||||
fake_target_object.Update.assert_called_with()
|
||||
else:
|
||||
fake_target_object.Connect.assert_called_once_with(
|
||||
IsPersistent=True, NodeAddress=self._FAKE_TARGET, **auth)
|
||||
|
||||
def test_login_connected_target(self):
|
||||
self._test_login_target(target_connected=True)
|
||||
|
||||
def test_login_disconncted_target(self):
|
||||
self._test_login_target()
|
||||
|
||||
def test_login_target_exception(self):
|
||||
self._test_login_target(raise_exception=True)
|
||||
|
||||
def test_login_target_using_chap(self):
|
||||
self._test_login_target(use_chap=True)
|
||||
|
||||
def test_logout_storage_target(self):
|
||||
mock_msft_target = self._volutilsv2._conn_storage.MSFT_iSCSITarget
|
||||
mock_msft_session = self._volutilsv2._conn_storage.MSFT_iSCSISession
|
||||
|
||||
mock_target = mock.MagicMock()
|
||||
mock_target.IsConnected = True
|
||||
mock_msft_target.return_value = [mock_target]
|
||||
|
||||
mock_session = mock.MagicMock()
|
||||
mock_session.IsPersistent = True
|
||||
mock_msft_session.return_value = [mock_session]
|
||||
|
||||
self._volutilsv2.logout_storage_target(self._FAKE_TARGET)
|
||||
|
||||
mock_msft_target.assert_called_once_with(NodeAddress=self._FAKE_TARGET)
|
||||
mock_msft_session.assert_called_once_with(
|
||||
TargetNodeAddress=self._FAKE_TARGET)
|
||||
|
||||
mock_session.Unregister.assert_called_once_with()
|
||||
mock_target.Disconnect.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(volumeutilsv2.VolumeUtilsV2, 'logout_storage_target')
|
||||
def test_execute_log_out(self, mock_logout_target):
|
||||
sess_class = self._volutilsv2._conn_wmi.MSiSCSIInitiator_SessionClass
|
||||
|
||||
mock_session = mock.MagicMock()
|
||||
sess_class.return_value = [mock_session]
|
||||
|
||||
self._volutilsv2.execute_log_out(mock.sentinel.FAKE_SESSION_ID)
|
||||
|
||||
sess_class.assert_called_once_with(
|
||||
SessionId=mock.sentinel.FAKE_SESSION_ID)
|
||||
mock_logout_target.assert_called_once_with(mock_session.TargetName)
|
0
oslo_windows/utils/__init__.py
Normal file
0
oslo_windows/utils/__init__.py
Normal file
149
oslo_windows/utils/basevolumeutils.py
Normal file
149
oslo_windows/utils/basevolumeutils.py
Normal file
@ -0,0 +1,149 @@
|
||||
#
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes,
|
||||
and storage repositories
|
||||
"""
|
||||
|
||||
import abc
|
||||
import re
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import _winreg
|
||||
import wmi
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova import block_device
|
||||
from nova.i18n import _LI
|
||||
from nova.virt import driver
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseVolumeUtils(object):
|
||||
_FILE_DEVICE_DISK = 7
|
||||
|
||||
def __init__(self, host='.'):
|
||||
if sys.platform == 'win32':
|
||||
self._conn_wmi = wmi.WMI(moniker='//%s/root/wmi' % host)
|
||||
self._conn_cimv2 = wmi.WMI(moniker='//%s/root/cimv2' % host)
|
||||
self._drive_number_regex = re.compile(r'DeviceID=\"[^,]*\\(\d+)\"')
|
||||
|
||||
@abc.abstractmethod
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def logout_storage_target(self, target_iqn):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute_log_out(self, session_id):
|
||||
pass
|
||||
|
||||
def get_iscsi_initiator(self):
|
||||
"""Get iscsi initiator name for this machine."""
|
||||
|
||||
computer_system = self._conn_cimv2.Win32_ComputerSystem()[0]
|
||||
hostname = computer_system.name
|
||||
keypath = ("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
|
||||
"iSCSI\\Discovery")
|
||||
try:
|
||||
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keypath, 0,
|
||||
_winreg.KEY_ALL_ACCESS)
|
||||
temp = _winreg.QueryValueEx(key, 'DefaultInitiatorName')
|
||||
initiator_name = str(temp[0])
|
||||
_winreg.CloseKey(key)
|
||||
except Exception:
|
||||
LOG.info(_LI("The ISCSI initiator name can't be found. "
|
||||
"Choosing the default one"))
|
||||
initiator_name = "iqn.1991-05.com.microsoft:" + hostname.lower()
|
||||
if computer_system.PartofDomain:
|
||||
initiator_name += '.' + computer_system.Domain.lower()
|
||||
return initiator_name
|
||||
|
||||
def volume_in_mapping(self, mount_device, block_device_info):
|
||||
block_device_list = [block_device.strip_dev(vol['mount_device'])
|
||||
for vol in
|
||||
driver.block_device_info_get_mapping(
|
||||
block_device_info)]
|
||||
swap = driver.block_device_info_get_swap(block_device_info)
|
||||
if driver.swap_is_usable(swap):
|
||||
block_device_list.append(
|
||||
block_device.strip_dev(swap['device_name']))
|
||||
block_device_list += [block_device.strip_dev(
|
||||
ephemeral['device_name'])
|
||||
for ephemeral in
|
||||
driver.block_device_info_get_ephemerals(block_device_info)]
|
||||
|
||||
LOG.debug("block_device_list %s", block_device_list)
|
||||
return block_device.strip_dev(mount_device) in block_device_list
|
||||
|
||||
def _get_drive_number_from_disk_path(self, disk_path):
|
||||
drive_number = self._drive_number_regex.findall(disk_path)
|
||||
if drive_number:
|
||||
return int(drive_number[0])
|
||||
|
||||
def get_session_id_from_mounted_disk(self, physical_drive_path):
|
||||
drive_number = self._get_drive_number_from_disk_path(
|
||||
physical_drive_path)
|
||||
if not drive_number:
|
||||
return None
|
||||
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
|
||||
for initiator_session in initiator_sessions:
|
||||
devices = initiator_session.Devices
|
||||
for device in devices:
|
||||
device_number = device.DeviceNumber
|
||||
if device_number == drive_number:
|
||||
return initiator_session.SessionId
|
||||
|
||||
def _get_devices_for_target(self, target_iqn):
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
|
||||
TargetName=target_iqn)
|
||||
if not initiator_sessions:
|
||||
return []
|
||||
|
||||
return initiator_sessions[0].Devices
|
||||
|
||||
def get_device_number_for_target(self, target_iqn, target_lun):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
|
||||
for device in devices:
|
||||
if device.ScsiLun == target_lun:
|
||||
return device.DeviceNumber
|
||||
|
||||
def get_target_lun_count(self, target_iqn):
|
||||
devices = self._get_devices_for_target(target_iqn)
|
||||
disk_devices = [device for device in devices
|
||||
if device.DeviceType == self._FILE_DEVICE_DISK]
|
||||
return len(disk_devices)
|
||||
|
||||
def get_target_from_disk_path(self, disk_path):
|
||||
initiator_sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass()
|
||||
drive_number = self._get_drive_number_from_disk_path(disk_path)
|
||||
if not drive_number:
|
||||
return None
|
||||
|
||||
for initiator_session in initiator_sessions:
|
||||
devices = initiator_session.Devices
|
||||
for device in devices:
|
||||
if device.DeviceNumber == drive_number:
|
||||
return (device.TargetName, device.ScsiLun)
|
102
oslo_windows/utils/constants.py
Normal file
102
oslo_windows/utils/constants.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Copyright 2012 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Constants used in ops classes
|
||||
"""
|
||||
|
||||
from nova.compute import arch
|
||||
from nova.compute import power_state
|
||||
|
||||
HYPERV_VM_STATE_OTHER = 1
|
||||
HYPERV_VM_STATE_ENABLED = 2
|
||||
HYPERV_VM_STATE_DISABLED = 3
|
||||
HYPERV_VM_STATE_SHUTTING_DOWN = 4
|
||||
HYPERV_VM_STATE_REBOOT = 10
|
||||
HYPERV_VM_STATE_PAUSED = 32768
|
||||
HYPERV_VM_STATE_SUSPENDED = 32769
|
||||
|
||||
HYPERV_POWER_STATE = {
|
||||
HYPERV_VM_STATE_DISABLED: power_state.SHUTDOWN,
|
||||
HYPERV_VM_STATE_SHUTTING_DOWN: power_state.SHUTDOWN,
|
||||
HYPERV_VM_STATE_ENABLED: power_state.RUNNING,
|
||||
HYPERV_VM_STATE_PAUSED: power_state.PAUSED,
|
||||
HYPERV_VM_STATE_SUSPENDED: power_state.SUSPENDED
|
||||
}
|
||||
|
||||
WMI_WIN32_PROCESSOR_ARCHITECTURE = {
|
||||
0: arch.I686,
|
||||
1: arch.MIPS,
|
||||
2: arch.ALPHA,
|
||||
3: arch.PPC,
|
||||
5: arch.ARMV7,
|
||||
6: arch.IA64,
|
||||
9: arch.X86_64,
|
||||
}
|
||||
|
||||
PROCESSOR_FEATURE = {
|
||||
7: '3dnow',
|
||||
3: 'mmx',
|
||||
12: 'nx',
|
||||
9: 'pae',
|
||||
8: 'rdtsc',
|
||||
20: 'slat',
|
||||
13: 'sse3',
|
||||
21: 'vmx',
|
||||
6: 'sse',
|
||||
10: 'sse2',
|
||||
17: 'xsave',
|
||||
}
|
||||
|
||||
WMI_JOB_STATUS_STARTED = 4096
|
||||
WMI_JOB_STATE_RUNNING = 4
|
||||
WMI_JOB_STATE_COMPLETED = 7
|
||||
|
||||
VM_SUMMARY_NUM_PROCS = 4
|
||||
VM_SUMMARY_ENABLED_STATE = 100
|
||||
VM_SUMMARY_MEMORY_USAGE = 103
|
||||
VM_SUMMARY_UPTIME = 105
|
||||
|
||||
CTRL_TYPE_IDE = "IDE"
|
||||
CTRL_TYPE_SCSI = "SCSI"
|
||||
|
||||
DISK = "VHD"
|
||||
DISK_FORMAT = DISK
|
||||
DVD = "DVD"
|
||||
DVD_FORMAT = "ISO"
|
||||
|
||||
DISK_FORMAT_MAP = {
|
||||
DISK_FORMAT.lower(): DISK,
|
||||
DVD_FORMAT.lower(): DVD
|
||||
}
|
||||
|
||||
DISK_FORMAT_VHD = "VHD"
|
||||
DISK_FORMAT_VHDX = "VHDX"
|
||||
|
||||
VHD_TYPE_FIXED = 2
|
||||
VHD_TYPE_DYNAMIC = 3
|
||||
|
||||
SCSI_CONTROLLER_SLOTS_NUMBER = 64
|
||||
|
||||
HOST_POWER_ACTION_SHUTDOWN = "shutdown"
|
||||
HOST_POWER_ACTION_REBOOT = "reboot"
|
||||
HOST_POWER_ACTION_STARTUP = "startup"
|
||||
|
||||
IMAGE_PROP_VM_GEN = "hw_machine_type"
|
||||
IMAGE_PROP_VM_GEN_1 = "hyperv-gen1"
|
||||
IMAGE_PROP_VM_GEN_2 = "hyperv-gen2"
|
||||
|
||||
VM_GEN_1 = 1
|
||||
VM_GEN_2 = 2
|
120
oslo_windows/utils/hostutils.py
Normal file
120
oslo_windows/utils/hostutils.py
Normal file
@ -0,0 +1,120 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ctypes
|
||||
import socket
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import constants
|
||||
|
||||
|
||||
class HostUtils(object):
|
||||
|
||||
_HOST_FORCED_REBOOT = 6
|
||||
_HOST_FORCED_SHUTDOWN = 12
|
||||
_DEFAULT_VM_GENERATION = constants.IMAGE_PROP_VM_GEN_1
|
||||
|
||||
def __init__(self):
|
||||
if sys.platform == 'win32':
|
||||
self._conn_cimv2 = wmi.WMI(privileges=["Shutdown"])
|
||||
self._init_wmi_virt_conn()
|
||||
|
||||
def _init_wmi_virt_conn(self):
|
||||
self._conn_virt = None
|
||||
|
||||
def get_cpus_info(self):
|
||||
cpus = self._conn_cimv2.query("SELECT * FROM Win32_Processor "
|
||||
"WHERE ProcessorType = 3")
|
||||
cpus_list = []
|
||||
for cpu in cpus:
|
||||
cpu_info = {'Architecture': cpu.Architecture,
|
||||
'Name': cpu.Name,
|
||||
'Manufacturer': cpu.Manufacturer,
|
||||
'NumberOfCores': cpu.NumberOfCores,
|
||||
'NumberOfLogicalProcessors':
|
||||
cpu.NumberOfLogicalProcessors}
|
||||
cpus_list.append(cpu_info)
|
||||
return cpus_list
|
||||
|
||||
def is_cpu_feature_present(self, feature_key):
|
||||
return ctypes.windll.kernel32.IsProcessorFeaturePresent(feature_key)
|
||||
|
||||
def get_memory_info(self):
|
||||
"""Returns a tuple with total visible memory and free physical memory
|
||||
expressed in kB.
|
||||
"""
|
||||
mem_info = self._conn_cimv2.query("SELECT TotalVisibleMemorySize, "
|
||||
"FreePhysicalMemory "
|
||||
"FROM win32_operatingsystem")[0]
|
||||
return (long(mem_info.TotalVisibleMemorySize),
|
||||
long(mem_info.FreePhysicalMemory))
|
||||
|
||||
def get_volume_info(self, drive):
|
||||
"""Returns a tuple with total size and free space
|
||||
expressed in bytes.
|
||||
"""
|
||||
logical_disk = self._conn_cimv2.query("SELECT Size, FreeSpace "
|
||||
"FROM win32_logicaldisk "
|
||||
"WHERE DeviceID='%s'"
|
||||
% drive)[0]
|
||||
return (long(logical_disk.Size), long(logical_disk.FreeSpace))
|
||||
|
||||
def check_min_windows_version(self, major, minor, build=0):
|
||||
version_str = self.get_windows_version()
|
||||
return map(int, version_str.split('.')) >= [major, minor, build]
|
||||
|
||||
def get_windows_version(self):
|
||||
return self._conn_cimv2.Win32_OperatingSystem()[0].Version
|
||||
|
||||
def get_local_ips(self):
|
||||
addr_info = socket.getaddrinfo(socket.gethostname(), None, 0, 0, 0)
|
||||
# Returns IPv4 and IPv6 addresses, ordered by protocol family
|
||||
addr_info.sort()
|
||||
return [a[4][0] for a in addr_info]
|
||||
|
||||
def get_host_tick_count64(self):
|
||||
return ctypes.windll.kernel32.GetTickCount64()
|
||||
|
||||
def host_power_action(self, action):
|
||||
win32_os = self._conn_cimv2.Win32_OperatingSystem()[0]
|
||||
|
||||
if action == constants.HOST_POWER_ACTION_SHUTDOWN:
|
||||
win32_os.Win32Shutdown(self._HOST_FORCED_SHUTDOWN)
|
||||
elif action == constants.HOST_POWER_ACTION_REBOOT:
|
||||
win32_os.Win32Shutdown(self._HOST_FORCED_REBOOT)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
_("Host %(action)s is not supported by the Hyper-V driver") %
|
||||
{"action": action})
|
||||
|
||||
def get_supported_vm_types(self):
|
||||
"""Get the supported Hyper-V VM generations.
|
||||
Hyper-V Generation 2 VMs are supported in Windows 8.1,
|
||||
Windows Server / Hyper-V Server 2012 R2 or newer.
|
||||
|
||||
:returns: array of supported VM generations (ex. ['hyperv-gen1'])
|
||||
"""
|
||||
if self.check_min_windows_version(6, 3):
|
||||
return [constants.IMAGE_PROP_VM_GEN_1,
|
||||
constants.IMAGE_PROP_VM_GEN_2]
|
||||
else:
|
||||
return [constants.IMAGE_PROP_VM_GEN_1]
|
||||
|
||||
def get_default_vm_generation(self):
|
||||
return self._DEFAULT_VM_GENERATION
|
34
oslo_windows/utils/hostutilsv2.py
Normal file
34
oslo_windows/utils/hostutilsv2.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright 2015 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from nova.virt.hyperv import hostutils
|
||||
|
||||
|
||||
class HostUtilsV2(hostutils.HostUtils):
|
||||
|
||||
FEATURE_RDS_VIRTUALIZATION = 322
|
||||
|
||||
def __init__(self):
|
||||
super(HostUtilsV2, self).__init__()
|
||||
self._init_wmi_virt_conn()
|
||||
|
||||
def _init_wmi_virt_conn(self):
|
||||
if sys.platform == 'win32':
|
||||
self._conn_virt = wmi.WMI(moniker='//./root/virtualization/v2')
|
73
oslo_windows/utils/ioutils.py
Normal file
73
oslo_windows/utils/ioutils.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright 2014 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import errno
|
||||
import os
|
||||
|
||||
from eventlet import patcher
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.i18n import _LE
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
native_threading = patcher.original('threading')
|
||||
|
||||
|
||||
class IOThread(native_threading.Thread):
|
||||
def __init__(self, src, dest, max_bytes):
|
||||
super(IOThread, self).__init__()
|
||||
self.setDaemon(True)
|
||||
self._src = src
|
||||
self._dest = dest
|
||||
self._dest_archive = dest + '.1'
|
||||
self._max_bytes = max_bytes
|
||||
self._stopped = native_threading.Event()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self._copy()
|
||||
except IOError as err:
|
||||
self._stopped.set()
|
||||
# Invalid argument error means that the vm console pipe was closed,
|
||||
# probably the vm was stopped. The worker can stop it's execution.
|
||||
if err.errno != errno.EINVAL:
|
||||
LOG.error(_LE("Error writing vm console log file from "
|
||||
"serial console pipe. Error: %s") % err)
|
||||
|
||||
def _copy(self):
|
||||
with open(self._src, 'rb') as src:
|
||||
with open(self._dest, 'ab', 0) as dest:
|
||||
dest.seek(0, os.SEEK_END)
|
||||
log_size = dest.tell()
|
||||
while (not self._stopped.isSet()):
|
||||
# Read one byte at a time to avoid blocking.
|
||||
data = src.read(1)
|
||||
dest.write(data)
|
||||
log_size += len(data)
|
||||
if (log_size >= self._max_bytes):
|
||||
dest.close()
|
||||
if os.path.exists(self._dest_archive):
|
||||
os.remove(self._dest_archive)
|
||||
os.rename(self._dest, self._dest_archive)
|
||||
dest = open(self._dest, 'ab', 0)
|
||||
log_size = 0
|
||||
|
||||
def join(self):
|
||||
self._stopped.set()
|
||||
super(IOThread, self).join()
|
||||
|
||||
def is_active(self):
|
||||
return not self._stopped.isSet()
|
252
oslo_windows/utils/livemigrationutils.py
Normal file
252
oslo_windows/utils/livemigrationutils.py
Normal file
@ -0,0 +1,252 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _, _LE
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import vmutilsv2
|
||||
from nova.virt.hyperv import volumeutilsv2
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LiveMigrationUtils(object):
|
||||
|
||||
def __init__(self):
|
||||
self._vmutils = vmutilsv2.VMUtilsV2()
|
||||
self._volutils = volumeutilsv2.VolumeUtilsV2()
|
||||
|
||||
def _get_conn_v2(self, host='localhost'):
|
||||
try:
|
||||
return wmi.WMI(moniker='//%s/root/virtualization/v2' % host)
|
||||
except wmi.x_wmi as ex:
|
||||
LOG.exception(_LE('Get version 2 connection error'))
|
||||
if ex.com_error.hresult == -2147217394:
|
||||
msg = (_('Live migration is not supported on target host "%s"')
|
||||
% host)
|
||||
elif ex.com_error.hresult == -2147023174:
|
||||
msg = (_('Target live migration host "%s" is unreachable')
|
||||
% host)
|
||||
else:
|
||||
msg = _('Live migration failed: %s') % ex.message
|
||||
raise vmutils.HyperVException(msg)
|
||||
|
||||
def check_live_migration_config(self):
|
||||
conn_v2 = self._get_conn_v2()
|
||||
migration_svc = conn_v2.Msvm_VirtualSystemMigrationService()[0]
|
||||
vsmssds = migration_svc.associators(
|
||||
wmi_association_class='Msvm_ElementSettingData',
|
||||
wmi_result_class='Msvm_VirtualSystemMigrationServiceSettingData')
|
||||
vsmssd = vsmssds[0]
|
||||
if not vsmssd.EnableVirtualSystemMigration:
|
||||
raise vmutils.HyperVException(
|
||||
_('Live migration is not enabled on this host'))
|
||||
if not migration_svc.MigrationServiceListenerIPAddressList:
|
||||
raise vmutils.HyperVException(
|
||||
_('Live migration networks are not configured on this host'))
|
||||
|
||||
def _get_vm(self, conn_v2, vm_name):
|
||||
vms = conn_v2.Msvm_ComputerSystem(ElementName=vm_name)
|
||||
n = len(vms)
|
||||
if not n:
|
||||
raise exception.NotFound(_('VM not found: %s') % vm_name)
|
||||
elif n > 1:
|
||||
raise vmutils.HyperVException(_('Duplicate VM name found: %s')
|
||||
% vm_name)
|
||||
return vms[0]
|
||||
|
||||
def _destroy_planned_vm(self, conn_v2_remote, planned_vm):
|
||||
LOG.debug("Destroying existing remote planned VM: %s",
|
||||
planned_vm.ElementName)
|
||||
vs_man_svc = conn_v2_remote.Msvm_VirtualSystemManagementService()[0]
|
||||
(job_path, ret_val) = vs_man_svc.DestroySystem(planned_vm.path_())
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _check_existing_planned_vm(self, conn_v2_remote, vm):
|
||||
# Make sure that there's not yet a remote planned VM on the target
|
||||
# host for this VM
|
||||
planned_vms = conn_v2_remote.Msvm_PlannedComputerSystem(Name=vm.Name)
|
||||
if planned_vms:
|
||||
self._destroy_planned_vm(conn_v2_remote, planned_vms[0])
|
||||
|
||||
def _create_remote_planned_vm(self, conn_v2_local, conn_v2_remote,
|
||||
vm, rmt_ip_addr_list, dest_host):
|
||||
# Staged
|
||||
vsmsd = conn_v2_local.query("select * from "
|
||||
"Msvm_VirtualSystemMigrationSettingData "
|
||||
"where MigrationType = 32770")[0]
|
||||
vsmsd.DestinationIPAddressList = rmt_ip_addr_list
|
||||
migration_setting_data = vsmsd.GetText_(1)
|
||||
|
||||
LOG.debug("Creating remote planned VM for VM: %s",
|
||||
vm.ElementName)
|
||||
migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
|
||||
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
|
||||
ComputerSystem=vm.path_(),
|
||||
DestinationHost=dest_host,
|
||||
MigrationSettingData=migration_setting_data)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
return conn_v2_remote.Msvm_PlannedComputerSystem(Name=vm.Name)[0]
|
||||
|
||||
def _get_physical_disk_paths(self, vm_name):
|
||||
ide_ctrl_path = self._vmutils.get_vm_ide_controller(vm_name, 0)
|
||||
if ide_ctrl_path:
|
||||
ide_paths = self._vmutils.get_controller_volume_paths(
|
||||
ide_ctrl_path)
|
||||
else:
|
||||
ide_paths = {}
|
||||
|
||||
scsi_ctrl_path = self._vmutils.get_vm_scsi_controller(vm_name)
|
||||
scsi_paths = self._vmutils.get_controller_volume_paths(scsi_ctrl_path)
|
||||
|
||||
return dict(ide_paths.items() + scsi_paths.items())
|
||||
|
||||
def _get_remote_disk_data(self, vmutils_remote, disk_paths, dest_host):
|
||||
volutils_remote = volumeutilsv2.VolumeUtilsV2(dest_host)
|
||||
|
||||
disk_paths_remote = {}
|
||||
for (rasd_rel_path, disk_path) in disk_paths.items():
|
||||
target = self._volutils.get_target_from_disk_path(disk_path)
|
||||
if target:
|
||||
(target_iqn, target_lun) = target
|
||||
|
||||
dev_num = volutils_remote.get_device_number_for_target(
|
||||
target_iqn, target_lun)
|
||||
disk_path_remote = (
|
||||
vmutils_remote.get_mounted_disk_by_drive_number(dev_num))
|
||||
|
||||
disk_paths_remote[rasd_rel_path] = disk_path_remote
|
||||
else:
|
||||
LOG.debug("Could not retrieve iSCSI target "
|
||||
"from disk path: %s", disk_path)
|
||||
|
||||
return disk_paths_remote
|
||||
|
||||
def _update_planned_vm_disk_resources(self, vmutils_remote, conn_v2_remote,
|
||||
planned_vm, vm_name,
|
||||
disk_paths_remote):
|
||||
vm_settings = planned_vm.associators(
|
||||
wmi_association_class='Msvm_SettingsDefineState',
|
||||
wmi_result_class='Msvm_VirtualSystemSettingData')[0]
|
||||
|
||||
updated_resource_setting_data = []
|
||||
sasds = vm_settings.associators(
|
||||
wmi_association_class='Msvm_VirtualSystemSettingDataComponent')
|
||||
for sasd in sasds:
|
||||
if (sasd.ResourceType == 17 and sasd.ResourceSubType ==
|
||||
"Microsoft:Hyper-V:Physical Disk Drive" and
|
||||
sasd.HostResource):
|
||||
# Replace the local disk target with the correct remote one
|
||||
old_disk_path = sasd.HostResource[0]
|
||||
new_disk_path = disk_paths_remote.pop(sasd.path().RelPath)
|
||||
|
||||
LOG.debug("Replacing host resource "
|
||||
"%(old_disk_path)s with "
|
||||
"%(new_disk_path)s on planned VM %(vm_name)s",
|
||||
{'old_disk_path': old_disk_path,
|
||||
'new_disk_path': new_disk_path,
|
||||
'vm_name': vm_name})
|
||||
sasd.HostResource = [new_disk_path]
|
||||
updated_resource_setting_data.append(sasd.GetText_(1))
|
||||
|
||||
LOG.debug("Updating remote planned VM disk paths for VM: %s",
|
||||
vm_name)
|
||||
vsmsvc = conn_v2_remote.Msvm_VirtualSystemManagementService()[0]
|
||||
(res_settings, job_path, ret_val) = vsmsvc.ModifyResourceSettings(
|
||||
ResourceSettings=updated_resource_setting_data)
|
||||
vmutils_remote.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_vhd_setting_data(self, vm):
|
||||
vm_settings = vm.associators(
|
||||
wmi_association_class='Msvm_SettingsDefineState',
|
||||
wmi_result_class='Msvm_VirtualSystemSettingData')[0]
|
||||
|
||||
new_resource_setting_data = []
|
||||
sasds = vm_settings.associators(
|
||||
wmi_association_class='Msvm_VirtualSystemSettingDataComponent',
|
||||
wmi_result_class='Msvm_StorageAllocationSettingData')
|
||||
for sasd in sasds:
|
||||
if (sasd.ResourceType == 31 and sasd.ResourceSubType ==
|
||||
"Microsoft:Hyper-V:Virtual Hard Disk"):
|
||||
new_resource_setting_data.append(sasd.GetText_(1))
|
||||
return new_resource_setting_data
|
||||
|
||||
def _live_migrate_vm(self, conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
|
||||
new_resource_setting_data, dest_host):
|
||||
# VirtualSystemAndStorage
|
||||
vsmsd = conn_v2_local.query("select * from "
|
||||
"Msvm_VirtualSystemMigrationSettingData "
|
||||
"where MigrationType = 32771")[0]
|
||||
vsmsd.DestinationIPAddressList = rmt_ip_addr_list
|
||||
if planned_vm:
|
||||
vsmsd.DestinationPlannedVirtualSystemId = planned_vm.Name
|
||||
migration_setting_data = vsmsd.GetText_(1)
|
||||
|
||||
migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
|
||||
|
||||
LOG.debug("Starting live migration for VM: %s", vm.ElementName)
|
||||
(job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
|
||||
ComputerSystem=vm.path_(),
|
||||
DestinationHost=dest_host,
|
||||
MigrationSettingData=migration_setting_data,
|
||||
NewResourceSettingData=new_resource_setting_data)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_remote_ip_address_list(self, conn_v2_remote, dest_host):
|
||||
LOG.debug("Getting live migration networks for remote host: %s",
|
||||
dest_host)
|
||||
migr_svc_rmt = conn_v2_remote.Msvm_VirtualSystemMigrationService()[0]
|
||||
return migr_svc_rmt.MigrationServiceListenerIPAddressList
|
||||
|
||||
def live_migrate_vm(self, vm_name, dest_host):
|
||||
self.check_live_migration_config()
|
||||
|
||||
conn_v2_local = self._get_conn_v2()
|
||||
conn_v2_remote = self._get_conn_v2(dest_host)
|
||||
|
||||
vm = self._get_vm(conn_v2_local, vm_name)
|
||||
self._check_existing_planned_vm(conn_v2_remote, vm)
|
||||
|
||||
rmt_ip_addr_list = self._get_remote_ip_address_list(conn_v2_remote,
|
||||
dest_host)
|
||||
|
||||
planned_vm = None
|
||||
disk_paths = self._get_physical_disk_paths(vm_name)
|
||||
if disk_paths:
|
||||
vmutils_remote = vmutilsv2.VMUtilsV2(dest_host)
|
||||
disk_paths_remote = self._get_remote_disk_data(vmutils_remote,
|
||||
disk_paths,
|
||||
dest_host)
|
||||
|
||||
planned_vm = self._create_remote_planned_vm(conn_v2_local,
|
||||
conn_v2_remote,
|
||||
vm, rmt_ip_addr_list,
|
||||
dest_host)
|
||||
|
||||
self._update_planned_vm_disk_resources(vmutils_remote,
|
||||
conn_v2_remote, planned_vm,
|
||||
vm_name, disk_paths_remote)
|
||||
|
||||
new_resource_setting_data = self._get_vhd_setting_data(vm)
|
||||
self._live_migrate_vm(conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
|
||||
new_resource_setting_data, dest_host)
|
68
oslo_windows/utils/networkutils.py
Normal file
68
oslo_windows/utils/networkutils.py
Normal file
@ -0,0 +1,68 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility class for network related operations.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class NetworkUtils(object):
|
||||
def __init__(self):
|
||||
if sys.platform == 'win32':
|
||||
self._conn = wmi.WMI(moniker='//./root/virtualization')
|
||||
|
||||
def get_external_vswitch(self, vswitch_name):
|
||||
if vswitch_name:
|
||||
vswitches = self._conn.Msvm_VirtualSwitch(ElementName=vswitch_name)
|
||||
else:
|
||||
# Find the vswitch that is connected to the first physical nic.
|
||||
ext_port = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]
|
||||
port = ext_port.associators(wmi_result_class='Msvm_SwitchPort')[0]
|
||||
vswitches = port.associators(wmi_result_class='Msvm_VirtualSwitch')
|
||||
|
||||
if not len(vswitches):
|
||||
raise vmutils.HyperVException(_('vswitch "%s" not found')
|
||||
% vswitch_name)
|
||||
return vswitches[0].path_()
|
||||
|
||||
def create_vswitch_port(self, vswitch_path, port_name):
|
||||
switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
|
||||
# Create a port on the vswitch.
|
||||
(new_port, ret_val) = switch_svc.CreateSwitchPort(
|
||||
Name=str(uuid.uuid4()),
|
||||
FriendlyName=port_name,
|
||||
ScopeOfResidence="",
|
||||
VirtualSwitch=vswitch_path)
|
||||
if ret_val != 0:
|
||||
raise vmutils.HyperVException(_("Failed to create vswitch port "
|
||||
"%(port_name)s on switch "
|
||||
"%(vswitch_path)s") %
|
||||
{'port_name': port_name,
|
||||
'vswitch_path': vswitch_path})
|
||||
return new_port
|
||||
|
||||
def vswitch_port_needed(self):
|
||||
# NOTE(alexpilotti): In WMI V2 the vswitch_path is set in the VM
|
||||
# setting data without the need for a vswitch port.
|
||||
return True
|
63
oslo_windows/utils/networkutilsv2.py
Normal file
63
oslo_windows/utils/networkutilsv2.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility class for network related operations.
|
||||
Based on the "root/virtualization/v2" namespace available starting with
|
||||
Hyper-V Server / Windows Server 2012.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import networkutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
class NetworkUtilsV2(networkutils.NetworkUtils):
|
||||
def __init__(self):
|
||||
if sys.platform == 'win32':
|
||||
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
|
||||
|
||||
def get_external_vswitch(self, vswitch_name):
|
||||
if vswitch_name:
|
||||
vswitches = self._conn.Msvm_VirtualEthernetSwitch(
|
||||
ElementName=vswitch_name)
|
||||
if not len(vswitches):
|
||||
raise vmutils.HyperVException(_('vswitch "%s" not found')
|
||||
% vswitch_name)
|
||||
else:
|
||||
# Find the vswitch that is connected to the first physical nic.
|
||||
ext_port = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]
|
||||
lep = ext_port.associators(wmi_result_class='Msvm_LANEndpoint')[0]
|
||||
lep1 = lep.associators(wmi_result_class='Msvm_LANEndpoint')[0]
|
||||
esw = lep1.associators(
|
||||
wmi_result_class='Msvm_EthernetSwitchPort')[0]
|
||||
vswitches = esw.associators(
|
||||
wmi_result_class='Msvm_VirtualEthernetSwitch')
|
||||
|
||||
if not len(vswitches):
|
||||
raise vmutils.HyperVException(_('No external vswitch found'))
|
||||
|
||||
return vswitches[0].path_()
|
||||
|
||||
def create_vswitch_port(self, vswitch_path, port_name):
|
||||
raise NotImplementedError()
|
||||
|
||||
def vswitch_port_needed(self):
|
||||
return False
|
271
oslo_windows/utils/pathutils.py
Normal file
271
oslo_windows/utils/pathutils.py
Normal file
@ -0,0 +1,271 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
hyperv_opts = [
|
||||
cfg.StrOpt('instances_path_share',
|
||||
default="",
|
||||
help='The name of a Windows share name mapped to the '
|
||||
'"instances_path" dir and used by the resize feature '
|
||||
'to copy files to the target host. If left blank, an '
|
||||
'administrative share will be used, looking for the same '
|
||||
'"instances_path" used locally'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(hyperv_opts, 'hyperv')
|
||||
CONF.import_opt('instances_path', 'nova.compute.manager')
|
||||
|
||||
ERROR_INVALID_NAME = 123
|
||||
ERROR_DIR_IS_NOT_EMPTY = 145
|
||||
|
||||
|
||||
class PathUtils(object):
|
||||
def __init__(self):
|
||||
self._smb_conn = wmi.WMI(moniker=r"root\Microsoft\Windows\SMB")
|
||||
|
||||
def open(self, path, mode):
|
||||
"""Wrapper on __builtin__.open used to simplify unit testing."""
|
||||
import __builtin__
|
||||
return __builtin__.open(path, mode)
|
||||
|
||||
def exists(self, path):
|
||||
return os.path.exists(path)
|
||||
|
||||
def makedirs(self, path):
|
||||
os.makedirs(path)
|
||||
|
||||
def remove(self, path):
|
||||
os.remove(path)
|
||||
|
||||
def rename(self, src, dest):
|
||||
os.rename(src, dest)
|
||||
|
||||
def copyfile(self, src, dest):
|
||||
self.copy(src, dest)
|
||||
|
||||
def copy(self, src, dest):
|
||||
# With large files this is 2x-3x faster than shutil.copy(src, dest),
|
||||
# especially when copying to a UNC target.
|
||||
# shutil.copyfileobj(...) with a proper buffer is better than
|
||||
# shutil.copy(...) but still 20% slower than a shell copy.
|
||||
# It can be replaced with Win32 API calls to avoid the process
|
||||
# spawning overhead.
|
||||
LOG.debug('Copying file from %s to %s', src, dest)
|
||||
output, ret = utils.execute('cmd.exe', '/C', 'copy', '/Y', src, dest)
|
||||
if ret:
|
||||
raise IOError(_('The file copy from %(src)s to %(dest)s failed')
|
||||
% {'src': src, 'dest': dest})
|
||||
|
||||
def move_folder_files(self, src_dir, dest_dir):
|
||||
"""Moves the files of the given src_dir to dest_dir.
|
||||
It will ignore any nested folders.
|
||||
|
||||
:param src_dir: Given folder from which to move files.
|
||||
:param dest_dir: Folder to which to move files.
|
||||
"""
|
||||
|
||||
for fname in os.listdir(src_dir):
|
||||
src = os.path.join(src_dir, fname)
|
||||
# ignore subdirs.
|
||||
if os.path.isfile(src):
|
||||
self.rename(src, os.path.join(dest_dir, fname))
|
||||
|
||||
def rmtree(self, path):
|
||||
# This will be removed once support for Windows Server 2008R2 is
|
||||
# stopped
|
||||
for i in range(5):
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
return
|
||||
except WindowsError as e:
|
||||
if e.winerror == ERROR_DIR_IS_NOT_EMPTY:
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise e
|
||||
|
||||
def get_instances_dir(self, remote_server=None):
|
||||
local_instance_path = os.path.normpath(CONF.instances_path)
|
||||
|
||||
if remote_server:
|
||||
if CONF.hyperv.instances_path_share:
|
||||
path = CONF.hyperv.instances_path_share
|
||||
else:
|
||||
# Use an administrative share
|
||||
path = local_instance_path.replace(':', '$')
|
||||
return ('\\\\%(remote_server)s\\%(path)s' %
|
||||
{'remote_server': remote_server, 'path': path})
|
||||
else:
|
||||
return local_instance_path
|
||||
|
||||
def _check_create_dir(self, path):
|
||||
if not self.exists(path):
|
||||
LOG.debug('Creating directory: %s', path)
|
||||
self.makedirs(path)
|
||||
|
||||
def _check_remove_dir(self, path):
|
||||
if self.exists(path):
|
||||
LOG.debug('Removing directory: %s', path)
|
||||
self.rmtree(path)
|
||||
|
||||
def _get_instances_sub_dir(self, dir_name, remote_server=None,
|
||||
create_dir=True, remove_dir=False):
|
||||
instances_path = self.get_instances_dir(remote_server)
|
||||
path = os.path.join(instances_path, dir_name)
|
||||
try:
|
||||
if remove_dir:
|
||||
self._check_remove_dir(path)
|
||||
if create_dir:
|
||||
self._check_create_dir(path)
|
||||
return path
|
||||
except WindowsError as ex:
|
||||
if ex.winerror == ERROR_INVALID_NAME:
|
||||
raise vmutils.HyperVException(_(
|
||||
"Cannot access \"%(instances_path)s\", make sure the "
|
||||
"path exists and that you have the proper permissions. "
|
||||
"In particular Nova-Compute must not be executed with the "
|
||||
"builtin SYSTEM account or other accounts unable to "
|
||||
"authenticate on a remote host.") %
|
||||
{'instances_path': instances_path})
|
||||
raise
|
||||
|
||||
def get_instance_migr_revert_dir(self, instance_name, create_dir=False,
|
||||
remove_dir=False):
|
||||
dir_name = '%s_revert' % instance_name
|
||||
return self._get_instances_sub_dir(dir_name, None, create_dir,
|
||||
remove_dir)
|
||||
|
||||
def get_instance_dir(self, instance_name, remote_server=None,
|
||||
create_dir=True, remove_dir=False):
|
||||
return self._get_instances_sub_dir(instance_name, remote_server,
|
||||
create_dir, remove_dir)
|
||||
|
||||
def _lookup_vhd_path(self, instance_name, vhd_path_func):
|
||||
vhd_path = None
|
||||
for format_ext in ['vhd', 'vhdx']:
|
||||
test_path = vhd_path_func(instance_name, format_ext)
|
||||
if self.exists(test_path):
|
||||
vhd_path = test_path
|
||||
break
|
||||
return vhd_path
|
||||
|
||||
def lookup_root_vhd_path(self, instance_name):
|
||||
return self._lookup_vhd_path(instance_name, self.get_root_vhd_path)
|
||||
|
||||
def lookup_configdrive_path(self, instance_name):
|
||||
configdrive_path = None
|
||||
for format_ext in constants.DISK_FORMAT_MAP:
|
||||
test_path = self.get_configdrive_path(instance_name, format_ext)
|
||||
if self.exists(test_path):
|
||||
configdrive_path = test_path
|
||||
break
|
||||
return configdrive_path
|
||||
|
||||
def lookup_ephemeral_vhd_path(self, instance_name):
|
||||
return self._lookup_vhd_path(instance_name,
|
||||
self.get_ephemeral_vhd_path)
|
||||
|
||||
def get_root_vhd_path(self, instance_name, format_ext):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'root.' + format_ext.lower())
|
||||
|
||||
def get_configdrive_path(self, instance_name, format_ext,
|
||||
remote_server=None):
|
||||
instance_path = self.get_instance_dir(instance_name, remote_server)
|
||||
return os.path.join(instance_path, 'configdrive.' + format_ext.lower())
|
||||
|
||||
def get_ephemeral_vhd_path(self, instance_name, format_ext):
|
||||
instance_path = self.get_instance_dir(instance_name)
|
||||
return os.path.join(instance_path, 'ephemeral.' + format_ext.lower())
|
||||
|
||||
def get_base_vhd_dir(self):
|
||||
return self._get_instances_sub_dir('_base')
|
||||
|
||||
def get_export_dir(self, instance_name):
|
||||
dir_name = os.path.join('export', instance_name)
|
||||
return self._get_instances_sub_dir(dir_name, create_dir=True,
|
||||
remove_dir=True)
|
||||
|
||||
def get_vm_console_log_paths(self, vm_name, remote_server=None):
|
||||
instance_dir = self.get_instance_dir(vm_name,
|
||||
remote_server)
|
||||
console_log_path = os.path.join(instance_dir, 'console.log')
|
||||
return console_log_path, console_log_path + '.1'
|
||||
|
||||
def check_smb_mapping(self, smbfs_share):
|
||||
mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share)
|
||||
|
||||
if not mappings:
|
||||
return False
|
||||
|
||||
if os.path.exists(smbfs_share):
|
||||
LOG.debug('Share already mounted: %s', smbfs_share)
|
||||
return True
|
||||
else:
|
||||
LOG.debug('Share exists but is unavailable: %s ', smbfs_share)
|
||||
self.unmount_smb_share(smbfs_share, force=True)
|
||||
return False
|
||||
|
||||
def mount_smb_share(self, smbfs_share, username=None, password=None):
|
||||
try:
|
||||
LOG.debug('Mounting share: %s', smbfs_share)
|
||||
self._smb_conn.Msft_SmbMapping.Create(RemotePath=smbfs_share,
|
||||
UserName=username,
|
||||
Password=password)
|
||||
except wmi.x_wmi as exc:
|
||||
err_msg = (_(
|
||||
'Unable to mount SMBFS share: %(smbfs_share)s '
|
||||
'WMI exception: %(wmi_exc)s'), {'smbfs_share': smbfs_share,
|
||||
'wmi_exc': exc})
|
||||
raise vmutils.HyperVException(err_msg)
|
||||
|
||||
def unmount_smb_share(self, smbfs_share, force=False):
|
||||
mappings = self._smb_conn.Msft_SmbMapping(RemotePath=smbfs_share)
|
||||
if not mappings:
|
||||
LOG.debug('Share %s is not mounted. Skipping unmount.',
|
||||
smbfs_share)
|
||||
|
||||
for mapping in mappings:
|
||||
# Due to a bug in the WMI module, getting the output of
|
||||
# methods returning None will raise an AttributeError
|
||||
try:
|
||||
mapping.Remove(Force=force)
|
||||
except AttributeError:
|
||||
pass
|
||||
except wmi.x_wmi:
|
||||
# If this fails, a 'Generic Failure' exception is raised.
|
||||
# This happens even if we unforcefully unmount an in-use
|
||||
# share, for which reason we'll simply ignore it in this
|
||||
# case.
|
||||
if force:
|
||||
raise vmutils.HyperVException(
|
||||
_("Could not unmount share: %s"), smbfs_share)
|
21
oslo_windows/utils/rdpconsoleutils.py
Normal file
21
oslo_windows/utils/rdpconsoleutils.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
class RDPConsoleUtils(object):
|
||||
_DEFAULT_HYPERV_RDP_PORT = 2179
|
||||
|
||||
def get_rdp_console_port(self):
|
||||
return self._DEFAULT_HYPERV_RDP_PORT
|
31
oslo_windows/utils/rdpconsoleutilsv2.py
Normal file
31
oslo_windows/utils/rdpconsoleutilsv2.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
|
||||
from nova.virt.hyperv import rdpconsoleutils
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
|
||||
class RDPConsoleUtilsV2(rdpconsoleutils.RDPConsoleUtils):
|
||||
def __init__(self):
|
||||
if sys.platform == 'win32':
|
||||
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
|
||||
|
||||
def get_rdp_console_port(self):
|
||||
rdp_setting_data = self._conn.Msvm_TerminalServiceSettingData()[0]
|
||||
return rdp_setting_data.ListenerPort
|
212
oslo_windows/utils/vhdutils.py
Normal file
212
oslo_windows/utils/vhdutils.py
Normal file
@ -0,0 +1,212 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility class for VHD related operations.
|
||||
|
||||
Official VHD format specs can be retrieved at:
|
||||
http://technet.microsoft.com/en-us/library/bb676673.aspx
|
||||
See "Download the Specifications Without Registering"
|
||||
|
||||
Official VHDX format specs can be retrieved at:
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=34750
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
|
||||
VHD_HEADER_SIZE_FIX = 512
|
||||
VHD_BAT_ENTRY_SIZE = 4
|
||||
VHD_DYNAMIC_DISK_HEADER_SIZE = 1024
|
||||
VHD_HEADER_SIZE_DYNAMIC = 512
|
||||
VHD_FOOTER_SIZE_DYNAMIC = 512
|
||||
VHD_BLK_SIZE_OFFSET = 544
|
||||
|
||||
VHD_SIGNATURE = 'conectix'
|
||||
VHDX_SIGNATURE = 'vhdxfile'
|
||||
|
||||
|
||||
class VHDUtils(object):
|
||||
|
||||
def __init__(self):
|
||||
self._vmutils = vmutils.VMUtils()
|
||||
if sys.platform == 'win32':
|
||||
self._conn = wmi.WMI(moniker='//./root/virtualization')
|
||||
|
||||
def validate_vhd(self, vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = image_man_svc.ValidateVirtualHardDisk(
|
||||
Path=vhd_path)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def create_dynamic_vhd(self, path, max_internal_size, format):
|
||||
if format != constants.DISK_FORMAT_VHD:
|
||||
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
|
||||
format)
|
||||
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = image_man_svc.CreateDynamicVirtualHardDisk(
|
||||
Path=path, MaxInternalSize=max_internal_size)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def create_differencing_vhd(self, path, parent_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = image_man_svc.CreateDifferencingVirtualHardDisk(
|
||||
Path=path, ParentPath=parent_path)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = image_man_svc.ReconnectParentVirtualHardDisk(
|
||||
ChildPath=child_vhd_path,
|
||||
ParentPath=parent_vhd_path,
|
||||
Force=True)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def merge_vhd(self, src_vhd_path, dest_vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = image_man_svc.MergeVirtualHardDisk(
|
||||
SourcePath=src_vhd_path,
|
||||
DestinationPath=dest_vhd_path)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_resize_method(self):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
return image_man_svc.ExpandVirtualHardDisk
|
||||
|
||||
def resize_vhd(self, vhd_path, new_max_size, is_file_max_size=True):
|
||||
if is_file_max_size:
|
||||
new_internal_max_size = self.get_internal_vhd_size_by_file_size(
|
||||
vhd_path, new_max_size)
|
||||
else:
|
||||
new_internal_max_size = new_max_size
|
||||
|
||||
resize = self._get_resize_method()
|
||||
|
||||
(job_path, ret_val) = resize(
|
||||
Path=vhd_path, MaxInternalSize=new_internal_max_size)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def get_internal_vhd_size_by_file_size(self, vhd_path, new_vhd_file_size):
|
||||
"""Fixed VHD size = Data Block size + 512 bytes
|
||||
| Dynamic_VHD_size = Dynamic Disk Header
|
||||
| + Copy of hard disk footer
|
||||
| + Hard Disk Footer
|
||||
| + Data Block
|
||||
| + BAT
|
||||
| Dynamic Disk header fields
|
||||
| Copy of hard disk footer (512 bytes)
|
||||
| Dynamic Disk Header (1024 bytes)
|
||||
| BAT (Block Allocation table)
|
||||
| Data Block 1
|
||||
| Data Block 2
|
||||
| Data Block n
|
||||
| Hard Disk Footer (512 bytes)
|
||||
| Default block size is 2M
|
||||
| BAT entry size is 4byte
|
||||
"""
|
||||
base_vhd_info = self.get_vhd_info(vhd_path)
|
||||
vhd_type = base_vhd_info['Type']
|
||||
|
||||
if vhd_type == constants.VHD_TYPE_FIXED:
|
||||
vhd_header_size = VHD_HEADER_SIZE_FIX
|
||||
return new_vhd_file_size - vhd_header_size
|
||||
elif vhd_type == constants.VHD_TYPE_DYNAMIC:
|
||||
bs = self._get_vhd_dynamic_blk_size(vhd_path)
|
||||
bes = VHD_BAT_ENTRY_SIZE
|
||||
ddhs = VHD_DYNAMIC_DISK_HEADER_SIZE
|
||||
hs = VHD_HEADER_SIZE_DYNAMIC
|
||||
fs = VHD_FOOTER_SIZE_DYNAMIC
|
||||
|
||||
max_internal_size = (new_vhd_file_size -
|
||||
(hs + ddhs + fs)) * bs / (bes + bs)
|
||||
return max_internal_size
|
||||
else:
|
||||
vhd_parent = self.get_vhd_parent_path(vhd_path)
|
||||
return self.get_internal_vhd_size_by_file_size(vhd_parent,
|
||||
new_vhd_file_size)
|
||||
|
||||
def _get_vhd_dynamic_blk_size(self, vhd_path):
|
||||
blk_size_offset = VHD_BLK_SIZE_OFFSET
|
||||
try:
|
||||
with open(vhd_path, "rb") as f:
|
||||
f.seek(blk_size_offset)
|
||||
version = f.read(4)
|
||||
except IOError:
|
||||
raise vmutils.HyperVException(_("Unable to obtain block size from"
|
||||
" VHD %(vhd_path)s") %
|
||||
{"vhd_path": vhd_path})
|
||||
return struct.unpack('>i', version)[0]
|
||||
|
||||
def get_vhd_parent_path(self, vhd_path):
|
||||
return self.get_vhd_info(vhd_path).get("ParentPath")
|
||||
|
||||
def get_vhd_info(self, vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
|
||||
(vhd_info,
|
||||
job_path,
|
||||
ret_val) = image_man_svc.GetVirtualHardDiskInfo(vhd_path)
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
vhd_info_dict = {}
|
||||
|
||||
et = ElementTree.fromstring(vhd_info)
|
||||
for item in et.findall("PROPERTY"):
|
||||
name = item.attrib["NAME"]
|
||||
value_text = item.find("VALUE").text
|
||||
if name == "ParentPath":
|
||||
vhd_info_dict[name] = value_text
|
||||
elif name in ["FileSize", "MaxInternalSize"]:
|
||||
vhd_info_dict[name] = long(value_text)
|
||||
elif name in ["InSavedState", "InUse"]:
|
||||
vhd_info_dict[name] = bool(value_text)
|
||||
elif name == "Type":
|
||||
vhd_info_dict[name] = int(value_text)
|
||||
|
||||
return vhd_info_dict
|
||||
|
||||
def get_vhd_format(self, path):
|
||||
with open(path, 'rb') as f:
|
||||
# Read header
|
||||
if f.read(8) == VHDX_SIGNATURE:
|
||||
return constants.DISK_FORMAT_VHDX
|
||||
|
||||
# Read footer
|
||||
f.seek(0, 2)
|
||||
file_size = f.tell()
|
||||
if file_size >= 512:
|
||||
f.seek(-512, 2)
|
||||
if f.read(8) == VHD_SIGNATURE:
|
||||
return constants.DISK_FORMAT_VHD
|
||||
|
||||
raise vmutils.HyperVException(_('Unsupported virtual disk format'))
|
||||
|
||||
def get_best_supported_vhd_format(self):
|
||||
return constants.DISK_FORMAT_VHD
|
247
oslo_windows/utils/vhdutilsv2.py
Normal file
247
oslo_windows/utils/vhdutilsv2.py
Normal file
@ -0,0 +1,247 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility class for VHD related operations.
|
||||
Based on the "root/virtualization/v2" namespace available starting with
|
||||
Hyper-V Server / Windows Server 2012.
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from oslo_utils import units
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vhdutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import vmutilsv2
|
||||
|
||||
|
||||
VHDX_BAT_ENTRY_SIZE = 8
|
||||
VHDX_HEADER_OFFSETS = [64 * units.Ki, 128 * units.Ki]
|
||||
VHDX_HEADER_SECTION_SIZE = units.Mi
|
||||
VHDX_LOG_LENGTH_OFFSET = 68
|
||||
VHDX_METADATA_SIZE_OFFSET = 64
|
||||
VHDX_REGION_TABLE_OFFSET = 192 * units.Ki
|
||||
VHDX_BS_METADATA_ENTRY_OFFSET = 48
|
||||
|
||||
|
||||
class VHDUtilsV2(vhdutils.VHDUtils):
|
||||
|
||||
_VHD_TYPE_DYNAMIC = 3
|
||||
_VHD_TYPE_DIFFERENCING = 4
|
||||
|
||||
_vhd_format_map = {
|
||||
constants.DISK_FORMAT_VHD: 2,
|
||||
constants.DISK_FORMAT_VHDX: 3,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._vmutils = vmutilsv2.VMUtilsV2()
|
||||
if sys.platform == 'win32':
|
||||
self._conn = wmi.WMI(moniker='//./root/virtualization/v2')
|
||||
|
||||
def create_dynamic_vhd(self, path, max_internal_size, format):
|
||||
vhd_format = self._vhd_format_map.get(format)
|
||||
if not vhd_format:
|
||||
raise vmutils.HyperVException(_("Unsupported disk format: %s") %
|
||||
format)
|
||||
|
||||
self._create_vhd(self._VHD_TYPE_DYNAMIC, vhd_format, path,
|
||||
max_internal_size=max_internal_size)
|
||||
|
||||
def create_differencing_vhd(self, path, parent_path):
|
||||
# Although this method can take a size argument in case of VHDX
|
||||
# images, avoid it as the underlying Win32 is currently not
|
||||
# resizing the disk properly. This can be reconsidered once the
|
||||
# Win32 issue is fixed.
|
||||
parent_vhd_info = self.get_vhd_info(parent_path)
|
||||
self._create_vhd(self._VHD_TYPE_DIFFERENCING,
|
||||
parent_vhd_info["Format"],
|
||||
path, parent_path=parent_path)
|
||||
|
||||
def _create_vhd(self, vhd_type, format, path, max_internal_size=None,
|
||||
parent_path=None):
|
||||
vhd_info = self._conn.Msvm_VirtualHardDiskSettingData.new()
|
||||
|
||||
vhd_info.Type = vhd_type
|
||||
vhd_info.Format = format
|
||||
vhd_info.Path = path
|
||||
vhd_info.ParentPath = parent_path
|
||||
|
||||
if max_internal_size:
|
||||
vhd_info.MaxInternalSize = max_internal_size
|
||||
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
(job_path, ret_val) = image_man_svc.CreateVirtualHardDisk(
|
||||
VirtualDiskSettingData=vhd_info.GetText_(1))
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def reconnect_parent_vhd(self, child_vhd_path, parent_vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, child_vhd_path)
|
||||
|
||||
et = ElementTree.fromstring(vhd_info_xml)
|
||||
item = et.find(".//PROPERTY[@NAME='ParentPath']/VALUE")
|
||||
if item is not None:
|
||||
item.text = parent_vhd_path
|
||||
else:
|
||||
msg = (_("Failed to reconnect image %(child_vhd_path)s to "
|
||||
"parent %(parent_vhd_path)s. The child image has no "
|
||||
"parent path property.") %
|
||||
{'child_vhd_path': child_vhd_path,
|
||||
'parent_vhd_path': parent_vhd_path})
|
||||
raise vmutils.HyperVException(msg)
|
||||
|
||||
vhd_info_xml = ElementTree.tostring(et)
|
||||
|
||||
(job_path, ret_val) = image_man_svc.SetVirtualHardDiskSettingData(
|
||||
VirtualDiskSettingData=vhd_info_xml)
|
||||
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _get_resize_method(self):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
return image_man_svc.ResizeVirtualHardDisk
|
||||
|
||||
def get_internal_vhd_size_by_file_size(self, vhd_path,
|
||||
new_vhd_file_size):
|
||||
"""Get internal size of a VHD according to new VHD file size.
|
||||
|
||||
VHDX Size = Header (1MB) + Log + Metadata Region + BAT + Payload Blocks
|
||||
|
||||
The chunk size is the maximum number of bytes described by a SB
|
||||
block.
|
||||
|
||||
Chunk size = 2^{23} * LogicalSectorSize
|
||||
|
||||
:param str vhd_path: VHD file path
|
||||
:param new_vhd_file_size: Size of the new VHD file.
|
||||
:return: Internal VHD size according to new VHD file size.
|
||||
"""
|
||||
vhd_format = self.get_vhd_format(vhd_path)
|
||||
if vhd_format == constants.DISK_FORMAT_VHD:
|
||||
return super(VHDUtilsV2,
|
||||
self).get_internal_vhd_size_by_file_size(
|
||||
vhd_path, new_vhd_file_size)
|
||||
else:
|
||||
vhd_info = self.get_vhd_info(vhd_path)
|
||||
vhd_type = vhd_info['Type']
|
||||
if vhd_type == self._VHD_TYPE_DIFFERENCING:
|
||||
vhd_parent = self.get_vhd_parent_path(vhd_path)
|
||||
return self.get_internal_vhd_size_by_file_size(vhd_parent,
|
||||
new_vhd_file_size)
|
||||
else:
|
||||
try:
|
||||
with open(vhd_path, 'rb') as f:
|
||||
hs = VHDX_HEADER_SECTION_SIZE
|
||||
bes = VHDX_BAT_ENTRY_SIZE
|
||||
|
||||
lss = vhd_info['LogicalSectorSize']
|
||||
bs = self._get_vhdx_block_size(f)
|
||||
ls = self._get_vhdx_log_size(f)
|
||||
ms = self._get_vhdx_metadata_size_and_offset(f)[0]
|
||||
|
||||
chunk_ratio = (1 << 23) * lss / bs
|
||||
size = new_vhd_file_size
|
||||
|
||||
max_internal_size = (bs * chunk_ratio * (size - hs -
|
||||
ls - ms - bes - bes / chunk_ratio) / (bs *
|
||||
chunk_ratio + bes * chunk_ratio + bes))
|
||||
|
||||
return max_internal_size - (max_internal_size % bs)
|
||||
|
||||
except IOError as ex:
|
||||
raise vmutils.HyperVException(_("Unable to obtain "
|
||||
"internal size from VHDX: "
|
||||
"%(vhd_path)s. Exception: "
|
||||
"%(ex)s") %
|
||||
{"vhd_path": vhd_path,
|
||||
"ex": ex})
|
||||
|
||||
def _get_vhdx_current_header_offset(self, vhdx_file):
|
||||
sequence_numbers = []
|
||||
for offset in VHDX_HEADER_OFFSETS:
|
||||
vhdx_file.seek(offset + 8)
|
||||
sequence_numbers.append(struct.unpack('<Q',
|
||||
vhdx_file.read(8))[0])
|
||||
current_header = sequence_numbers.index(max(sequence_numbers))
|
||||
return VHDX_HEADER_OFFSETS[current_header]
|
||||
|
||||
def _get_vhdx_log_size(self, vhdx_file):
|
||||
current_header_offset = self._get_vhdx_current_header_offset(vhdx_file)
|
||||
offset = current_header_offset + VHDX_LOG_LENGTH_OFFSET
|
||||
vhdx_file.seek(offset)
|
||||
log_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
||||
return log_size
|
||||
|
||||
def _get_vhdx_metadata_size_and_offset(self, vhdx_file):
|
||||
offset = VHDX_METADATA_SIZE_OFFSET + VHDX_REGION_TABLE_OFFSET
|
||||
vhdx_file.seek(offset)
|
||||
metadata_offset = struct.unpack('<Q', vhdx_file.read(8))[0]
|
||||
metadata_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
||||
return metadata_size, metadata_offset
|
||||
|
||||
def _get_vhdx_block_size(self, vhdx_file):
|
||||
metadata_offset = self._get_vhdx_metadata_size_and_offset(vhdx_file)[1]
|
||||
offset = metadata_offset + VHDX_BS_METADATA_ENTRY_OFFSET
|
||||
vhdx_file.seek(offset)
|
||||
file_parameter_offset = struct.unpack('<I', vhdx_file.read(4))[0]
|
||||
|
||||
vhdx_file.seek(file_parameter_offset + metadata_offset)
|
||||
block_size = struct.unpack('<I', vhdx_file.read(4))[0]
|
||||
return block_size
|
||||
|
||||
def _get_vhd_info_xml(self, image_man_svc, vhd_path):
|
||||
(job_path,
|
||||
ret_val,
|
||||
vhd_info_xml) = image_man_svc.GetVirtualHardDiskSettingData(vhd_path)
|
||||
|
||||
self._vmutils.check_ret_val(ret_val, job_path)
|
||||
|
||||
return vhd_info_xml.encode('utf8', 'xmlcharrefreplace')
|
||||
|
||||
def get_vhd_info(self, vhd_path):
|
||||
image_man_svc = self._conn.Msvm_ImageManagementService()[0]
|
||||
vhd_info_xml = self._get_vhd_info_xml(image_man_svc, vhd_path)
|
||||
|
||||
vhd_info_dict = {}
|
||||
et = ElementTree.fromstring(vhd_info_xml)
|
||||
for item in et.findall("PROPERTY"):
|
||||
name = item.attrib["NAME"]
|
||||
value_item = item.find("VALUE")
|
||||
if value_item is None:
|
||||
value_text = None
|
||||
else:
|
||||
value_text = value_item.text
|
||||
|
||||
if name in ["Path", "ParentPath"]:
|
||||
vhd_info_dict[name] = value_text
|
||||
elif name in ["BlockSize", "LogicalSectorSize",
|
||||
"PhysicalSectorSize", "MaxInternalSize"]:
|
||||
vhd_info_dict[name] = long(value_text)
|
||||
elif name in ["Type", "Format"]:
|
||||
vhd_info_dict[name] = int(value_text)
|
||||
|
||||
return vhd_info_dict
|
||||
|
||||
def get_best_supported_vhd_format(self):
|
||||
return constants.DISK_FORMAT_VHDX
|
822
oslo_windows/utils/vmutils.py
Normal file
822
oslo_windows/utils/vmutils.py
Normal file
@ -0,0 +1,822 @@
|
||||
# Copyright (c) 2010 Cloud.com, Inc
|
||||
# Copyright 2012 Cloudbase Solutions Srl / Pedro Navarro Perez
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Utility class for VM related operations on Hyper-V.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
from six.moves import range
|
||||
|
||||
from nova import exception
|
||||
from nova.i18n import _, _LW
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import hostutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO(alexpilotti): Move the exceptions to a separate module
|
||||
# TODO(alexpilotti): Add more domain exceptions
|
||||
class HyperVException(exception.NovaException):
|
||||
def __init__(self, message=None):
|
||||
super(HyperVException, self).__init__(message)
|
||||
|
||||
|
||||
# TODO(alexpilotti): Add a storage exception base class
|
||||
class VHDResizeException(HyperVException):
|
||||
def __init__(self, message=None):
|
||||
super(HyperVException, self).__init__(message)
|
||||
|
||||
|
||||
class HyperVAuthorizationException(HyperVException):
|
||||
def __init__(self, message=None):
|
||||
super(HyperVException, self).__init__(message)
|
||||
|
||||
|
||||
class UnsupportedConfigDriveFormatException(HyperVException):
|
||||
def __init__(self, message=None):
|
||||
super(HyperVException, self).__init__(message)
|
||||
|
||||
|
||||
class VMUtils(object):
|
||||
|
||||
# These constants can be overridden by inherited classes
|
||||
_PHYS_DISK_RES_SUB_TYPE = 'Microsoft Physical Disk Drive'
|
||||
_DISK_DRIVE_RES_SUB_TYPE = 'Microsoft Synthetic Disk Drive'
|
||||
_DVD_DRIVE_RES_SUB_TYPE = 'Microsoft Synthetic DVD Drive'
|
||||
_HARD_DISK_RES_SUB_TYPE = 'Microsoft Virtual Hard Disk'
|
||||
_DVD_DISK_RES_SUB_TYPE = 'Microsoft Virtual CD/DVD Disk'
|
||||
_IDE_CTRL_RES_SUB_TYPE = 'Microsoft Emulated IDE Controller'
|
||||
_SCSI_CTRL_RES_SUB_TYPE = 'Microsoft Synthetic SCSI Controller'
|
||||
_SERIAL_PORT_RES_SUB_TYPE = 'Microsoft Serial Port'
|
||||
|
||||
_SETTINGS_DEFINE_STATE_CLASS = 'Msvm_SettingsDefineState'
|
||||
_VIRTUAL_SYSTEM_SETTING_DATA_CLASS = 'Msvm_VirtualSystemSettingData'
|
||||
_RESOURCE_ALLOC_SETTING_DATA_CLASS = 'Msvm_ResourceAllocationSettingData'
|
||||
_PROCESSOR_SETTING_DATA_CLASS = 'Msvm_ProcessorSettingData'
|
||||
_MEMORY_SETTING_DATA_CLASS = 'Msvm_MemorySettingData'
|
||||
_STORAGE_ALLOC_SETTING_DATA_CLASS = _RESOURCE_ALLOC_SETTING_DATA_CLASS
|
||||
_SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS = \
|
||||
'Msvm_SyntheticEthernetPortSettingData'
|
||||
_AFFECTED_JOB_ELEMENT_CLASS = "Msvm_AffectedJobElement"
|
||||
_COMPUTER_SYSTEM_CLASS = "Msvm_ComputerSystem"
|
||||
|
||||
_VM_ENABLED_STATE_PROP = "EnabledState"
|
||||
|
||||
_SHUTDOWN_COMPONENT = "Msvm_ShutdownComponent"
|
||||
_VIRTUAL_SYSTEM_CURRENT_SETTINGS = 3
|
||||
_AUTOMATIC_STARTUP_ACTION_NONE = 0
|
||||
|
||||
_vm_power_states_map = {constants.HYPERV_VM_STATE_ENABLED: 2,
|
||||
constants.HYPERV_VM_STATE_DISABLED: 3,
|
||||
constants.HYPERV_VM_STATE_SHUTTING_DOWN: 4,
|
||||
constants.HYPERV_VM_STATE_REBOOT: 10,
|
||||
constants.HYPERV_VM_STATE_PAUSED: 32768,
|
||||
constants.HYPERV_VM_STATE_SUSPENDED: 32769}
|
||||
|
||||
def __init__(self, host='.'):
|
||||
self._enabled_states_map = {v: k for k, v in
|
||||
six.iteritems(self._vm_power_states_map)}
|
||||
if sys.platform == 'win32':
|
||||
self._init_hyperv_wmi_conn(host)
|
||||
self._conn_cimv2 = wmi.WMI(moniker='//%s/root/cimv2' % host)
|
||||
|
||||
# On version of Hyper-V prior to 2012 trying to directly set properties
|
||||
# in default setting data WMI objects results in an exception
|
||||
self._clone_wmi_objs = False
|
||||
if sys.platform == 'win32':
|
||||
hostutls = hostutils.HostUtils()
|
||||
self._clone_wmi_objs = not hostutls.check_min_windows_version(6, 2)
|
||||
|
||||
def _init_hyperv_wmi_conn(self, host):
|
||||
self._conn = wmi.WMI(moniker='//%s/root/virtualization' % host)
|
||||
|
||||
def list_instance_notes(self):
|
||||
instance_notes = []
|
||||
|
||||
for vs in self._conn.Msvm_VirtualSystemSettingData(
|
||||
['ElementName', 'Notes'],
|
||||
SettingType=self._VIRTUAL_SYSTEM_CURRENT_SETTINGS):
|
||||
if vs.Notes is not None:
|
||||
instance_notes.append(
|
||||
(vs.ElementName, [v for v in vs.Notes.split('\n') if v]))
|
||||
|
||||
return instance_notes
|
||||
|
||||
def list_instances(self):
|
||||
"""Return the names of all the instances known to Hyper-V."""
|
||||
return [v.ElementName for v in
|
||||
self._conn.Msvm_VirtualSystemSettingData(
|
||||
['ElementName'],
|
||||
SettingType=self._VIRTUAL_SYSTEM_CURRENT_SETTINGS)]
|
||||
|
||||
def get_vm_summary_info(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
vmsettings = vm.associators(
|
||||
wmi_association_class=self._SETTINGS_DEFINE_STATE_CLASS,
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
settings_paths = [v.path_() for v in vmsettings]
|
||||
# See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
|
||||
(ret_val, summary_info) = vs_man_svc.GetSummaryInformation(
|
||||
[constants.VM_SUMMARY_NUM_PROCS,
|
||||
constants.VM_SUMMARY_ENABLED_STATE,
|
||||
constants.VM_SUMMARY_MEMORY_USAGE,
|
||||
constants.VM_SUMMARY_UPTIME],
|
||||
settings_paths)
|
||||
if ret_val:
|
||||
raise HyperVException(_('Cannot get VM summary data for: %s')
|
||||
% vm_name)
|
||||
|
||||
si = summary_info[0]
|
||||
memory_usage = None
|
||||
if si.MemoryUsage is not None:
|
||||
memory_usage = long(si.MemoryUsage)
|
||||
up_time = None
|
||||
if si.UpTime is not None:
|
||||
up_time = long(si.UpTime)
|
||||
|
||||
# Nova requires a valid state to be returned. Hyper-V has more
|
||||
# states than Nova, typically intermediate ones and since there is
|
||||
# no direct mapping for those, ENABLED is the only reasonable option
|
||||
# considering that in all the non mappable states the instance
|
||||
# is running.
|
||||
enabled_state = self._enabled_states_map.get(si.EnabledState,
|
||||
constants.HYPERV_VM_STATE_ENABLED)
|
||||
|
||||
summary_info_dict = {'NumberOfProcessors': si.NumberOfProcessors,
|
||||
'EnabledState': enabled_state,
|
||||
'MemoryUsage': memory_usage,
|
||||
'UpTime': up_time}
|
||||
return summary_info_dict
|
||||
|
||||
def _lookup_vm_check(self, vm_name):
|
||||
|
||||
vm = self._lookup_vm(vm_name)
|
||||
if not vm:
|
||||
raise exception.NotFound(_('VM not found: %s') % vm_name)
|
||||
return vm
|
||||
|
||||
def _lookup_vm(self, vm_name):
|
||||
vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
|
||||
n = len(vms)
|
||||
if n == 0:
|
||||
return None
|
||||
elif n > 1:
|
||||
raise HyperVException(_('Duplicate VM name found: %s') % vm_name)
|
||||
else:
|
||||
return vms[0]
|
||||
|
||||
def vm_exists(self, vm_name):
|
||||
return self._lookup_vm(vm_name) is not None
|
||||
|
||||
def get_vm_id(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
return vm.Name
|
||||
|
||||
def _get_vm_setting_data(self, vm):
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
# Avoid snapshots
|
||||
return [s for s in vmsettings if s.SettingType == 3][0]
|
||||
|
||||
def _set_vm_memory(self, vm, vmsetting, memory_mb, dynamic_memory_ratio):
|
||||
mem_settings = vmsetting.associators(
|
||||
wmi_result_class=self._MEMORY_SETTING_DATA_CLASS)[0]
|
||||
|
||||
max_mem = long(memory_mb)
|
||||
mem_settings.Limit = max_mem
|
||||
|
||||
if dynamic_memory_ratio > 1:
|
||||
mem_settings.DynamicMemoryEnabled = True
|
||||
# Must be a multiple of 2
|
||||
reserved_mem = min(
|
||||
long(max_mem / dynamic_memory_ratio) >> 1 << 1,
|
||||
max_mem)
|
||||
else:
|
||||
mem_settings.DynamicMemoryEnabled = False
|
||||
reserved_mem = max_mem
|
||||
|
||||
mem_settings.Reservation = reserved_mem
|
||||
# Start with the minimum memory
|
||||
mem_settings.VirtualQuantity = reserved_mem
|
||||
|
||||
self._modify_virt_resource(mem_settings, vm.path_())
|
||||
|
||||
def _set_vm_vcpus(self, vm, vmsetting, vcpus_num, limit_cpu_features):
|
||||
procsetting = vmsetting.associators(
|
||||
wmi_result_class=self._PROCESSOR_SETTING_DATA_CLASS)[0]
|
||||
vcpus = long(vcpus_num)
|
||||
procsetting.VirtualQuantity = vcpus
|
||||
procsetting.Reservation = vcpus
|
||||
procsetting.Limit = 100000 # static assignment to 100%
|
||||
procsetting.LimitProcessorFeatures = limit_cpu_features
|
||||
|
||||
self._modify_virt_resource(procsetting, vm.path_())
|
||||
|
||||
def update_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features,
|
||||
dynamic_memory_ratio):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vmsetting = self._get_vm_setting_data(vm)
|
||||
self._set_vm_memory(vm, vmsetting, memory_mb, dynamic_memory_ratio)
|
||||
self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features)
|
||||
|
||||
def check_admin_permissions(self):
|
||||
if not self._conn.Msvm_VirtualSystemManagementService():
|
||||
msg = _("The Windows account running nova-compute on this Hyper-V"
|
||||
" host doesn't have the required permissions to create or"
|
||||
" operate the virtual machine.")
|
||||
raise HyperVAuthorizationException(msg)
|
||||
|
||||
def create_vm(self, vm_name, memory_mb, vcpus_num, limit_cpu_features,
|
||||
dynamic_memory_ratio, vm_gen, instance_path, notes=None):
|
||||
"""Creates a VM."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
LOG.debug('Creating VM %s', vm_name)
|
||||
vm = self._create_vm_obj(vs_man_svc, vm_name, vm_gen, notes,
|
||||
dynamic_memory_ratio, instance_path)
|
||||
|
||||
vmsetting = self._get_vm_setting_data(vm)
|
||||
|
||||
LOG.debug('Setting memory for vm %s', vm_name)
|
||||
self._set_vm_memory(vm, vmsetting, memory_mb, dynamic_memory_ratio)
|
||||
|
||||
LOG.debug('Set vCPUs for vm %s', vm_name)
|
||||
self._set_vm_vcpus(vm, vmsetting, vcpus_num, limit_cpu_features)
|
||||
|
||||
def _create_vm_obj(self, vs_man_svc, vm_name, vm_gen, notes,
|
||||
dynamic_memory_ratio, instance_path):
|
||||
vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new()
|
||||
vs_gs_data.ElementName = vm_name
|
||||
# Don't start automatically on host boot
|
||||
vs_gs_data.AutomaticStartupAction = self._AUTOMATIC_STARTUP_ACTION_NONE
|
||||
vs_gs_data.ExternalDataRoot = instance_path
|
||||
vs_gs_data.SnapshotDataRoot = instance_path
|
||||
|
||||
(vm_path,
|
||||
job_path,
|
||||
ret_val) = vs_man_svc.DefineVirtualSystem([], None,
|
||||
vs_gs_data.GetText_(1))
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
vm = self._get_wmi_obj(vm_path)
|
||||
|
||||
if notes:
|
||||
vmsetting = self._get_vm_setting_data(vm)
|
||||
vmsetting.Notes = '\n'.join(notes)
|
||||
self._modify_virtual_system(vs_man_svc, vm_path, vmsetting)
|
||||
|
||||
return self._get_wmi_obj(vm_path)
|
||||
|
||||
def _modify_virtual_system(self, vs_man_svc, vm_path, vmsetting):
|
||||
(job_path, ret_val) = vs_man_svc.ModifyVirtualSystem(
|
||||
ComputerSystem=vm_path,
|
||||
SystemSettingData=vmsetting.GetText_(1))[1:]
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def get_vm_scsi_controller(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
return self._get_vm_scsi_controller(vm)
|
||||
|
||||
def _get_vm_scsi_controller(self, vm):
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
rasds = vmsettings[0].associators(
|
||||
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
|
||||
res = [r for r in rasds
|
||||
if r.ResourceSubType == self._SCSI_CTRL_RES_SUB_TYPE][0]
|
||||
return res.path_()
|
||||
|
||||
def _get_vm_ide_controller(self, vm, ctrller_addr):
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
rasds = vmsettings[0].associators(
|
||||
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
|
||||
ide_ctrls = [r for r in rasds
|
||||
if r.ResourceSubType == self._IDE_CTRL_RES_SUB_TYPE
|
||||
and r.Address == str(ctrller_addr)]
|
||||
|
||||
return ide_ctrls[0].path_() if ide_ctrls else None
|
||||
|
||||
def get_vm_ide_controller(self, vm_name, ctrller_addr):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
return self._get_vm_ide_controller(vm, ctrller_addr)
|
||||
|
||||
def get_attached_disks(self, scsi_controller_path):
|
||||
volumes = self._conn.query(
|
||||
self._get_attached_disks_query_string(scsi_controller_path))
|
||||
return volumes
|
||||
|
||||
def _get_attached_disks_query_string(self, scsi_controller_path):
|
||||
return ("SELECT * FROM %(class_name)s WHERE ("
|
||||
"ResourceSubType='%(res_sub_type)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_virt)s') AND "
|
||||
"Parent='%(parent)s'" % {
|
||||
'class_name': self._RESOURCE_ALLOC_SETTING_DATA_CLASS,
|
||||
'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE,
|
||||
'res_sub_type_virt': self._DISK_DRIVE_RES_SUB_TYPE,
|
||||
'parent': scsi_controller_path.replace("'", "''")})
|
||||
|
||||
def _get_new_setting_data(self, class_name):
|
||||
obj = self._conn.query("SELECT * FROM %s WHERE InstanceID "
|
||||
"LIKE '%%\\Default'" % class_name)[0]
|
||||
return self._check_clone_wmi_obj(class_name, obj)
|
||||
|
||||
def _get_new_resource_setting_data(self, resource_sub_type,
|
||||
class_name=None):
|
||||
if class_name is None:
|
||||
class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS
|
||||
obj = self._conn.query("SELECT * FROM %(class_name)s "
|
||||
"WHERE ResourceSubType = "
|
||||
"'%(res_sub_type)s' AND "
|
||||
"InstanceID LIKE '%%\\Default'" %
|
||||
{"class_name": class_name,
|
||||
"res_sub_type": resource_sub_type})[0]
|
||||
return self._check_clone_wmi_obj(class_name, obj)
|
||||
|
||||
def _check_clone_wmi_obj(self, class_name, obj):
|
||||
if self._clone_wmi_objs:
|
||||
return self._clone_wmi_obj(class_name, obj)
|
||||
else:
|
||||
return obj
|
||||
|
||||
def _clone_wmi_obj(self, class_name, obj):
|
||||
wmi_class = getattr(self._conn, class_name)
|
||||
new_obj = wmi_class.new()
|
||||
# Copy the properties from the original.
|
||||
for prop in obj._properties:
|
||||
value = obj.Properties_.Item(prop).Value
|
||||
new_obj.Properties_.Item(prop).Value = value
|
||||
return new_obj
|
||||
|
||||
def attach_scsi_drive(self, vm_name, path, drive_type=constants.DISK):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
ctrller_path = self._get_vm_scsi_controller(vm)
|
||||
drive_addr = self.get_free_controller_slot(ctrller_path)
|
||||
self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type)
|
||||
|
||||
def attach_ide_drive(self, vm_name, path, ctrller_addr, drive_addr,
|
||||
drive_type=constants.DISK):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
ctrller_path = self._get_vm_ide_controller(vm, ctrller_addr)
|
||||
self.attach_drive(vm_name, path, ctrller_path, drive_addr, drive_type)
|
||||
|
||||
def attach_drive(self, vm_name, path, ctrller_path, drive_addr,
|
||||
drive_type=constants.DISK):
|
||||
"""Create a drive and attach it to the vm."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
if drive_type == constants.DISK:
|
||||
res_sub_type = self._DISK_DRIVE_RES_SUB_TYPE
|
||||
elif drive_type == constants.DVD:
|
||||
res_sub_type = self._DVD_DRIVE_RES_SUB_TYPE
|
||||
|
||||
drive = self._get_new_resource_setting_data(res_sub_type)
|
||||
|
||||
# Set the ctrller as parent.
|
||||
drive.Parent = ctrller_path
|
||||
drive.Address = drive_addr
|
||||
# Add the cloned disk drive object to the vm.
|
||||
new_resources = self._add_virt_resource(drive, vm.path_())
|
||||
drive_path = new_resources[0]
|
||||
|
||||
if drive_type == constants.DISK:
|
||||
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
|
||||
elif drive_type == constants.DVD:
|
||||
res_sub_type = self._DVD_DISK_RES_SUB_TYPE
|
||||
|
||||
res = self._get_new_resource_setting_data(res_sub_type)
|
||||
# Set the new drive as the parent.
|
||||
res.Parent = drive_path
|
||||
res.Connection = [path]
|
||||
|
||||
# Add the new vhd object as a virtual hard disk to the vm.
|
||||
self._add_virt_resource(res, vm.path_())
|
||||
|
||||
def create_scsi_controller(self, vm_name):
|
||||
"""Create an iscsi controller ready to mount volumes."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
scsicontrl = self._get_new_resource_setting_data(
|
||||
self._SCSI_CTRL_RES_SUB_TYPE)
|
||||
|
||||
scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
|
||||
self._add_virt_resource(scsicontrl, vm.path_())
|
||||
|
||||
def attach_volume_to_controller(self, vm_name, controller_path, address,
|
||||
mounted_disk_path):
|
||||
"""Attach a volume to a controller."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
diskdrive = self._get_new_resource_setting_data(
|
||||
self._PHYS_DISK_RES_SUB_TYPE)
|
||||
|
||||
diskdrive.Address = address
|
||||
diskdrive.Parent = controller_path
|
||||
diskdrive.HostResource = [mounted_disk_path]
|
||||
self._add_virt_resource(diskdrive, vm.path_())
|
||||
|
||||
def _get_disk_resource_address(self, disk_resource):
|
||||
return disk_resource.Address
|
||||
|
||||
def set_disk_host_resource(self, vm_name, controller_path, address,
|
||||
mounted_disk_path):
|
||||
disk_found = False
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
(disk_resources, volume_resources) = self._get_vm_disks(vm)
|
||||
for disk_resource in disk_resources + volume_resources:
|
||||
if (disk_resource.Parent == controller_path and
|
||||
self._get_disk_resource_address(disk_resource) ==
|
||||
str(address)):
|
||||
if (disk_resource.HostResource and
|
||||
disk_resource.HostResource[0] != mounted_disk_path):
|
||||
LOG.debug('Updating disk host resource "%(old)s" to '
|
||||
'"%(new)s"' %
|
||||
{'old': disk_resource.HostResource[0],
|
||||
'new': mounted_disk_path})
|
||||
disk_resource.HostResource = [mounted_disk_path]
|
||||
self._modify_virt_resource(disk_resource, vm.path_())
|
||||
disk_found = True
|
||||
break
|
||||
if not disk_found:
|
||||
LOG.warning(_LW('Disk not found on controller '
|
||||
'"%(controller_path)s" with '
|
||||
'address "%(address)s"'),
|
||||
{'controller_path': controller_path,
|
||||
'address': address})
|
||||
|
||||
def set_nic_connection(self, vm_name, nic_name, vswitch_conn_data):
|
||||
nic_data = self._get_nic_data_by_name(nic_name)
|
||||
nic_data.Connection = [vswitch_conn_data]
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
self._modify_virt_resource(nic_data, vm.path_())
|
||||
|
||||
def _get_nic_data_by_name(self, name):
|
||||
return self._conn.Msvm_SyntheticEthernetPortSettingData(
|
||||
ElementName=name)[0]
|
||||
|
||||
def create_nic(self, vm_name, nic_name, mac_address):
|
||||
"""Create a (synthetic) nic and attach it to the vm."""
|
||||
# Create a new nic
|
||||
new_nic_data = self._get_new_setting_data(
|
||||
self._SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS)
|
||||
|
||||
# Configure the nic
|
||||
new_nic_data.ElementName = nic_name
|
||||
new_nic_data.Address = mac_address.replace(':', '')
|
||||
new_nic_data.StaticMacAddress = 'True'
|
||||
new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
|
||||
|
||||
# Add the new nic to the vm
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
self._add_virt_resource(new_nic_data, vm.path_())
|
||||
|
||||
def soft_shutdown_vm(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
shutdown_component = vm.associators(
|
||||
wmi_result_class=self._SHUTDOWN_COMPONENT)
|
||||
|
||||
if not shutdown_component:
|
||||
# If no shutdown_component is found, it means the VM is already
|
||||
# in a shutdown state.
|
||||
return
|
||||
|
||||
reason = 'Soft shutdown requested by OpenStack Nova.'
|
||||
(ret_val, ) = shutdown_component[0].InitiateShutdown(Force=False,
|
||||
Reason=reason)
|
||||
self.check_ret_val(ret_val, None)
|
||||
|
||||
def set_vm_state(self, vm_name, req_state):
|
||||
"""Set the desired state of the VM."""
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
(job_path,
|
||||
ret_val) = vm.RequestStateChange(self._vm_power_states_map[req_state])
|
||||
# Invalid state for current operation (32775) typically means that
|
||||
# the VM is already in the state requested
|
||||
self.check_ret_val(ret_val, job_path, [0, 32775])
|
||||
LOG.debug("Successfully changed vm state of %(vm_name)s "
|
||||
"to %(req_state)s",
|
||||
{'vm_name': vm_name, 'req_state': req_state})
|
||||
|
||||
def _get_disk_resource_disk_path(self, disk_resource):
|
||||
return disk_resource.Connection
|
||||
|
||||
def get_vm_storage_paths(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
(disk_resources, volume_resources) = self._get_vm_disks(vm)
|
||||
|
||||
volume_drives = []
|
||||
for volume_resource in volume_resources:
|
||||
drive_path = volume_resource.HostResource[0]
|
||||
volume_drives.append(drive_path)
|
||||
|
||||
disk_files = []
|
||||
for disk_resource in disk_resources:
|
||||
disk_files.extend(
|
||||
[c for c in self._get_disk_resource_disk_path(disk_resource)])
|
||||
|
||||
return (disk_files, volume_drives)
|
||||
|
||||
def _get_vm_disks(self, vm):
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
rasds = vmsettings[0].associators(
|
||||
wmi_result_class=self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
disk_resources = [r for r in rasds if
|
||||
r.ResourceSubType in
|
||||
[self._HARD_DISK_RES_SUB_TYPE,
|
||||
self._DVD_DISK_RES_SUB_TYPE]]
|
||||
|
||||
if (self._RESOURCE_ALLOC_SETTING_DATA_CLASS !=
|
||||
self._STORAGE_ALLOC_SETTING_DATA_CLASS):
|
||||
rasds = vmsettings[0].associators(
|
||||
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
|
||||
|
||||
volume_resources = [r for r in rasds if
|
||||
r.ResourceSubType == self._PHYS_DISK_RES_SUB_TYPE]
|
||||
|
||||
return (disk_resources, volume_resources)
|
||||
|
||||
def destroy_vm(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
# Remove the VM. Does not destroy disks.
|
||||
(job_path, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_())
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def check_ret_val(self, ret_val, job_path, success_values=[0]):
|
||||
if ret_val == constants.WMI_JOB_STATUS_STARTED:
|
||||
return self._wait_for_job(job_path)
|
||||
elif ret_val not in success_values:
|
||||
raise HyperVException(_('Operation failed with return value: %s')
|
||||
% ret_val)
|
||||
|
||||
def _wait_for_job(self, job_path):
|
||||
"""Poll WMI job state and wait for completion."""
|
||||
job = self._get_wmi_obj(job_path)
|
||||
|
||||
while job.JobState == constants.WMI_JOB_STATE_RUNNING:
|
||||
time.sleep(0.1)
|
||||
job = self._get_wmi_obj(job_path)
|
||||
if job.JobState != constants.WMI_JOB_STATE_COMPLETED:
|
||||
job_state = job.JobState
|
||||
if job.path().Class == "Msvm_ConcreteJob":
|
||||
err_sum_desc = job.ErrorSummaryDescription
|
||||
err_desc = job.ErrorDescription
|
||||
err_code = job.ErrorCode
|
||||
raise HyperVException(_("WMI job failed with status "
|
||||
"%(job_state)d. Error details: "
|
||||
"%(err_sum_desc)s - %(err_desc)s - "
|
||||
"Error code: %(err_code)d") %
|
||||
{'job_state': job_state,
|
||||
'err_sum_desc': err_sum_desc,
|
||||
'err_desc': err_desc,
|
||||
'err_code': err_code})
|
||||
else:
|
||||
(error, ret_val) = job.GetError()
|
||||
if not ret_val and error:
|
||||
raise HyperVException(_("WMI job failed with status "
|
||||
"%(job_state)d. Error details: "
|
||||
"%(error)s") %
|
||||
{'job_state': job_state,
|
||||
'error': error})
|
||||
else:
|
||||
raise HyperVException(_("WMI job failed with status "
|
||||
"%d. No error "
|
||||
"description available") %
|
||||
job_state)
|
||||
desc = job.Description
|
||||
elap = job.ElapsedTime
|
||||
LOG.debug("WMI job succeeded: %(desc)s, Elapsed=%(elap)s",
|
||||
{'desc': desc, 'elap': elap})
|
||||
return job
|
||||
|
||||
def _get_wmi_obj(self, path):
|
||||
return wmi.WMI(moniker=path.replace('\\', '/'))
|
||||
|
||||
def _add_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Adds a new resource to the VM."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
res_xml = [res_setting_data.GetText_(1)]
|
||||
(job_path,
|
||||
new_resources,
|
||||
ret_val) = vs_man_svc.AddVirtualSystemResources(res_xml, vm_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
return new_resources
|
||||
|
||||
def _modify_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Updates a VM resource."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
(job_path, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
|
||||
ResourceSettingData=[res_setting_data.GetText_(1)],
|
||||
ComputerSystem=vm_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _remove_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Removes a VM resource."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
res_path = [res_setting_data.path_()]
|
||||
(job_path, ret_val) = vs_man_svc.RemoveVirtualSystemResources(res_path,
|
||||
vm_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def take_vm_snapshot(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
(job_path, ret_val,
|
||||
snp_setting_data) = vs_man_svc.CreateVirtualSystemSnapshot(vm.path_())
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
job_wmi_path = job_path.replace('\\', '/')
|
||||
job = wmi.WMI(moniker=job_wmi_path)
|
||||
snp_setting_data = job.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
|
||||
return snp_setting_data.path_()
|
||||
|
||||
def remove_vm_snapshot(self, snapshot_path):
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
|
||||
(job_path, ret_val) = vs_man_svc.RemoveVirtualSystemSnapshot(
|
||||
snapshot_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def detach_vm_disk(self, vm_name, disk_path, is_physical=True):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
disk_resource = self._get_mounted_disk_resource_from_path(disk_path,
|
||||
is_physical)
|
||||
|
||||
if disk_resource:
|
||||
parent = self._conn.query("SELECT * FROM "
|
||||
"Msvm_ResourceAllocationSettingData "
|
||||
"WHERE __PATH = '%s'" %
|
||||
disk_resource.Parent)[0]
|
||||
|
||||
self._remove_virt_resource(disk_resource, vm.path_())
|
||||
if not is_physical:
|
||||
self._remove_virt_resource(parent, vm.path_())
|
||||
|
||||
def _get_mounted_disk_resource_from_path(self, disk_path, is_physical):
|
||||
if is_physical:
|
||||
class_name = self._RESOURCE_ALLOC_SETTING_DATA_CLASS
|
||||
res_sub_type = self._PHYS_DISK_RES_SUB_TYPE
|
||||
else:
|
||||
class_name = self._STORAGE_ALLOC_SETTING_DATA_CLASS
|
||||
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
|
||||
|
||||
disk_resources = self._conn.query("SELECT * FROM %(class_name)s "
|
||||
"WHERE ResourceSubType = "
|
||||
"'%(res_sub_type)s'" %
|
||||
{"class_name": class_name,
|
||||
"res_sub_type": res_sub_type})
|
||||
|
||||
for disk_resource in disk_resources:
|
||||
if disk_resource.HostResource:
|
||||
if disk_resource.HostResource[0].lower() == disk_path.lower():
|
||||
return disk_resource
|
||||
|
||||
def get_mounted_disk_by_drive_number(self, device_number):
|
||||
mounted_disks = self._conn.query("SELECT * FROM Msvm_DiskDrive "
|
||||
"WHERE DriveNumber=" +
|
||||
str(device_number))
|
||||
if len(mounted_disks):
|
||||
return mounted_disks[0].path_()
|
||||
|
||||
def get_controller_volume_paths(self, controller_path):
|
||||
disks = self._conn.query("SELECT * FROM %(class_name)s "
|
||||
"WHERE ResourceSubType = '%(res_sub_type)s' "
|
||||
"AND Parent='%(parent)s'" %
|
||||
{"class_name":
|
||||
self._RESOURCE_ALLOC_SETTING_DATA_CLASS,
|
||||
"res_sub_type":
|
||||
self._PHYS_DISK_RES_SUB_TYPE,
|
||||
"parent":
|
||||
controller_path})
|
||||
disk_data = {}
|
||||
for disk in disks:
|
||||
if disk.HostResource:
|
||||
disk_data[disk.path().RelPath] = disk.HostResource[0]
|
||||
return disk_data
|
||||
|
||||
def get_free_controller_slot(self, scsi_controller_path):
|
||||
attached_disks = self.get_attached_disks(scsi_controller_path)
|
||||
used_slots = [int(disk.AddressOnParent) for disk in attached_disks]
|
||||
|
||||
for slot in range(constants.SCSI_CONTROLLER_SLOTS_NUMBER):
|
||||
if slot not in used_slots:
|
||||
return slot
|
||||
raise HyperVException(_("Exceeded the maximum number of slots"))
|
||||
|
||||
def enable_vm_metrics_collection(self, vm_name):
|
||||
raise NotImplementedError(_("Metrics collection is not supported on "
|
||||
"this version of Hyper-V"))
|
||||
|
||||
def get_vm_serial_port_connection(self, vm_name, update_connection=None):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
rasds = vmsettings[0].associators(
|
||||
wmi_result_class=self._RESOURCE_ALLOC_SETTING_DATA_CLASS)
|
||||
serial_port = (
|
||||
[r for r in rasds if
|
||||
r.ResourceSubType == self._SERIAL_PORT_RES_SUB_TYPE][0])
|
||||
|
||||
if update_connection:
|
||||
serial_port.Connection = [update_connection]
|
||||
self._modify_virt_resource(serial_port, vm.path_())
|
||||
|
||||
if len(serial_port.Connection) > 0:
|
||||
return serial_port.Connection[0]
|
||||
|
||||
def get_active_instances(self):
|
||||
"""Return the names of all the active instances known to Hyper-V."""
|
||||
vm_names = self.list_instances()
|
||||
vms = [self._lookup_vm(vm_name) for vm_name in vm_names]
|
||||
active_vm_names = [v.ElementName for v in vms
|
||||
if v.EnabledState == constants.HYPERV_VM_STATE_ENABLED]
|
||||
|
||||
return active_vm_names
|
||||
|
||||
def get_vm_power_state_change_listener(self, timeframe, filtered_states):
|
||||
field = self._VM_ENABLED_STATE_PROP
|
||||
query = self._get_event_wql_query(cls=self._COMPUTER_SYSTEM_CLASS,
|
||||
field=field,
|
||||
timeframe=timeframe,
|
||||
filtered_states=filtered_states)
|
||||
return self._conn.Msvm_ComputerSystem.watch_for(raw_wql=query,
|
||||
fields=[field])
|
||||
|
||||
def _get_event_wql_query(self, cls, field,
|
||||
timeframe, filtered_states=None):
|
||||
"""Return a WQL query used for polling WMI events.
|
||||
|
||||
:param cls: the WMI class polled for events
|
||||
:param field: the field checked
|
||||
:param timeframe: check for events that occurred in
|
||||
the specified timeframe
|
||||
:param filtered_states: only catch events triggered when a WMI
|
||||
object transitioned into one of those
|
||||
states.
|
||||
"""
|
||||
query = ("SELECT %(field)s, TargetInstance "
|
||||
"FROM __InstanceModificationEvent "
|
||||
"WITHIN %(timeframe)s "
|
||||
"WHERE TargetInstance ISA '%(class)s' "
|
||||
"AND TargetInstance.%(field)s != "
|
||||
"PreviousInstance.%(field)s" %
|
||||
{'class': cls,
|
||||
'field': field,
|
||||
'timeframe': timeframe})
|
||||
if filtered_states:
|
||||
checks = ["TargetInstance.%s = '%s'" % (field, state)
|
||||
for state in filtered_states]
|
||||
query += " AND (%s)" % " OR ".join(checks)
|
||||
return query
|
||||
|
||||
def _get_instance_notes(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vmsettings = self._get_vm_setting_data(vm)
|
||||
return [note for note in vmsettings.Notes.split('\n') if note]
|
||||
|
||||
def get_instance_uuid(self, vm_name):
|
||||
instance_notes = self._get_instance_notes(vm_name)
|
||||
if instance_notes and uuidutils.is_uuid_like(instance_notes[0]):
|
||||
return instance_notes[0]
|
||||
|
||||
def get_vm_power_state(self, vm_enabled_state):
|
||||
return self._enabled_states_map.get(vm_enabled_state,
|
||||
constants.HYPERV_VM_STATE_OTHER)
|
328
oslo_windows/utils/vmutilsv2.py
Normal file
328
oslo_windows/utils/vmutilsv2.py
Normal file
@ -0,0 +1,328 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
Utility class for VM related operations.
|
||||
Based on the "root/virtualization/v2" namespace available starting with
|
||||
Hyper-V Server / Windows Server 2012.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.virt.hyperv import constants
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VMUtilsV2(vmutils.VMUtils):
|
||||
|
||||
_PHYS_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Physical Disk Drive'
|
||||
_DISK_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic Disk Drive'
|
||||
_DVD_DRIVE_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic DVD Drive'
|
||||
_SCSI_RES_SUBTYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller'
|
||||
_HARD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual Hard Disk'
|
||||
_DVD_DISK_RES_SUB_TYPE = 'Microsoft:Hyper-V:Virtual CD/DVD Disk'
|
||||
_IDE_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Emulated IDE Controller'
|
||||
_SCSI_CTRL_RES_SUB_TYPE = 'Microsoft:Hyper-V:Synthetic SCSI Controller'
|
||||
_SERIAL_PORT_RES_SUB_TYPE = 'Microsoft:Hyper-V:Serial Port'
|
||||
|
||||
_VIRTUAL_SYSTEM_TYPE_REALIZED = 'Microsoft:Hyper-V:System:Realized'
|
||||
_VIRTUAL_SYSTEM_SUBTYPE_GEN2 = 'Microsoft:Hyper-V:SubType:2'
|
||||
|
||||
_SNAPSHOT_FULL = 2
|
||||
|
||||
_METRIC_AGGR_CPU_AVG = 'Aggregated Average CPU Utilization'
|
||||
_METRIC_AGGR_MEMORY_AVG = 'Aggregated Average Memory Utilization'
|
||||
_METRIC_ENABLED = 2
|
||||
|
||||
_STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData'
|
||||
_ETHERNET_PORT_ALLOCATION_SETTING_DATA_CLASS = \
|
||||
'Msvm_EthernetPortAllocationSettingData'
|
||||
|
||||
_AUTOMATIC_STARTUP_ACTION_NONE = 2
|
||||
|
||||
_vm_power_states_map = {constants.HYPERV_VM_STATE_ENABLED: 2,
|
||||
constants.HYPERV_VM_STATE_DISABLED: 3,
|
||||
constants.HYPERV_VM_STATE_SHUTTING_DOWN: 4,
|
||||
constants.HYPERV_VM_STATE_REBOOT: 11,
|
||||
constants.HYPERV_VM_STATE_PAUSED: 9,
|
||||
constants.HYPERV_VM_STATE_SUSPENDED: 6}
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(VMUtilsV2, self).__init__(host)
|
||||
|
||||
def _init_hyperv_wmi_conn(self, host):
|
||||
self._conn = wmi.WMI(moniker='//%s/root/virtualization/v2' % host)
|
||||
|
||||
def list_instance_notes(self):
|
||||
instance_notes = []
|
||||
|
||||
for vs in self._conn.Msvm_VirtualSystemSettingData(
|
||||
['ElementName', 'Notes'],
|
||||
VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED):
|
||||
if vs.Notes is not None:
|
||||
instance_notes.append(
|
||||
(vs.ElementName, [v for v in vs.Notes if v]))
|
||||
|
||||
return instance_notes
|
||||
|
||||
def list_instances(self):
|
||||
"""Return the names of all the instances known to Hyper-V."""
|
||||
return [v.ElementName for v in
|
||||
self._conn.Msvm_VirtualSystemSettingData(
|
||||
['ElementName'],
|
||||
VirtualSystemType=self._VIRTUAL_SYSTEM_TYPE_REALIZED)]
|
||||
|
||||
def _create_vm_obj(self, vs_man_svc, vm_name, vm_gen, notes,
|
||||
dynamic_memory_ratio, instance_path):
|
||||
vs_data = self._conn.Msvm_VirtualSystemSettingData.new()
|
||||
vs_data.ElementName = vm_name
|
||||
vs_data.Notes = notes
|
||||
# Don't start automatically on host boot
|
||||
vs_data.AutomaticStartupAction = self._AUTOMATIC_STARTUP_ACTION_NONE
|
||||
|
||||
# vNUMA and dynamic memory are mutually exclusive
|
||||
if dynamic_memory_ratio > 1:
|
||||
vs_data.VirtualNumaEnabled = False
|
||||
|
||||
if vm_gen == constants.VM_GEN_2:
|
||||
vs_data.VirtualSystemSubType = self._VIRTUAL_SYSTEM_SUBTYPE_GEN2
|
||||
vs_data.SecureBootEnabled = False
|
||||
|
||||
# Created VMs must have their *DataRoot paths in the same location as
|
||||
# the instances' path.
|
||||
vs_data.ConfigurationDataRoot = instance_path
|
||||
vs_data.LogDataRoot = instance_path
|
||||
vs_data.SnapshotDataRoot = instance_path
|
||||
vs_data.SuspendDataRoot = instance_path
|
||||
vs_data.SwapFileDataRoot = instance_path
|
||||
|
||||
(job_path,
|
||||
vm_path,
|
||||
ret_val) = vs_man_svc.DefineSystem(ResourceSettings=[],
|
||||
ReferenceConfiguration=None,
|
||||
SystemSettings=vs_data.GetText_(1))
|
||||
job = self.check_ret_val(ret_val, job_path)
|
||||
if not vm_path and job:
|
||||
vm_path = job.associators(self._AFFECTED_JOB_ELEMENT_CLASS)[0]
|
||||
return self._get_wmi_obj(vm_path)
|
||||
|
||||
def _get_vm_setting_data(self, vm):
|
||||
vmsettings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)
|
||||
# Avoid snapshots
|
||||
return [s for s in vmsettings if
|
||||
s.VirtualSystemType == self._VIRTUAL_SYSTEM_TYPE_REALIZED][0]
|
||||
|
||||
def _get_attached_disks_query_string(self, scsi_controller_path):
|
||||
# DVD Drives can be attached to SCSI as well, if the VM Generation is 2
|
||||
return ("SELECT * FROM Msvm_ResourceAllocationSettingData WHERE ("
|
||||
"ResourceSubType='%(res_sub_type)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_virt)s' OR "
|
||||
"ResourceSubType='%(res_sub_type_dvd)s') AND "
|
||||
"Parent = '%(parent)s'" % {
|
||||
'res_sub_type': self._PHYS_DISK_RES_SUB_TYPE,
|
||||
'res_sub_type_virt': self._DISK_DRIVE_RES_SUB_TYPE,
|
||||
'res_sub_type_dvd': self._DVD_DRIVE_RES_SUB_TYPE,
|
||||
'parent': scsi_controller_path.replace("'", "''")})
|
||||
|
||||
def attach_drive(self, vm_name, path, ctrller_path, drive_addr,
|
||||
drive_type=constants.DISK):
|
||||
"""Create a drive and attach it to the vm."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
if drive_type == constants.DISK:
|
||||
res_sub_type = self._DISK_DRIVE_RES_SUB_TYPE
|
||||
elif drive_type == constants.DVD:
|
||||
res_sub_type = self._DVD_DRIVE_RES_SUB_TYPE
|
||||
|
||||
drive = self._get_new_resource_setting_data(res_sub_type)
|
||||
|
||||
# Set the ctrller as parent.
|
||||
drive.Parent = ctrller_path
|
||||
drive.Address = drive_addr
|
||||
drive.AddressOnParent = drive_addr
|
||||
# Add the cloned disk drive object to the vm.
|
||||
new_resources = self._add_virt_resource(drive, vm.path_())
|
||||
drive_path = new_resources[0]
|
||||
|
||||
if drive_type == constants.DISK:
|
||||
res_sub_type = self._HARD_DISK_RES_SUB_TYPE
|
||||
elif drive_type == constants.DVD:
|
||||
res_sub_type = self._DVD_DISK_RES_SUB_TYPE
|
||||
|
||||
res = self._get_new_resource_setting_data(
|
||||
res_sub_type, self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
|
||||
res.Parent = drive_path
|
||||
res.HostResource = [path]
|
||||
|
||||
self._add_virt_resource(res, vm.path_())
|
||||
|
||||
def attach_volume_to_controller(self, vm_name, controller_path, address,
|
||||
mounted_disk_path):
|
||||
"""Attach a volume to a controller."""
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
diskdrive = self._get_new_resource_setting_data(
|
||||
self._PHYS_DISK_RES_SUB_TYPE)
|
||||
|
||||
diskdrive.AddressOnParent = address
|
||||
diskdrive.Parent = controller_path
|
||||
diskdrive.HostResource = [mounted_disk_path]
|
||||
|
||||
self._add_virt_resource(diskdrive, vm.path_())
|
||||
|
||||
def _get_disk_resource_address(self, disk_resource):
|
||||
return disk_resource.AddressOnParent
|
||||
|
||||
def create_scsi_controller(self, vm_name):
|
||||
"""Create an iscsi controller ready to mount volumes."""
|
||||
scsicontrl = self._get_new_resource_setting_data(
|
||||
self._SCSI_RES_SUBTYPE)
|
||||
|
||||
scsicontrl.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}']
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
self._add_virt_resource(scsicontrl, vm.path_())
|
||||
|
||||
def _get_disk_resource_disk_path(self, disk_resource):
|
||||
return disk_resource.HostResource
|
||||
|
||||
def destroy_vm(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
# Remove the VM. It does not destroy any associated virtual disk.
|
||||
(job_path, ret_val) = vs_man_svc.DestroySystem(vm.path_())
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _add_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Adds a new resource to the VM."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
res_xml = [res_setting_data.GetText_(1)]
|
||||
(job_path,
|
||||
new_resources,
|
||||
ret_val) = vs_man_svc.AddResourceSettings(vm_path, res_xml)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
return new_resources
|
||||
|
||||
def _modify_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Updates a VM resource."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
(job_path,
|
||||
out_res_setting_data,
|
||||
ret_val) = vs_man_svc.ModifyResourceSettings(
|
||||
ResourceSettings=[res_setting_data.GetText_(1)])
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def _remove_virt_resource(self, res_setting_data, vm_path):
|
||||
"""Removes a VM resource."""
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
res_path = [res_setting_data.path_()]
|
||||
(job_path, ret_val) = vs_man_svc.RemoveResourceSettings(res_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def get_vm_state(self, vm_name):
|
||||
settings = self.get_vm_summary_info(vm_name)
|
||||
return settings['EnabledState']
|
||||
|
||||
def take_vm_snapshot(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vs_snap_svc = self._conn.Msvm_VirtualSystemSnapshotService()[0]
|
||||
|
||||
(job_path, snp_setting_data, ret_val) = vs_snap_svc.CreateSnapshot(
|
||||
AffectedSystem=vm.path_(),
|
||||
SnapshotType=self._SNAPSHOT_FULL)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
job_wmi_path = job_path.replace('\\', '/')
|
||||
job = wmi.WMI(moniker=job_wmi_path)
|
||||
snp_setting_data = job.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
|
||||
|
||||
return snp_setting_data.path_()
|
||||
|
||||
def remove_vm_snapshot(self, snapshot_path):
|
||||
vs_snap_svc = self._conn.Msvm_VirtualSystemSnapshotService()[0]
|
||||
(job_path, ret_val) = vs_snap_svc.DestroySnapshot(snapshot_path)
|
||||
self.check_ret_val(ret_val, job_path)
|
||||
|
||||
def set_nic_connection(self, vm_name, nic_name, vswitch_conn_data):
|
||||
nic_data = self._get_nic_data_by_name(nic_name)
|
||||
|
||||
eth_port_data = self._get_new_setting_data(
|
||||
self._ETHERNET_PORT_ALLOCATION_SETTING_DATA_CLASS)
|
||||
|
||||
eth_port_data.HostResource = [vswitch_conn_data]
|
||||
eth_port_data.Parent = nic_data.path_()
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
self._add_virt_resource(eth_port_data, vm.path_())
|
||||
|
||||
def enable_vm_metrics_collection(self, vm_name):
|
||||
metric_names = [self._METRIC_AGGR_CPU_AVG,
|
||||
self._METRIC_AGGR_MEMORY_AVG]
|
||||
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
metric_svc = self._conn.Msvm_MetricService()[0]
|
||||
(disks, volumes) = self._get_vm_disks(vm)
|
||||
filtered_disks = [d for d in disks if
|
||||
d.ResourceSubType is not self._DVD_DISK_RES_SUB_TYPE]
|
||||
|
||||
# enable metrics for disk.
|
||||
for disk in filtered_disks:
|
||||
self._enable_metrics(metric_svc, disk)
|
||||
|
||||
for metric_name in metric_names:
|
||||
metric_def = self._conn.CIM_BaseMetricDefinition(Name=metric_name)
|
||||
if not metric_def:
|
||||
LOG.debug("Metric not found: %s", metric_name)
|
||||
else:
|
||||
self._enable_metrics(metric_svc, vm, metric_def[0].path_())
|
||||
|
||||
def _enable_metrics(self, metric_svc, element, definition_path=None):
|
||||
metric_svc.ControlMetrics(
|
||||
Subject=element.path_(),
|
||||
Definition=definition_path,
|
||||
MetricCollectionEnabled=self._METRIC_ENABLED)
|
||||
|
||||
def get_vm_dvd_disk_paths(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
|
||||
settings = vm.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA_CLASS)[0]
|
||||
sasds = settings.associators(
|
||||
wmi_result_class=self._STORAGE_ALLOC_SETTING_DATA_CLASS)
|
||||
|
||||
dvd_paths = [sasd.HostResource[0] for sasd in sasds
|
||||
if sasd.ResourceSubType == self._DVD_DISK_RES_SUB_TYPE]
|
||||
|
||||
return dvd_paths
|
||||
|
||||
def _get_instance_notes(self, vm_name):
|
||||
vm = self._lookup_vm_check(vm_name)
|
||||
vmsettings = self._get_vm_setting_data(vm)
|
||||
return [note for note in vmsettings.Notes if note]
|
122
oslo_windows/utils/volumeutils.py
Normal file
122
oslo_windows/utils/volumeutils.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes,
|
||||
and storage repositories
|
||||
|
||||
Official Microsoft iSCSI Initiator and iSCSI command line interface
|
||||
documentation can be retrieved at:
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=34750
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from six.moves import range
|
||||
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
from nova.virt.hyperv import basevolumeutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class VolumeUtils(basevolumeutils.BaseVolumeUtils):
|
||||
|
||||
def __init__(self):
|
||||
super(VolumeUtils, self).__init__()
|
||||
|
||||
def execute(self, *args, **kwargs):
|
||||
stdout_value, stderr_value = utils.execute(*args, **kwargs)
|
||||
if stdout_value.find('The operation completed successfully') == -1:
|
||||
raise vmutils.HyperVException(_('An error has occurred when '
|
||||
'calling the iscsi initiator: %s')
|
||||
% stdout_value)
|
||||
return stdout_value
|
||||
|
||||
def _login_target_portal(self, target_portal):
|
||||
(target_address,
|
||||
target_port) = utils.parse_server_string(target_portal)
|
||||
|
||||
output = self.execute('iscsicli.exe', 'ListTargetPortals')
|
||||
pattern = r'Address and Socket *: (.*)'
|
||||
portals = [addr.split() for addr in re.findall(pattern, output)]
|
||||
LOG.debug("Ensuring connection to portal: %s" % target_portal)
|
||||
if [target_address, str(target_port)] in portals:
|
||||
self.execute('iscsicli.exe', 'RefreshTargetPortal',
|
||||
target_address, target_port)
|
||||
else:
|
||||
# Adding target portal to iscsi initiator. Sending targets
|
||||
self.execute('iscsicli.exe', 'AddTargetPortal',
|
||||
target_address, target_port,
|
||||
'*', '*', '*', '*', '*', '*', '*', '*', '*', '*', '*',
|
||||
'*', '*')
|
||||
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal,
|
||||
auth_username=None, auth_password=None):
|
||||
"""Ensure that the target is logged in."""
|
||||
|
||||
self._login_target_portal(target_portal)
|
||||
# Listing targets
|
||||
self.execute('iscsicli.exe', 'ListTargets')
|
||||
|
||||
retry_count = CONF.hyperv.volume_attach_retry_count
|
||||
|
||||
# If the target is not connected, at least two iterations are needed:
|
||||
# one for performing the login and another one for checking if the
|
||||
# target was logged in successfully.
|
||||
if retry_count < 2:
|
||||
retry_count = 2
|
||||
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
session_info = self.execute('iscsicli.exe', 'SessionList')
|
||||
if session_info.find(target_iqn) == -1:
|
||||
# Sending login
|
||||
self.execute('iscsicli.exe', 'qlogintarget', target_iqn,
|
||||
auth_username, auth_password)
|
||||
else:
|
||||
return
|
||||
except vmutils.HyperVException as exc:
|
||||
LOG.debug("Attempt %(attempt)d to connect to target "
|
||||
"%(target_iqn)s failed. Retrying. "
|
||||
"Exceptipn: %(exc)s ",
|
||||
{'target_iqn': target_iqn,
|
||||
'exc': exc,
|
||||
'attempt': attempt})
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
|
||||
raise vmutils.HyperVException(_('Failed to login target %s') %
|
||||
target_iqn)
|
||||
|
||||
def logout_storage_target(self, target_iqn):
|
||||
"""Logs out storage target through its session id."""
|
||||
|
||||
sessions = self._conn_wmi.query("SELECT * FROM "
|
||||
"MSiSCSIInitiator_SessionClass "
|
||||
"WHERE TargetName='%s'" % target_iqn)
|
||||
for session in sessions:
|
||||
self.execute_log_out(session.SessionId)
|
||||
|
||||
def execute_log_out(self, session_id):
|
||||
"""Executes log out of the session described by its session ID."""
|
||||
self.execute('iscsicli.exe', 'logouttarget', session_id)
|
132
oslo_windows/utils/volumeutilsv2.py
Normal file
132
oslo_windows/utils/volumeutilsv2.py
Normal file
@ -0,0 +1,132 @@
|
||||
# Copyright 2012 Pedro Navarro Perez
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Helper methods for operations related to the management of volumes
|
||||
and storage repositories on Windows Server 2012 and above
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import wmi
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from six.moves import range
|
||||
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
from nova.virt.hyperv import basevolumeutils
|
||||
from nova.virt.hyperv import vmutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class VolumeUtilsV2(basevolumeutils.BaseVolumeUtils):
|
||||
_CHAP_AUTH_TYPE = 'ONEWAYCHAP'
|
||||
|
||||
def __init__(self, host='.'):
|
||||
super(VolumeUtilsV2, self).__init__(host)
|
||||
|
||||
storage_namespace = '//%s/root/microsoft/windows/storage' % host
|
||||
if sys.platform == 'win32':
|
||||
self._conn_storage = wmi.WMI(moniker=storage_namespace)
|
||||
|
||||
def _login_target_portal(self, target_portal):
|
||||
(target_address,
|
||||
target_port) = utils.parse_server_string(target_portal)
|
||||
|
||||
# Checking if the portal is already connected.
|
||||
portal = self._conn_storage.query("SELECT * FROM "
|
||||
"MSFT_iSCSITargetPortal "
|
||||
"WHERE TargetPortalAddress='%s' "
|
||||
"AND TargetPortalPortNumber='%s'"
|
||||
% (target_address, target_port))
|
||||
if portal:
|
||||
portal[0].Update()
|
||||
else:
|
||||
# Adding target portal to iscsi initiator. Sending targets
|
||||
portal = self._conn_storage.MSFT_iSCSITargetPortal
|
||||
portal.New(TargetPortalAddress=target_address,
|
||||
TargetPortalPortNumber=target_port)
|
||||
|
||||
def login_storage_target(self, target_lun, target_iqn, target_portal,
|
||||
auth_username=None, auth_password=None):
|
||||
"""Ensure that the target is logged in."""
|
||||
|
||||
self._login_target_portal(target_portal)
|
||||
|
||||
retry_count = CONF.hyperv.volume_attach_retry_count
|
||||
|
||||
# If the target is not connected, at least two iterations are needed:
|
||||
# one for performing the login and another one for checking if the
|
||||
# target was logged in successfully.
|
||||
if retry_count < 2:
|
||||
retry_count = 2
|
||||
|
||||
for attempt in range(retry_count):
|
||||
target = self._conn_storage.query("SELECT * FROM MSFT_iSCSITarget "
|
||||
"WHERE NodeAddress='%s' " %
|
||||
target_iqn)
|
||||
|
||||
if target and target[0].IsConnected:
|
||||
if attempt == 0:
|
||||
# The target was already connected but an update may be
|
||||
# required
|
||||
target[0].Update()
|
||||
return
|
||||
try:
|
||||
target = self._conn_storage.MSFT_iSCSITarget
|
||||
auth = {}
|
||||
if auth_username and auth_password:
|
||||
auth['AuthenticationType'] = self._CHAP_AUTH_TYPE
|
||||
auth['ChapUsername'] = auth_username
|
||||
auth['ChapSecret'] = auth_password
|
||||
target.Connect(NodeAddress=target_iqn,
|
||||
IsPersistent=True, **auth)
|
||||
time.sleep(CONF.hyperv.volume_attach_retry_interval)
|
||||
except wmi.x_wmi as exc:
|
||||
LOG.debug("Attempt %(attempt)d to connect to target "
|
||||
"%(target_iqn)s failed. Retrying. "
|
||||
"WMI exception: %(exc)s " %
|
||||
{'target_iqn': target_iqn,
|
||||
'exc': exc,
|
||||
'attempt': attempt})
|
||||
raise vmutils.HyperVException(_('Failed to login target %s') %
|
||||
target_iqn)
|
||||
|
||||
def logout_storage_target(self, target_iqn):
|
||||
"""Logs out storage target through its session id."""
|
||||
targets = self._conn_storage.MSFT_iSCSITarget(NodeAddress=target_iqn)
|
||||
if targets:
|
||||
target = targets[0]
|
||||
if target.IsConnected:
|
||||
sessions = self._conn_storage.MSFT_iSCSISession(
|
||||
TargetNodeAddress=target_iqn)
|
||||
|
||||
for session in sessions:
|
||||
if session.IsPersistent:
|
||||
session.Unregister()
|
||||
|
||||
target.Disconnect()
|
||||
|
||||
def execute_log_out(self, session_id):
|
||||
sessions = self._conn_wmi.MSiSCSIInitiator_SessionClass(
|
||||
SessionId=session_id)
|
||||
if sessions:
|
||||
self.logout_storage_target(sessions[0].TargetName)
|
110
oslo_windows/utilsfactory.py
Normal file
110
oslo_windows/utilsfactory.py
Normal file
@ -0,0 +1,110 @@
|
||||
# Copyright 2013 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.virt.hyperv import hostutils
|
||||
from nova.virt.hyperv import hostutilsv2
|
||||
from nova.virt.hyperv import livemigrationutils
|
||||
from nova.virt.hyperv import networkutils
|
||||
from nova.virt.hyperv import networkutilsv2
|
||||
from nova.virt.hyperv import pathutils
|
||||
from nova.virt.hyperv import rdpconsoleutils
|
||||
from nova.virt.hyperv import rdpconsoleutilsv2
|
||||
from nova.virt.hyperv import vhdutils
|
||||
from nova.virt.hyperv import vhdutilsv2
|
||||
from nova.virt.hyperv import vmutils
|
||||
from nova.virt.hyperv import vmutilsv2
|
||||
from nova.virt.hyperv import volumeutils
|
||||
from nova.virt.hyperv import volumeutilsv2
|
||||
|
||||
hyper_opts = [
|
||||
cfg.BoolOpt('force_hyperv_utils_v1',
|
||||
default=False,
|
||||
help='Force V1 WMI utility classes'),
|
||||
cfg.BoolOpt('force_volumeutils_v1',
|
||||
default=False,
|
||||
help='Force V1 volume utility class'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(hyper_opts, 'hyperv')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
utils = hostutils.HostUtils()
|
||||
|
||||
|
||||
def _get_class(v1_class, v2_class, force_v1_flag):
|
||||
# V2 classes are supported starting from Hyper-V Server 2012 and
|
||||
# Windows Server 2012 (kernel version 6.2)
|
||||
if not force_v1_flag and utils.check_min_windows_version(6, 2):
|
||||
cls = v2_class
|
||||
else:
|
||||
cls = v1_class
|
||||
LOG.debug("Loading class: %(module_name)s.%(class_name)s",
|
||||
{'module_name': cls.__module__, 'class_name': cls.__name__})
|
||||
return cls
|
||||
|
||||
|
||||
def _get_virt_utils_class(v1_class, v2_class):
|
||||
# The "root/virtualization" WMI namespace is no longer supported on
|
||||
# Windows Server / Hyper-V Server 2012 R2 / Windows 8.1
|
||||
# (kernel version 6.3) or above.
|
||||
if (CONF.hyperv.force_hyperv_utils_v1 and
|
||||
utils.check_min_windows_version(6, 3)):
|
||||
raise vmutils.HyperVException(
|
||||
_('The "force_hyperv_utils_v1" option cannot be set to "True" '
|
||||
'on Windows Server / Hyper-V Server 2012 R2 or above as the WMI '
|
||||
'"root/virtualization" namespace is no longer supported.'))
|
||||
return _get_class(v1_class, v2_class, CONF.hyperv.force_hyperv_utils_v1)
|
||||
|
||||
|
||||
def get_vmutils(host='.'):
|
||||
return _get_virt_utils_class(vmutils.VMUtils, vmutilsv2.VMUtilsV2)(host)
|
||||
|
||||
|
||||
def get_vhdutils():
|
||||
return _get_virt_utils_class(vhdutils.VHDUtils, vhdutilsv2.VHDUtilsV2)()
|
||||
|
||||
|
||||
def get_networkutils():
|
||||
return _get_virt_utils_class(networkutils.NetworkUtils,
|
||||
networkutilsv2.NetworkUtilsV2)()
|
||||
|
||||
|
||||
def get_hostutils():
|
||||
return _get_virt_utils_class(hostutils.HostUtils,
|
||||
hostutilsv2.HostUtilsV2)()
|
||||
|
||||
|
||||
def get_pathutils():
|
||||
return pathutils.PathUtils()
|
||||
|
||||
|
||||
def get_volumeutils():
|
||||
return _get_class(volumeutils.VolumeUtils, volumeutilsv2.VolumeUtilsV2,
|
||||
CONF.hyperv.force_volumeutils_v1)()
|
||||
|
||||
|
||||
def get_livemigrationutils():
|
||||
return livemigrationutils.LiveMigrationUtils()
|
||||
|
||||
|
||||
def get_rdpconsoleutils():
|
||||
return _get_virt_utils_class(rdpconsoleutils.RDPConsoleUtils,
|
||||
rdpconsoleutilsv2.RDPConsoleUtilsV2)()
|
Loading…
Reference in New Issue
Block a user