Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
import org.libvirt.Domain;
import org.libvirt.LibvirtException;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@ResourceWrapper(handles = PlugNicCommand.class)
public final class LibvirtPlugNicCommandWrapper extends CommandWrapper<PlugNicCommand, Answer, LibvirtComputingResource> {
Expand Down Expand Up @@ -65,6 +69,17 @@ public Answer execute(final PlugNicCommand command, final LibvirtComputingResour
if (command.getDetails() != null) {
libvirtComputingResource.setInterfaceDefQueueSettings(command.getDetails(), null, interfaceDef);
}

// 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");
}
Comment on lines +73 to +81

vm.attachDevice(interfaceDef.toString());

// apply default network rules on new nic
Expand Down Expand Up @@ -96,4 +111,46 @@ public Answer execute(final PlugNicCommand command, final LibvirtComputingResour
}
}
}

/**
* 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));
}
Comment on lines +123 to +131

// 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;
}
Comment on lines +115 to +154
}
}
Loading