Save previous hd boot device when setting Pxe

Assuming that sushy_tool ignores "BootSourceOverrideEnabled": "Once" for
libvirt backend, we define a new boot device permanently. And if the pxe
server does not reply after the first successful  provision, we just get a
vm which doesn't manage to boot after provision because netboot is the only
option and no reply from pxe.

This patch adds two additional lists of previously configured boot hard drives
for the vm at the end of the boot order. The first list is for the os section
If it is defined, it just adds all hard drives to the list. The second list is
for the actual drive section with the boot directive. 

Tried to save script logic as much as possible. But it might be reasonable to
pass one additional argument to the set_boot_device function to do such
altering when "BootSourceOverrideEnabled" is defined and has "Once" as a value

Added testcase to verify boot order definition

Change-Id: I0abcab9ccbffcc9b06382b0e6d335edd20db25bf
This commit is contained in:
arddennis 2023-01-05 15:44:13 +01:00
parent 8427349985
commit 2a628d4099
3 changed files with 119 additions and 2 deletions

View File

@ -403,9 +403,11 @@ class LibvirtDriver(AbstractSystemsDriver):
tree = ET.fromstring(self.get_xml_desc(domain))
# Remove bootloader configuration
os_element_order = []
for os_element in tree.findall('os'):
for boot_element in os_element.findall('boot'):
os_element_order.append(boot_element.get('dev'))
os_element.remove(boot_element)
if self.SUSHY_EMULATOR_IGNORE_BOOT_DEVICE:
@ -428,20 +430,31 @@ class LibvirtDriver(AbstractSystemsDriver):
raise error.FishyError(msg)
target_device_elements = []
cur_hd_osboot_elements = []
cur_hd_order_elements = []
# Remove per-disk boot configuration
# We should save at least hdd boot entries instead of just removing
# everything. In some scenarious PXE after provisioning stops replying
# and if there is no other boot device, then vm will fail to boot
# cdrom and floppy are ignored.
for disk_element in devices_element.findall('disk'):
device_attr = disk_element.get('device')
if device_attr is None:
continue
boot_elements = disk_element.findall('boot')
# NOTE(etingof): multiple devices of the same type not supported
if device_attr == target:
target_device_elements.append(disk_element)
elif 'hd' in os_element_order:
cur_hd_osboot_elements.append(disk_element)
elif boot_elements:
cur_hd_order_elements.append(disk_element)
for boot_element in disk_element.findall('boot'):
for boot_element in boot_elements:
disk_element.remove(boot_element)
target = self.INTERFACE_MAP.get(boot_source)
@ -463,6 +476,15 @@ class LibvirtDriver(AbstractSystemsDriver):
raise error.FishyError(msg)
# OS boot and per device boot order are mutually exclusive
if cur_hd_osboot_elements:
sorted_hd_elements = sorted(
cur_hd_osboot_elements,
key=lambda child: child.find('target').get('dev'))
target_device_elements.extend(sorted_hd_elements)
else:
target_device_elements.extend(cur_hd_order_elements)
# NOTE(etingof): Make all chosen devices bootable (important for NICs)
for order, target_device_element in enumerate(target_device_elements):
@ -701,7 +723,10 @@ class LibvirtDriver(AbstractSystemsDriver):
def _process_bios(self, identity,
bios_attributes=DEFAULT_BIOS_ATTRIBUTES,
update_existing_attributes=False):
"""Process Libvirt domain XML for BIOS attributes and update it if necessary
"""Process Libvirt domain XML for BIOS attributes
Process Libvirt domain XML for BIOS attributes and update it if
necessary
:param identity: libvirt domain name or ID
:param bios_attributes: Full list of BIOS attributes to use if
@ -712,6 +737,7 @@ class LibvirtDriver(AbstractSystemsDriver):
:raises: `error.FishyError` if BIOS attributes cannot be saved
"""
domain = self._get_domain(identity)
result = self._process_bios_attributes(

View File

@ -0,0 +1,31 @@
<domain type='qemu'>
<name>QEmu-fedora-i686</name>
<uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
<memory>219200</memory>
<currentMemory>219200</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch='x86_64' machine='pc'>hvm</type>
<boot dev='hd'/>
<loader type='rom'/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='cdrom'>
<source file='/home/user/boot.iso'/>
<target dev='hdc'/>
<readonly/>
</disk>
<disk type='file' device='disk'>
<source file='/home/user/fedora.img'/>
<target dev='hda'/>
</disk>
<interface type='network'>
<source network='default'/>
<mac address='52:54:00:da:ac:54'/>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x01' function='0x0'/>
</interface>
<graphics type='vnc' port='-1'/>
</devices>
</domain>

View File

@ -327,6 +327,66 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.assertIn(expected, conn_mock.defineXML.call_args[0][0])
@mock.patch('libvirt.open', autospec=True)
def test_set_boot_device_network_from_hd(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/'
'domain_to_boot_pxe.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
with mock.patch.object(
self.test_driver, 'get_power_state', return_value='Off'):
self.test_driver.set_boot_device(self.uuid, 'Pxe')
conn_mock.defineXML.assert_called_once_with(mock.ANY)
newtree = ET.fromstring(conn_mock.defineXML.call_args[0][0])
# check that os section does not have boot defined.
# We find all os sections if any. Then count about of boot sections.
# And the final summ should be 0
os_boot_amount = sum(
[len(ossec.findall('boot')) for ossec in newtree.findall('os')])
self.assertEqual(0, os_boot_amount)
# check that Network device has order=1
interface_orders = [
theint.find('boot').get('order')
for theint in newtree.find('devices').findall('interface')]
self.assertIn('1', interface_orders)
# Check that we have at least one hd device set after a network device
diskdrives_order_sum = len([
thedrive.find('boot').get('order')
for thedrive in newtree.find('devices').findall('disk')])
self.assertEqual(2, diskdrives_order_sum)
# Check overal config to match expected fixture
expected = '<domain type="qemu">\n <name>QEmu-fedora-i686</name>\n '\
' <uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>\n '\
'<memory>219200</memory>\n '\
'<currentMemory>219200</currentMemory>\n <vcpu>2</vcpu>\n '\
'<os>\n <type arch="x86_64" machine="pc">hvm</type>\n '\
'<loader type="rom" />\n </os>\n <devices>\n '\
'<emulator>/usr/bin/qemu-system-x86_64</emulator>\n '\
'<disk type="file" device="cdrom">\n '\
'<source file="/home/user/boot.iso" />\n '\
'<target dev="hdc" />\n <readonly />\n '\
'<boot order="3" /></disk>\n '\
'<disk type="file" device="disk">\n '\
'<source file="/home/user/fedora.img" />\n '\
'<target dev="hda" />\n <boot order="2" /></disk>\n '\
'<interface type="network">\n '\
'<source network="default" />\n '\
'<mac address="52:54:00:da:ac:54" />\n '\
'<model type="virtio" />\n <address type="pci" '\
'domain="0x0000" bus="0x01" slot="0x01" function="0x0" />\n '\
'<boot order="1" /></interface>\n '\
'<graphics type="vnc" port="-1" />\n </devices>\n</domain>'
self.assertIn(expected, conn_mock.defineXML.call_args[0][0])
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_boot_mode(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f: