diff --git a/oslo_windows/tests/__init__.py b/oslo_windows/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/oslo_windows/tests/test_utilsfactory.py b/oslo_windows/tests/test_utilsfactory.py
new file mode 100644
index 00000000..e656a057
--- /dev/null
+++ b/oslo_windows/tests/test_utilsfactory.py
@@ -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)
diff --git a/oslo_windows/tests/utils/__init__.py b/oslo_windows/tests/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/oslo_windows/tests/utils/test_basevolumeutils.py b/oslo_windows/tests/utils/test_basevolumeutils.py
new file mode 100644
index 00000000..75c24afa
--- /dev/null
+++ b/oslo_windows/tests/utils/test_basevolumeutils.py
@@ -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
diff --git a/oslo_windows/tests/utils/test_hostutils.py b/oslo_windows/tests/utils/test_hostutils.py
new file mode 100644
index 00000000..0d6b641c
--- /dev/null
+++ b/oslo_windows/tests/utils/test_hostutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_hostutilsv2.py b/oslo_windows/tests/utils/test_hostutilsv2.py
new file mode 100644
index 00000000..b2e426bf
--- /dev/null
+++ b/oslo_windows/tests/utils/test_hostutilsv2.py
@@ -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()
diff --git a/oslo_windows/tests/utils/test_ioutils.py b/oslo_windows/tests/utils/test_ioutils.py
new file mode 100644
index 00000000..b412ed43
--- /dev/null
+++ b/oslo_windows/tests/utils/test_ioutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_livemigrationutils.py b/oslo_windows/tests/utils/test_livemigrationutils.py
new file mode 100644
index 00000000..f3c6f19b
--- /dev/null
+++ b/oslo_windows/tests/utils/test_livemigrationutils.py
@@ -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
diff --git a/oslo_windows/tests/utils/test_networkutils.py b/oslo_windows/tests/utils/test_networkutils.py
new file mode 100644
index 00000000..281df298
--- /dev/null
+++ b/oslo_windows/tests/utils/test_networkutils.py
@@ -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())
diff --git a/oslo_windows/tests/utils/test_networkutilsv2.py b/oslo_windows/tests/utils/test_networkutilsv2.py
new file mode 100644
index 00000000..1038e886
--- /dev/null
+++ b/oslo_windows/tests/utils/test_networkutilsv2.py
@@ -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())
diff --git a/oslo_windows/tests/utils/test_pathutils.py b/oslo_windows/tests/utils/test_pathutils.py
new file mode 100644
index 00000000..c0f17d6a
--- /dev/null
+++ b/oslo_windows/tests/utils/test_pathutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_rdpconsoleutils.py b/oslo_windows/tests/utils/test_rdpconsoleutils.py
new file mode 100644
index 00000000..98d4484b
--- /dev/null
+++ b/oslo_windows/tests/utils/test_rdpconsoleutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_rdpconsoleutilsv2.py b/oslo_windows/tests/utils/test_rdpconsoleutilsv2.py
new file mode 100644
index 00000000..bcdfaf92
--- /dev/null
+++ b/oslo_windows/tests/utils/test_rdpconsoleutilsv2.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_vhdutils.py b/oslo_windows/tests/utils/test_vhdutils.py
new file mode 100644
index 00000000..659dd5ef
--- /dev/null
+++ b/oslo_windows/tests/utils/test_vhdutils.py
@@ -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 = (
+ """
+
+33554432
+
+
+Virtual Hard Disk Setting Data
+
+
+Setting Data for a Virtual Hard Disk.
+
+
+fake_path.vhdx
+
+
+%(format)s
+
+
+52794B89-AC06-4349-AC57-486CAAD52F69
+
+
+4096
+
+
+%(max_internal_size)s
+
+
+%(parent_path)s
+
+
+%(path)s
+
+
+4096
+
+
+%(type)s
+
+""" % {'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)
diff --git a/oslo_windows/tests/utils/test_vhdutilsv2.py b/oslo_windows/tests/utils/test_vhdutilsv2.py
new file mode 100644
index 00000000..765d1ddb
--- /dev/null
+++ b/oslo_windows/tests/utils/test_vhdutilsv2.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_vmutils.py b/oslo_windows/tests/utils/test_vmutils.py
new file mode 100644
index 00000000..745f9b28
--- /dev/null
+++ b/oslo_windows/tests/utils/test_vmutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_vmutilsv2.py b/oslo_windows/tests/utils/test_vmutilsv2.py
new file mode 100644
index 00000000..0dbb2c94
--- /dev/null
+++ b/oslo_windows/tests/utils/test_vmutilsv2.py
@@ -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])
diff --git a/oslo_windows/tests/utils/test_volumeutils.py b/oslo_windows/tests/utils/test_volumeutils.py
new file mode 100644
index 00000000..0a3f661c
--- /dev/null
+++ b/oslo_windows/tests/utils/test_volumeutils.py
@@ -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)
diff --git a/oslo_windows/tests/utils/test_volumeutilsv2.py b/oslo_windows/tests/utils/test_volumeutilsv2.py
new file mode 100644
index 00000000..5c73f0ce
--- /dev/null
+++ b/oslo_windows/tests/utils/test_volumeutilsv2.py
@@ -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)
diff --git a/oslo_windows/utils/__init__.py b/oslo_windows/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/oslo_windows/utils/basevolumeutils.py b/oslo_windows/utils/basevolumeutils.py
new file mode 100644
index 00000000..5fcb4fac
--- /dev/null
+++ b/oslo_windows/utils/basevolumeutils.py
@@ -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)
diff --git a/oslo_windows/utils/constants.py b/oslo_windows/utils/constants.py
new file mode 100644
index 00000000..5d2fd6b6
--- /dev/null
+++ b/oslo_windows/utils/constants.py
@@ -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
diff --git a/oslo_windows/utils/hostutils.py b/oslo_windows/utils/hostutils.py
new file mode 100644
index 00000000..7f073e6a
--- /dev/null
+++ b/oslo_windows/utils/hostutils.py
@@ -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
diff --git a/oslo_windows/utils/hostutilsv2.py b/oslo_windows/utils/hostutilsv2.py
new file mode 100644
index 00000000..7bed7a57
--- /dev/null
+++ b/oslo_windows/utils/hostutilsv2.py
@@ -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')
diff --git a/oslo_windows/utils/ioutils.py b/oslo_windows/utils/ioutils.py
new file mode 100644
index 00000000..e28deb84
--- /dev/null
+++ b/oslo_windows/utils/ioutils.py
@@ -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()
diff --git a/oslo_windows/utils/livemigrationutils.py b/oslo_windows/utils/livemigrationutils.py
new file mode 100644
index 00000000..71f406a8
--- /dev/null
+++ b/oslo_windows/utils/livemigrationutils.py
@@ -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)
diff --git a/oslo_windows/utils/networkutils.py b/oslo_windows/utils/networkutils.py
new file mode 100644
index 00000000..07ad4891
--- /dev/null
+++ b/oslo_windows/utils/networkutils.py
@@ -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
diff --git a/oslo_windows/utils/networkutilsv2.py b/oslo_windows/utils/networkutilsv2.py
new file mode 100644
index 00000000..558f7c44
--- /dev/null
+++ b/oslo_windows/utils/networkutilsv2.py
@@ -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
diff --git a/oslo_windows/utils/pathutils.py b/oslo_windows/utils/pathutils.py
new file mode 100644
index 00000000..657717bb
--- /dev/null
+++ b/oslo_windows/utils/pathutils.py
@@ -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)
diff --git a/oslo_windows/utils/rdpconsoleutils.py b/oslo_windows/utils/rdpconsoleutils.py
new file mode 100644
index 00000000..c897ea8c
--- /dev/null
+++ b/oslo_windows/utils/rdpconsoleutils.py
@@ -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
diff --git a/oslo_windows/utils/rdpconsoleutilsv2.py b/oslo_windows/utils/rdpconsoleutilsv2.py
new file mode 100644
index 00000000..4c173e22
--- /dev/null
+++ b/oslo_windows/utils/rdpconsoleutilsv2.py
@@ -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
diff --git a/oslo_windows/utils/vhdutils.py b/oslo_windows/utils/vhdutils.py
new file mode 100644
index 00000000..e5ac1311
--- /dev/null
+++ b/oslo_windows/utils/vhdutils.py
@@ -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
diff --git a/oslo_windows/utils/vhdutilsv2.py b/oslo_windows/utils/vhdutilsv2.py
new file mode 100644
index 00000000..07f267d4
--- /dev/null
+++ b/oslo_windows/utils/vhdutilsv2.py
@@ -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(' 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)
diff --git a/oslo_windows/utils/vmutilsv2.py b/oslo_windows/utils/vmutilsv2.py
new file mode 100644
index 00000000..a78564e4
--- /dev/null
+++ b/oslo_windows/utils/vmutilsv2.py
@@ -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]
diff --git a/oslo_windows/utils/volumeutils.py b/oslo_windows/utils/volumeutils.py
new file mode 100644
index 00000000..fa5f2ff9
--- /dev/null
+++ b/oslo_windows/utils/volumeutils.py
@@ -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)
diff --git a/oslo_windows/utils/volumeutilsv2.py b/oslo_windows/utils/volumeutilsv2.py
new file mode 100644
index 00000000..0a29e01d
--- /dev/null
+++ b/oslo_windows/utils/volumeutilsv2.py
@@ -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)
diff --git a/oslo_windows/utilsfactory.py b/oslo_windows/utilsfactory.py
new file mode 100644
index 00000000..453993bd
--- /dev/null
+++ b/oslo_windows/utilsfactory.py
@@ -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)()