KVM: Assign explicit PCI slot when hot-plugging NIC for sequential naming#12826
KVM: Assign explicit PCI slot when hot-plugging NIC for sequential naming#12826jmsperu wants to merge 1 commit intoapache:4.20from
Conversation
…ial naming When hot-plugging a NIC to a running VM, libvirt auto-assigns the next free PCI slot. Since non-NIC devices (virtio-serial, disk, balloon, watchdog) occupy slots immediately after existing NICs, the hot-plugged NIC gets a much higher slot number (e.g. 0x09 instead of 0x05), causing the guest to see non-sequential interface names (ens9 instead of ens5). This fix queries the domain XML to find all used PCI slots and assigns the next free slot after the highest existing NIC slot. This matches the approach already used by LibvirtReplugNicCommandWrapper which preserves PCI slots during re-plug operations. Fixes apache#12825 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## 4.20 #12826 +/- ##
=============================================
- Coverage 16.24% 4.15% -12.10%
=============================================
Files 5664 404 -5260
Lines 500463 32966 -467497
Branches 60779 5893 -54886
=============================================
- Hits 81308 1370 -79938
+ Misses 410059 31420 -378639
+ Partials 9096 176 -8920
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR aims to make KVM NIC hot-plug behavior more deterministic by explicitly setting a PCI slot for the newly attached NIC, with the goal of preserving predictable/sequential interface naming inside the guest.
Changes:
- Adds logic in
LibvirtPlugNicCommandWrapperto compute the next available PCI slot from the domain XML. - Sets the computed PCI slot on the
InterfaceDefbefore callingDomain.attachDevice(). - Introduces fallback behavior to let libvirt auto-assign the slot when XML cannot be read or when no slots appear available.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| String domXml = vm.getXMLDesc(0); | ||
|
|
||
| // Parse all PCI slot numbers currently in use | ||
| Set<Integer> usedSlots = new HashSet<>(); | ||
| Pattern slotPattern = Pattern.compile("slot='0x([0-9a-fA-F]+)'"); | ||
| Matcher matcher = slotPattern.matcher(domXml); | ||
| while (matcher.find()) { | ||
| usedSlots.add(Integer.parseInt(matcher.group(1), 16)); | ||
| } |
| /** | ||
| * Finds the next available PCI slot for a hot-plugged NIC by examining | ||
| * all PCI slots currently in use by the domain. This ensures the new NIC | ||
| * gets a sequential PCI address relative to existing NICs, resulting in | ||
| * predictable interface naming in the guest OS (e.g. ens5 instead of ens9). | ||
| */ | ||
| private Integer findNextAvailablePciSlot(final Domain vm, final List<InterfaceDef> pluggedNics) { | ||
| try { | ||
| String domXml = vm.getXMLDesc(0); | ||
|
|
||
| // Parse all PCI slot numbers currently in use | ||
| Set<Integer> usedSlots = new HashSet<>(); | ||
| Pattern slotPattern = Pattern.compile("slot='0x([0-9a-fA-F]+)'"); | ||
| Matcher matcher = slotPattern.matcher(domXml); | ||
| while (matcher.find()) { | ||
| usedSlots.add(Integer.parseInt(matcher.group(1), 16)); | ||
| } | ||
|
|
||
| // Find the highest PCI slot used by existing NICs | ||
| int maxNicSlot = 0; | ||
| for (InterfaceDef pluggedNic : pluggedNics) { | ||
| if (pluggedNic.getSlot() != null && pluggedNic.getSlot() > maxNicSlot) { | ||
| maxNicSlot = pluggedNic.getSlot(); | ||
| } | ||
| } | ||
|
|
||
| // Find next free slot starting from maxNicSlot + 1 | ||
| // PCI slots range from 0x01 to 0x1f (slot 0 is reserved for host bridge) | ||
| for (int slot = maxNicSlot + 1; slot <= 0x1f; slot++) { | ||
| if (!usedSlots.contains(slot)) { | ||
| return slot; | ||
| } | ||
| } | ||
|
|
||
| logger.warn("No free PCI slots available, letting libvirt auto-assign"); | ||
| return null; | ||
| } catch (LibvirtException e) { | ||
| logger.warn("Failed to get domain XML for PCI slot calculation, letting libvirt auto-assign", e); | ||
| return null; | ||
| } |
| // Explicitly assign PCI slot to ensure sequential NIC naming in the guest. | ||
| // Without this, libvirt auto-assigns the next free PCI slot which may be | ||
| // non-sequential with existing NICs (e.g. ens9 instead of ens5), causing | ||
| // guest network configuration to fail. | ||
| Integer nextSlot = findNextAvailablePciSlot(vm, pluggedNics); | ||
| if (nextSlot != null) { | ||
| interfaceDef.setSlot(nextSlot); | ||
| logger.debug("Assigning PCI slot 0x" + String.format("%02x", nextSlot) + " to hot-plugged NIC"); | ||
| } |
Description
Fixes #12825
When hot-plugging a NIC to a running KVM VM, libvirt auto-assigns the next free PCI slot. Since non-NIC PCI devices (virtio-serial controller, virtio-disk, memballoon, watchdog) occupy slots immediately after existing NICs (slots 0x05-0x08), the hot-plugged NIC gets a much higher slot number (e.g., 0x09), causing the guest to see
ens9instead of the expected sequentialens5. The NIC shows as DOWN with no IP.Changes
Modified
LibvirtPlugNicCommandWrapper.javato explicitly assign a PCI slot when hot-plugging:This matches the approach already used by
LibvirtReplugNicCommandWrapperwhich preserves PCI slots during re-plug operations (line 70:interfaceDef.setSlot(oldPluggedNic.getSlot())).Falls back gracefully to libvirt auto-assignment if:
Evidence
Before fix — hot-plugged NIC at non-sequential PCI slot 0x09:
After relocating non-NIC devices to high slots (proving the concept):
Note: For complete sequential naming on existing VMs, a follow-up change is needed to assign non-NIC PCI devices to higher slot numbers during VM creation in
createDevicesDef().Test plan
🤖 Generated with Claude Code