From d68c04299aa2c04aea16e881d93076236cc64d7b Mon Sep 17 00:00:00 2001 From: Adelina Tuvenie Date: Fri, 20 Nov 2015 03:27:24 -0800 Subject: [PATCH] Add support for setting boot order in Hyper-V This patch adds support for setting boot order for Hyper-V instances. Unit tests were updated accordingly. Partially implements: blueprint hyper-v-set-boot-order Change-Id: I8516ba5f27146ea773eb59a82e0f2d246c742446 --- .../virt/hyperv/test_block_device_manager.py | 69 +++++++++++++++++++ .../unit/virt/hyperv/test_migrationops.py | 6 ++ nova/tests/unit/virt/hyperv/test_vmops.py | 15 ++++ nova/virt/hyperv/block_device_manager.py | 42 +++++++++++ nova/virt/hyperv/migrationops.py | 4 +- nova/virt/hyperv/vmops.py | 11 ++- ...per-v-set-boot-order-1e76b08ca6783add.yaml | 8 +++ 7 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/hyper-v-set-boot-order-1e76b08ca6783add.yaml diff --git a/nova/tests/unit/virt/hyperv/test_block_device_manager.py b/nova/tests/unit/virt/hyperv/test_block_device_manager.py index 8ef7ae2c9649..939666e87b71 100644 --- a/nova/tests/unit/virt/hyperv/test_block_device_manager.py +++ b/nova/tests/unit/virt/hyperv/test_block_device_manager.py @@ -360,3 +360,72 @@ class BlockDeviceManagerTestCase(test_base.HyperVBaseTestCase): self.assertRaises(exception.InvalidDiskInfo, self._bdman._check_and_update_bdm, mock.sentinel.FAKE_SLOT_MAP, constants.VM_GEN_1, bdm) + + def test_sort_by_boot_order(self): + original = [{'boot_index': 2}, {'boot_index': None}, {'boot_index': 1}] + expected = [original[2], original[0], original[1]] + + self._bdman._sort_by_boot_order(original) + self.assertEqual(expected, original) + + @mock.patch.object(block_device_manager.BlockDeviceInfoManager, + '_get_boot_order_gen1') + def test_get_boot_order_gen1_vm(self, mock_get_boot_order): + self._bdman.get_boot_order(constants.VM_GEN_1, + mock.sentinel.BLOCK_DEV_INFO) + mock_get_boot_order.assert_called_once_with( + mock.sentinel.BLOCK_DEV_INFO) + + @mock.patch.object(block_device_manager.BlockDeviceInfoManager, + '_get_boot_order_gen2') + def test_get_boot_order_gen2_vm(self, mock_get_boot_order): + self._bdman.get_boot_order(constants.VM_GEN_2, + mock.sentinel.BLOCK_DEV_INFO) + mock_get_boot_order.assert_called_once_with( + mock.sentinel.BLOCK_DEV_INFO) + + def test_get_boot_order_gen1_iso(self): + fake_bdi = {'root_disk': {'type': 'iso'}} + expected = [os_win_const.BOOT_DEVICE_CDROM, + os_win_const.BOOT_DEVICE_HARDDISK, + os_win_const.BOOT_DEVICE_NETWORK, + os_win_const.BOOT_DEVICE_FLOPPY] + + res = self._bdman._get_boot_order_gen1(fake_bdi) + self.assertEqual(expected, res) + + def test_get_boot_order_gen1_vhd(self): + fake_bdi = {'root_disk': {'type': 'vhd'}} + expected = [os_win_const.BOOT_DEVICE_HARDDISK, + os_win_const.BOOT_DEVICE_CDROM, + os_win_const.BOOT_DEVICE_NETWORK, + os_win_const.BOOT_DEVICE_FLOPPY] + + res = self._bdman._get_boot_order_gen1(fake_bdi) + self.assertEqual(expected, res) + + def test_get_boot_order_gen2(self): + fake_root_disk = {'boot_index': 0, + 'path': mock.sentinel.FAKE_ROOT_PATH} + fake_eph1 = {'boot_index': 2, + 'path': mock.sentinel.FAKE_EPH_PATH1} + fake_eph2 = {'boot_index': 3, + 'path': mock.sentinel.FAKE_EPH_PATH2} + fake_bdm = {'boot_index': 1, + 'connection_info': mock.sentinel.FAKE_CONN_INFO} + fake_bdi = {'root_disk': fake_root_disk, + 'ephemerals': [fake_eph1, + fake_eph2], + 'block_device_mapping': [fake_bdm]} + + self._bdman._volops.get_mounted_disk_path_from_volume = ( + mock.MagicMock(return_value=fake_bdm['connection_info'])) + + expected_res = [mock.sentinel.FAKE_ROOT_PATH, + mock.sentinel.FAKE_CONN_INFO, + mock.sentinel.FAKE_EPH_PATH1, + mock.sentinel.FAKE_EPH_PATH2] + + res = self._bdman._get_boot_order_gen2(fake_bdi) + + self.assertEqual(expected_res, res) diff --git a/nova/tests/unit/virt/hyperv/test_migrationops.py b/nova/tests/unit/virt/hyperv/test_migrationops.py index 0973e509c990..257fd4b67f53 100644 --- a/nova/tests/unit/virt/hyperv/test_migrationops.py +++ b/nova/tests/unit/virt/hyperv/test_migrationops.py @@ -276,6 +276,9 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): mock_image.return_value) mock_check_attach_config_drive.assert_called_once_with( mock_instance, get_image_vm_gen.return_value) + self._migrationops._vmops.set_boot_order.assert_called_once_with( + mock_instance.name, get_image_vm_gen.return_value, + block_device_info) self._migrationops._vmops.power_on.assert_called_once_with( mock_instance) @@ -438,6 +441,9 @@ class MigrationOpsTestCase(test_base.HyperVBaseTestCase): mock.sentinel.image_meta) mock_check_attach_config_drive.assert_called_once_with( mock_instance, get_image_vm_gen.return_value) + self._migrationops._vmops.set_boot_order.assert_called_once_with( + mock_instance.name, get_image_vm_gen.return_value, + block_device_info) self._migrationops._vmops.power_on.assert_called_once_with( mock_instance) diff --git a/nova/tests/unit/virt/hyperv/test_vmops.py b/nova/tests/unit/virt/hyperv/test_vmops.py index 8893c6f58370..dc97230058f8 100644 --- a/nova/tests/unit/virt/hyperv/test_vmops.py +++ b/nova/tests/unit/virt/hyperv/test_vmops.py @@ -379,8 +379,20 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): self.assertEqual(mock_InstanceDeviceMetadata.return_value, mock_instance.device_metadata) + def test_set_boot_order(self): + self._vmops.set_boot_order(mock.sentinel.instance_name, + mock.sentinel.vm_gen, + mock.sentinel.bdi) + + mock_get_boot_order = self._vmops._block_dev_man.get_boot_order + mock_get_boot_order.assert_called_once_with( + mock.sentinel.vm_gen, mock.sentinel.bdi) + self._vmops._vmutils.set_boot_order.assert_called_once_with( + mock.sentinel.instance_name, mock_get_boot_order.return_value) + @mock.patch('nova.virt.hyperv.vmops.VMOps.destroy') @mock.patch('nova.virt.hyperv.vmops.VMOps.power_on') + @mock.patch('nova.virt.hyperv.vmops.VMOps.set_boot_order') @mock.patch('nova.virt.hyperv.vmops.VMOps.attach_config_drive') @mock.patch('nova.virt.hyperv.vmops.VMOps._create_config_drive') @mock.patch('nova.virt.configdrive.required_by') @@ -395,6 +407,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock_create_instance, mock_save_device_metadata, mock_configdrive_required, mock_create_config_drive, mock_attach_config_drive, + mock_set_boot_order, mock_power_on, mock_destroy, exists, configdrive_required, fail, fake_vm_gen=constants.VM_GEN_2): @@ -452,6 +465,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): mock.sentinel.INFO) mock_attach_config_drive.assert_called_once_with( mock_instance, fake_config_drive_path, fake_vm_gen) + mock_set_boot_order.assert_called_once_with( + mock_instance.name, fake_vm_gen, block_device_info) mock_power_on.assert_called_once_with(mock_instance) def test_spawn(self): diff --git a/nova/virt/hyperv/block_device_manager.py b/nova/virt/hyperv/block_device_manager.py index c744795d7283..3f2ff7d3fee8 100644 --- a/nova/virt/hyperv/block_device_manager.py +++ b/nova/virt/hyperv/block_device_manager.py @@ -223,3 +223,45 @@ class BlockDeviceInfoManager(object): # make sure that boot_index is set. bdm['boot_index'] = bdm.get('boot_index') + + def _sort_by_boot_order(self, bd_list): + # we sort the block devices by boot_index leaving the ones that don't + # have a specified boot_index at the end + bd_list.sort(key=lambda x: (x['boot_index'] is None, x['boot_index'])) + + def get_boot_order(self, vm_gen, block_device_info): + if vm_gen == constants.VM_GEN_1: + return self._get_boot_order_gen1(block_device_info) + else: + return self._get_boot_order_gen2(block_device_info) + + def _get_boot_order_gen1(self, block_device_info): + if block_device_info['root_disk']['type'] == 'iso': + return [os_win_const.BOOT_DEVICE_CDROM, + os_win_const.BOOT_DEVICE_HARDDISK, + os_win_const.BOOT_DEVICE_NETWORK, + os_win_const.BOOT_DEVICE_FLOPPY] + else: + return [os_win_const.BOOT_DEVICE_HARDDISK, + os_win_const.BOOT_DEVICE_CDROM, + os_win_const.BOOT_DEVICE_NETWORK, + os_win_const.BOOT_DEVICE_FLOPPY] + + def _get_boot_order_gen2(self, block_device_info): + devices = [block_device_info['root_disk']] + devices += driver.block_device_info_get_ephemerals( + block_device_info) + devices += driver.block_device_info_get_mapping(block_device_info) + + self._sort_by_boot_order(devices) + + boot_order = [] + for dev in devices: + if dev.get('connection_info'): + dev_path = self._volops.get_mounted_disk_path_from_volume( + dev['connection_info']) + boot_order.append(dev_path) + else: + boot_order.append(dev['path']) + + return boot_order diff --git a/nova/virt/hyperv/migrationops.py b/nova/virt/hyperv/migrationops.py index 3445b712e0be..1d900291f298 100644 --- a/nova/virt/hyperv/migrationops.py +++ b/nova/virt/hyperv/migrationops.py @@ -187,7 +187,7 @@ class MigrationOps(object): block_device_info, vm_gen, image_meta) self._check_and_attach_config_drive(instance, vm_gen) - + self._vmops.set_boot_order(instance_name, vm_gen, block_device_info) if power_on: self._vmops.power_on(instance) @@ -296,7 +296,7 @@ class MigrationOps(object): block_device_info, vm_gen, image_meta) self._check_and_attach_config_drive(instance, vm_gen) - + self._vmops.set_boot_order(instance_name, vm_gen, block_device_info) if power_on: self._vmops.power_on(instance) diff --git a/nova/virt/hyperv/vmops.py b/nova/virt/hyperv/vmops.py index c73cd1663826..a47533a8a513 100644 --- a/nova/virt/hyperv/vmops.py +++ b/nova/virt/hyperv/vmops.py @@ -269,6 +269,15 @@ class VMOps(object): instance.device_metadata = objects.InstanceDeviceMetadata( devices=metadata) + def set_boot_order(self, instance_name, vm_gen, block_device_info): + boot_order = self._block_dev_man.get_boot_order( + vm_gen, block_device_info) + LOG.debug("Setting boot order for instance: %(instance_name)s: " + "%(boot_order)s", {'instance_name': instance_name, + 'boot_order': boot_order}) + + self._vmutils.set_boot_order(instance_name, boot_order) + @check_admin_permissions def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info, block_device_info=None): @@ -303,7 +312,7 @@ class VMOps(object): network_info) self.attach_config_drive(instance, configdrive_path, vm_gen) - + self.set_boot_order(instance.name, vm_gen, block_device_info) self.power_on(instance) except Exception: with excutils.save_and_reraise_exception(): diff --git a/releasenotes/notes/hyper-v-set-boot-order-1e76b08ca6783add.yaml b/releasenotes/notes/hyper-v-set-boot-order-1e76b08ca6783add.yaml new file mode 100644 index 000000000000..05cbf830eb7b --- /dev/null +++ b/releasenotes/notes/hyper-v-set-boot-order-1e76b08ca6783add.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added boot order support in the Hyper-V driver. + The HyperVDriver can now set the requested boot order for instances that are + Generation 2 VMs (the given image has the property "hw_machine_type=hyperv-gen2"). + For Generation 1 VMs, the spawned VM's boot order is changed only if the given + image is an ISO, booting from ISO first.