From 706910e440527c22f176bcab0032c37c60357c25 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Fri, 29 Jan 2021 12:25:44 +0100 Subject: Add support for QEMU VMs (based on Libvirt domain XML documents) --- .../java/org/openslx/util/vm/QemuMetaData.java | 796 ++++++++++++++++++--- 1 file changed, 709 insertions(+), 87 deletions(-) (limited to 'src/main/java/org/openslx/util/vm/QemuMetaData.java') 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 +/** + * 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 arguments = new HashMap(); - // the above map's elements will take the place of 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 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 +{ + /** + * 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 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 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 -enable-kvm\nqemu-system-x86_64 -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( "", 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 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( "", 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( "", 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 + * 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 = 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 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 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 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 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 graphicDevices = this.vmConfig.getGraphicDevices(); + ArrayList