package org.openslx.virtualization.configuration; import java.io.File; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.openslx.bwlp.thrift.iface.OperatingSystem; import org.openslx.libvirt.domain.Domain; import org.openslx.libvirt.domain.DomainUtils; 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.libosinfo.LibOsInfo; import org.openslx.libvirt.libosinfo.os.Os; import org.openslx.libvirt.xml.LibvirtXmlDocumentException; import org.openslx.libvirt.xml.LibvirtXmlSerializationException; import org.openslx.libvirt.xml.LibvirtXmlValidationException; import org.openslx.util.LevenshteinDistance; import org.openslx.util.Util; import org.openslx.virtualization.Version; import org.openslx.virtualization.hardware.AbstractConfigurableOption; import org.openslx.virtualization.hardware.ConfigurationGroups; import org.openslx.virtualization.hardware.Ethernet; import org.openslx.virtualization.hardware.SoundCard; import org.openslx.virtualization.hardware.Usb; import org.openslx.virtualization.virtualizer.VirtualizerQemu; /** * Virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. * * @author Manuel Bentele * @version 1.0 */ public class VirtualizationConfigurationQemu extends VirtualizationConfiguration { /** * Name of the network bridge for the LAN. */ public static final String NETWORK_BRIDGE_LAN_DEFAULT = "br0"; /** * Name of the network bridge for the default NAT network. */ public static final String NETWORK_BRIDGE_NAT_DEFAULT = "nat1"; /** * Name of the network for the isolated host network (host only). */ public static final String NETWORK_BRIDGE_HOST_ONLY_DEFAULT = "vsw2"; /** * Default physical CDROM drive of the hypervisor host. */ public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0"; /** * File name extension for QEMU (Libvirt) virtualization configuration files. */ public static final String FILE_NAME_EXTENSION = "xml"; /** * Libvirt XML configuration file to modify configuration of virtual machine for QEMU. */ private Domain vmConfig = null; /** * 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 VirtualizationConfigurationException Libvirt XML configuration cannot be processed. */ public VirtualizationConfigurationQemu( List osList, File file ) throws VirtualizationConfigurationException { super( new VirtualizerQemu(), osList ); try { // read and parse Libvirt domain XML configuration document this.vmConfig = new Domain( file ); } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) { throw new VirtualizationConfigurationException( e.getLocalizedMessage() ); } // parse VM config and initialize fields of QemuMetaData class this.parseVmConfig(); } /** * Creates new virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. * * @param osList list of operating systems. * @param vmContent file content for the QEMU hypervisor. * @param length number of bytes of the file content. * * @throws VirtualizationConfigurationException Libvirt XML configuration cannot be processed. */ public VirtualizationConfigurationQemu( List osList, byte[] vmContent, int length ) throws VirtualizationConfigurationException { super( new VirtualizerQemu(), osList ); try { // read and parse Libvirt domain XML configuration document this.vmConfig = new Domain( new String( vmContent ) ); } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) { throw new VirtualizationConfigurationException( e.getLocalizedMessage() ); } // parse VM config and initialize fields of QemuMetaData class this.parseVmConfig(); } /** * Parses Libvirt domain XML configuration to initialize QEMU metadata. */ private void parseVmConfig() { // 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 ); } // detect the operating system from the optional embedded libosinfo metadata this.setOs( this.vmConfig.getLibOsInfoOsId() ); } /** * 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 ) { String hddChipsetModel = null; DriveBusType hddChipsetBus = VirtualizationConfigurationQemuUtils .convertBusType( storageDiskDevice.getBusType() ); String hddImagePath = storageDiskDevice.getStorageSource(); this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) ); } /** * Detects the operating system by the specified libosinfo operating system identifier. * * @param osId libosinfo operating system identifier. */ private OperatingSystem detectOperatingSystem( String osId ) { OperatingSystem os = null; if ( osId != null && !osId.isEmpty() ) { // lookup operating system identifier in the libosinfo database final Os osLookup = LibOsInfo.lookupOs( osId ); // check if entry in the database was found if ( osLookup != null ) { // operating system entry was found // so determine OpenSLX OS name with the smallest distance to the libosinfo OS name final LevenshteinDistance distance = new LevenshteinDistance( 2, 1, 1 ); int smallestDistance = Integer.MAX_VALUE; // get name of the OS and combine it with the optional available architecture String osLookupOsName = osLookup.getName(); final int osArchSize = VirtualizationConfigurationQemuUtils.getOsArchSize( this.vmConfig.getOsArch() ); if ( osArchSize > 0 ) { // append architecture size in bit if information is available from the specified architecture osLookupOsName += " (" + osArchSize + " Bit)"; } for ( final OperatingSystem osCandidate : this.osList ) { final int currentDistance = distance.calculateDistance( osLookupOsName, osCandidate.getOsName() ); if ( currentDistance < smallestDistance ) { // if the distance is smaller save the current distance and operating system as best candidate smallestDistance = currentDistance; os = osCandidate; } } } } return os; } @Override public void transformEditable() throws VirtualizationConfigurationException { // removes all specified boot order entries this.vmConfig.removeBootOrder(); // removes all source networks of all specified network interfaces this.vmConfig.removeInterfaceDevicesSource(); } @Override public boolean addEmptyHddTemplate() { return this.addHddTemplate( new String(), null, null ); } @Override public boolean addHddTemplate( File diskImage, String hddMode, String redoDir ) { return this.addHddTemplate( diskImage.getAbsolutePath(), hddMode, redoDir ); } @Override public boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir ) { int index = this.vmConfig.getDiskStorageDevices().size() - 1; index = ( index > 0 ) ? index : 0; return this.addHddTemplate( index, 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 * hddMode is set. * @return result state of adding the HDD. */ public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir ) { ArrayList storageDiskDevices = this.vmConfig.getDiskStorageDevices(); DiskStorage storageDiskDevice = VirtualizationConfigurationQemuUtils.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 = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "vd", index ); storageDiskDevice.setTargetDevice( targetDevName ); if ( diskImagePath == null || diskImagePath.isEmpty() ) { storageDiskDevice.removeStorage(); } else { 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 if ( diskImagePath == null || diskImagePath.isEmpty() ) { storageDiskDevice.removeStorage(); } else { storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); } } return true; } @Override public boolean addDefaultNat() { return this.addEthernet( EtherType.NAT ); } @Override public void setOs( String vendorOsId ) { final OperatingSystem os = this.detectOperatingSystem( vendorOsId ); this.setOs( os ); } @Override public boolean addDisplayName( String name ) { this.vmConfig.setName( name ); final boolean statusName = this.vmConfig.getName().equals( name ); return statusName; } @Override public boolean addRam( int mem ) { // convert given memory in MiB to memory in bytes for Libvirt XML Domain API functions final BigInteger memory = DomainUtils.decodeMemory( Integer.toString( mem ), "MiB" ); this.vmConfig.setMemory( memory ); this.vmConfig.setCurrentMemory( memory ); final boolean isMemorySet = this.vmConfig.getMemory().equals( memory ); final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().equals( memory ); return isMemorySet && isCurrentMemorySet; } @Override public void addFloppy( int index, String image, boolean readOnly ) { ArrayList floppyDiskDevices = this.vmConfig.getDiskFloppyDevices(); DiskFloppy floppyDiskDevice = VirtualizationConfigurationQemuUtils.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 = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "fd", index ); floppyDiskDevice.setTargetDevice( targetDevName ); floppyDiskDevice.setReadOnly( readOnly ); if ( image == null || image.isEmpty() ) { floppyDiskDevice.removeStorage(); } else { floppyDiskDevice.setStorage( StorageType.FILE, image ); } } else { // floppy device exists, so update existing floppy device floppyDiskDevice.setReadOnly( readOnly ); if ( image == null || image.isEmpty() ) { floppyDiskDevice.removeStorage(); } else { floppyDiskDevice.setStorage( StorageType.FILE, image ); } } } @Override public boolean addCdrom( String image ) { int index = this.vmConfig.getDiskCdromDevices().size() - 1; index = ( index > 0 ) ? index : 0; return this.addCdrom( index, 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 cdromDiskDevices = this.vmConfig.getDiskCdromDevices(); DiskCdrom cdromDiskDevice = VirtualizationConfigurationQemuUtils.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 = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "sd", index ); cdromDiskDevice.setTargetDevice( targetDevName ); cdromDiskDevice.setReadOnly( true ); if ( image == null ) { cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); } else { if ( image.isEmpty() ) { cdromDiskDevice.removeStorage(); } 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 { if ( image.isEmpty() ) { cdromDiskDevice.removeStorage(); } else { cdromDiskDevice.setStorage( StorageType.FILE, image ); } } } return true; } @Override public boolean addCpuCoreCount( int nrOfCores ) { this.vmConfig.setVCpu( nrOfCores ); boolean isVCpuSet = this.vmConfig.getVCpu() == nrOfCores; return isVCpuSet; } class QemuGfxType extends AbstractConfigurableOption { public QemuGfxType( String id, String displayName ) { super( id, displayName ); } @Override public void apply() { ArrayList graphicDevices = vmConfig.getGraphicDevices(); ArrayList