diff options
author | Manuel Bentele | 2021-01-29 12:25:44 +0100 |
---|---|---|
committer | Manuel Bentele | 2021-01-29 12:25:44 +0100 |
commit | 706910e440527c22f176bcab0032c37c60357c25 (patch) | |
tree | 1173cdb610cb81bc17dccff3d2df4ba0ecd1c078 | |
parent | Add implementation of Libvirt domain XML documents (diff) | |
download | master-sync-shared-706910e440527c22f176bcab0032c37c60357c25.tar.gz master-sync-shared-706910e440527c22f176bcab0032c37c60357c25.tar.xz master-sync-shared-706910e440527c22f176bcab0032c37c60357c25.zip |
Add support for QEMU VMs (based on Libvirt domain XML documents)
-rw-r--r-- | pom.xml | 27 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/DiskImage.java | 35 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java | 16 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/QemuMetaData.java | 796 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java | 188 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/VboxMetaData.java | 14 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/VmMetaData.java | 9 | ||||
-rw-r--r-- | src/main/java/org/openslx/util/vm/VmwareMetaData.java | 16 | ||||
-rw-r--r-- | src/main/resources/libvirt/xsl/xml-output-transformation.xsl | 12 | ||||
-rw-r--r-- | src/test/java/org/openslx/util/vm/QemuMetaDataTest.java | 466 |
10 files changed, 1487 insertions, 92 deletions
@@ -82,11 +82,21 @@ <version>3.0.0-M5</version> </plugin> </plugins> + <resources> + <resource> + <directory>${basedir}/src/main/resources</directory> + <includes> + <include>libvirt/rng/*</include> + <include>libvirt/xsl/*</include> + </includes> + </resource> + </resources> <testResources> <testResource> <directory>${basedir}/src/test/resources</directory> <includes> <include>disk/*</include> + <include>libvirt/xml/*</include> </includes> </testResource> </testResources> @@ -112,6 +122,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>5.5.2</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>[1.2.10,1.2.20]</version> @@ -140,9 +156,14 @@ <version>2.8.0</version> </dependency> <dependency> - <groupId>com.google.jimfs</groupId> - <artifactId>jimfs</artifactId> - <version>1.1</version> + <groupId>xalan</groupId> + <artifactId>xalan</artifactId> + <version>2.7.2</version> + </dependency> + <dependency> + <groupId>org.relaxng</groupId> + <artifactId>jing</artifactId> + <version>20181222</version> </dependency> </dependencies> </project> diff --git a/src/main/java/org/openslx/util/vm/DiskImage.java b/src/main/java/org/openslx/util/vm/DiskImage.java index 1602926..617fadd 100644 --- a/src/main/java/org/openslx/util/vm/DiskImage.java +++ b/src/main/java/org/openslx/util/vm/DiskImage.java @@ -5,6 +5,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; import org.apache.log4j.Logger; import org.openslx.bwlp.thrift.iface.Virtualizer; @@ -318,7 +320,40 @@ public class DiskImage } throw new UnknownImageFormatException(); } + + /** + * Creates new disk image and checks if image is supported by hypervisor's image formats. + * + * @param disk file to a disk storing the virtual machine content. + * @param supportedImageTypes list of supported image types of a hypervisor's image format. + * @throws FileNotFoundException cannot find virtual machine image file. + * @throws IOException cannot access the virtual machine image file. + * @throws UnknownImageFormatException virtual machine image file has an unknown image format. + */ + public DiskImage( File disk, List<ImageFormat> supportedImageTypes ) + throws FileNotFoundException, IOException, UnknownImageFormatException + { + this( disk ); + if ( !this.isSupportedByHypervisor( supportedImageTypes ) ) { + throw new UnknownImageFormatException(); + } + } + + /** + * Checks if image format is supported by a list of supported hypervisor image formats. + * + * @param supportedImageTypes list of supported image types of a hypervisor. + * @return <code>true</code> if image type is supported by the hypervisor; otherwise + * <code>false</code>. + */ + public boolean isSupportedByHypervisor( List<ImageFormat> supportedImageTypes ) + { + Predicate<ImageFormat> matchDiskFormat = supportedImageType -> supportedImageType.toString() + .equalsIgnoreCase( this.getImageFormat().toString() ); + return supportedImageTypes.stream().anyMatch( matchDiskFormat ); + } + private int findNull( byte[] buffer ) { for ( int i = 0; i < buffer.length; ++i ) { diff --git a/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java b/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java index 3ee964f..9698c52 100644 --- a/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java +++ b/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java @@ -3,13 +3,23 @@ package org.openslx.util.vm; import org.apache.log4j.Logger; import org.openslx.bwlp.thrift.iface.Virtualizer; import org.openslx.thrifthelper.TConst; +import org.openslx.util.vm.DiskImage.ImageFormat; import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; public class DockerMetaDataDummy extends VmMetaData { // TODO Define DOCKER CONSTANT + /** + * List of supported image formats by the Docker hypervisor. + */ + private static final List<DiskImage.ImageFormat> SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.DOCKER ) ); + private static final Logger LOGGER = Logger.getLogger( DockerMetaDataDummy.class); private final Virtualizer virtualizer = new Virtualizer( TConst.VIRT_DOCKER, "Docker" ); @@ -41,6 +51,12 @@ public class DockerMetaDataDummy extends VmMetaData { @Override public byte[] getFilteredDefinitionArray() { return dockerfile; } + + @Override + public List<DiskImage.ImageFormat> getSupportedImageFormats() + { + return DockerMetaDataDummy.SUPPORTED_IMAGE_FORMATS; + } @Override public void applySettingsForLocalEdit() { diff --git a/src/main/java/org/openslx/util/vm/QemuMetaData.java b/src/main/java/org/openslx/util/vm/QemuMetaData.java index 201ffd8..1e5dd33 100644 --- a/src/main/java/org/openslx/util/vm/QemuMetaData.java +++ b/src/main/java/org/openslx/util/vm/QemuMetaData.java @@ -1,233 +1,855 @@ package org.openslx.util.vm; import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; import org.apache.log4j.Logger; import org.openslx.bwlp.thrift.iface.OperatingSystem; import org.openslx.bwlp.thrift.iface.Virtualizer; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.ControllerUsb; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Disk.StorageType; +import org.openslx.libvirt.domain.device.DiskCdrom; +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.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.domain.device.Video; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; import org.openslx.thrifthelper.TConst; import org.openslx.util.vm.DiskImage.ImageFormat; -import org.openslx.util.vm.DiskImage.UnknownImageFormatException; -public class QemuMetaData extends VmMetaData<VBoxSoundCardMeta, VBoxDDAccelMeta, VBoxHWVersionMeta, VBoxEthernetDevTypeMeta, VBoxUsbSpeedMeta> +/** + * Metadata to describe the hardware type of a QEMU sound card. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuSoundCardMeta { + /** + * Stores the hardware model of the QEMU sound card. + */ + private final Sound.Model model; + + /** + * Creates metadata to describe the hardware model of a QEMU sound card. + * + * @param model hardware model of the QEMU sound card. + */ + public QemuSoundCardMeta( Sound.Model model ) + { + this.model = model; + } - private final Map<String, String> arguments = new HashMap<String, String>(); - // the above map's elements will take the place of <args> in the config string - private String config; - private static final Logger LOGGER = Logger.getLogger( QemuMetaData.class ); + /** + * Returns hardware model of the QEMU sound card. + * + * @return hardware model of the QEMU sound card. + */ + public Sound.Model getModel() + { + return this.model; + } +} - private static final Virtualizer virtualizer = new Virtualizer( TConst.VIRT_QEMU, "QEMU-KVM" ); +/** + * Metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuDDAccelMeta +{ + /** + * Stores state of the hardware acceleration for QEMU virtual graphics. + */ + private final boolean enabled; + + /** + * Creates metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @param enabled state of the hardware acceleration for QEMU virtual graphics. + */ + public QemuDDAccelMeta( boolean enabled ) + { + this.enabled = enabled; + } - public QemuMetaData( List<OperatingSystem> osList, File file ) throws FileNotFoundException, IOException, UnsupportedVirtualizerFormatException + /** + * Returns state of the hardware acceleration of QEMU virtual graphics. + * + * @return state of the hardware acceleration for QEMU virtual graphics. + */ + public boolean isEnabled() + { + return this.enabled; + } +} + +/** + * Metadata to describe the version of a QEMU virtual machine configuration. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuHWVersionMeta +{ + /** + * Stores the version of a QEMU virtual machine configuration. + */ + private final int version; + + /** + * Creates metadata to describe the version of a QEMU virtual machine configuration. + * + * @param version version of the QEMU virtual machine configuration. + */ + public QemuHWVersionMeta( int version ) + { + this.version = version; + } + + /** + * Returns version of the QEMU virtual machine configuration. + * + * @return version of the QEMU virtual machine configuration. + */ + public int getVersion() + { + return this.version; + } +} + +/** + * Metadata to describe the hardware type of a QEMU ethernet device. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuEthernetDevTypeMeta +{ + /** + * Stores the hardware model of the QEMU ethernet device. + */ + private final Interface.Model model; + + /** + * Creates metadata to describe the hardware type of a QEMU ethernet device. + * + * @param model hardware type of the QEMU ethernet device. + */ + public QemuEthernetDevTypeMeta( Interface.Model model ) + { + this.model = model; + } + + /** + * Returns the hardware type of a QEMU ethernet device. + * + * @return hardware type of the QEMU ethernet device. + */ + public Interface.Model getModel() + { + return this.model; + } +} + +/** + * Metadata to describe a QEMU USB controller. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuUsbSpeedMeta +{ + /** + * Stores the USB speed of the QEMU USB controller. + */ + private final int speed; + + /** + * Stores the QEMU hardware model of the USB controller. + */ + private final ControllerUsb.Model model; + + /** + * Creates metadata to describe a QEMU USB controller. + * + * @param speed USB speed of the QEMU USB controller. + * @param model QEMU hardware model of the USB controller. + */ + public QemuUsbSpeedMeta( int speed, ControllerUsb.Model model ) + { + this.speed = speed; + this.model = model; + } + + /** + * Returns the speed of the QEMU USB controller. + * + * @return speed of the QEMU USB controller. + */ + public int getSpeed() + { + return this.speed; + } + + /** + * Returns QEMU hardware model of the USB controller. + * + * @return hardware model of the QEMU USB controller. + */ + public ControllerUsb.Model getModel() + { + return this.model; + } +} + +/** + * Virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class QemuMetaData extends + VmMetaData<QemuSoundCardMeta, QemuDDAccelMeta, QemuHWVersionMeta, QemuEthernetDevTypeMeta, QemuUsbSpeedMeta> +{ + /** + * Default bridge name of the network bridge connected to the LAN. + */ + public static final String NETWORK_DEFAULT_BRIDGE = "brBwLehrpool"; + + /** + * Default network name of the isolated host network (host only). + */ + public static final String NETWORK_DEFAULT_HOST_ONLY = "host"; + + /** + * Default network name of the NAT network. + */ + public static final String NETWORK_DEFAULT_NAT = "nat"; + + /** + * Default physical CDROM drive of the hypervisor host. + */ + public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0"; + + /** + * List of supported image formats by the QEMU hypervisor. + */ + private static final List<DiskImage.ImageFormat> SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ); + + /** + * Representation of a QEMU hypervisor (managed by Libvirt). + */ + private static final Virtualizer VIRTUALIZER = new Virtualizer( TConst.VIRT_QEMU, "QEMU" ); + + /** + * Libvirt XML configuration file to modify configuration of virtual machine for QEMU. + */ + private Domain vmConfig = null; + + /** + * Stores current index of added HDD device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexHddAdd = 0; + + /** + * Stores current index of added CDROM device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexCdromAdd = 0; + + /** + * Stores current index of added ethernet device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexEthernetAdd = 0; + + /** + * Creates new virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. + * + * @param osList list of operating systems. + * @param file image file for the QEMU hypervisor. + * @throws UnsupportedVirtualizerFormatException Libvirt XML configuration cannot be processed. + */ + public QemuMetaData( List<OperatingSystem> osList, File file ) throws UnsupportedVirtualizerFormatException { super( osList ); - DiskImage di; + try { - di = new DiskImage( file ); - } catch ( UnknownImageFormatException e ) { - di = null; + // read and parse Libvirt domain XML configuration document + this.vmConfig = new Domain( file ); + } catch ( LibvirtXmlDocumentException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlSerializationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlValidationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); } - if ( di == null || di.format != ImageFormat.QCOW2 ) { - throw new UnsupportedVirtualizerFormatException( "This is not a qcow2 disk image" ); + + // register virtual hardware models for graphical editing of virtual devices (GPU, sound, USB, ...) + this.registerVirtualHW(); + + // set display name of VM + this.displayName = vmConfig.getName(); + + // this property cannot be checked with the Libvirt domain XML configuration + // to check if machine is in a paused/suspended state, look in the QEMU qcow2 image for snapshots and machine states + this.isMachineSnapshot = false; + + // add HDDs, SSDs to QEMU metadata + for ( DiskStorage storageDiskDevice : this.vmConfig.getDiskStorageDevices() ) { + this.addHddMetaData( storageDiskDevice ); } - config = "qemu-system-i386 <args> <image> -enable-kvm\nqemu-system-x86_64 <args> <image> -enable-kvm"; - displayName = file.getName().substring( 0, file.getName().indexOf( "." ) ); - setOs( "anyOs" ); - hdds.add( new HardDisk( "anychipset", DriveBusType.IDE, file.getAbsolutePath() ) ); - makeStartSequence(); } - // initiates the arguments map with a default working sequence that will later be used in the definition array - public void makeStartSequence() + /** + * Adds an existing and observed storage disk device to the HDD metadata. + * + * @param storageDiskDevice existing and observed storage disk that should be added to the + * metadata. + */ + private void addHddMetaData( DiskStorage storageDiskDevice ) { - arguments.put( "cpu", "host" ); - arguments.put( "smp", "2" ); - arguments.put( "m", "1024" ); - arguments.put( "vga", "std" ); + String hddChipsetModel = null; + DriveBusType hddChipsetBus = QemuMetaDataUtils.convertBusType( storageDiskDevice.getBusType() ); + String hddImagePath = storageDiskDevice.getStorageSource(); + + this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) ); } - private String configWithArgs() + @Override + public byte[] getFilteredDefinitionArray() { - String tempString = ""; - for ( String key : arguments.keySet() ) { - tempString += "-" + key + " " + arguments.get( key ) + " "; - } - return config.replaceAll( "<args>", tempString ); + // remove UUID in Libvirt domain XML configuration + this.vmConfig.removeUuid(); + + // removes all specified boot order entries + this.vmConfig.removeBootOrder(); + + // removes all referenced storage files of all specified CDROMs, Floppy drives and HDDs + this.vmConfig.removeDiskDevicesStorage(); + + // removes all source networks of all specified network interfaces + this.vmConfig.removeInterfaceDevicesSource(); + + // output filtered Libvirt domain XML configuration + String configuration = this.vmConfig.toString(); + return configuration.getBytes( StandardCharsets.UTF_8 ); } @Override - public byte[] getFilteredDefinitionArray() + public List<DiskImage.ImageFormat> getSupportedImageFormats() { - return configWithArgs().getBytes( StandardCharsets.UTF_8 ); + return QemuMetaData.SUPPORTED_IMAGE_FORMATS; } @Override public void applySettingsForLocalEdit() { + // NOT implemented yet } @Override public boolean addHddTemplate( File diskImage, String hddMode, String redoDir ) { - String tempS = config.replaceAll( "<image>", diskImage.getAbsolutePath() ); - config = tempS; - hdds.add( new HardDisk( "anychipset", DriveBusType.IDE, diskImage.getAbsolutePath() ) ); - return true; + return this.addHddTemplate( diskImage.getAbsolutePath(), hddMode, redoDir ); } @Override public boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir ) { - String tempS = config.replaceAll( "<image>", diskImagePath ); - config = tempS; - hdds.add( new HardDisk( "anychipset", DriveBusType.IDE, diskImagePath ) ); - return true; + return this.addHddTemplate( this.vmDeviceIndexHddAdd++, diskImagePath, hddMode, redoDir ); + } + + /** + * Adds hard disk drive (HDD) to the QEMU virtual machine configuration. + * + * @param index current index of HDD to be added to the virtual machine configuration. + * @param diskImagePath path to the virtual disk image for the HDD. + * @param hddMode operation mode of the HDD. + * @param redoDir directory for the redo log if an independent non-persistent + * <code>hddMode</code> is set. + * @return result state of adding the HDD. + */ + public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir ) + { + ArrayList<DiskStorage> storageDiskDevices = this.vmConfig.getDiskStorageDevices(); + DiskStorage storageDiskDevice = QemuMetaDataUtils.getArrayIndex( storageDiskDevices, index ); + + if ( storageDiskDevice == null ) { + // HDD does not exist, so create new storage (HDD) device + storageDiskDevice = this.vmConfig.addDiskStorageDevice(); + storageDiskDevice.setReadOnly( false ); + storageDiskDevice.setBusType( BusType.VIRTIO ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "vd", index ); + storageDiskDevice.setTargetDevice( targetDevName ); + storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); + + // add new created HDD to the metadata of the QemuMetaData object, too + this.addHddMetaData( storageDiskDevice ); + } else { + // HDD exists, so update existing storage (HDD) device + storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); + } + + return false; } @Override public boolean addDefaultNat() { - return true; + return this.addEthernet( EtherType.NAT ); } @Override public void setOs( String vendorOsId ) { - setOs( TConst.VIRT_QEMU, vendorOsId ); + this.setOs( vendorOsId ); } @Override public boolean addDisplayName( String name ) { - // TODO Auto-generated method stub - return false; + this.vmConfig.setName( name ); + + final boolean statusName = this.vmConfig.getName().equals( name ); + + return statusName; } @Override public boolean addRam( int mem ) { - this.arguments.put( "m", Integer.toString( mem ) ); - return true; + BigInteger memory = BigInteger.valueOf( mem ); + + this.vmConfig.setMemory( memory ); + this.vmConfig.setCurrentMemory( memory ); + + final boolean isMemorySet = this.vmConfig.getMemory().toString().equals( memory.toString() ); + final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().toString().equals( memory.toString() ); + + return isMemorySet && isCurrentMemorySet; } @Override public void addFloppy( int index, String image, boolean readOnly ) { - // TODO Auto-generated method stub - + ArrayList<DiskFloppy> floppyDiskDevices = this.vmConfig.getDiskFloppyDevices(); + DiskFloppy floppyDiskDevice = QemuMetaDataUtils.getArrayIndex( floppyDiskDevices, index ); + + if ( floppyDiskDevice == null ) { + // floppy device does not exist, so create new floppy device + floppyDiskDevice = this.vmConfig.addDiskFloppyDevice(); + floppyDiskDevice.setBusType( BusType.FDC ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "fd", index ); + floppyDiskDevice.setTargetDevice( targetDevName ); + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } else { + // floppy device exists, so update existing floppy device + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } } @Override public boolean addCdrom( String image ) { - // TODO Auto-generated method stub + return this.addCdrom( this.vmDeviceIndexCdromAdd++, image ); + } + + /** + * Adds CDROM drive to the QEMU virtual machine configuration. + * + * @param index current index of CDROM drive to be added to the virtual machine configuration. + * @param image path to a virtual image that will be inserted as CDROM into the drive. + * @return result state of adding the CDROM drive. + */ + public boolean addCdrom( int index, String image ) + { + ArrayList<DiskCdrom> cdromDiskDevices = this.vmConfig.getDiskCdromDevices(); + DiskCdrom cdromDiskDevice = QemuMetaDataUtils.getArrayIndex( cdromDiskDevices, index ); + + if ( cdromDiskDevice == null ) { + // CDROM device does not exist, so create new CDROM device + cdromDiskDevice = this.vmConfig.addDiskCdromDevice(); + cdromDiskDevice.setBusType( BusType.SATA ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "sd", index ); + cdromDiskDevice.setTargetDevice( targetDevName ); + cdromDiskDevice.setReadOnly( true ); + + if ( image == null ) { + cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); + } else { + cdromDiskDevice.setStorage( StorageType.FILE, image ); + } + } else { + // CDROM device exists, so update existing CDROM device + cdromDiskDevice.setReadOnly( true ); + + if ( image == null ) { + cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); + } else { + cdromDiskDevice.setStorage( StorageType.FILE, image ); + } + } + return false; } @Override public boolean addCpuCoreCount( int nrOfCores ) { - this.arguments.put( "smp", Integer.toString( nrOfCores ) ); - return true; + this.vmConfig.setVCpu( nrOfCores ); + + boolean isVCpuSet = this.vmConfig.getVCpu() == nrOfCores; + + return isVCpuSet; } @Override - public void setSoundCard( VmMetaData.SoundCardType type ) + public void setSoundCard( SoundCardType type ) { + QemuSoundCardMeta soundDeviceConfig = this.soundCards.get( type ); + ArrayList<Sound> soundDevices = this.vmConfig.getSoundDevices(); + Sound.Model soundDeviceModel = soundDeviceConfig.getModel(); + + if ( soundDevices.isEmpty() ) { + // create new sound device with 'soundDeviceModel' hardware + Sound soundDevice = this.vmConfig.addSoundDevice(); + soundDevice.setModel( soundDeviceModel ); + } else { + // update sound device model type of existing sound devices + for ( Sound soundDevice : soundDevices ) { + soundDevice.setModel( soundDeviceModel ); + } + } } @Override - public VmMetaData.SoundCardType getSoundCard() + public SoundCardType getSoundCard() { - return null; + ArrayList<Sound> soundDevices = this.vmConfig.getSoundDevices(); + SoundCardType soundDeviceType = SoundCardType.DEFAULT; + + if ( soundDevices.isEmpty() ) { + // the VM configuration does not contain a sound card device + soundDeviceType = SoundCardType.NONE; + } else { + // the VM configuration at least one sound card device, so return the type of the first one + Sound.Model soundDeviceModel = soundDevices.get( 0 ).getModel(); + soundDeviceType = QemuMetaDataUtils.convertSoundDeviceModel( soundDeviceModel ); + } + + return soundDeviceType; } @Override - public void setDDAcceleration( VmMetaData.DDAcceleration type ) + public void setDDAcceleration( DDAcceleration type ) { + QemuDDAccelMeta accelerationConfig = this.ddacc.get( type ); + ArrayList<Graphics> graphicDevices = this.vmConfig.getGraphicDevices(); + ArrayList<Video> videoDevices = this.vmConfig.getVideoDevices(); + final boolean accelerationEnabled = accelerationConfig.isEnabled(); + + boolean acceleratedGraphicsAvailable = false; + + if ( graphicDevices.isEmpty() ) { + // add new graphics device with enabled acceleration to VM configuration + GraphicsSpice graphicSpiceDevice = this.vmConfig.addGraphicsSpiceDevice(); + graphicSpiceDevice.setOpenGl( true ); + acceleratedGraphicsAvailable = true; + } else { + // enable graphic acceleration of existing graphics devices + for ( Graphics graphicDevice : graphicDevices ) { + // set hardware acceleration for SPICE based graphics output + // other graphic devices do not support hardware acceleration + if ( graphicDevice instanceof GraphicsSpice ) { + GraphicsSpice.class.cast( graphicDevice ).setOpenGl( true ); + acceleratedGraphicsAvailable = true; + } + } + } + + // only configure hardware acceleration of video card(s) if graphics with hardware acceleration is available + if ( acceleratedGraphicsAvailable ) { + if ( videoDevices.isEmpty() ) { + // add new video device with enabled acceleration to VM configuration + Video videoDevice = this.vmConfig.addVideoDevice(); + videoDevice.setModel( Video.Model.VIRTIO ); + videoDevice.set2DAcceleration( true ); + videoDevice.set3DAcceleration( true ); + } else { + // enable graphic acceleration of existing graphics and video devices + for ( Video videoDevice : videoDevices ) { + // set hardware acceleration for Virtio GPUs + // other GPUs do not support hardware acceleration + if ( videoDevice.getModel() == Video.Model.VIRTIO ) { + videoDevice.set2DAcceleration( accelerationEnabled ); + videoDevice.set3DAcceleration( accelerationEnabled ); + } + } + } + } } @Override - public VmMetaData.DDAcceleration getDDAcceleration() + public DDAcceleration getDDAcceleration() { - return null; + ArrayList<Graphics> graphicsDevices = this.vmConfig.getGraphicDevices(); + ArrayList<Video> videoDevices = this.vmConfig.getVideoDevices(); + DDAcceleration accelerationState = DDAcceleration.OFF; + + boolean acceleratedGraphicsAvailable = false; + boolean acceleratedVideoDevAvailable = false; + + // search for hardware accelerated graphics + for ( Graphics graphicDevice : graphicsDevices ) { + // only SPICE based graphic devices support hardware acceleration + if ( graphicDevice instanceof GraphicsSpice ) { + acceleratedGraphicsAvailable = true; + break; + } + } + + // search for hardware accelerated video devices + for ( Video videoDevice : videoDevices ) { + // only Virtio based video devices support hardware acceleration + if ( videoDevice.getModel() == Video.Model.VIRTIO ) { + acceleratedVideoDevAvailable = true; + break; + } + } + + // hardware acceleration is available if at least one accelerated graphics and video device is available + if ( acceleratedGraphicsAvailable && acceleratedVideoDevAvailable ) { + accelerationState = DDAcceleration.ON; + } else { + accelerationState = DDAcceleration.OFF; + } + + return accelerationState; } @Override - public void setHWVersion( VmMetaData.HWVersion type ) + public void setHWVersion( HWVersion type ) { + // NOT supported by the QEMU hypervisor } @Override - public VmMetaData.HWVersion getHWVersion() + public HWVersion getHWVersion() { + // NOT supported by the QEMU hypervisor return null; } @Override - public void setEthernetDevType( int cardIndex, VmMetaData.EthernetDevType type ) + public void setEthernetDevType( int cardIndex, EthernetDevType type ) { + QemuEthernetDevTypeMeta networkDeviceConfig = this.networkCards.get( type ); + ArrayList<Interface> networkDevices = this.vmConfig.getInterfaceDevices(); + Interface networkDevice = QemuMetaDataUtils.getArrayIndex( networkDevices, cardIndex ); + Interface.Model networkDeviceModel = networkDeviceConfig.getModel(); + + if ( networkDevice != null ) { + networkDevice.setModel( networkDeviceModel ); + } } @Override - public VmMetaData.EthernetDevType getEthernetDevType( int cardIndex ) + public EthernetDevType getEthernetDevType( int cardIndex ) { - return null; + ArrayList<Interface> networkDevices = this.vmConfig.getInterfaceDevices(); + Interface networkDevice = QemuMetaDataUtils.getArrayIndex( networkDevices, cardIndex ); + EthernetDevType networkDeviceType = EthernetDevType.NONE; + + if ( networkDevice == null ) { + // network interface device is not present + networkDeviceType = EthernetDevType.NONE; + } else { + // get model of existing network interface device + Interface.Model networkDeviceModel = networkDevice.getModel(); + networkDeviceType = QemuMetaDataUtils.convertNetworkDeviceModel( networkDeviceModel ); + } + + return networkDeviceType; } @Override - public byte[] getDefinitionArray() + public void setMaxUsbSpeed( UsbSpeed speed ) { - return configWithArgs().getBytes( StandardCharsets.UTF_8 ); + QemuUsbSpeedMeta usbControllerConfig = this.usbSpeeds.get( speed ); + ArrayList<ControllerUsb> usbControllerDevices = this.vmConfig.getUsbControllerDevices(); + ControllerUsb.Model usbControllerModel = usbControllerConfig.getModel(); + + if ( usbControllerDevices.isEmpty() ) { + // add new USB controller with specified speed 'usbControllerModel' + ControllerUsb usbControllerDevice = this.vmConfig.addControllerUsbDevice(); + usbControllerDevice.setModel( usbControllerModel ); + } else { + // update model of all USB controller devices to support the maximum speed + for ( ControllerUsb usbControllerDevice : usbControllerDevices ) { + usbControllerDevice.setModel( usbControllerModel ); + } + } } @Override - public boolean addEthernet( VmMetaData.EtherType type ) + public UsbSpeed getMaxUsbSpeed() { - return false; + ArrayList<ControllerUsb> usbControllerDevices = this.vmConfig.getUsbControllerDevices(); + UsbSpeed maxUsbSpeed = VmMetaData.UsbSpeed.NONE; + int maxUsbSpeedNumeric = 0; + + for ( ControllerUsb usbControllerDevice : usbControllerDevices ) { + ControllerUsb.Model usbControllerModel = usbControllerDevice.getModel(); + + for ( Entry<UsbSpeed, QemuUsbSpeedMeta> usbSpeedEntry : this.usbSpeeds.entrySet() ) { + QemuUsbSpeedMeta usbSpeed = usbSpeedEntry.getValue(); + if ( usbSpeed.getSpeed() > maxUsbSpeedNumeric && usbSpeed.getModel() == usbControllerModel ) { + maxUsbSpeed = usbSpeedEntry.getKey(); + maxUsbSpeedNumeric = usbSpeed.getSpeed(); + } + } + } + + return maxUsbSpeed; } @Override - public Virtualizer getVirtualizer() + public byte[] getDefinitionArray() { - return virtualizer; + String configuration = this.vmConfig.toString(); + + if ( configuration == null ) { + return null; + } else { + // append newline at the end of the XML content to match the structure of an original Libvirt XML file + configuration += System.lineSeparator(); + return configuration.getBytes( StandardCharsets.UTF_8 ); + } } @Override - public boolean tweakForNonPersistent() + public boolean addEthernet( EtherType type ) + { + return this.addEthernet( this.vmDeviceIndexEthernetAdd++, type ); + } + + /** + * Adds an ethernet card to the QEMU virtual machine configuration. + * + * @param index current index of the ethernet card to be added to the virtual machine + * configuration. + * @param type card model of the ethernet card. + * @return result state of adding the ethernet card. + */ + public boolean addEthernet( int index, EtherType type ) { + QemuEthernetDevTypeMeta defaultNetworkDeviceConfig = this.networkCards.get( EthernetDevType.AUTO ); + ArrayList<Interface> interfaceDevices = this.vmConfig.getInterfaceDevices(); + Interface interfaceDevice = QemuMetaDataUtils.getArrayIndex( interfaceDevices, index ); + + final Interface.Model defaultNetworkDeviceModel = defaultNetworkDeviceConfig.getModel(); + + if ( interfaceDevice == null ) { + // network interface device does not exist, so create new network interface device + switch ( type ) { + case BRIDGED: + // add network bridge interface device + interfaceDevice = this.vmConfig.addInterfaceBridgeDevice(); + interfaceDevice.setModel( defaultNetworkDeviceModel ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_BRIDGE ); + break; + case HOST_ONLY: + // add network interface device with link to the isolated host network + interfaceDevice = this.vmConfig.addInterfaceNetworkDevice(); + interfaceDevice.setModel( defaultNetworkDeviceModel ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_HOST_ONLY ); + break; + case NAT: + // add network interface device with link to the NAT network + interfaceDevice = this.vmConfig.addInterfaceNetworkDevice(); + interfaceDevice.setModel( defaultNetworkDeviceModel ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_NAT ); + break; + } + } else { + // network interface device exists, so update existing network interface device + switch ( type ) { + case BRIDGED: + interfaceDevice.setType( Interface.Type.BRIDGE ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_BRIDGE ); + break; + case HOST_ONLY: + interfaceDevice.setType( Interface.Type.NETWORK ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_HOST_ONLY ); + break; + case NAT: + interfaceDevice.setType( Interface.Type.NETWORK ); + interfaceDevice.setSource( QemuMetaData.NETWORK_DEFAULT_NAT ); + break; + } + } + return false; } @Override - public void registerVirtualHW() + public Virtualizer getVirtualizer() { + return QemuMetaData.VIRTUALIZER; } @Override - public void setMaxUsbSpeed( VmMetaData.UsbSpeed speed ) + public boolean tweakForNonPersistent() { - // TODO: Actual speed setting? - if ( speed == null || speed == VmMetaData.UsbSpeed.NONE ) { - arguments.remove( "usb" ); - } else { - arguments.put( "usb", "" ); - } + // NOT implemented yet + return false; } @Override - public VmMetaData.UsbSpeed getMaxUsbSpeed() + public void registerVirtualHW() { - if ( arguments.containsKey( "usb" ) ) - return VmMetaData.UsbSpeed.USB2_0; // TODO - return VmMetaData.UsbSpeed.NONE; + // @formatter:off + soundCards.put( VmMetaData.SoundCardType.NONE, new QemuSoundCardMeta( null ) ); + soundCards.put( VmMetaData.SoundCardType.DEFAULT, new QemuSoundCardMeta( Sound.Model.ICH9 ) ); + soundCards.put( VmMetaData.SoundCardType.SOUND_BLASTER, new QemuSoundCardMeta( Sound.Model.SB16 ) ); + soundCards.put( VmMetaData.SoundCardType.ES, new QemuSoundCardMeta( Sound.Model.ES1370 ) ); + soundCards.put( VmMetaData.SoundCardType.AC, new QemuSoundCardMeta( Sound.Model.AC97 ) ); + soundCards.put( VmMetaData.SoundCardType.HD_AUDIO, new QemuSoundCardMeta( Sound.Model.ICH9 ) ); + + ddacc.put( VmMetaData.DDAcceleration.OFF, new QemuDDAccelMeta( false ) ); + ddacc.put( VmMetaData.DDAcceleration.ON, new QemuDDAccelMeta( true ) ); + + hwversion.put( VmMetaData.HWVersion.DEFAULT, new QemuHWVersionMeta( 0 ) ); + + networkCards.put( VmMetaData.EthernetDevType.NONE, new QemuEthernetDevTypeMeta( null ) ); + networkCards.put( VmMetaData.EthernetDevType.AUTO, new QemuEthernetDevTypeMeta( Interface.Model.VIRTIO_NET_PCI ) ); + networkCards.put( VmMetaData.EthernetDevType.PCNETPCI2, new QemuEthernetDevTypeMeta( Interface.Model.PCNET ) ); + networkCards.put( VmMetaData.EthernetDevType.E1000, new QemuEthernetDevTypeMeta( Interface.Model.E1000 ) ); + networkCards.put( VmMetaData.EthernetDevType.E1000E, new QemuEthernetDevTypeMeta( Interface.Model.E1000E ) ); + networkCards.put( VmMetaData.EthernetDevType.VMXNET3, new QemuEthernetDevTypeMeta( Interface.Model.VMXNET3 ) ); + networkCards.put( VmMetaData.EthernetDevType.PARAVIRT, new QemuEthernetDevTypeMeta( Interface.Model.VIRTIO_NET_PCI ) ); + + usbSpeeds.put( VmMetaData.UsbSpeed.NONE, new QemuUsbSpeedMeta( 0, ControllerUsb.Model.NONE ) ); + usbSpeeds.put( VmMetaData.UsbSpeed.USB1_1, new QemuUsbSpeedMeta( 1, ControllerUsb.Model.ICH9_UHCI1 ) ); + usbSpeeds.put( VmMetaData.UsbSpeed.USB2_0, new QemuUsbSpeedMeta( 2, ControllerUsb.Model.ICH9_EHCI1 ) ); + usbSpeeds.put( VmMetaData.UsbSpeed.USB3_0, new QemuUsbSpeedMeta( 3, ControllerUsb.Model.QEMU_XHCI ) ); + // @formatter:on } - } diff --git a/src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java b/src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java new file mode 100644 index 0000000..42c3fb6 --- /dev/null +++ b/src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java @@ -0,0 +1,188 @@ +package org.openslx.util.vm; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.device.Disk; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.util.vm.VmMetaData.DriveBusType; +import org.openslx.util.vm.VmMetaData.EthernetDevType; +import org.openslx.util.vm.VmMetaData.SoundCardType; + +/** + * Collection of utils to convert data types from bwLehrpool to Libvirt and vice versa. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class QemuMetaDataUtils +{ + /** + * Converts a Libvirt disk device bus type to a VM metadata driver bus type. + * + * @param busType Libvirt disk device bus type. + * @return VM metadata bus type of the disk drive. + */ + public static DriveBusType convertBusType( Disk.BusType busType ) + { + DriveBusType type = null; + + switch ( busType ) { + case IDE: + type = DriveBusType.IDE; + break; + case SATA: + type = DriveBusType.SATA; + break; + case SCSI: + type = DriveBusType.SCSI; + break; + default: + type = null; + break; + } + + return type; + } + + /** + * Converts a VM metadata driver bus type to a Libvirt disk device bus type. + * + * @param busType VM metadata bus type of the disk drive. + * @return Libvirt disk device bus type. + */ + public static Disk.BusType convertBusType( DriveBusType busType ) + { + Disk.BusType type = null; + + switch ( busType ) { + case IDE: + type = BusType.IDE; + break; + case SATA: + type = BusType.SATA; + break; + case SCSI: + type = BusType.SCSI; + break; + } + + return type; + } + + /** + * Converts a Libvirt sound device model to a VM metadata sound card type. + * + * @param soundDeviceModel Libvirt sound device model. + * @return VM metadata sound card type. + */ + public static SoundCardType convertSoundDeviceModel( Sound.Model soundDeviceModel ) + { + SoundCardType type = SoundCardType.NONE; + + switch ( soundDeviceModel ) { + case AC97: + type = SoundCardType.AC; + break; + case ES1370: + type = SoundCardType.ES; + break; + case ICH6: + type = SoundCardType.HD_AUDIO; + break; + case ICH9: + type = SoundCardType.HD_AUDIO; + break; + case SB16: + type = SoundCardType.SOUND_BLASTER; + break; + } + + return type; + } + + /** + * Converts a Libvirt network device model to a VM metadata ethernet device type. + * + * @param soundDeviceModel Libvirt network device model. + * @return VM metadata ethernet device type. + */ + public static EthernetDevType convertNetworkDeviceModel( Interface.Model networkDeviceModel ) + { + EthernetDevType type = EthernetDevType.NONE; + + switch ( networkDeviceModel ) { + case E1000: + type = EthernetDevType.E1000; + break; + case E1000E: + type = EthernetDevType.E1000E; + break; + case PCNET: + type = EthernetDevType.PCNETPCI2; + break; + case VIRTIO: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI_NON_TRANSITIONAL: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI_TRANSITIONAL: + type = EthernetDevType.PARAVIRT; + break; + case VMXNET3: + type = EthernetDevType.VMXNET3; + break; + default: + type = EthernetDevType.AUTO; + break; + } + + return type; + } + + /** + * Returns an item from a given {@link ArrayList}. + * + * The item is selected by a given index. If the item is not available within the + * {@link ArrayList}, <code>null</code> is returned. + * + * @param <T> type of the {@link ArrayList}. + * @param array {@link ArrayList} of type <code>T</code>. + * @param index selects the item from the {@link ArrayList}. + * @return selected item of the {@link ArrayList}. + */ + public static <T> T getArrayIndex( ArrayList<T> array, int index ) + { + T ret; + + try { + ret = array.get( index ); + } catch ( IndexOutOfBoundsException e ) { + ret = null; + } + + return ret; + } + + /** + * Creates an alphabetical device name constructed from a device prefix and a device number. + * + * @param devicePrefix prefix of the constructed device name. + * @param deviceNumber number of the device. + * @return alphabetical device name. + */ + public static String createAlphabeticalDeviceName( String devicePrefix, int deviceNumber ) + { + if ( deviceNumber < 0 || deviceNumber >= ( 'z' - 'a' ) ) { + String errorMsg = new String( "Device number is out of range to be able to create a valid device name." ); + throw new IllegalArgumentException( errorMsg ); + } + + return devicePrefix + ( 'a' + deviceNumber ); + } +} diff --git a/src/main/java/org/openslx/util/vm/VboxMetaData.java b/src/main/java/org/openslx/util/vm/VboxMetaData.java index da5189e..82936a7 100644 --- a/src/main/java/org/openslx/util/vm/VboxMetaData.java +++ b/src/main/java/org/openslx/util/vm/VboxMetaData.java @@ -6,6 +6,7 @@ import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.UUID; @@ -14,6 +15,7 @@ import org.apache.log4j.Logger; import org.openslx.bwlp.thrift.iface.OperatingSystem; import org.openslx.bwlp.thrift.iface.Virtualizer; import org.openslx.thrifthelper.TConst; +import org.openslx.util.vm.DiskImage.ImageFormat; import org.openslx.util.vm.VboxConfig.PlaceHolder; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -77,6 +79,12 @@ class VBoxUsbSpeedMeta public class VboxMetaData extends VmMetaData<VBoxSoundCardMeta, VBoxDDAccelMeta, VBoxHWVersionMeta, VBoxEthernetDevTypeMeta, VBoxUsbSpeedMeta> { + /** + * List of supported image formats by the VirtualBox hypervisor. + */ + private static final List<DiskImage.ImageFormat> SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.VDI ) ); + private static final Logger LOGGER = Logger.getLogger( VboxMetaData.class ); private static final Virtualizer virtualizer = new Virtualizer( TConst.VIRT_VIRTUALBOX, "VirtualBox" ); @@ -125,6 +133,12 @@ public class VboxMetaData extends VmMetaData<VBoxSoundCardMeta, VBoxDDAccelMeta, { return virtualizer; } + + @Override + public List<DiskImage.ImageFormat> getSupportedImageFormats() + { + return VboxMetaData.SUPPORTED_IMAGE_FORMATS; + } @Override public void applySettingsForLocalEdit() diff --git a/src/main/java/org/openslx/util/vm/VmMetaData.java b/src/main/java/org/openslx/util/vm/VmMetaData.java index c836697..c872450 100644 --- a/src/main/java/org/openslx/util/vm/VmMetaData.java +++ b/src/main/java/org/openslx/util/vm/VmMetaData.java @@ -282,6 +282,13 @@ public abstract class VmMetaData<T, U, V, W, X> } /** + * Returns list of image formats supported by the VM's hypervisor. + * + * @return list of image formats. + */ + public abstract List<DiskImage.ImageFormat> getSupportedImageFormats(); + + /** * Apply config options that are desired when locally editing a VM. for vmware, * this disables automatic DPI scaling of the guest. */ @@ -310,7 +317,7 @@ public abstract class VmMetaData<T, U, V, W, X> try { return new QemuMetaData( osList, file ); } catch ( Exception e ) { - LOGGER.info( "Not a QEmu file", e ); + LOGGER.info( "Not a Qemu file", e ); } try { // TODO This will work for each file because simple read as byte array diff --git a/src/main/java/org/openslx/util/vm/VmwareMetaData.java b/src/main/java/org/openslx/util/vm/VmwareMetaData.java index 2835e22..1793655 100644 --- a/src/main/java/org/openslx/util/vm/VmwareMetaData.java +++ b/src/main/java/org/openslx/util/vm/VmwareMetaData.java @@ -3,6 +3,8 @@ package org.openslx.util.vm; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,6 +17,7 @@ import org.openslx.bwlp.thrift.iface.OperatingSystem; import org.openslx.bwlp.thrift.iface.Virtualizer; import org.openslx.thrifthelper.TConst; import org.openslx.util.Util; +import org.openslx.util.vm.DiskImage.ImageFormat; import org.openslx.util.vm.VmwareConfig.ConfigEntry; class VmWareSoundCardMeta @@ -73,7 +76,12 @@ class VmwareUsbSpeed public class VmwareMetaData extends VmMetaData<VmWareSoundCardMeta, VmWareDDAccelMeta, VmWareHWVersionMeta, VmWareEthernetDevTypeMeta, VmwareUsbSpeed> { - + /** + * List of supported image formats by the VMware hypervisor. + */ + private static final List<DiskImage.ImageFormat> SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.VMDK ) ); + private static final Logger LOGGER = Logger.getLogger( VmwareMetaData.class ); private static final Virtualizer virtualizer = new Virtualizer( TConst.VIRT_VMWARE, "VMware" ); @@ -246,6 +254,12 @@ public class VmwareMetaData extends VmMetaData<VmWareSoundCardMeta, VmWareDDAcce } @Override + public List<DiskImage.ImageFormat> getSupportedImageFormats() + { + return VmwareMetaData.SUPPORTED_IMAGE_FORMATS; + } + + @Override public boolean addHddTemplate( File diskImage, String hddMode, String redoDir ) { return addHddTemplate( diskImage.getName(), hddMode, redoDir ); diff --git a/src/main/resources/libvirt/xsl/xml-output-transformation.xsl b/src/main/resources/libvirt/xsl/xml-output-transformation.xsl new file mode 100644 index 0000000..febed54 --- /dev/null +++ b/src/main/resources/libvirt/xsl/xml-output-transformation.xsl @@ -0,0 +1,12 @@ +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:xalan="http://xml.apache.org/xalan"> + <xsl:output method="xml" omit-xml-declaration="yes" + encoding="UTF-8" indent="yes" xalan:indent-amount="2" /> + <xsl:strip-space elements="*" /> + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()" /> + </xsl:copy> + </xsl:template> +</xsl:stylesheet> diff --git a/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java b/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java new file mode 100644 index 0000000..f201a77 --- /dev/null +++ b/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java @@ -0,0 +1,466 @@ +package org.openslx.util.vm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.ControllerUsb; +import org.openslx.libvirt.domain.device.DiskCdrom; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.libvirt.domain.device.DiskStorage; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.xml.LibvirtXmlTestResources; +import org.openslx.util.vm.DiskImage.ImageFormat; +import org.openslx.util.vm.VmMetaData.EtherType; +import org.openslx.util.vm.VmMetaData.EthernetDevType; +import org.openslx.util.vm.VmMetaData.SoundCardType; +import org.openslx.util.vm.VmMetaData.UsbSpeed; + +public class QemuMetaDataTest +{ + private static Domain getPrivateDomainFromQemuMetaData( QemuMetaData qemuMetadata ) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException + { + Field privateDomainField = QemuMetaData.class.getDeclaredField( "vmConfig" ); + privateDomainField.setAccessible( true ); + return Domain.class.cast( privateDomainField.get( qemuMetadata ) ); + } + + @BeforeAll + public static void setUp() + { + // disable logging with log4j + LogManager.getRootLogger().setLevel( Level.OFF ); + } + + @Test + @DisplayName( "Test display name from VM configuration" ) + public void testQemuMetaDataGetDisplayName() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final String displayName = vmConfig.getDisplayName(); + + assertEquals( "archlinux", displayName ); + } + + @Test + @DisplayName( "Test machine snapshot state from VM configuration" ) + public void testQemuMetaDataIsMachineSnapshot() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final boolean isVmSnapshot = vmConfig.isMachineSnapshot(); + + assertEquals( false, isVmSnapshot ); + } + + @Test + @DisplayName( "Test supported image formats from VM configuration" ) + public void testQemuMetaDataGetSupportedImageFormats() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final List<DiskImage.ImageFormat> supportedImageFormats = vmConfig.getSupportedImageFormats(); + + assertNotNull( supportedImageFormats ); + assertEquals( 3, supportedImageFormats.size() ); + assertEquals( true, supportedImageFormats + .containsAll( Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ) ); + } + + @Test + @DisplayName( "Test output of HDDs from VM configuration" ) + public void testQemuMetaDataGetHdds() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final List<VmMetaData.HardDisk> hdds = vmConfig.getHdds(); + + assertNotNull( hdds ); + assertEquals( 1, hdds.size() ); + assertEquals( "/var/lib/libvirt/images/archlinux.qcow2", hdds.get( 0 ).diskImage ); + } + + @Test + @DisplayName( "Test output of unfiltered VM configuration" ) + public void testQemuMetaDataGetDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final String unfilteredXmlConfig = new String( vmConfig.getDefinitionArray(), StandardCharsets.UTF_8 ); + final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); + + assertNotNull( unfilteredXmlConfig ); + + final int lengthUnfilteredXmlConfig = unfilteredXmlConfig.split( System.lineSeparator() ).length; + final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; + + assertEquals( lengthOriginalXmlConfig, lengthUnfilteredXmlConfig ); + } + + @Test + @DisplayName( "Test output of filtered VM configuration" ) + public void testQemuMetaDataGetFilteredDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final int numberOfDeletedElements = 4; + + final String filteredXmlConfig = new String( vmConfig.getFilteredDefinitionArray(), StandardCharsets.UTF_8 ); + final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); + + assertNotNull( filteredXmlConfig ); + + final int lengthFilteredXmlConfig = filteredXmlConfig.split( System.lineSeparator() ).length; + final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; + + assertEquals( lengthOriginalXmlConfig, lengthFilteredXmlConfig + numberOfDeletedElements ); + } + + @ParameterizedTest + @DisplayName( "Test add HDD to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-hdd.xml" } ) + public void testQemuMetaDataAddHdd( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numHddsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); + final int numHddsQemuMetaDataBeforeAdd = vmConfig.hdds.size(); + + vmConfig.addHddTemplate( diskFile, null, null ); + + final int numHddsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); + final int numHddsQemuMetaDataAfterAdd = vmConfig.hdds.size(); + + assertTrue( numHddsLibvirtDomainXmlBeforeAdd == numHddsQemuMetaDataBeforeAdd ); + assertTrue( numHddsLibvirtDomainXmlAfterAdd == numHddsQemuMetaDataAfterAdd ); + assertTrue( numHddsQemuMetaDataBeforeAdd >= 0 ); + assertTrue( numHddsQemuMetaDataAfterAdd > 0 ); + + if ( numHddsQemuMetaDataBeforeAdd >= 1 ) { + // update existing HDD in the Libvirt XML config, but do not add a new HDD + assertEquals( numHddsQemuMetaDataBeforeAdd, numHddsQemuMetaDataAfterAdd ); + } else { + // numHddsQemuMetaDataBeforeAdd == 0 + // add a HDD to the Libvirt XML config, since there was no HDD available + assertEquals( numHddsQemuMetaDataBeforeAdd + 1, numHddsQemuMetaDataAfterAdd ); + } + + DiskStorage addedStorageDevice = vmLibvirtDomainConfig.getDiskStorageDevices().get( 0 ); + assertEquals( diskFile.getAbsolutePath(), addedStorageDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add CDROM to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) + public void testQemuMetaDataAddCdrom( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + vmConfig.addCdrom( 0, diskFile.getAbsolutePath() ); + + final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); + + DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); + assertEquals( diskFile.getAbsolutePath(), addedCdromDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add physical CDROM drive to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) + public void testQemuMetaDataAddPhysicalCdromDrive( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + vmConfig.addCdrom( 0, null ); + + final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); + + DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); + assertEquals( QemuMetaData.CDROM_DEFAULT_PHYSICAL_DRIVE, addedCdromDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add floppy to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-floppy.xml" } ) + public void testQemuMetaDataAddFloppy( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numFloppiesLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); + + vmConfig.addFloppy( 0, diskFile.getAbsolutePath(), true ); + + final int numFloppiesLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); + + assertTrue( numFloppiesLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numFloppiesLibvirtDomainXmlAfterAdd > 0 ); + + DiskFloppy addedFloppyDevice = vmLibvirtDomainConfig.getDiskFloppyDevices().get( 0 ); + assertTrue( addedFloppyDevice.isReadOnly() ); + assertEquals( diskFile.getAbsolutePath(), addedFloppyDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add CPU core count to VM configuration" ) + @ValueSource( ints = { 2, 4, 6, 8 } ) + public void testQemuMetaDataAddCpuCoreCount( int coreCount ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + vmConfig.addCpuCoreCount( coreCount ); + + assertEquals( coreCount, vmLibvirtDomainConfig.getVCpu() ); + } + + @ParameterizedTest + @DisplayName( "Test get sound card from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) + public void testQemuMetaDataGetSoundCardType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + SoundCardType soundCardType = vmConfig.getSoundCard(); + + if ( vmLibvirtDomainConfig.getSoundDevices().isEmpty() ) { + assertEquals( SoundCardType.NONE, soundCardType ); + } else { + assertEquals( SoundCardType.HD_AUDIO, soundCardType ); + } + } + + @ParameterizedTest + @DisplayName( "Test set sound card in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) + public void testQemuMetaDataSetSoundCardType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numSoundDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getSoundDevices().size(); + + vmConfig.setSoundCard( SoundCardType.SOUND_BLASTER ); + + final int numSoundDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getSoundDevices().size(); + + assertTrue( numSoundDevsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numSoundDevsLibvirtDomainXmlAfterAdd > 0 ); + + Sound addedSoundDevice = vmLibvirtDomainConfig.getSoundDevices().get( 0 ); + assertEquals( Sound.Model.SB16, addedSoundDevice.getModel() ); + } + + @ParameterizedTest + @DisplayName( "Test get ethernet device type from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) + public void testQemuMetaDataGetEthernetDevType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + EthernetDevType ethernetDeviceType = vmConfig.getEthernetDevType( 0 ); + + if ( vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { + assertEquals( EthernetDevType.NONE, ethernetDeviceType ); + } else { + assertEquals( EthernetDevType.PARAVIRT, ethernetDeviceType ); + } + } + + @ParameterizedTest + @DisplayName( "Test set ethernet device type in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) + public void testQemuMetaDataSetEthernetDevType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + vmConfig.setEthernetDevType( 0, EthernetDevType.E1000E ); + + if ( !vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { + Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); + assertEquals( Interface.Model.E1000E, addedEthernetDevice.getModel() ); + } + } + + @ParameterizedTest + @DisplayName( "Test get maximal USB speed from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) + public void testQemuMetaDataGetMaxUsbSpeed( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + UsbSpeed maxUsbSpeed = vmConfig.getMaxUsbSpeed(); + + if ( vmLibvirtDomainConfig.getUsbControllerDevices().isEmpty() ) { + assertEquals( UsbSpeed.NONE, maxUsbSpeed ); + } else { + assertEquals( UsbSpeed.USB3_0, maxUsbSpeed ); + } + } + + @ParameterizedTest + @DisplayName( "Test set maximal USB speed in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) + public void testQemuMetaDataSetMaxUsbSpeed( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numUsbControllersLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); + + vmConfig.setMaxUsbSpeed( UsbSpeed.USB2_0 ); + + final int numUsbControllersLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); + + assertTrue( numUsbControllersLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numUsbControllersLibvirtDomainXmlAfterAdd > 0 ); + + ControllerUsb addedUsbControllerDevice = vmLibvirtDomainConfig.getUsbControllerDevices().get( 0 ); + assertEquals( ControllerUsb.Model.ICH9_EHCI1, addedUsbControllerDevice.getModel() ); + } + + static Stream<Arguments> configAndEthernetTypeProvider() + { + return Stream.of( + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.BRIDGED ), + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.HOST_ONLY ), + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.NAT ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.BRIDGED ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.HOST_ONLY ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.NAT ) ); + } + + @ParameterizedTest + @DisplayName( "Test add ethernet device to VM configuration" ) + @MethodSource( "configAndEthernetTypeProvider" ) + public void testQemuMetaDataAddEthernet( String xmlFileName, EtherType ethernetType ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numEthernetDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); + + vmConfig.addEthernet( ethernetType ); + + final int numEthernetDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); + + assertTrue( numEthernetDevsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numEthernetDevsLibvirtDomainXmlAfterAdd > 0 ); + + Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); + switch ( ethernetType ) { + case BRIDGED: + assertEquals( Interface.Type.BRIDGE, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_BRIDGE, addedEthernetDevice.getSource() ); + break; + case HOST_ONLY: + assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_HOST_ONLY, addedEthernetDevice.getSource() ); + break; + case NAT: + assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_NAT, addedEthernetDevice.getSource() ); + break; + } + } +} |