The "seabios" BIOS code (used by QEMU as the initial boot code for every virtual machine) can be configured to prioritize the order in which various boot targets (hard disk 1, CD, PXE, etc) are checked for valid bootstrap code to load an operating system, and libvirt supports setting these priorities.
Seabios' boot selector works like this: it starts with the highest priority boot target, looking for valid bootstrap code. If seabios is unable to get valid bootstrap code from this target, it moves on to the next target in the list (for example, if booting from CD fails, an attempt will be made to boot from hard disk if that is next in the list), and so on all the way down the list. If all targets fail, the bios will pause for 60 seconds, then restart the virtual machine, causing a retry of this process, again starting from the highest priority target.
However, if any target does have "valid" bootstrap code (determined by computing a checksum of the returned data block, and meaning that it contains bootstrap code which will attempt to load a full operating system), that code will be executed and the bios will consider its job done (and so no restart/retry will be attempted), even if that valid boot sector is actually unable to boot an OS. For example most hard disks that have a partition table but don't yet have an operating system installed will still have a valid boot sector, and when that boot sector is executed, it will display a message something like "BOOT DISK FAILURE, PRESS ANY KEY", and wait there "forever" until a key is pressed, after which the bootstrap code that was loaded from the hard disk will again attempt to boot from the same hard disk ad infinitum until the guest is manually restarted.
Unfortunately, seabios will attempt to retrieve a boot sector from *every* potential boot target, not just those that have been assigned a priority, so there is no way to completely disable any specific boot target (i.e. remove it from the list); it can only be moved lower in the priority list.
This can be problematic if, for example, PXE (network) boot is set as the highest priority on a system that also contains a non-bootable hard disk; if the PXE boot code is unable to contact the PXE boot server before it times out (the timeout is quite short, and unfortunately not configurable), the seabios boot selector will move on to the hard disk (even if it hasn't been given any boot priority by libvirt at virtual machine startup time), and execute its boot sector, which results in the virtual machine being stuck at a "press any key" input prompt, thus requiring direct user intervention to boot the virtual machine.
Workaround: The only known way to work around this problem currently is to create a small disk image with a valid boot sector that simply reboots the virtual machine, and mark that disk with a boot priority higher than the priority of the existing hard disk (but lower than the priority of the desired boot target). Here is a short shell command that will create the appropriate disk image:
echo -e "00: b0 fe e6 64\n1fc: 00 00 55 aa\n" \
"7fffc: 00 00 00 00" \
| xxd -r -g 1 -c 4 \
(the xxd command is part of the vim-common package). And here is the XML required to add that device to a guest:
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<target dev='hdb' bus='ide'/>
<address type='drive' controller='0' bus='0'
Assuming you've saved the above XML to the file reboot.xml, you can add this disk to domain "X" with the following command:
virsh attach-device X reboot.xml --config
(You can change the controller/bus/target/unit and/or target dev to any available values you like, as long as <boot order='x'/> places this device after PXE boot, and prior to any unwanted device in the priority list)
Note the "<boot order='2'/>" - this assumes that you will add "<boot order='1'/> to the guest's <interface> definition to ensure that PXE boot is attempted before booting from this disk. (Also note that specifying <boot order='x'/> for individual devices is incompatible with the older method of specifying multiple "<boot dev='hd|net|etc'/> elements inside the <os> element of the guest configuration - you will need to remove any such lines from your guest's configuration).