diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 08c3d945560c..82ed268f11c5 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -895,7 +895,7 @@ class GetImageFromSystemMetadataTestCase(test.NoDBTestCase): self.assertNotIn("foo1", image) -class VersionTestCase(test.TestCase): +class VersionTestCase(test.NoDBTestCase): def test_convert_version_to_int(self): self.assertEqual(utils.convert_version_to_int('6.2.0'), 6002000) self.assertEqual(utils.convert_version_to_int((6, 4, 3)), 6004003) @@ -909,3 +909,17 @@ class VersionTestCase(test.TestCase): def test_convert_version_to_tuple(self): self.assertEqual(utils.convert_version_to_tuple('6.7.0'), (6, 7, 0)) + + def test_get_major_minor_version_from_string(self): + self.assertEqual(utils.get_major_minor_version('6.1.3'), 6.1) + self.assertEqual(utils.get_major_minor_version('6.4'), 6.4) + self.assertEqual(utils.get_major_minor_version('6'), 6) + + def test_get_major_minor_version_from_float(self): + self.assertEqual(utils.get_major_minor_version(6.1), 6.1) + self.assertEqual(utils.get_major_minor_version(6), 6.0) + + def test_get_major_minor_version_raises_exception(self): + exc = self.assertRaises(exception.NovaException, + utils.get_major_minor_version, '5a.6b') + self.assertEqual("Version 5a.6b invalid", exc.message) diff --git a/nova/tests/virt/xenapi/test_vm_utils.py b/nova/tests/virt/xenapi/test_vm_utils.py index 660b9a2c4b9b..7d84e35520b5 100644 --- a/nova/tests/virt/xenapi/test_vm_utils.py +++ b/nova/tests/virt/xenapi/test_vm_utils.py @@ -1816,3 +1816,81 @@ class StripBaseMirrorTestCase(VMUtilsTestBase): self.assertEqual(expected, session.call_xenapi.call_args_list) expected = [mock.call(session, "ref"), mock.call(session, "ref")] self.assertEqual(expected, mock_strip.call_args_list) + + +class DeviceIdTestCase(VMUtilsTestBase): + def test_device_id_is_none_if_not_specified_in_meta_data(self): + image_meta = {} + session = mock.Mock() + session.product_version = '6.1' + self.assertIsNone(vm_utils.get_vm_device_id(session, image_meta)) + + def test_get_device_id_if_hypervisor_version_is_greater_than_6_1(self): + image_meta = {'xenapi_device_id': '0002'} + session = mock.Mock() + session.product_version = '6.2' + self.assertEqual('0002', + vm_utils.get_vm_device_id(session, image_meta)) + session.product_version = '6.3.1' + self.assertEqual('0002', + vm_utils.get_vm_device_id(session, image_meta)) + + def test_raise_exception_if_device_id_not_supported_by_hyp_version(self): + image_meta = {'xenapi_device_id': '0002'} + session = mock.Mock() + session.product_version = '6.0' + exc = self.assertRaises(exception.NovaException, + vm_utils.get_vm_device_id, session, image_meta) + self.assertEqual("Device id 0002 specified is not supported by " + "hypervisor version 6.0", exc.message) + + +class CreateVmRecordTestCase(VMUtilsTestBase): + @mock.patch.object(flavors, 'extract_flavor') + def test_create_vm_record(self, mock_extract_flavor): + session = mock.Mock() + instance = {"uuid": "uuid123"} + instance_type = {"memory_mb": 1024, "vcpus": 1, "vcpu_weight": 2} + mock_extract_flavor.return_value = instance_type + + vm_utils.create_vm(session, instance, "name", "kernel", "ramdisk", + device_id="0002") + + expected_vm_rec = { + 'VCPUs_params': {'cap': '0', 'weight': '2'}, + 'PV_args': '', + 'memory_static_min': '0', + 'ha_restart_priority': '', + 'HVM_boot_policy': 'BIOS order', + 'PV_bootloader': '', + 'tags': [], + 'VCPUs_max': '1', + 'memory_static_max': '1073741824', + 'actions_after_shutdown': 'destroy', + 'memory_dynamic_max': '1073741824', + 'user_version': '0', + 'xenstore_data': {'vm-data/allowvssprovider': 'false'}, + 'blocked_operations': {}, + 'is_a_template': False, + 'name_description': '', + 'memory_dynamic_min': '1073741824', + 'actions_after_crash': 'destroy', + 'memory_target': '1073741824', + 'PV_ramdisk': '', + 'PV_bootloader_args': '', + 'PCI_bus': '', + 'other_config': {'nova_uuid': 'uuid123'}, + 'name_label': 'name', + 'actions_after_reboot': 'restart', + 'VCPUs_at_startup': '1', + 'HVM_boot_params': {'order': 'dc'}, + 'platform': {'nx': 'true', 'pae': 'true', 'apic': 'true', + 'timeoffset': '0', 'viridian': 'true', 'acpi': 'true', + 'device_id': '0002'}, + 'PV_legacy_args': '', + 'PV_kernel': '', + 'affinity': '', + 'recommendations': '', + 'ha_always_run': False} + + session.call_xenapi.assert_called_with('VM.create', expected_vm_rec) diff --git a/nova/tests/virt/xenapi/test_vmops.py b/nova/tests/virt/xenapi/test_vmops.py index 2ea763711d6d..73aa14d7da99 100644 --- a/nova/tests/virt/xenapi/test_vmops.py +++ b/nova/tests/virt/xenapi/test_vmops.py @@ -294,7 +294,8 @@ class SpawnTestCase(VMOpsTestBase): self.vmops._ensure_instance_name_unique(name_label) self.vmops._ensure_enough_free_mem(instance) self.vmops._create_vm_record(context, instance, name_label, - di_type, kernel_file, ramdisk_file).AndReturn(vm_ref) + di_type, kernel_file, + ramdisk_file, image_meta).AndReturn(vm_ref) step += 1 self.vmops._update_instance_progress(context, instance, step, steps) @@ -400,7 +401,8 @@ class SpawnTestCase(VMOpsTestBase): vm_ref = "fake_vm_ref" self.vmops._create_vm_record(context, instance, name_label, - di_type, kernel_file, ramdisk_file).AndReturn(vm_ref) + di_type, kernel_file, + ramdisk_file, image_meta).AndReturn(vm_ref) if resize_instance: self.vmops._resize_up_root_vdi(instance, root_vdi) @@ -746,3 +748,31 @@ class MigrateDiskResizingUpTestCase(VMOpsTestBase): mock_restore.assert_called_once_with(instance) mock_migrate_vhd.assert_called_once_with(self.vmops._session, instance, "parent", dest, sr_path, 1) + + +class CreateVMRecordTestCase(VMOpsTestBase): + @mock.patch.object(vm_utils, 'determine_vm_mode') + @mock.patch.object(vm_utils, 'get_vm_device_id') + @mock.patch.object(vm_utils, 'create_vm') + def test_create_vm_record_with_vm_device_id(self, mock_create_vm, + mock_get_vm_device_id, mock_determine_vm_mode): + + context = "context" + instance = {"vm_mode": "vm_mode", "uuid": "uuid123"} + name_label = "dummy" + disk_image_type = "vhd" + kernel_file = "kernel" + ramdisk_file = "ram" + image_meta = "image_meta" + device_id = "0002" + session = "session" + self.vmops._session = session + mock_get_vm_device_id.return_value = device_id + mock_determine_vm_mode.return_value = "vm_mode" + + self.vmops._create_vm_record(context, instance, name_label, + disk_image_type, kernel_file, ramdisk_file, image_meta) + + vm_utils.get_vm_device_id.assert_called_with(session, image_meta) + mock_create_vm.assert_called_with(session, instance, name_label, + kernel_file, ramdisk_file, False, device_id) diff --git a/nova/utils.py b/nova/utils.py index 566874ff1527..15c33b0b0fe4 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -1033,6 +1033,18 @@ def convert_version_to_tuple(version_str): return tuple(int(part) for part in version_str.split('.')) +def get_major_minor_version(version): + try: + if type(version) == int or type(version) == float: + return version + if type(version) == str: + major_minor_versions = version.split(".")[0:2] + version_as_float = float(".".join(major_minor_versions)) + return version_as_float + except Exception: + raise exception.NovaException(_("Version %s invalid") % version) + + def is_neutron(): global _IS_NEUTRON_ATTEMPTED global _IS_NEUTRON diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index a260e8aa9d8e..7b388e690fae 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -216,8 +216,33 @@ class ImageType(object): }.get(image_type_id) +def get_vm_device_id(session, image_meta): + # NOTE: device_id should be 2 for windows VMs which run new xentools + # (>=6.1). Refer to http://support.citrix.com/article/CTX135099 for more + # information. + if image_meta is None: + image_meta = {} + device_id = image_meta.get('xenapi_device_id') + + # The device_id is required to be set for hypervisor version 6.1 and above + if device_id: + hypervisor_version = session.product_version + if _hypervisor_supports_device_id(hypervisor_version): + return device_id + else: + msg = _("Device id %(id)s specified is not supported by " + "hypervisor version %(version)s") % {'id': device_id, + 'version': hypervisor_version} + raise exception.NovaException(msg) + + +def _hypervisor_supports_device_id(version): + hypervisor_major_minor_version = utils.get_major_minor_version(version) + return(hypervisor_major_minor_version >= 6.1) + + def create_vm(session, instance, name_label, kernel, ramdisk, - use_pv_kernel=False): + use_pv_kernel=False, device_id=None): """Create a VM record. Returns new VM reference. the use_pv_kernel flag indicates whether the guest is HVM or PV @@ -294,6 +319,9 @@ def create_vm(session, instance, name_label, kernel, ramdisk, rec['HVM_boot_params'] = {'order': 'dc'} rec['HVM_boot_policy'] = 'BIOS order' + if device_id: + rec['platform']['device_id'] = device_id + vm_ref = session.call_xenapi('VM.create', rec) LOG.debug(_('Created VM'), instance=instance) return vm_ref diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0993c648aea4..b96d56eaec95 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -387,7 +387,8 @@ class VMOps(object): def create_vm_record_step(undo_mgr, disk_image_type, kernel_file, ramdisk_file): vm_ref = self._create_vm_record(context, instance, name_label, - disk_image_type, kernel_file, ramdisk_file) + disk_image_type, kernel_file, + ramdisk_file, image_meta) def undo_create_vm(): self._destroy(instance, vm_ref, network_info=network_info) @@ -524,8 +525,8 @@ class VMOps(object): if not vm_utils.is_enough_free_mem(self._session, instance): raise exception.InsufficientFreeMemory(uuid=instance['uuid']) - def _create_vm_record(self, context, instance, name_label, - disk_image_type, kernel_file, ramdisk_file): + def _create_vm_record(self, context, instance, name_label, disk_image_type, + kernel_file, ramdisk_file, image_meta): """Create the VM record in Xen, making sure that we do not create a duplicate name-label. Also do a rough sanity check on memory to try to short-circuit a potential failure later. (The memory @@ -538,10 +539,12 @@ class VMOps(object): self._virtapi.instance_update(context, instance['uuid'], {'vm_mode': mode}) + device_id = vm_utils.get_vm_device_id(self._session, image_meta) use_pv_kernel = (mode == vm_mode.XEN) LOG.debug(_("Using PV kernel: %s") % use_pv_kernel, instance=instance) vm_ref = vm_utils.create_vm(self._session, instance, name_label, - kernel_file, ramdisk_file, use_pv_kernel) + kernel_file, ramdisk_file, + use_pv_kernel, device_id) return vm_ref def _attach_disks(self, instance, vm_ref, name_label, vdis,