diff options
author | Simon Rettberg | 2025-03-27 14:10:08 +0100 |
---|---|---|
committer | Simon Rettberg | 2025-03-27 14:10:08 +0100 |
commit | a306c5617fd1d4e09739655fa7070a300dac2b13 (patch) | |
tree | 8576afa93831213dbbebb81d7a796cf59b5416d0 | |
parent | Fix comment (diff) | |
download | master-sync-shared-a306c56.tar.gz master-sync-shared-a306c56.tar.xz master-sync-shared-a306c56.zip |
[libvirt] Make sure VMs have at least 5 spicevmc usb ports
7 files changed, 123 insertions, 34 deletions
diff --git a/src/main/java/org/openslx/libvirt/domain/device/ControllerUsb.java b/src/main/java/org/openslx/libvirt/domain/device/ControllerUsb.java index 1798027..1be42e5 100644 --- a/src/main/java/org/openslx/libvirt/domain/device/ControllerUsb.java +++ b/src/main/java/org/openslx/libvirt/domain/device/ControllerUsb.java @@ -1,6 +1,7 @@ package org.openslx.libvirt.domain.device; import org.openslx.libvirt.xml.LibvirtXmlNode; +import org.openslx.util.Util; /** * A USB controller device node in a Libvirt domain XML document. @@ -136,4 +137,12 @@ public class ControllerUsb extends Controller return null; } } + + /** + * Get number of ports this controller provides. + */ + public int getPortCount() + { + return Util.parseInt( this.getXmlElementAttributeValue( "ports" ), -1 ); + } } diff --git a/src/main/java/org/openslx/libvirt/domain/device/Device.java b/src/main/java/org/openslx/libvirt/domain/device/Device.java index d743522..f391663 100644 --- a/src/main/java/org/openslx/libvirt/domain/device/Device.java +++ b/src/main/java/org/openslx/libvirt/domain/device/Device.java @@ -1,5 +1,6 @@ package org.openslx.libvirt.domain.device; +import org.apache.commons.lang3.NotImplementedException; import org.openslx.libvirt.xml.LibvirtXmlNode; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -48,7 +49,7 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H * @param deviceType type of the Libvirt XML device element. * @return created Libvirt XML device node. */ - private static LibvirtXmlNode createDeviceElement( LibvirtXmlNode xmlParentNode, Type deviceType ) + private static LibvirtXmlNode createDeviceElement( LibvirtXmlNode xmlParentNode, DeviceClass deviceType ) { // create XML element as part of the Libvirt XML document Document xmlDocument = xmlParentNode.getXmlDocument(); @@ -72,38 +73,41 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H Device createdDevice = null; if ( device instanceof Controller ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.CONTROLLER ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.CONTROLLER ); createdDevice = Controller.createInstance( Controller.class.cast( device ), xmlNode ); } else if ( device instanceof Disk ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.DISK ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.DISK ); createdDevice = Disk.createInstance( Disk.class.cast( device ), xmlNode ); } else if ( device instanceof FileSystem ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.FILESYSTEM ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.FILESYSTEM ); createdDevice = FileSystem.createInstance( xmlNode ); } else if ( device instanceof Hostdev ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.HOSTDEV ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.HOSTDEV ); createdDevice = Hostdev.createInstance( Hostdev.class.cast( device ), xmlNode ); } else if ( device instanceof Interface ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.INTERFACE ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.INTERFACE ); createdDevice = Interface.createInstance( Interface.class.cast( device ), xmlNode ); } else if ( device instanceof Graphics ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.GRAPHICS ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.GRAPHICS ); createdDevice = Graphics.createInstance( Graphics.class.cast( device ), xmlNode ); } else if ( device instanceof Parallel ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.PARALLEL ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.PARALLEL ); createdDevice = Parallel.createInstance( xmlNode ); } else if ( device instanceof Serial ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.SERIAL ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.SERIAL ); createdDevice = Serial.createInstance( xmlNode ); } else if ( device instanceof Shmem ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.SHMEM ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.SHMEM ); createdDevice = Shmem.createInstance( xmlNode ); } else if ( device instanceof Sound ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.SOUND ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.SOUND ); createdDevice = Sound.createInstance( xmlNode ); } else if ( device instanceof Video ) { - LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, Type.VIDEO ); + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.VIDEO ); createdDevice = Video.createInstance( xmlNode ); + } else if ( device instanceof RedirDevice ) { + LibvirtXmlNode xmlNode = Device.createDeviceElement( xmlParentNode, DeviceClass.REDIRDEV ); + createdDevice = RedirDevice.createInstance( xmlNode ); } return createdDevice; @@ -124,10 +128,10 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H return null; } else { Device device = null; - Type type = Type.fromString( element.getNodeName() ); + DeviceClass type = DeviceClass.fromString( element.getNodeName() ); if ( type == null ) { - return null; + return new Device( xmlNode ); } switch ( type ) { @@ -167,6 +171,8 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H case VIDEO: device = Video.newInstance( xmlNode ); break; + default: + throw new NotImplementedException( "Class not implemented" ); } return device; @@ -260,7 +266,23 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H return HostdevUsbDeviceAddress.valueOf( bus, port ); } - + + /** + * Returns which bus this device is connected to, or null if unknown + */ + public BusType getDeviceBusType() + { + return BusType.fromString( this.getXmlElementAttributeValue( "bus" ) ); + } + + /** + * Get class of device, i.e. enum value representing the XML tag + */ + public DeviceClass getDeviceClass() + { + return DeviceClass.fromString( this.getXmlBaseNode().getLocalName() ); + } + /** * Returns this devices USB bus/port address, or null if it doesn't have an explicit one * set, or if the address type isn't USB. @@ -275,7 +297,6 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H public void setUsbTarget( HostdevUsbDeviceAddress address ) { this.setUsbAddress( "address", address ); - this.setXmlElementAttributeValue( "address", "type", "pci" ); } /** @@ -284,7 +305,7 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H * @author Manuel Bentele * @version 1.0 */ - enum Type + public enum DeviceClass { // @formatter:off CONTROLLER( "controller" ), @@ -311,7 +332,7 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H * * @param type valid name of the virtual machine device type in a Libvirt domain XML document. */ - Type( String type ) + DeviceClass( String type ) { this.type = type; } @@ -328,9 +349,9 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H * @param type name of the virtual machine device type in a Libvirt domain XML document. * @return valid virtual machine device type. */ - public static Type fromString( String type ) + public static DeviceClass fromString( String type ) { - for ( Type t : Type.values() ) { + for ( DeviceClass t : DeviceClass.values() ) { if ( t.type.equalsIgnoreCase( type ) ) { return t; } @@ -339,4 +360,5 @@ public class Device extends LibvirtXmlNode implements HostdevAddressableTarget<H return null; } } + } diff --git a/src/main/java/org/openslx/libvirt/domain/device/RedirDevice.java b/src/main/java/org/openslx/libvirt/domain/device/RedirDevice.java index 2656bbe..d841413 100644 --- a/src/main/java/org/openslx/libvirt/domain/device/RedirDevice.java +++ b/src/main/java/org/openslx/libvirt/domain/device/RedirDevice.java @@ -42,14 +42,6 @@ public class RedirDevice extends Device { this.setXmlElementAttributeValue( "type", type.toString() ); } - - /** - * Get bus type. - */ - public BusType getBus() - { - return BusType.fromString( this.getXmlElementAttributeValue( "bus" ) ); - } /** * Creates a non-existent video device as Libvirt XML device element. @@ -126,4 +118,9 @@ public class RedirDevice extends Device return null; } } + + public void setBusType( BusType usb ) + { + this.setXmlElementAttributeValue( "bus", usb.toString() ); + } } diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java index d714fc4..4952d3d 100644 --- a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java @@ -13,6 +13,7 @@ import org.openslx.libvirt.domain.Domain; import org.openslx.libvirt.domain.DomainUtils; import org.openslx.libvirt.domain.device.BusType; import org.openslx.libvirt.domain.device.ControllerUsb; +import org.openslx.libvirt.domain.device.Device; import org.openslx.libvirt.domain.device.Disk; import org.openslx.libvirt.domain.device.Disk.StorageType; import org.openslx.libvirt.domain.device.DiskCdrom; @@ -20,8 +21,10 @@ import org.openslx.libvirt.domain.device.DiskFloppy; import org.openslx.libvirt.domain.device.DiskStorage; import org.openslx.libvirt.domain.device.Graphics; import org.openslx.libvirt.domain.device.GraphicsSpice; +import org.openslx.libvirt.domain.device.HostdevUsbDeviceAddress; import org.openslx.libvirt.domain.device.Interface; import org.openslx.libvirt.domain.device.RedirDevice; +import org.openslx.libvirt.domain.device.RedirDevice.SrcType; import org.openslx.libvirt.domain.device.Sound; import org.openslx.libvirt.domain.device.Video; import org.openslx.libvirt.libosinfo.LibOsInfo; @@ -47,6 +50,7 @@ import org.openslx.virtualization.virtualizer.VirtualizerQemu; */ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration { + /** * Name of the network bridge for the LAN. */ @@ -117,7 +121,7 @@ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration try { // read and parse Libvirt domain XML configuration document - this.vmConfig = new Domain( new String( vmContent, StandardCharsets.UTF_8 ) ); + this.vmConfig = new Domain( new String( vmContent, 0, length, StandardCharsets.UTF_8 ) ); } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) { throw new VirtualizationConfigurationException( e.getLocalizedMessage() ); } @@ -127,6 +131,61 @@ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration } /** + * See if a USB controller is present and if so, make sure we have + * a bunch of spicevmc ports for dynamic pass-through. + * + * @param onlyIfAtLeastOne Only add more spicevmc usb devices if we have at least one existing + */ + private void preconfigureUsb( boolean onlyIfAtLeastOne ) + { + final int MAX_BUSES = 4; // We assume max. 4 USB controllers + final int MAX_DEV_PER_BUS = 16; // ...and max. 16 ports on a controller + final int MIN_USB_REDIR_DEVS = 5; // 5 additional USB devices ought to be enough for everyone + int devs = 0; + byte busPort[][] = new byte[ MAX_BUSES ][]; + + for ( int i = 0; i < busPort.length; ++i ) { + busPort[i] = new byte[ MAX_DEV_PER_BUS ]; + } + // Check how many we got, and what they are connected to + for ( Device d : vmConfig.getDevices() ) { + // is spicevmc? + if ( d.getDeviceBusType() == BusType.USB && d.getDeviceClass() == Device.DeviceClass.REDIRDEV + && "spicevmc".equals( d.getXmlElementAttributeValue( "type" ) ) ) { + devs++; + } + // Which bus/port? + HostdevUsbDeviceAddress addr = d.getUsbTarget(); + if ( addr == null ) + continue; + int bus = addr.getUsbBus(); + int port = addr.getUsbPort(); + if ( bus >= 0 && port > 0 && bus < busPort.length && port < busPort[bus].length ) { + busPort[bus][port] = 1; + } + } + if ( devs >= MIN_USB_REDIR_DEVS ) + return; + for ( ControllerUsb c : vmConfig.getUsbControllerDevices() ) { + int bus = c.getIndex(); + if ( bus < 0 || bus >= busPort.length ) + continue; + int ports = c.getPortCount(); + for ( int port = 1; port < ports; ++port ) { + if ( busPort[bus][port] == 0 ) { + // Free port on this controller, use + RedirDevice dev = (RedirDevice)vmConfig.addDevice( new RedirDevice() ); + dev.setSrcType( SrcType.SPICEVMC ); + dev.setBusType( BusType.USB ); + dev.setUsbTarget( new HostdevUsbDeviceAddress( bus, port ) ); + if ( ++devs >= MIN_USB_REDIR_DEVS ) + return; // Enough + } + } + } + } + + /** * Parses Libvirt domain XML configuration to initialize QEMU metadata. */ private void parseVmConfig() @@ -832,7 +891,7 @@ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration @Override public void transformNonPersistent() throws VirtualizationConfigurationException { - // NOT implemented yet + this.preconfigureUsb( false ); } @Override @@ -914,7 +973,7 @@ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration // attach any new USB devices. ArrayList<RedirDevice> list = vmConfig.getRedirectDevices(); for (RedirDevice dev : list ) { - if ( dev.getBus() == BusType.USB ) { + if ( dev.getDeviceBusType() == BusType.USB ) { dev.remove(); } } diff --git a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java index 2d3e861..8703769 100644 --- a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java +++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java @@ -73,6 +73,8 @@ public class ConfigurationLogicDozModServerToStatelessClient this.validateInputs( config, args ); // apply settings to run virtualized system in a stateless manner + // call this one early on as methods further down might customize + // remove things we do here... try { config.transformNonPersistent(); } catch ( VirtualizationConfigurationException e ) { diff --git a/src/test/java/org/openslx/libvirt/domain/DomainTest.java b/src/test/java/org/openslx/libvirt/domain/DomainTest.java index d73abe0..5fccd1c 100644 --- a/src/test/java/org/openslx/libvirt/domain/DomainTest.java +++ b/src/test/java/org/openslx/libvirt/domain/DomainTest.java @@ -412,7 +412,7 @@ public class DomainTest public void testGetDevices() { Domain vm = DomainTest.getDomain( "qemu-kvm_default-ubuntu-20-04-vm.xml" ); - assertEquals( 24, vm.getDevices().size() ); + assertEquals( 33, vm.getDevices().size() ); } @Test diff --git a/src/test/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuTest.java b/src/test/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuTest.java index 04dc118..5505509 100644 --- a/src/test/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuTest.java +++ b/src/test/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuTest.java @@ -490,11 +490,11 @@ public class VirtualizationConfigurationQemuTest final Domain vmLibvirtDomainConfig = VirtualizationConfigurationQemuTest .getPrivateDomainFromQemuMetaData( vmConfig ); - assertEquals( vmLibvirtDomainConfig.getRedirectDevices().size(), 2 ); + assertEquals( 2, vmLibvirtDomainConfig.getRedirectDevices().size() ); vmConfig.disableUsb(); - assertEquals( vmLibvirtDomainConfig.getRedirectDevices().size(), 0 ); + assertEquals( 0, vmLibvirtDomainConfig.getRedirectDevices().size() ); assertDoesNotThrow( () -> vmConfig.validate() ); } |