Skip to content

Commit c572b98

Browse files
James Peruclaude
authored andcommitted
KVM: assign explicit PCI slot when hot-plugging NIC to ensure sequential 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 #12825 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 61afb4c commit c572b98

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
import org.libvirt.Domain;
3535
import org.libvirt.LibvirtException;
3636

37+
import java.util.HashSet;
3738
import java.util.List;
39+
import java.util.Set;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
3842

3943
@ResourceWrapper(handles = PlugNicCommand.class)
4044
public final class LibvirtPlugNicCommandWrapper extends CommandWrapper<PlugNicCommand, Answer, LibvirtComputingResource> {
@@ -65,6 +69,17 @@ public Answer execute(final PlugNicCommand command, final LibvirtComputingResour
6569
if (command.getDetails() != null) {
6670
libvirtComputingResource.setInterfaceDefQueueSettings(command.getDetails(), null, interfaceDef);
6771
}
72+
73+
// Explicitly assign PCI slot to ensure sequential NIC naming in the guest.
74+
// Without this, libvirt auto-assigns the next free PCI slot which may be
75+
// non-sequential with existing NICs (e.g. ens9 instead of ens5), causing
76+
// guest network configuration to fail.
77+
Integer nextSlot = findNextAvailablePciSlot(vm, pluggedNics);
78+
if (nextSlot != null) {
79+
interfaceDef.setSlot(nextSlot);
80+
logger.debug("Assigning PCI slot 0x" + String.format("%02x", nextSlot) + " to hot-plugged NIC");
81+
}
82+
6883
vm.attachDevice(interfaceDef.toString());
6984

7085
// apply default network rules on new nic
@@ -96,4 +111,46 @@ public Answer execute(final PlugNicCommand command, final LibvirtComputingResour
96111
}
97112
}
98113
}
114+
115+
/**
116+
* Finds the next available PCI slot for a hot-plugged NIC by examining
117+
* all PCI slots currently in use by the domain. This ensures the new NIC
118+
* gets a sequential PCI address relative to existing NICs, resulting in
119+
* predictable interface naming in the guest OS (e.g. ens5 instead of ens9).
120+
*/
121+
private Integer findNextAvailablePciSlot(final Domain vm, final List<InterfaceDef> pluggedNics) {
122+
try {
123+
String domXml = vm.getXMLDesc(0);
124+
125+
// Parse all PCI slot numbers currently in use
126+
Set<Integer> usedSlots = new HashSet<>();
127+
Pattern slotPattern = Pattern.compile("slot='0x([0-9a-fA-F]+)'");
128+
Matcher matcher = slotPattern.matcher(domXml);
129+
while (matcher.find()) {
130+
usedSlots.add(Integer.parseInt(matcher.group(1), 16));
131+
}
132+
133+
// Find the highest PCI slot used by existing NICs
134+
int maxNicSlot = 0;
135+
for (InterfaceDef pluggedNic : pluggedNics) {
136+
if (pluggedNic.getSlot() != null && pluggedNic.getSlot() > maxNicSlot) {
137+
maxNicSlot = pluggedNic.getSlot();
138+
}
139+
}
140+
141+
// Find next free slot starting from maxNicSlot + 1
142+
// PCI slots range from 0x01 to 0x1f (slot 0 is reserved for host bridge)
143+
for (int slot = maxNicSlot + 1; slot <= 0x1f; slot++) {
144+
if (!usedSlots.contains(slot)) {
145+
return slot;
146+
}
147+
}
148+
149+
logger.warn("No free PCI slots available, letting libvirt auto-assign");
150+
return null;
151+
} catch (LibvirtException e) {
152+
logger.warn("Failed to get domain XML for PCI slot calculation, letting libvirt auto-assign", e);
153+
return null;
154+
}
155+
}
99156
}

0 commit comments

Comments
 (0)