diff --git a/nova/tests/unit/virt/hyperv/test_base.py b/nova/tests/unit/virt/hyperv/test_base.py index 4effe9e65e37..84b998df3ad6 100644 --- a/nova/tests/unit/virt/hyperv/test_base.py +++ b/nova/tests/unit/virt/hyperv/test_base.py @@ -15,9 +15,9 @@ # under the License. import mock +from os_win import utilsfactory from nova import test -from nova.virt.hyperv import utilsfactory class HyperVBaseTestCase(test.NoDBTestCase): @@ -28,14 +28,12 @@ class HyperVBaseTestCase(test.NoDBTestCase): wmi_patcher = mock.patch('__builtin__.wmi', create=True, new=self._mock_wmi) platform_patcher = mock.patch('sys.platform', 'win32') - hostutils_patcher = mock.patch.object(utilsfactory, 'utils') + utilsfactory_patcher = mock.patch.object(utilsfactory, '_get_class') platform_patcher.start() wmi_patcher.start() - patched_hostutils = hostutils_patcher.start() - - patched_hostutils.check_min_windows_version.return_value = False + utilsfactory_patcher.start() self.addCleanup(wmi_patcher.stop) self.addCleanup(platform_patcher.stop) - self.addCleanup(hostutils_patcher.stop) + self.addCleanup(utilsfactory_patcher.stop) diff --git a/nova/tests/unit/virt/hyperv/test_driver.py b/nova/tests/unit/virt/hyperv/test_driver.py index 4362e9a0960e..cd726abbf1c1 100644 --- a/nova/tests/unit/virt/hyperv/test_driver.py +++ b/nova/tests/unit/virt/hyperv/test_driver.py @@ -20,10 +20,12 @@ Unit tests for the Hyper-V Driver. import platform import mock +from os_win import exceptions as os_win_exc from nova import exception from nova.tests.unit import fake_instance from nova.tests.unit.virt.hyperv import test_base +from nova import utils from nova.virt import driver as base_driver from nova.virt.hyperv import driver @@ -46,16 +48,45 @@ class HyperVDriverTestCase(test_base.HyperVBaseTestCase): self.driver._migrationops = mock.MagicMock() self.driver._rdpconsoleops = mock.MagicMock() - @mock.patch.object(driver.hostutils.HostUtils, 'check_min_windows_version') - def test_check_minimum_windows_version(self, mock_check_min_win_version): - mock_check_min_win_version.return_value = False + @mock.patch.object(driver.utilsfactory, 'get_hostutils') + def test_check_minimum_windows_version(self, mock_get_hostutils): + mock_hostutils = mock_get_hostutils.return_value + mock_hostutils.check_min_windows_version.return_value = False self.assertRaises(exception.HypervisorTooOld, self.driver._check_minimum_windows_version) def test_public_api_signatures(self): - self.assertPublicAPISignatures(base_driver.ComputeDriver(None), - self.driver) + # NOTE(claudiub): wrapped functions do not keep the same signature in + # Python 2.7, which causes this test to fail. Instead, we should + # compare the public API signatures of the unwrapped methods. + + for attr in driver.HyperVDriver.__dict__: + class_member = getattr(driver.HyperVDriver, attr) + if callable(class_member): + mocked_method = mock.patch.object( + driver.HyperVDriver, attr, + utils.get_wrapped_function(class_member)) + mocked_method.start() + self.addCleanup(mocked_method.stop) + + self.assertPublicAPISignatures(base_driver.ComputeDriver, + driver.HyperVDriver) + + def test_converted_exception(self): + self.driver._vmops.get_info.side_effect = ( + os_win_exc.OSWinException) + self.assertRaises(exception.NovaException, + self.driver.get_info, mock.sentinel.instance) + + self.driver._vmops.get_info.side_effect = os_win_exc.HyperVException + self.assertRaises(exception.NovaException, + self.driver.get_info, mock.sentinel.instance) + + self.driver._vmops.get_info.side_effect = ( + os_win_exc.HyperVVMNotFoundException(vm_name='foofoo')) + self.assertRaises(exception.InstanceNotFound, + self.driver.get_info, mock.sentinel.instance) @mock.patch.object(driver.eventhandler, 'InstanceEventHandler') def test_init_host(self, mock_InstanceEventHandler): diff --git a/nova/tests/unit/virt/hyperv/test_eventhandler.py b/nova/tests/unit/virt/hyperv/test_eventhandler.py index 04228dec94d7..439c59ce03f1 100644 --- a/nova/tests/unit/virt/hyperv/test_eventhandler.py +++ b/nova/tests/unit/virt/hyperv/test_eventhandler.py @@ -15,13 +15,13 @@ import eventlet import mock +from os_win import exceptions as os_win_exc +from os_win import utilsfactory -from nova import exception from nova.tests.unit.virt.hyperv import test_base from nova import utils from nova.virt.hyperv import constants from nova.virt.hyperv import eventhandler -from nova.virt.hyperv import utilsfactory class EventHandlerTestCase(test_base.HyperVBaseTestCase): @@ -123,7 +123,8 @@ class EventHandlerTestCase(test_base.HyperVBaseTestCase): side_effect = (mock.sentinel.instance_uuid if not missing_uuid else None, ) else: - side_effect = exception.InstanceNotFound('fake_instance_uuid') + side_effect = os_win_exc.HyperVVMNotFoundException( + vm_name=mock.sentinel.instance_name) mock_get_uuid = self._event_handler._vmutils.get_instance_uuid mock_get_uuid.side_effect = side_effect diff --git a/nova/tests/unit/virt/hyperv/test_imagecache.py b/nova/tests/unit/virt/hyperv/test_imagecache.py index 2e4fc3a5ebca..96afa44205e2 100644 --- a/nova/tests/unit/virt/hyperv/test_imagecache.py +++ b/nova/tests/unit/virt/hyperv/test_imagecache.py @@ -48,16 +48,12 @@ class ImageCacheTestCase(test.NoDBTestCase): # in order to return the proper Utils Class, so it must be mocked. patched_get_hostutils = mock.patch.object(imagecache.utilsfactory, "get_hostutils") - patched_get_pathutils = mock.patch.object(imagecache.utilsfactory, - "get_pathutils") patched_get_vhdutils = mock.patch.object(imagecache.utilsfactory, "get_vhdutils") patched_get_hostutils.start() - patched_get_pathutils.start() patched_get_vhdutils.start() self.addCleanup(patched_get_hostutils.stop) - self.addCleanup(patched_get_pathutils.stop) self.addCleanup(patched_get_vhdutils.stop) self.imagecache = imagecache.ImageCache() @@ -82,8 +78,8 @@ class ImageCacheTestCase(test.NoDBTestCase): @mock.patch.object(imagecache.ImageCache, '_get_root_vhd_size_gb') def test_resize_and_cache_vhd_smaller(self, mock_get_vhd_size_gb): - self.imagecache._vhdutils.get_vhd_info.return_value = { - 'MaxInternalSize': (self.FAKE_VHD_SIZE_GB + 1) * units.Gi + self.imagecache._vhdutils.get_vhd_size.return_value = { + 'VirtualSize': (self.FAKE_VHD_SIZE_GB + 1) * units.Gi } mock_get_vhd_size_gb.return_value = self.FAKE_VHD_SIZE_GB mock_internal_vhd_size = ( @@ -95,7 +91,7 @@ class ImageCacheTestCase(test.NoDBTestCase): mock.sentinel.instance, mock.sentinel.vhd_path) - self.imagecache._vhdutils.get_vhd_info.assert_called_once_with( + self.imagecache._vhdutils.get_vhd_size.assert_called_once_with( mock.sentinel.vhd_path) mock_get_vhd_size_gb.assert_called_once_with(mock.sentinel.instance) mock_internal_vhd_size.assert_called_once_with( diff --git a/nova/tests/unit/virt/hyperv/test_livemigrationops.py b/nova/tests/unit/virt/hyperv/test_livemigrationops.py index 5926f766e993..e2ab8f131173 100644 --- a/nova/tests/unit/virt/hyperv/test_livemigrationops.py +++ b/nova/tests/unit/virt/hyperv/test_livemigrationops.py @@ -14,12 +14,12 @@ # under the License. import mock +from os_win import exceptions as os_win_exc from oslo_config import cfg from nova.tests.unit import fake_instance from nova.tests.unit.virt.hyperv import test_base from nova.virt.hyperv import livemigrationops -from nova.virt.hyperv import vmutils CONF = cfg.CONF @@ -44,8 +44,8 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase): fake_dest = mock.sentinel.DESTINATION self._livemigrops._livemigrutils.live_migrate_vm.side_effect = [ side_effect] - if side_effect is vmutils.HyperVException: - self.assertRaises(vmutils.HyperVException, + if side_effect is os_win_exc.HyperVException: + self.assertRaises(os_win_exc.HyperVException, self._livemigrops.live_migration, self.context, mock_instance, fake_dest, mock_post, mock_recover, False, None) @@ -70,7 +70,7 @@ class LiveMigrationOpsTestCase(test_base.HyperVBaseTestCase): self._test_live_migration(side_effect=None) def test_live_migration_exception(self): - self._test_live_migration(side_effect=vmutils.HyperVException) + self._test_live_migration(side_effect=os_win_exc.HyperVException) def test_live_migration_wrong_os_version(self): self._livemigrops._livemigrutils = None diff --git a/nova/tests/unit/virt/hyperv/test_migrationops.py b/nova/tests/unit/virt/hyperv/test_migrationops.py index e1371aca20c0..20a17a13d00b 100644 --- a/nova/tests/unit/virt/hyperv/test_migrationops.py +++ b/nova/tests/unit/virt/hyperv/test_migrationops.py @@ -15,6 +15,7 @@ import os import mock +from os_win import exceptions as os_win_exc from oslo_utils import units from nova import exception @@ -23,7 +24,6 @@ from nova import test from nova.tests.unit import fake_instance from nova.tests.unit.virt.hyperv import test_base from nova.virt.hyperv import migrationops -from nova.virt.hyperv import vmutils class MigrationOpsTestCase(test_base.HyperVBaseTestCase): @@ -288,7 +288,7 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): recon_parent_vhd.assert_called_once_with(fake_diff_vhd_path, base_vhd_copy_path) self._migrationops._vhdutils.merge_vhd.assert_called_once_with( - fake_diff_vhd_path, base_vhd_copy_path) + fake_diff_vhd_path) self._migrationops._pathutils.rename.assert_called_once_with( base_vhd_copy_path, fake_diff_vhd_path) @@ -300,10 +300,10 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): os.path.basename(fake_base_vhd_path)) self._migrationops._vhdutils.reconnect_parent_vhd.side_effect = ( - vmutils.HyperVException) + os_win_exc.HyperVException) self._migrationops._pathutils.exists.return_value = True - self.assertRaises(vmutils.HyperVException, + self.assertRaises(os_win_exc.HyperVException, self._migrationops._merge_base_vhd, fake_diff_vhd_path, fake_base_vhd_path) self._migrationops._pathutils.exists.assert_called_once_with( @@ -314,7 +314,7 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch.object(migrationops.MigrationOps, '_resize_vhd') def test_check_resize_vhd(self, mock_resize_vhd): self._migrationops._check_resize_vhd( - vhd_path=mock.sentinel.vhd_path, vhd_info={'MaxInternalSize': 1}, + vhd_path=mock.sentinel.vhd_path, vhd_info={'VirtualSize': 1}, new_size=2) mock_resize_vhd.assert_called_once_with(mock.sentinel.vhd_path, 2) @@ -322,7 +322,7 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): self.assertRaises(exception.CannotResizeDisk, self._migrationops._check_resize_vhd, mock.sentinel.vhd_path, - {'MaxInternalSize': 1}, 0) + {'VirtualSize': 1}, 0) @mock.patch.object(migrationops.MigrationOps, '_merge_base_vhd') def test_resize_vhd(self, mock_merge_base_vhd): diff --git a/nova/tests/unit/virt/hyperv/test_pathutils.py b/nova/tests/unit/virt/hyperv/test_pathutils.py index c3517d60c711..d4ef28d121e6 100644 --- a/nova/tests/unit/virt/hyperv/test_pathutils.py +++ b/nova/tests/unit/virt/hyperv/test_pathutils.py @@ -20,7 +20,6 @@ from nova import exception 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): @@ -33,44 +32,6 @@ class PathUtilsTestCase(test_base.HyperVBaseTestCase): self._pathutils = pathutils.PathUtils() - def _test_smb_conn(self, smb_available=True): - self._mock_wmi.x_wmi = Exception - self._mock_wmi.WMI.side_effect = None if smb_available else Exception - - self._pathutils._set_smb_conn() - - if smb_available: - expected_conn = self._mock_wmi.WMI.return_value - self.assertEqual(expected_conn, self._pathutils._smb_conn) - else: - self.assertRaises(vmutils.HyperVException, - getattr, - self._pathutils, '_smb_conn') - - def test_smb_conn_available(self): - self._test_smb_conn() - - def test_smb_conn_unavailable(self): - self._test_smb_conn(smb_available=False) - - @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) @@ -98,83 +59,6 @@ class PathUtilsTestCase(test_base.HyperVBaseTestCase): 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('time.sleep') - @mock.patch('shutil.rmtree') - def test_rmtree(self, mock_rmtree, mock_sleep): - 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_sleep.assert_called_once_with(1) - @mock.patch('os.path.join') def test_get_instances_sub_dir(self, fake_path_join): @@ -184,7 +68,7 @@ class PathUtilsTestCase(test_base.HyperVBaseTestCase): fake_dir_name = "fake_dir_name" fake_windows_error = WindowsError - self._pathutils._check_create_dir = mock.MagicMock( + self._pathutils.check_create_dir = mock.MagicMock( side_effect=WindowsError(pathutils.ERROR_INVALID_NAME)) with mock.patch('__builtin__.WindowsError', fake_windows_error, create=True): diff --git a/nova/tests/unit/virt/hyperv/test_snapshotops.py b/nova/tests/unit/virt/hyperv/test_snapshotops.py index b1503039a3fa..c5d0fc0b692a 100644 --- a/nova/tests/unit/virt/hyperv/test_snapshotops.py +++ b/nova/tests/unit/virt/hyperv/test_snapshotops.py @@ -16,8 +16,10 @@ import os import mock +from os_win import exceptions as os_win_exc from nova.compute import task_states +from nova import exception from nova.tests.unit import fake_instance from nova.tests.unit.virt.hyperv import test_base from nova.virt.hyperv import snapshotops @@ -96,7 +98,7 @@ class SnapshotOpsTestCase(test_base.HyperVBaseTestCase): mock_reconnect.assert_called_once_with(dest_vhd_path, base_dest_disk_path) self._snapshotops._vhdutils.merge_vhd.assert_called_once_with( - dest_vhd_path, base_dest_disk_path) + dest_vhd_path) mock_save_glance_image.assert_called_once_with( self.context, mock.sentinel.IMAGE_ID, base_dest_disk_path) else: @@ -119,3 +121,18 @@ class SnapshotOpsTestCase(test_base.HyperVBaseTestCase): def test_snapshot_no_base_disk(self): self._test_snapshot(base_disk_path=None) + + @mock.patch.object(snapshotops.SnapshotOps, '_snapshot') + def test_snapshot_instance_not_found(self, mock_snapshot): + mock_instance = fake_instance.fake_instance_obj(self.context) + mock_snapshot.side_effect = os_win_exc.HyperVVMNotFoundException( + vm_name=mock_instance.name) + + self.assertRaises(exception.InstanceNotFound, + self._snapshotops.snapshot, + self.context, mock_instance, mock.sentinel.image_id, + mock.sentinel.update_task_state) + + mock_snapshot.assert_called_once_with(self.context, mock_instance, + mock.sentinel.image_id, + mock.sentinel.update_task_state) diff --git a/nova/tests/unit/virt/hyperv/test_utilsfactory.py b/nova/tests/unit/virt/hyperv/test_utilsfactory.py index 38dfe138934a..b13913bd19c6 100644 --- a/nova/tests/unit/virt/hyperv/test_utilsfactory.py +++ b/nova/tests/unit/virt/hyperv/test_utilsfactory.py @@ -21,7 +21,6 @@ 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 volumeutils from nova.virt.hyperv import volumeutilsv2 @@ -31,18 +30,25 @@ CONF = cfg.CONF class TestHyperVUtilsFactory(test.NoDBTestCase): def test_get_volumeutils_v2(self): - self._test_returned_class(volumeutilsv2.VolumeUtilsV2, False, True) + self._test_returned_class(expected_class=volumeutilsv2.VolumeUtilsV2, + os_supports_v2=True) def test_get_volumeutils_v1(self): - self._test_returned_class(volumeutils.VolumeUtils, False, False) + self._test_returned_class(expected_class=volumeutils.VolumeUtils) def test_get_volumeutils_force_v1_and_not_min_version(self): - self._test_returned_class(volumeutils.VolumeUtils, True, False) + self._test_returned_class(expected_class=volumeutils.VolumeUtils, + force_v1=True) - def _test_returned_class(self, expected_class, force_v1, os_supports_v2): - CONF.set_override('force_volumeutils_v1', force_v1, 'hyperv') + @mock.patch.object(utilsfactory, 'CONF') + def _test_returned_class(self, mock_CONF, expected_class, force_v1=False, + os_supports_v2=False): + # NOTE(claudiub): temporary change, in order for unit tests to pass. + # force_hyperv_utils_v1 CONF flag does not exist anymore. + # utilsfactory and its test cases will be removed next commit. + mock_CONF.hyperv.force_volumeutils_v1 = force_v1 with mock.patch.object( - hostutils.HostUtils, + utilsfactory.utils, 'check_min_windows_version') as mock_check_min_windows_version: mock_check_min_windows_version.return_value = os_supports_v2 diff --git a/nova/tests/unit/virt/hyperv/test_vif.py b/nova/tests/unit/virt/hyperv/test_vif.py new file mode 100644 index 000000000000..9717619e2017 --- /dev/null +++ b/nova/tests/unit/virt/hyperv/test_vif.py @@ -0,0 +1,35 @@ +# 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.tests.unit.virt.hyperv import test_base +from nova.virt.hyperv import vif + + +class HyperVNovaNetworkVIFDriverTestCase(test_base.HyperVBaseTestCase): + def setUp(self): + super(HyperVNovaNetworkVIFDriverTestCase, self).setUp() + self.vif_driver = vif.HyperVNovaNetworkVIFDriver() + + def test_plug(self): + self.flags(vswitch_name=mock.sentinel.vswitch_name, group='hyperv') + fake_vif = {'id': mock.sentinel.fake_id} + + self.vif_driver.plug(mock.sentinel.instance, fake_vif) + netutils = self.vif_driver._netutils + netutils.connect_vnic_to_vswitch.assert_called_once_with( + mock.sentinel.vswitch_name, mock.sentinel.fake_id) diff --git a/nova/tests/unit/virt/hyperv/test_vmops.py b/nova/tests/unit/virt/hyperv/test_vmops.py index 75480275980a..3c78594de28e 100644 --- a/nova/tests/unit/virt/hyperv/test_vmops.py +++ b/nova/tests/unit/virt/hyperv/test_vmops.py @@ -16,6 +16,7 @@ import os from eventlet import timeout as etimeout import mock +from os_win import exceptions as os_win_exc from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import units @@ -28,9 +29,7 @@ from nova.tests.unit.objects import test_virtual_interface from nova.tests.unit.virt.hyperv import test_base from nova.virt import hardware from nova.virt.hyperv import constants -from nova.virt.hyperv import ioutils from nova.virt.hyperv import vmops -from nova.virt.hyperv import vmutils CONF = cfg.CONF @@ -126,7 +125,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock_instance = fake_instance.fake_instance_obj(self.context) mock_instance.root_gb = self.FAKE_SIZE self.flags(use_cow_images=use_cow_images) - self._vmops._vhdutils.get_vhd_info.return_value = {'MaxInternalSize': + self._vmops._vhdutils.get_vhd_info.return_value = {'VirtualSize': vhd_size * units.Gi} self._vmops._vhdutils.get_vhd_format.return_value = vhd_format root_vhd_internal_size = mock_instance.root_gb * units.Gi @@ -259,8 +258,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self._vmops._pathutils.get_ephemeral_vhd_path.assert_called_with( mock_instance.name, mock.sentinel.FAKE_FORMAT) self._vmops._vhdutils.create_dynamic_vhd.assert_called_with( - mock.sentinel.FAKE_PATH, mock_instance.ephemeral_gb * units.Gi, - mock.sentinel.FAKE_FORMAT) + mock.sentinel.FAKE_PATH, mock_instance.ephemeral_gb * units.Gi) self.assertEqual(mock.sentinel.FAKE_PATH, response) @mock.patch('nova.virt.hyperv.vmops.VMOps.destroy') @@ -301,8 +299,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self.context, mock_instance, mock_image_meta, [mock.sentinel.FILE], mock.sentinel.PASSWORD, mock.sentinel.INFO, mock.sentinel.DEV_INFO) - elif fail is vmutils.HyperVException: - self.assertRaises(vmutils.HyperVException, self._vmops.spawn, + elif fail is os_win_exc.HyperVException: + self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn, self.context, mock_instance, mock_image_meta, [mock.sentinel.FILE], mock.sentinel.PASSWORD, mock.sentinel.INFO, mock.sentinel.DEV_INFO) @@ -347,7 +345,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): def test_spawn_create_instance_exception(self): self._test_spawn(exists=False, boot_from_volume=False, configdrive_required=True, - fail=vmutils.HyperVException) + fail=os_win_exc.HyperVException) def test_spawn_not_required(self): self._test_spawn(exists=False, boot_from_volume=False, @@ -359,8 +357,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): def test_spawn_no_admin_permissions(self): self._vmops._vmutils.check_admin_permissions.side_effect = ( - vmutils.HyperVException) - self.assertRaises(vmutils.HyperVException, + os_win_exc.HyperVException) + self.assertRaises(os_win_exc.HyperVException, self._vmops.spawn, self.context, mock.DEFAULT, mock.DEFAULT, [mock.sentinel.FILE], mock.sentinel.PASSWORD, @@ -663,10 +661,11 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch('nova.virt.hyperv.vmops.VMOps.power_off') def test_destroy_exception(self, mock_power_off): mock_instance = fake_instance.fake_instance_obj(self.context) - self._vmops._vmutils.destroy_vm.side_effect = vmutils.HyperVException + self._vmops._vmutils.destroy_vm.side_effect = ( + os_win_exc.HyperVException) self._vmops._vmutils.vm_exists.return_value = True - self.assertRaises(vmutils.HyperVException, + self.assertRaises(os_win_exc.HyperVException, self._vmops.destroy, mock_instance) def test_reboot_hard(self): @@ -689,10 +688,11 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch("nova.virt.hyperv.vmops.VMOps._soft_shutdown") def test_reboot_soft_exception(self, mock_soft_shutdown, mock_power_on): mock_soft_shutdown.return_value = True - mock_power_on.side_effect = vmutils.HyperVException("Expected failure") + mock_power_on.side_effect = os_win_exc.HyperVException( + "Expected failure") instance = fake_instance.fake_instance_obj(self.context) - self.assertRaises(vmutils.HyperVException, self._vmops.reboot, + self.assertRaises(os_win_exc.HyperVException, self._vmops.reboot, instance, {}, vmops.REBOOT_TYPE_SOFT) mock_soft_shutdown.assert_called_once_with(instance) @@ -723,7 +723,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): instance = fake_instance.fake_instance_obj(self.context) mock_shutdown_vm = self._vmops._vmutils.soft_shutdown_vm - mock_shutdown_vm.side_effect = vmutils.HyperVException( + mock_shutdown_vm.side_effect = os_win_exc.HyperVException( "Expected failure.") result = self._vmops._soft_shutdown(instance, self._FAKE_TIMEOUT) @@ -820,8 +820,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch("nova.virt.hyperv.vmops.VMOps._soft_shutdown") def test_power_off_unexisting_instance(self, mock_soft_shutdown): - mock_soft_shutdown.side_effect = ( - exception.InstanceNotFound('fake_instance_uuid')) + mock_soft_shutdown.side_effect = os_win_exc.HyperVVMNotFoundException( + vm_name=mock.sentinel.vm_name) self._test_power_off(timeout=1, set_state_expected=False) @mock.patch('nova.virt.hyperv.vmops.VMOps._set_vm_state') @@ -875,8 +875,10 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): def test_set_vm_state_exception(self): mock_instance = fake_instance.fake_instance_obj(self.context) - self._vmops._vmutils.set_vm_state.side_effect = vmutils.HyperVException - self.assertRaises(vmutils.HyperVException, self._vmops._set_vm_state, + self._vmops._vmutils.set_vm_state.side_effect = ( + os_win_exc.HyperVException) + self.assertRaises(os_win_exc.HyperVException, + self._vmops._set_vm_state, mock_instance, mock.sentinel.STATE) def test_get_vm_state(self): @@ -904,7 +906,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock.sentinel.FAKE_VM_NAME, vmops.SHUTDOWN_TIME_INCREMENT) self.assertFalse(result) - @mock.patch.object(ioutils, 'IOThread') + @mock.patch.object(vmops.ioutils, 'IOThread') def _test_log_vm_serial_output(self, mock_io_thread, worker_running=False, worker_exists=False): @@ -1005,7 +1007,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch("os.path.exists") def test_get_console_output_exception(self, fake_path_exists, fake_open): fake_vm = mock.MagicMock() - fake_open.side_effect = IOError fake_path_exists.return_value = True self._vmops._pathutils.get_vm_console_log_paths.return_value = ( @@ -1179,7 +1180,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): @mock.patch.object(vmops.VMOps, '_check_hotplug_available') def test_detach_interface_missing_instance(self, mock_check_hotplug): - mock_check_hotplug.side_effect = exception.NotFound + mock_check_hotplug.side_effect = os_win_exc.HyperVVMNotFoundException( + vm_name='fake_vm') self.assertRaises(exception.InterfaceDetachFailed, self._vmops.detach_interface, mock.MagicMock(), mock.sentinel.fake_vif) diff --git a/nova/tests/unit/virt/hyperv/test_volumeops.py b/nova/tests/unit/virt/hyperv/test_volumeops.py index 8fc01d5b0518..f53d8f190759 100644 --- a/nova/tests/unit/virt/hyperv/test_volumeops.py +++ b/nova/tests/unit/virt/hyperv/test_volumeops.py @@ -17,14 +17,13 @@ import os import mock +from os_win import exceptions as os_win_exc from oslo_config import cfg from nova import exception from nova import test from nova.tests.unit import fake_block_device from nova.tests.unit.virt.hyperv import test_base -from nova.virt.hyperv import pathutils -from nova.virt.hyperv import vmutils from nova.virt.hyperv import volumeops CONF = cfg.CONF @@ -120,16 +119,15 @@ class VolumeOpsTestCase(test_base.HyperVBaseTestCase): fake_volume_driver.disconnect_volumes.assert_called_once_with( block_device_mapping) - def test_ebs_root_in_block_devices(self): + @mock.patch('nova.block_device.volume_in_mapping') + def test_ebs_root_in_block_devices(self, mock_vol_in_mapping): block_device_info = get_fake_block_dev_info() response = self._volumeops.ebs_root_in_block_devices(block_device_info) - self._volumeops._volutils.volume_in_mapping.assert_called_once_with( + mock_vol_in_mapping.assert_called_once_with( self._volumeops._default_root_device, block_device_info) - self.assertEqual( - self._volumeops._volutils.volume_in_mapping.return_value, - response) + self.assertEqual(mock_vol_in_mapping.return_value, response) def test_get_volume_connector(self): mock_instance = mock.DEFAULT @@ -239,9 +237,9 @@ class ISCSIVolumeDriverTestCase(test_base.HyperVBaseTestCase): mock_logout_storage_target, mock_get_mounted_disk): connection_info = get_fake_connection_info() - mock_get_mounted_disk.side_effect = vmutils.HyperVException + mock_get_mounted_disk.side_effect = os_win_exc.HyperVException - self.assertRaises(vmutils.HyperVException, + self.assertRaises(os_win_exc.HyperVException, self._volume_driver.attach_volume, connection_info, mock.sentinel.instance_name) mock_logout_storage_target.assert_called_with(mock.sentinel.fake_iqn) @@ -418,6 +416,8 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase): super(SMBFSVolumeDriverTestCase, self).setUp() self._volume_driver = volumeops.SMBFSVolumeDriver() self._volume_driver._vmutils = mock.MagicMock() + self._volume_driver._pathutils = mock.MagicMock() + self._volume_driver._volutils = mock.MagicMock() @mock.patch.object(volumeops.SMBFSVolumeDriver, 'ensure_share_mounted') @mock.patch.object(volumeops.SMBFSVolumeDriver, '_get_disk_path') @@ -468,15 +468,14 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase): def test_attach_non_existing_image(self, mock_get_disk_path, mock_ensure_share_mounted): self._volume_driver._vmutils.attach_drive.side_effect = ( - vmutils.HyperVException()) + os_win_exc.HyperVException) self.assertRaises(exception.VolumeAttachFailed, self._volume_driver.attach_volume, self._FAKE_CONNECTION_INFO, mock.sentinel.instance_name) @mock.patch.object(volumeops.SMBFSVolumeDriver, '_get_disk_path') - @mock.patch.object(pathutils.PathUtils, 'unmount_smb_share') - def test_detach_volume(self, mock_unmount_smb_share, mock_get_disk_path): + def test_detach_volume(self, mock_get_disk_path): mock_get_disk_path.return_value = ( mock.sentinel.disk_path) @@ -510,12 +509,10 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase): self.assertEqual(expected, disk_path) @mock.patch.object(volumeops.SMBFSVolumeDriver, '_parse_credentials') - @mock.patch.object(pathutils.PathUtils, 'check_smb_mapping') - @mock.patch.object(pathutils.PathUtils, 'mount_smb_share') - def _test_ensure_mounted(self, mock_mount_smb_share, - mock_check_smb_mapping, mock_parse_credentials, - is_mounted=False): - mock_check_smb_mapping.return_value = is_mounted + def _test_ensure_mounted(self, mock_parse_credentials, is_mounted=False): + mock_mount_smb_share = self._volume_driver._pathutils.mount_smb_share + self._volume_driver._pathutils.check_smb_mapping.return_value = ( + is_mounted) mock_parse_credentials.return_value = ( self._FAKE_USERNAME, self._FAKE_PASSWORD) @@ -537,10 +534,10 @@ class SMBFSVolumeDriverTestCase(test_base.HyperVBaseTestCase): def test_ensure_already_mounted(self): self._test_ensure_mounted(is_mounted=True) - @mock.patch.object(pathutils.PathUtils, 'unmount_smb_share') - def test_disconnect_volumes(self, mock_unmount_smb_share): + def test_disconnect_volumes(self): block_device_mapping = [ {'connection_info': self._FAKE_CONNECTION_INFO}] self._volume_driver.disconnect_volumes(block_device_mapping) - mock_unmount_smb_share.assert_called_once_with( + mock_unmount_share = self._volume_driver._pathutils.unmount_smb_share + mock_unmount_share.assert_called_once_with( self._FAKE_SHARE_NORMALIZED) diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py index 289c9a79ecbc..90f7eb2015b6 100644 --- a/nova/virt/hyperv/driver.py +++ b/nova/virt/hyperv/driver.py @@ -17,9 +17,13 @@ A Hyper-V Nova Compute driver. """ +import functools import platform +from os_win import exceptions as os_win_exc +from os_win import utilsfactory from oslo_log import log as logging +import six from nova import exception from nova.i18n import _, _LE @@ -27,7 +31,6 @@ from nova import objects from nova.virt import driver from nova.virt.hyperv import eventhandler from nova.virt.hyperv import hostops -from nova.virt.hyperv import hostutils from nova.virt.hyperv import livemigrationops from nova.virt.hyperv import migrationops from nova.virt.hyperv import rdpconsoleops @@ -38,6 +41,49 @@ from nova.virt.hyperv import volumeops LOG = logging.getLogger(__name__) +def convert_exceptions(function, exception_map): + expected_exceptions = tuple(exception_map.keys()) + + @functools.wraps(function) + def wrapper(*args, **kwargs): + try: + return function(*args, **kwargs) + except expected_exceptions as ex: + raised_exception = exception_map.get(type(ex)) + if not raised_exception: + # exception might be a subclass of an expected exception. + for expected in expected_exceptions: + if isinstance(ex, expected): + raised_exception = exception_map[expected] + break + + raise raised_exception(six.text_type(ex)) + return wrapper + + +def decorate_all_methods(decorator, *args, **kwargs): + def decorate(cls): + for attr in cls.__dict__: + class_member = getattr(cls, attr) + if callable(class_member): + setattr(cls, attr, decorator(class_member, *args, **kwargs)) + return cls + + return decorate + + +exception_conversion_map = { + # expected_exception: converted_exception + os_win_exc.OSWinException: exception.NovaException, + os_win_exc.HyperVVMNotFoundException: exception.InstanceNotFound, +} + +# NOTE(claudiub): the purpose of the decorator below is to prevent any +# os_win exceptions (subclasses of OSWinException) to leak outside of the +# HyperVDriver. + + +@decorate_all_methods(convert_exceptions, exception_conversion_map) class HyperVDriver(driver.ComputeDriver): capabilities = { "has_imagecache": False, @@ -61,7 +107,7 @@ class HyperVDriver(driver.ComputeDriver): self._rdpconsoleops = rdpconsoleops.RDPConsoleOps() def _check_minimum_windows_version(self): - if not hostutils.HostUtils().check_min_windows_version(6, 2): + if not utilsfactory.get_hostutils().check_min_windows_version(6, 2): # the version is of Windows is older than Windows Server 2012 R2. # Log an error, lettingusers know that this version is not # supported any longer. diff --git a/nova/virt/hyperv/eventhandler.py b/nova/virt/hyperv/eventhandler.py index b7e5dc09ab09..36734c579b64 100644 --- a/nova/virt/hyperv/eventhandler.py +++ b/nova/virt/hyperv/eventhandler.py @@ -20,15 +20,15 @@ import sys if sys.platform == 'win32': import wmi +from os_win import exceptions as os_win_exc +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging -from nova import exception from nova.i18n import _LW from nova import utils from nova.virt import event as virtevent from nova.virt.hyperv import constants -from nova.virt.hyperv import utilsfactory LOG = logging.getLogger(__name__) @@ -117,7 +117,7 @@ class InstanceEventHandler(object): "will be ignored."), instance_name) return instance_uuid - except exception.InstanceNotFound: + except os_win_exc.HyperVVMNotFoundException: # The instance has been deleted. pass diff --git a/nova/virt/hyperv/hostops.py b/nova/virt/hyperv/hostops.py index 8877ee858479..b7e74b4e366d 100644 --- a/nova/virt/hyperv/hostops.py +++ b/nova/virt/hyperv/hostops.py @@ -21,6 +21,7 @@ import os import platform import time +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils @@ -31,7 +32,7 @@ from nova.compute import hv_type from nova.compute import vm_mode from nova.i18n import _ from nova.virt.hyperv import constants -from nova.virt.hyperv import utilsfactory +from nova.virt.hyperv import pathutils CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') @@ -41,7 +42,7 @@ LOG = logging.getLogger(__name__) class HostOps(object): def __init__(self): self._hostutils = utilsfactory.get_hostutils() - self._pathutils = utilsfactory.get_pathutils() + self._pathutils = pathutils.PathUtils() def _get_cpu_info(self): """Get the CPU information. diff --git a/nova/virt/hyperv/imagecache.py b/nova/virt/hyperv/imagecache.py index 9c3172c23e58..ee84247202c1 100644 --- a/nova/virt/hyperv/imagecache.py +++ b/nova/virt/hyperv/imagecache.py @@ -17,6 +17,7 @@ Image caching and management. """ import os +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils @@ -24,7 +25,7 @@ from oslo_utils import units from nova import exception from nova import utils -from nova.virt.hyperv import utilsfactory +from nova.virt.hyperv import pathutils from nova.virt import images LOG = logging.getLogger(__name__) @@ -35,7 +36,7 @@ CONF.import_opt('use_cow_images', 'nova.virt.driver') class ImageCache(object): def __init__(self): - self._pathutils = utilsfactory.get_pathutils() + self._pathutils = pathutils.PathUtils() self._vhdutils = utilsfactory.get_vhdutils() def _get_root_vhd_size_gb(self, instance): @@ -45,8 +46,7 @@ class ImageCache(object): return instance.root_gb def _resize_and_cache_vhd(self, instance, vhd_path): - vhd_info = self._vhdutils.get_vhd_info(vhd_path) - vhd_size = vhd_info['MaxInternalSize'] + vhd_size = self._vhdutils.get_vhd_size(vhd_path)['VirtualSize'] root_vhd_size_gb = self._get_root_vhd_size_gb(instance) root_vhd_size = root_vhd_size_gb * units.Gi diff --git a/nova/virt/hyperv/livemigrationops.py b/nova/virt/hyperv/livemigrationops.py index 0cf5121929d0..2c5b678ab898 100644 --- a/nova/virt/hyperv/livemigrationops.py +++ b/nova/virt/hyperv/livemigrationops.py @@ -18,15 +18,15 @@ Management class for live migration VM operations. """ import functools +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from nova.i18n import _ from nova.virt.hyperv import imagecache -from nova.virt.hyperv import utilsfactory +from nova.virt.hyperv import pathutils from nova.virt.hyperv import vmops -from nova.virt.hyperv import vmutilsv2 from nova.virt.hyperv import volumeops LOG = logging.getLogger(__name__) @@ -53,11 +53,11 @@ class LiveMigrationOps(object): else: self._livemigrutils = None - self._pathutils = utilsfactory.get_pathutils() + self._pathutils = pathutils.PathUtils() self._vmops = vmops.VMOps() self._volumeops = volumeops.VolumeOps() self._imagecache = imagecache.ImageCache() - self._vmutils = vmutilsv2.VMUtilsV2() + self._vmutils = utilsfactory.get_vmutils() @check_os_version_requirement def live_migration(self, context, instance_ref, dest, post_method, diff --git a/nova/virt/hyperv/migrationops.py b/nova/virt/hyperv/migrationops.py index 000eefd27c2b..db8526dad3b6 100644 --- a/nova/virt/hyperv/migrationops.py +++ b/nova/virt/hyperv/migrationops.py @@ -18,6 +18,7 @@ Management class for migration / resize operations. """ import os +from os_win import utilsfactory from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import units @@ -27,7 +28,7 @@ from nova.i18n import _, _LE from nova import objects from nova.virt import configdrive from nova.virt.hyperv import imagecache -from nova.virt.hyperv import utilsfactory +from nova.virt.hyperv import pathutils from nova.virt.hyperv import vmops from nova.virt.hyperv import volumeops @@ -39,7 +40,7 @@ class MigrationOps(object): self._hostutils = utilsfactory.get_hostutils() self._vmutils = utilsfactory.get_vmutils() self._vhdutils = utilsfactory.get_vhdutils() - self._pathutils = utilsfactory.get_pathutils() + self._pathutils = pathutils.PathUtils() self._volumeops = volumeops.VolumeOps() self._vmops = vmops.VMOps() self._imagecache = imagecache.ImageCache() @@ -201,11 +202,9 @@ class MigrationOps(object): self._vhdutils.reconnect_parent_vhd(diff_vhd_path, base_vhd_copy_path) - LOG.debug("Merging base disk %(base_vhd_copy_path)s and " - "diff disk %(diff_vhd_path)s", - {'base_vhd_copy_path': base_vhd_copy_path, - 'diff_vhd_path': diff_vhd_path}) - self._vhdutils.merge_vhd(diff_vhd_path, base_vhd_copy_path) + LOG.debug("Merging differential disk %s into its parent.", + diff_vhd_path) + self._vhdutils.merge_vhd(diff_vhd_path) # Replace the differential VHD with the merged one self._pathutils.rename(base_vhd_copy_path, diff_vhd_path) @@ -215,7 +214,7 @@ class MigrationOps(object): self._pathutils.remove(base_vhd_copy_path) def _check_resize_vhd(self, vhd_path, vhd_info, new_size): - curr_size = vhd_info['MaxInternalSize'] + curr_size = vhd_info['VirtualSize'] if new_size < curr_size: raise exception.CannotResizeDisk( reason=_("Cannot resize the root disk to a smaller size. " diff --git a/nova/virt/hyperv/pathutils.py b/nova/virt/hyperv/pathutils.py index 3b9e5d26941f..34e2b7955dab 100644 --- a/nova/virt/hyperv/pathutils.py +++ b/nova/virt/hyperv/pathutils.py @@ -14,21 +14,14 @@ # under the License. import os -import shutil -import sys -import time - -if sys.platform == 'win32': - import wmi +from os_win.utils import pathutils from oslo_config import cfg from oslo_log import log as logging from nova import exception from nova.i18n import _ -from nova import utils from nova.virt.hyperv import constants -from nova.virt.hyperv import vmutils LOG = logging.getLogger(__name__) @@ -47,89 +40,14 @@ CONF.register_opts(hyperv_opts, 'hyperv') CONF.import_opt('instances_path', 'nova.compute.manager') ERROR_INVALID_NAME = 123 -ERROR_DIR_IS_NOT_EMPTY = 145 + +# NOTE(claudiub): part of the pre-existing PathUtils is nova-specific and +# it does not belong in the os-win library. In order to ensure the same +# functionality with the least amount of changes necessary, adding as a mixin +# the os_win.pathutils.PathUtils class into this PathUtils. -class PathUtils(object): - def __init__(self): - self._set_smb_conn() - - @property - def _smb_conn(self): - if self._smb_conn_attr: - return self._smb_conn_attr - raise vmutils.HyperVException(_("The SMB WMI namespace is not " - "available on this OS version.")) - - def _set_smb_conn(self): - # The following namespace is not available prior to Windows - # Server 2012. utilsfactory is not used in order to avoid a - # circular dependency. - try: - self._smb_conn_attr = wmi.WMI( - moniker=r"root\Microsoft\Windows\SMB") - except wmi.x_wmi: - self._smb_conn_attr = None - - 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 +class PathUtils(pathutils.PathUtils): def get_instances_dir(self, remote_server=None): local_instance_path = os.path.normpath(CONF.instances_path) @@ -145,25 +63,15 @@ class PathUtils(object): 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) + self.check_remove_dir(path) if create_dir: - self._check_create_dir(path) + self.check_create_dir(path) return path except WindowsError as ex: if ex.winerror == ERROR_INVALID_NAME: @@ -238,52 +146,3 @@ class PathUtils(object): 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/nova/virt/hyperv/rdpconsoleops.py b/nova/virt/hyperv/rdpconsoleops.py index e712ad8f613c..edfc1e1fa1b7 100644 --- a/nova/virt/hyperv/rdpconsoleops.py +++ b/nova/virt/hyperv/rdpconsoleops.py @@ -13,11 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +from os_win import utilsfactory from oslo_log import log as logging from nova.console import type as ctype from nova.virt.hyperv import hostops -from nova.virt.hyperv import utilsfactory LOG = logging.getLogger(__name__) diff --git a/nova/virt/hyperv/snapshotops.py b/nova/virt/hyperv/snapshotops.py index 123ad5863d59..93fa5f716a8c 100644 --- a/nova/virt/hyperv/snapshotops.py +++ b/nova/virt/hyperv/snapshotops.py @@ -18,14 +18,17 @@ Management class for VM snapshot operations. """ import os +from os_win import exceptions as os_win_exc +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging from nova.compute import task_states +from nova import exception from nova.i18n import _LW from nova.image import glance from nova import utils -from nova.virt.hyperv import utilsfactory +from nova.virt.hyperv import pathutils CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -33,7 +36,7 @@ LOG = logging.getLogger(__name__) class SnapshotOps(object): def __init__(self): - self._pathutils = utilsfactory.get_pathutils() + self._pathutils = pathutils.PathUtils() self._vmutils = utilsfactory.get_vmutils() self._vhdutils = utilsfactory.get_vhdutils() @@ -54,7 +57,11 @@ class SnapshotOps(object): def instance_synchronized_snapshot(): self._snapshot(context, instance, image_id, update_task_state) - instance_synchronized_snapshot() + try: + instance_synchronized_snapshot() + except os_win_exc.HyperVVMNotFoundException: + # the instance might dissapear before starting the operation. + raise exception.InstanceNotFound(instance_id=instance.uuid) def _snapshot(self, context, instance, image_id, update_task_state): """Create snapshot from a running VM instance.""" @@ -103,11 +110,9 @@ class SnapshotOps(object): self._vhdutils.reconnect_parent_vhd(dest_vhd_path, dest_base_disk_path) - LOG.debug("Merging base disk %(dest_base_disk_path)s and " - "diff disk %(dest_vhd_path)s", - {'dest_base_disk_path': dest_base_disk_path, - 'dest_vhd_path': dest_vhd_path}) - self._vhdutils.merge_vhd(dest_vhd_path, dest_base_disk_path) + LOG.debug("Merging diff disk %s into its parent.", + dest_vhd_path) + self._vhdutils.merge_vhd(dest_vhd_path) image_vhd_path = dest_base_disk_path LOG.debug("Updating Glance image %(image_id)s with content from " diff --git a/nova/virt/hyperv/utilsfactory.py b/nova/virt/hyperv/utilsfactory.py index 26c9121d7ab1..96c612777ed6 100644 --- a/nova/virt/hyperv/utilsfactory.py +++ b/nova/virt/hyperv/utilsfactory.py @@ -27,16 +27,10 @@ from nova.virt.hyperv import vmutilsv2 from nova.virt.hyperv import volumeutils from nova.virt.hyperv import volumeutilsv2 -hyper_opts = [ - 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__) +CONF.import_group('hyperv', 'os_win.utilsfactory') utils = hostutils.HostUtils() diff --git a/nova/virt/hyperv/vif.py b/nova/virt/hyperv/vif.py index f93e8b132da0..0dc5e91b6621 100644 --- a/nova/virt/hyperv/vif.py +++ b/nova/virt/hyperv/vif.py @@ -16,11 +16,10 @@ import abc +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging -from nova.virt.hyperv import utilsfactory - hyperv_opts = [ cfg.StrOpt('vswitch_name', help='External virtual switch Name, ' @@ -60,22 +59,11 @@ class HyperVNovaNetworkVIFDriver(HyperVBaseVIFDriver): """Nova network VIF driver.""" def __init__(self): - self._vmutils = utilsfactory.get_vmutils() self._netutils = utilsfactory.get_networkutils() def plug(self, instance, vif): - vswitch_path = self._netutils.get_external_vswitch( - CONF.hyperv.vswitch_name) - - vm_name = instance.name - LOG.debug('Creating vswitch port for instance: %s', vm_name) - if self._netutils.vswitch_port_needed(): - vswitch_data = self._netutils.create_vswitch_port(vswitch_path, - vm_name) - else: - vswitch_data = vswitch_path - - self._vmutils.set_nic_connection(vm_name, vif['id'], vswitch_data) + self._netutils.connect_vnic_to_vswitch(CONF.hyperv.vswitch_name, + vif['id']) def unplug(self, instance, vif): # TODO(alepilotti) Not implemented diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py index 8bbc5ca78e87..5bef354059e2 100644 --- a/nova/virt/hyperv/vmops.py +++ b/nova/virt/hyperv/vmops.py @@ -22,6 +22,9 @@ import os import time from eventlet import timeout as etimeout +from os_win import exceptions as os_win_exc +from os_win.utils.io import ioutils +from os_win import utilsfactory from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging @@ -41,9 +44,7 @@ from nova.virt import configdrive from nova.virt import hardware from nova.virt.hyperv import constants from nova.virt.hyperv import imagecache -from nova.virt.hyperv import ioutils -from nova.virt.hyperv import utilsfactory -from nova.virt.hyperv import vmutils +from nova.virt.hyperv import pathutils from nova.virt.hyperv import volumeops LOG = logging.getLogger(__name__) @@ -130,8 +131,8 @@ class VMOps(object): def __init__(self): self._vmutils = utilsfactory.get_vmutils() self._vhdutils = utilsfactory.get_vhdutils() - self._pathutils = utilsfactory.get_pathutils() self._hostutils = utilsfactory.get_hostutils() + self._pathutils = pathutils.PathUtils() self._volumeops = volumeops.VolumeOps() self._imagecache = imagecache.ImageCache() self._vif_driver = None @@ -180,7 +181,7 @@ class VMOps(object): def _create_root_vhd(self, context, instance): base_vhd_path = self._imagecache.get_cached_image(context, instance) base_vhd_info = self._vhdutils.get_vhd_info(base_vhd_path) - base_vhd_size = base_vhd_info['MaxInternalSize'] + base_vhd_size = base_vhd_info['VirtualSize'] format_ext = base_vhd_path.split('.')[-1] root_vhd_path = self._pathutils.get_root_vhd_path(instance.name, format_ext) @@ -246,8 +247,7 @@ class VMOps(object): eph_vhd_path = self._pathutils.get_ephemeral_vhd_path( instance.name, vhd_format) - self._vhdutils.create_dynamic_vhd(eph_vhd_path, eph_vhd_size, - vhd_format) + self._vhdutils.create_dynamic_vhd(eph_vhd_path, eph_vhd_size) return eph_vhd_path @check_admin_permissions @@ -495,7 +495,7 @@ class VMOps(object): LOG.info(_LI("Soft shutdown succeeded."), instance=instance) return True - except vmutils.HyperVException as e: + except os_win_exc.HyperVException as e: # Exception is raised when trying to shutdown the instance # while it is still booting. LOG.debug("Soft shutdown failed: %s", e, instance=instance) @@ -545,7 +545,7 @@ class VMOps(object): self._set_vm_state(instance, constants.HYPERV_VM_STATE_DISABLED) - except exception.InstanceNotFound: + except os_win_exc.HyperVVMNotFoundException: # The manager can call the stop API after receiving instance # power off events. If this is triggered when the instance # is being deleted, it might attempt to power off an unexisting @@ -761,7 +761,7 @@ class VMOps(object): LOG.debug('Detaching vif: %s', vif['id'], instance=instance) self._vif_driver.unplug(instance, vif) self._vmutils.destroy_nic(instance.name, vif['id']) - except exception.NotFound: + except os_win_exc.HyperVVMNotFoundException: # TODO(claudiub): add set log level to error after string freeze. LOG.debug("Instance not found during detach interface. It " "might have been destroyed beforehand.", diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py index 8a729fe2e789..d9bc93f3b860 100644 --- a/nova/virt/hyperv/volumeops.py +++ b/nova/virt/hyperv/volumeops.py @@ -22,17 +22,18 @@ import os import re import time +from os_win import exceptions as os_win_exc +from os_win import utilsfactory from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from six.moves import range +from nova import block_device from nova import exception from nova.i18n import _, _LE, _LW from nova import utils from nova.virt import driver -from nova.virt.hyperv import utilsfactory -from nova.virt.hyperv import vmutils LOG = logging.getLogger(__name__) @@ -65,7 +66,7 @@ class VolumeOps(object): def __init__(self): self._vmutils = utilsfactory.get_vmutils() - self._volutils = utilsfactory.get_volumeutils() + self._volutils = utilsfactory.get_iscsi_initiator_utils() self._initiator = None self._default_root_device = 'vda' self.volume_drivers = {'smbfs': SMBFSVolumeDriver(), @@ -111,8 +112,8 @@ class VolumeOps(object): root_device = block_device_info.get('root_device_name') if not root_device: root_device = self._default_root_device - return self._volutils.volume_in_mapping(root_device, - block_device_info) + return block_device.volume_in_mapping(root_device, + block_device_info) def fix_instance_volume_disk_paths(self, instance_name, block_device_info): mapping = driver.block_device_info_get_mapping(block_device_info) @@ -161,7 +162,7 @@ class VolumeOps(object): class ISCSIVolumeDriver(object): def __init__(self): self._vmutils = utilsfactory.get_vmutils() - self._volutils = utilsfactory.get_volumeutils() + self._volutils = utilsfactory.get_iscsi_initiator_utils() def login_storage_target(self, connection_info): data = connection_info['data'] @@ -358,7 +359,6 @@ class SMBFSVolumeDriver(object): def __init__(self): self._pathutils = utilsfactory.get_pathutils() self._vmutils = utilsfactory.get_vmutils() - self._volutils = utilsfactory.get_volumeutils() self._username_regex = re.compile(r'user(?:name)?=([^, ]+)') self._password_regex = re.compile(r'pass(?:word)?=([^, ]+)') @@ -382,7 +382,7 @@ class SMBFSVolumeDriver(object): disk_path, ctrller_path, slot) - except vmutils.HyperVException as exn: + except os_win_exc.HyperVException as exn: LOG.exception(_LE('Attach volume failed to %(instance_name)s: ' '%(exn)s'), {'instance_name': instance_name, 'exn': exn}) diff --git a/nova/virt/opts.py b/nova/virt/opts.py index bea8a2a999b3..6ce5c2221fff 100644 --- a/nova/virt/opts.py +++ b/nova/virt/opts.py @@ -20,7 +20,6 @@ import nova.virt.driver import nova.virt.firewall import nova.virt.hardware import nova.virt.hyperv.pathutils -import nova.virt.hyperv.utilsfactory import nova.virt.hyperv.vif import nova.virt.hyperv.vmops import nova.virt.hyperv.volumeops @@ -70,7 +69,6 @@ def list_opts(): ('hyperv', itertools.chain( nova.virt.hyperv.pathutils.hyperv_opts, - nova.virt.hyperv.utilsfactory.hyper_opts, nova.virt.hyperv.vif.hyperv_opts, nova.virt.hyperv.vmops.hyperv_opts, nova.virt.hyperv.volumeops.hyper_volumeops_opts, diff --git a/requirements.txt b/requirements.txt index ee3f47579c77..b3a1d34b39ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,3 +52,4 @@ psutil<2.0.0,>=1.1.1 oslo.versionedobjects>=0.9.0 alembic>=0.8.0 os-brick>=0.4.0 # Apache-2.0 +os-win>=0.0.6 # Apache-2.0 diff --git a/tests-py3.txt b/tests-py3.txt index 21fc383828de..4ede0b456b81 100644 --- a/tests-py3.txt +++ b/tests-py3.txt @@ -255,6 +255,7 @@ nova.tests.unit.virt.hyperv.test_snapshotops.SnapshotOpsTestCase nova.tests.unit.virt.hyperv.test_utilsfactory.TestHyperVUtilsFactory nova.tests.unit.virt.hyperv.test_vhdutils.VHDUtilsTestCase nova.tests.unit.virt.hyperv.test_vhdutilsv2.VHDUtilsV2TestCase +nova.tests.unit.virt.hyperv.test_vif.HyperVNovaNetworkVIFDriverTestCase nova.tests.unit.virt.hyperv.test_vmops.VMOpsTestCase nova.tests.unit.virt.hyperv.test_vmutils.VMUtilsTestCase nova.tests.unit.virt.hyperv.test_vmutilsv2.VMUtilsV2TestCase