# 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 datetime import mock from nova import context as nova_context from nova import exception from nova import objects from nova.objects import fields as obj_fields import os_resource_classes as orc from os_win import constants as os_win_const from oslo_serialization import jsonutils from oslo_utils import units import compute_hyperv.nova.conf from compute_hyperv.nova import constants from compute_hyperv.nova import hostops from compute_hyperv.tests.unit import test_base CONF = compute_hyperv.nova.conf.CONF class HostOpsTestCase(test_base.HyperVBaseTestCase): """Unit tests for the Hyper-V HostOps class.""" _autospec_classes = [ hostops.pathutils.PathUtils, hostops.vmops.VMOps, hostops.api.API, ] FAKE_ARCHITECTURE = 0 FAKE_NAME = 'fake_name' FAKE_MANUFACTURER = 'FAKE_MANUFACTURER' FAKE_NUM_CPUS = 1 FAKE_INSTANCE_DIR = "C:/fake/dir" FAKE_LOCAL_IP = '10.11.12.13' FAKE_TICK_COUNT = 1000000 def setUp(self): super(HostOpsTestCase, self).setUp() self._hostops = hostops.HostOps() def test_get_cpu_info(self): mock_processors = mock.MagicMock() info = {'Architecture': self.FAKE_ARCHITECTURE, 'Name': self.FAKE_NAME, 'Manufacturer': self.FAKE_MANUFACTURER, 'NumberOfCores': self.FAKE_NUM_CPUS, 'NumberOfLogicalProcessors': self.FAKE_NUM_CPUS} def getitem(key): return info[key] mock_processors.__getitem__.side_effect = getitem self._hostops._hostutils.get_cpus_info.return_value = [mock_processors] response = self._hostops._get_cpu_info() self._hostops._hostutils.get_cpus_info.assert_called_once_with() expected = [mock.call(fkey) for fkey in os_win_const.PROCESSOR_FEATURE.keys()] self._hostops._hostutils.is_cpu_feature_present.has_calls(expected) expected_response = self._get_mock_cpu_info() self.assertEqual(expected_response, response) def _get_mock_cpu_info(self): return {'vendor': self.FAKE_MANUFACTURER, 'model': self.FAKE_NAME, 'arch': constants.WMI_WIN32_PROCESSOR_ARCHITECTURE[ self.FAKE_ARCHITECTURE], 'features': list(os_win_const.PROCESSOR_FEATURE.values()), 'topology': {'cores': self.FAKE_NUM_CPUS, 'threads': self.FAKE_NUM_CPUS, 'sockets': self.FAKE_NUM_CPUS}} def _get_mock_gpu_info(self): return {'remotefx_total_video_ram': 4096, 'remotefx_available_video_ram': 2048, 'remotefx_gpu_info': mock.sentinel.FAKE_GPU_INFO} def test_get_memory_info(self): self._hostops._hostutils.get_memory_info.return_value = (2 * units.Ki, 1 * units.Ki) response = self._hostops._get_memory_info() self._hostops._hostutils.get_memory_info.assert_called_once_with() self.assertEqual((2, 1, 1), response) def test_get_storage_info_gb(self): self._hostops._pathutils.get_instances_dir.return_value = '' self._hostops._diskutils.get_disk_capacity.return_value = ( 2 * units.Gi, 1 * units.Gi) response = self._hostops._get_storage_info_gb() self._hostops._pathutils.get_instances_dir.assert_called_once_with() self._hostops._diskutils.get_disk_capacity.assert_called_once_with('') self.assertEqual((2, 1, 1), response) def test_get_hypervisor_version(self): self._hostops._hostutils.get_windows_version.return_value = '6.3.9600' response_lower = self._hostops._get_hypervisor_version() self._hostops._hostutils.get_windows_version.return_value = '10.1.0' response_higher = self._hostops._get_hypervisor_version() self.assertEqual(6003, response_lower) self.assertEqual(10001, response_higher) def test_get_remotefx_gpu_info(self): self.flags(enable_remotefx=True, group='hyperv') fake_gpus = [{'total_video_ram': '2048', 'available_video_ram': '1024'}, {'total_video_ram': '1024', 'available_video_ram': '1024'}] self._hostops._hostutils.get_remotefx_gpu_info.return_value = fake_gpus ret_val = self._hostops._get_remotefx_gpu_info() self.assertEqual(3072, ret_val['total_video_ram']) self.assertEqual(1024, ret_val['used_video_ram']) def test_get_remotefx_gpu_info_disabled(self): self.flags(enable_remotefx=False, group='hyperv') ret_val = self._hostops._get_remotefx_gpu_info() self.assertEqual(0, ret_val['total_video_ram']) self.assertEqual(0, ret_val['used_video_ram']) self._hostops._hostutils.get_remotefx_gpu_info.assert_not_called() @mock.patch.object(hostops.objects, 'NUMACell') @mock.patch.object(hostops.objects, 'NUMATopology') def test_get_host_numa_topology(self, mock_NUMATopology, mock_NUMACell): numa_node = {'id': mock.sentinel.id, 'memory': mock.sentinel.memory, 'memory_usage': mock.sentinel.memory_usage, 'cpuset': mock.sentinel.cpuset, 'cpu_usage': mock.sentinel.cpu_usage} self._hostops._hostutils.get_numa_nodes.return_value = [ numa_node.copy()] result = self._hostops._get_host_numa_topology() self.assertEqual(mock_NUMATopology.return_value, result) mock_NUMACell.assert_called_once_with( pinned_cpus=set([]), mempages=[], siblings=[], **numa_node) mock_NUMATopology.assert_called_once_with( cells=[mock_NUMACell.return_value]) @mock.patch.object(hostops.HostOps, '_get_pci_passthrough_devices') @mock.patch.object(hostops.HostOps, '_get_host_numa_topology') @mock.patch.object(hostops.HostOps, '_get_remotefx_gpu_info') @mock.patch.object(hostops.HostOps, '_get_cpu_info') @mock.patch.object(hostops.HostOps, '_get_memory_info') @mock.patch.object(hostops.HostOps, '_get_hypervisor_version') @mock.patch.object(hostops.HostOps, '_get_storage_info_gb') @mock.patch('platform.node') def test_get_available_resource(self, mock_node, mock_get_storage_info_gb, mock_get_hypervisor_version, mock_get_memory_info, mock_get_cpu_info, mock_get_gpu_info, mock_get_numa_topology, mock_get_pci_devices): mock_get_storage_info_gb.return_value = (mock.sentinel.LOCAL_GB, mock.sentinel.LOCAL_GB_FREE, mock.sentinel.LOCAL_GB_USED) mock_get_memory_info.return_value = (mock.sentinel.MEMORY_MB, mock.sentinel.MEMORY_MB_FREE, mock.sentinel.MEMORY_MB_USED) mock_cpu_info = self._get_mock_cpu_info() mock_get_cpu_info.return_value = mock_cpu_info mock_get_hypervisor_version.return_value = mock.sentinel.VERSION mock_get_numa_topology.return_value._to_json.return_value = ( mock.sentinel.numa_topology_json) mock_get_pci_devices.return_value = mock.sentinel.pcis mock_gpu_info = self._get_mock_gpu_info() mock_get_gpu_info.return_value = mock_gpu_info response = self._hostops.get_available_resource() mock_get_memory_info.assert_called_once_with() mock_get_cpu_info.assert_called_once_with() mock_get_hypervisor_version.assert_called_once_with() mock_get_pci_devices.assert_called_once_with() expected = {'supported_instances': [("i686", "hyperv", "hvm"), ("x86_64", "hyperv", "hvm")], 'hypervisor_hostname': mock_node(), 'cpu_info': jsonutils.dumps(mock_cpu_info), 'hypervisor_version': mock.sentinel.VERSION, 'memory_mb': mock.sentinel.MEMORY_MB, 'memory_mb_used': mock.sentinel.MEMORY_MB_USED, 'local_gb': mock.sentinel.LOCAL_GB, 'local_gb_used': mock.sentinel.LOCAL_GB_USED, 'disk_available_least': mock.sentinel.LOCAL_GB_FREE, 'vcpus': self.FAKE_NUM_CPUS, 'vcpus_used': 0, 'hypervisor_type': 'hyperv', 'numa_topology': mock.sentinel.numa_topology_json, 'remotefx_available_video_ram': 2048, 'remotefx_gpu_info': mock.sentinel.FAKE_GPU_INFO, 'remotefx_total_video_ram': 4096, 'pci_passthrough_devices': mock.sentinel.pcis, } self.assertEqual(expected, response) @mock.patch.object(hostops.jsonutils, 'dumps') def test_get_pci_passthrough_devices(self, mock_jsonutils_dumps): mock_pci_dev = {'vendor_id': 'fake_vendor_id', 'product_id': 'fake_product_id', 'dev_id': 'fake_dev_id', 'address': 'fake_address'} mock_get_pcis = self._hostops._hostutils.get_pci_passthrough_devices mock_get_pcis.return_value = [mock_pci_dev] expected_label = 'label_%(vendor_id)s_%(product_id)s' % { 'vendor_id': mock_pci_dev['vendor_id'], 'product_id': mock_pci_dev['product_id']} expected_pci_dev = mock_pci_dev.copy() expected_pci_dev.update(dev_type=obj_fields.PciDeviceType.STANDARD, label=expected_label, numa_node=None) result = self._hostops._get_pci_passthrough_devices() self.assertEqual(mock_jsonutils_dumps.return_value, result) mock_jsonutils_dumps.assert_called_once_with([expected_pci_dev]) def _test_host_power_action(self, action): self._hostops._hostutils.host_power_action = mock.Mock() self._hostops.host_power_action(action) self._hostops._hostutils.host_power_action.assert_called_with( action) def test_host_power_action_shutdown(self): self._test_host_power_action(constants.HOST_POWER_ACTION_SHUTDOWN) def test_host_power_action_reboot(self): self._test_host_power_action(constants.HOST_POWER_ACTION_REBOOT) def test_host_power_action_exception(self): self.assertRaises(NotImplementedError, self._hostops.host_power_action, constants.HOST_POWER_ACTION_STARTUP) def test_get_host_ip_addr(self): CONF.set_override('my_ip', None) self._hostops._hostutils.get_local_ips.return_value = [ self.FAKE_LOCAL_IP] response = self._hostops.get_host_ip_addr() self._hostops._hostutils.get_local_ips.assert_called_once_with() self.assertEqual(self.FAKE_LOCAL_IP, response) @mock.patch('time.strftime') def test_get_host_uptime(self, mock_time): self._hostops._hostutils.get_host_tick_count64.return_value = ( self.FAKE_TICK_COUNT) response = self._hostops.get_host_uptime() tdelta = datetime.timedelta(milliseconds=int(self.FAKE_TICK_COUNT)) expected = "%s up %s, 0 users, load average: 0, 0, 0" % ( str(mock_time()), str(tdelta)) self.assertEqual(expected, response) @mock.patch.object(hostops.HostOps, '_wait_for_instance_pending_task') @mock.patch.object(hostops.HostOps, '_set_service_state') @mock.patch.object(hostops.HostOps, '_migrate_vm') @mock.patch.object(nova_context, 'get_admin_context') def _test_host_maintenance_mode(self, mock_get_admin_context, mock_migrate_vm, mock_set_service_state, mock_wait_for_instance_pending_task, vm_counter): context = mock_get_admin_context.return_value self._hostops._vmutils.list_instances.return_value = [ mock.sentinel.VM_NAME] self._hostops._vmops.list_instance_uuids.return_value = [ mock.sentinel.UUID] * vm_counter if vm_counter == 0: result = self._hostops.host_maintenance_mode( host=mock.sentinel.HOST, mode=True) self.assertEqual('on_maintenance', result) else: self.assertRaises(exception.MigrationError, self._hostops.host_maintenance_mode, host=mock.sentinel.HOST, mode=True) mock_set_service_state.assert_called_once_with( host=mock.sentinel.HOST, binary='nova-compute', is_disabled=True) mock_migrate_vm.assert_called_with( context, mock.sentinel.VM_NAME, mock.sentinel.HOST) @mock.patch.object(hostops.HostOps, '_set_service_state') @mock.patch.object(nova_context, 'get_admin_context') def test_host_maintenance_mode_disabled(self, mock_get_admin_context, mock_set_service_state): result = self._hostops.host_maintenance_mode( host=mock.sentinel.HOST, mode=False) mock_set_service_state.assert_called_once_with( host=mock.sentinel.HOST, binary='nova-compute', is_disabled=False) self.assertEqual('off_maintenance', result) def test_host_maintenance_mode_enabled(self): self._test_host_maintenance_mode(vm_counter=0) def test_host_maintenance_mode_exception(self): self._test_host_maintenance_mode(vm_counter=2) @mock.patch.object(hostops.HostOps, '_wait_for_instance_pending_task') @mock.patch.object(objects.Instance, 'get_by_uuid') def _test_migrate_vm(self, mock_get_by_uuid, mock_wait_for_instance_pending_task, instance_uuid=None, vm_state='active'): self._hostops._vmutils.get_instance_uuid.return_value = instance_uuid instance = mock_get_by_uuid.return_value type(instance).vm_state = mock.PropertyMock( side_effect=[vm_state]) self._hostops._migrate_vm(ctxt=mock.sentinel.CONTEXT, vm_name=mock.sentinel.VM_NAME, host=mock.sentinel.HOST) if not instance_uuid: self.assertFalse(self._hostops._api.live_migrate.called) return if vm_state == 'active': self._hostops._api.live_migrate.assert_called_once_with( mock.sentinel.CONTEXT, instance, block_migration=False, disk_over_commit=False, host_name=None) else: self._hostops._api.resize.assert_called_once_with( mock.sentinel.CONTEXT, instance, flavor_id=None, clean_shutdown=True) mock_wait_for_instance_pending_task.assert_called_once_with( mock.sentinel.CONTEXT, instance_uuid) def test_migrate_vm_not_found(self): self._test_migrate_vm() def test_livemigrate_vm(self): self._test_migrate_vm(instance_uuid=mock.sentinel.INSTANCE_UUID) def test_resize_vm(self): self._test_migrate_vm(instance_uuid=mock.sentinel.INSTANCE_UUID, vm_state='shutoff') def test_migrate_vm_exception(self): self.assertRaises(exception.MigrationError, self._hostops._migrate_vm, ctxt=mock.sentinel.CONTEXT, vm_name=mock.sentinel.VM_NAME, host=mock.sentinel.HOST) @mock.patch("time.sleep") @mock.patch.object(objects.Instance, 'get_by_uuid') def test_wait_for_instance_pending_task(self, mock_get_by_uuid, mock_sleep): instance = mock_get_by_uuid.return_value type(instance).task_state = mock.PropertyMock( side_effect=['migrating', 'migrating', None]) self._hostops._wait_for_instance_pending_task( context=mock.sentinel.CONTEXT, vm_uuid=mock.sentinel.VM_UUID) instance.refresh.assert_called_once_with() @mock.patch("time.sleep") @mock.patch.object(objects.Instance, 'get_by_uuid') def test_wait_for_instance_pending_task_timeout(self, mock_get_by_uuid, mock_sleep): instance = mock_get_by_uuid.return_value self.flags(evacuate_task_state_timeout=2, group='hyperv') instance.task_state = 'migrating' self.assertRaises(exception.InternalError, self._hostops._wait_for_instance_pending_task, context=mock.sentinel.CONTEXT, vm_uuid=mock.sentinel.VM_UUID) @mock.patch.object(hostops.HostOps, 'get_available_resource') def test_update_provider_tree(self, mock_get_avail_res): resources = mock.MagicMock() allocation_ratios = mock.MagicMock() provider_tree = mock.Mock() mock_get_avail_res.return_value = resources self.flags(reserved_host_disk_mb=1) exp_inventory = { orc.VCPU: { 'total': resources['vcpus'], 'min_unit': 1, 'max_unit': resources['vcpus'], 'step_size': 1, 'allocation_ratio': allocation_ratios[orc.VCPU], 'reserved': CONF.reserved_host_cpus, }, orc.MEMORY_MB: { 'total': resources['memory_mb'], 'min_unit': 1, 'max_unit': resources['memory_mb'], 'step_size': 1, 'allocation_ratio': allocation_ratios[orc.MEMORY_MB], 'reserved': CONF.reserved_host_memory_mb, }, orc.DISK_GB: { 'total': resources['local_gb'], 'min_unit': 1, 'max_unit': resources['local_gb'], 'step_size': 1, 'allocation_ratio': allocation_ratios[orc.DISK_GB], 'reserved': 1, }, } self._hostops.update_provider_tree( provider_tree, mock.sentinel.node_name, allocation_ratios, mock.sentinel.allocations) provider_tree.update_inventory.assert_called_once_with( mock.sentinel.node_name, exp_inventory)