diff options
author | Manuel Bentele | 2021-06-25 14:23:19 +0200 |
---|---|---|
committer | Manuel Bentele | 2021-06-25 14:23:19 +0200 |
commit | c820bd818c488fb2ab14d51afa4d241b762d2fc6 (patch) | |
tree | 5f0c364c10820219ca418e6436c425074afc35e0 /core/modules/qemu/runvirt-plugin-qemu/src/main/java | |
parent | [debug-report-bwlp] add blkid output (diff) | |
parent | [libvirt] Enforce libvirt UIDs/GIDs to not collide with LDAP UIDs/GIDs (diff) | |
download | mltk-c820bd818c488fb2ab14d51afa4d241b762d2fc6.tar.gz mltk-c820bd818c488fb2ab14d51afa4d241b762d2fc6.tar.xz mltk-c820bd818c488fb2ab14d51afa4d241b762d2fc6.zip |
Merge branch 'feature/qemu-integration'
Diffstat (limited to 'core/modules/qemu/runvirt-plugin-qemu/src/main/java')
27 files changed, 3245 insertions, 0 deletions
diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java new file mode 100644 index 00000000..5ea7b720 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/App.java @@ -0,0 +1,285 @@ +package org.openslx.runvirt.plugin.qemu; + +import java.io.File; +import java.util.Arrays; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs.CmdLnOption; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgsException; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericCpu; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericDiskCdromDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericDiskFloppyDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericDiskStorageDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericFileSystemDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericInterfaceDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericMemory; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericName; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericParallelDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuSerialDevices; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationGenericUuid; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuArchitecture; +import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuGpuPassthroughNvidia; +import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; +import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu.QemuSessionType; +import org.openslx.runvirt.viewer.Viewer; +import org.openslx.runvirt.viewer.ViewerException; +import org.openslx.runvirt.viewer.ViewerLookingGlassClient; +import org.openslx.runvirt.viewer.ViewerVirtManager; +import org.openslx.runvirt.viewer.ViewerVirtViewer; +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachineException; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationManager; + +/** + * Run-virt QEMU plugin (command line tool) to finalize a Libvirt domain XML configuration. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class App +{ + /** + * Stores name of the run-virt QEMU plugin (command line tool). + */ + public static final String APP_NAME = "run-virt QEMU plugin"; + + /** + * Stores description of the run-virt QEMU plugin (command line tool). + */ + public static final String APP_DESC = "Finalize a Libvirt VM (domain XML) configuration and manage the VM."; + + /** + * Stores additional information for the run-virt QEMU plugin (command line tool). + */ + public static final String APP_INFO = "The " + APP_NAME + " is part of the bwLehrpool infrastructure."; + + /** + * Instance of a logger to log messages. + */ + private static final Logger LOGGER = LogManager.getLogger( App.class ); + + /** + * Entry point of the run-virt QEMU plugin (command line tool). + * + * @param args command line arguments passed to the run-virt QEMU plugin (command line tool). + */ + public static void main( String[] args ) + { + // initialize logging + BasicConfigurator.configure(); + + // parse command line arguments + CommandLineArgs cmdLn = new CommandLineArgs(); + + try { + cmdLn.parseCmdLnArgs( args ); + } catch ( CommandLineArgsException e ) { + LOGGER.error( "Parsing of command line arguments failed: " + e.getLocalizedMessage() ); + App.printUsage( cmdLn ); + System.exit( 1 ); + } + + // show help if 'help' command line option is set + if ( cmdLn.isHelpAquired() ) { + App.printUsage( cmdLn ); + System.exit( 0 ); + } + + // print command line arguments for debugging purposes + App.printCmdLnArgs( cmdLn ); + + // create connection to the QEMU hypervisor via Libvirt + LibvirtHypervisor hypervisor = null; + try { + hypervisor = new LibvirtHypervisorQemu( QemuSessionType.LOCAL_USER_SESSION ); + } catch ( LibvirtHypervisorException e ) { + LOGGER.error( "Failed to connect to the QEMU virtualizer (Libvirt daemon): " + e.getLocalizedMessage() ); + System.exit( 2 ); + } + + // read Libvirt XML domain configuration template + final String xmlInputFileName = cmdLn.getVmCfgInpFileName(); + Domain config = null; + try { + final File xmlInputFile = new File( xmlInputFileName ); + config = new Domain( xmlInputFile ); + } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) { + LOGGER.error( "Failed to read VM input configuration file: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 3 ); + } + + // create transformation manager to finalize VM configuration + final TransformationManager<Domain, CommandLineArgs> transformationManager; + transformationManager = new TransformationManager<Domain, CommandLineArgs>( config, cmdLn ); + + // register necessary transformations to finalize configuration template + transformationManager.register( new TransformationGenericName(), true ); + transformationManager.register( new TransformationGenericUuid(), true ); + transformationManager.register( new TransformationGenericCpu(), true ); + transformationManager.register( new TransformationGenericMemory(), true ); + transformationManager.register( new TransformationGenericDiskStorageDevices(), true ); + transformationManager.register( new TransformationGenericDiskCdromDevices(), true ); + transformationManager.register( new TransformationGenericDiskFloppyDevices(), true ); + transformationManager.register( new TransformationGenericInterfaceDevices(), true ); + transformationManager.register( new TransformationGenericParallelDevices(), true ); + transformationManager.register( new TransformationGenericFileSystemDevices(), true ); + + // register QEMU specific transformations to finalize configuration template + if ( hypervisor instanceof LibvirtHypervisorQemu ) { + final LibvirtHypervisorQemu hypervisorQemu = LibvirtHypervisorQemu.class.cast( hypervisor ); + + transformationManager.register( new TransformationSpecificQemuArchitecture( hypervisorQemu ), true ); + transformationManager.register( new TransformationSpecificQemuSerialDevices( hypervisorQemu ), true ); + transformationManager.register( new TransformationSpecificQemuGpuPassthroughNvidia( hypervisorQemu ), false ); + } + + // finalize Libvirt VM configuration template + try { + transformationManager.transform(); + } catch ( TransformationException e ) { + LOGGER.error( "Failed to finalize VM configuration file: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 4 ); + } + + // write finalized configuration to file if output file is specified + final String xmlOutputFileName = cmdLn.getVmCfgOutFileName(); + if ( xmlOutputFileName != null && !xmlOutputFileName.isEmpty() ) { + try { + final File xmlOutputFile = new File( xmlOutputFileName ); + config.toXml( xmlOutputFile ); + } catch ( LibvirtXmlSerializationException e ) { + LOGGER.error( "Failed to write VM output configuration file: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 5 ); + } + } + + // define Libvirt VM from finalized configuration + LibvirtVirtualMachine vm = null; + try { + vm = hypervisor.registerVm( config ); + } catch ( LibvirtHypervisorException e ) { + LOGGER.error( "Failed to define VM from configuration file: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 6 ); + } + + // start defined Libvirt VM + try { + vm.start(); + } catch ( LibvirtVirtualMachineException e ) { + LOGGER.error( "Failed to start defined VM: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 7 ); + } + + // create specific viewer to display Libvirt VM + final Viewer vmViewer; + if ( cmdLn.isNvidiaGpuPassthroughEnabled() ) { + // viewer for GPU passthrough (framebuffer access) is required + vmViewer = new ViewerLookingGlassClient( vm, hypervisor, cmdLn.isDebugEnabled() ); + } else { + // viewer for non-GPU passthrough (no framebuffer access) is required + if ( cmdLn.isDebugEnabled() ) { + // create specific Virtual Machine Manager viewer if debug mode is enabled + vmViewer = new ViewerVirtManager( vm, hypervisor ); + } else { + // create Virtual Viewer if debug mode is disabled + vmViewer = new ViewerVirtViewer( vm, hypervisor ); + } + } + + // display Libvirt VM with the specific viewer on the screen + try { + vmViewer.display(); + } catch ( ViewerException e ) { + LOGGER.error( "Failed to display VM: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 8 ); + } + + // undefine VM after usage + try { + hypervisor.deregisterVm( vm ); + } catch ( LibvirtHypervisorException | LibvirtVirtualMachineException e ) { + LOGGER.error( "Failed to undefine VM: " + e.getLocalizedMessage() ); + hypervisor.close(); + System.exit( 9 ); + } + + // close connection to hypervisor + hypervisor.close(); + + // return with successful exit code + System.exit( 0 ); + } + + /** + * Helper utility to print the run-virt QEMU plugin help text. + * + * @param cmdLn parsed command line arguments. + */ + public static void printUsage( CommandLineArgs cmdLn ) + { + final String newLine = System.lineSeparator(); + final String fullAppDesc = newLine + App.APP_DESC + newLine + newLine; + final String fullAppInfo = newLine + App.APP_INFO; + + cmdLn.printHelp( App.APP_NAME, fullAppDesc, fullAppInfo ); + } + + /** + * Helper utility to log the run-virt QEMU plugin's submitted command line arguments. + * + * @param cmdLn parsed command line arguments. + * + * @implNote This method is intended to be used for debugging purposes. + */ + public static void printCmdLnArgs( CommandLineArgs cmdLn ) + { + // determine length of longest command line option + int longOptionLengthMax = 0; + + for ( CmdLnOption option : CmdLnOption.values() ) { + // store length of current long option + final int longOptionLength = option.getLongOption().length(); + + // if length is longer than every length before, store this length as longest length + if ( longOptionLength > longOptionLengthMax ) { + longOptionLengthMax = longOptionLength; + } + } + + LOGGER.debug( "Command line arguments: --------------" ); + + for ( CmdLnOption option : CmdLnOption.values() ) { + final String paddedLongOption = String.format( "%-" + longOptionLengthMax + "s", option.getLongOption() ); + String[] longOptionArguments; + + // only request and log argument if option has an command line argument + if ( option.getNumArguments() > 0 ) { + longOptionArguments = cmdLn.getArguments( option ); + + if ( longOptionArguments == null ) { + longOptionArguments = new String[] { "no argument specified" }; + } + } else { + longOptionArguments = new String[] { "option has no argument" }; + } + + LOGGER.debug( "\t" + paddedLongOption + ": " + Arrays.toString( longOptionArguments ) ); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java new file mode 100644 index 00000000..589dd197 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgs.java @@ -0,0 +1,511 @@ +package org.openslx.runvirt.plugin.qemu.cmdln; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Command line argument parser for the run-virt QEMU plugin (command line tool). + * + * @author Manuel Bentele + * @version 1.0 + */ +public class CommandLineArgs +{ + /** + * Parser for parsing command line arguments. + */ + private CommandLineParser cmdLnParser = null; + + /** + * Stores specified command line options. + */ + private Options cmdLnOptions = null; + + /** + * Stores the parsed command line arguments. + */ + private CommandLine cmdLn = null; + + /** + * Creates a new command line argument parser for the run-virt QEMU plugin. + * + * @implNote Please call {@link CommandLineArgs#parseCmdLnArgs(String[])} manually after + * obtaining the command line argument parser from this method. + */ + public CommandLineArgs() + { + this.createCmdLnParser(); + this.createCmdLnOptions(); + } + + /** + * Creates a new command line argument parser for the run-virt QEMU plugin and parses the command + * line arguments. + * + * @param args command line arguments submitted to the application. + * + * @throws CommandLineArgsException parsing of command line arguments failed. + */ + public CommandLineArgs( String[] args ) throws CommandLineArgsException + { + this(); + this.parseCmdLnArgs( args ); + } + + /** + * Creates a new parser and empty command line options for parsing command line arguments. + */ + private void createCmdLnParser() + { + this.cmdLnParser = new DefaultParser(); + this.cmdLnOptions = new Options(); + } + + /** + * Creates command line options specified by {@link CmdLnOption}. + */ + private void createCmdLnOptions() + { + for ( CmdLnOption option : CmdLnOption.values() ) { + final Option cmdlnOption; + + final boolean hasArg = ( option.getNumArguments() > 0 ) ? true : false; + cmdlnOption = new Option( option.getShortOption(), option.getLongOption(), hasArg, option.getDescription() ); + cmdlnOption.setValueSeparator( ',' ); + cmdlnOption.setArgs( option.getNumArguments() ); + + this.cmdLnOptions.addOption( cmdlnOption ); + } + } + + /** + * Prints command line help for the current application. + * + * @param appName name of the current application. + * @param header header for the command line help. + * @param footer footer for the command line help. + */ + public void printHelp( String appName, String header, String footer ) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.setLeftPadding( 2 ); + formatter.printHelp( appName, header, this.cmdLnOptions, footer, true ); + } + + /** + * Parses command line arguments from a given argument {@link String}. + * + * @param args command line arguments submitted to the application. + * + * @throws CommandLineArgsException parsing of command line arguments failed. + */ + public void parseCmdLnArgs( String[] args ) throws CommandLineArgsException + { + try { + this.cmdLn = this.cmdLnParser.parse( this.cmdLnOptions, args ); + } catch ( ParseException e ) { + throw new CommandLineArgsException( e.getLocalizedMessage() ); + } + } + + /** + * Returns the parsed argument of the specified command line option. + * + * @param cmdLnOption command line option for that the parsed argument should be returned. + * @return parsed argument of the command line option. + */ + public String getArgument( CmdLnOption cmdLnOption ) + { + return this.cmdLn.getOptionValue( cmdLnOption.getShortOption() ); + } + + /** + * Returns the parsed arguments of the specified command line option. + * + * @param cmdLnOption command line option for that the parsed arguments should be returned. + * @return parsed argument of the command line option. + */ + public String[] getArguments( CmdLnOption cmdLnOption ) + { + return this.cmdLn.getOptionValues( cmdLnOption.getShortOption() ); + } + + /** + * Returns the presence of the command line option {@link CmdLnOption#HELP}. + * + * @return presence of the command line option {@link CmdLnOption#HELP}. + */ + public boolean isHelpAquired() + { + return this.cmdLn.hasOption( CmdLnOption.HELP.getShortOption() ); + } + + /** + * Returns the state of the command line option {@link CmdLnOption#DEBUG}. + * + * @return state of the command line option {@link CmdLnOption#DEBUG}. + */ + public boolean isDebugEnabled() + { + final String debugArg = this.getArgument( CmdLnOption.DEBUG ); + return ( "true".equals( debugArg ) ) ? true : false; + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_CFGINP}. + * + * @return argument of the command line option {@link CmdLnOption#VM_CFGINP}. + */ + public String getVmCfgInpFileName() + { + return this.getArgument( CmdLnOption.VM_CFGINP ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_CFGOUT}. + * + * @return argument of the command line option {@link CmdLnOption#VM_CFGOUT}. + */ + public String getVmCfgOutFileName() + { + return this.getArgument( CmdLnOption.VM_CFGOUT ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_NAME}. + * + * @return argument of the command line option {@link CmdLnOption#VM_NAME}. + */ + public String getVmName() + { + return this.getArgument( CmdLnOption.VM_NAME ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_UUID}. + * + * @return argument of the command line option {@link CmdLnOption#VM_UUID}. + */ + public String getVmUuid() + { + return this.getArgument( CmdLnOption.VM_UUID ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_DSPLNAME}. + * + * @return argument of the command line option {@link CmdLnOption#VM_DSPLNAME}. + */ + public String getVmDisplayName() + { + return this.getArgument( CmdLnOption.VM_DSPLNAME ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_OS}. + * + * @return argument of the command line option {@link CmdLnOption#VM_OS}. + */ + public String getVmOperatingSystem() + { + return this.getArgument( CmdLnOption.VM_OS ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_NCPUS}. + * + * @return argument of the command line option {@link CmdLnOption#VM_NCPUS}. + */ + public int getVmNumCpus() + { + final String numCpuArg = this.getArgument( CmdLnOption.VM_NCPUS ); + int numCpus = 0; + + if ( numCpuArg != null ) { + numCpus = Integer.parseInt( numCpuArg ); + } + + return numCpus; + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_MEM}. + * + * @return argument of the command line option {@link CmdLnOption#VM_MEM}. + */ + public String getVmMemory() + { + return this.getArgument( CmdLnOption.VM_MEM ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_HDD0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_HDD0}. + */ + public String getVmDiskFileNameHDD0() + { + return this.getArgument( CmdLnOption.VM_HDD0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FLOPPY0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FLOPPY0}. + */ + public String getVmDiskFileNameFloppy0() + { + return this.getArgument( CmdLnOption.VM_FLOPPY0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FLOPPY1}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FLOPPY1}. + */ + public String getVmDiskFileNameFloppy1() + { + return this.getArgument( CmdLnOption.VM_FLOPPY1 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_CDROM0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_CDROM0}. + */ + public String getVmDiskFileNameCdrom0() + { + return this.getArgument( CmdLnOption.VM_CDROM0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_CDROM1}. + * + * @return argument of the command line option {@link CmdLnOption#VM_CDROM1}. + */ + public String getVmDiskFileNameCdrom1() + { + return this.getArgument( CmdLnOption.VM_CDROM1 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_PARALLEL0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_SERIAL0}. + */ + public String getVmDeviceParallel0() + { + return this.getArgument( CmdLnOption.VM_PARALLEL0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_SERIAL0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_SERIAL0}. + */ + public String getVmDeviceSerial0() + { + return this.getArgument( CmdLnOption.VM_SERIAL0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_MAC0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_MAC0}. + */ + public String getVmMacAddress0() + { + return this.getArgument( CmdLnOption.VM_MAC0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FSSRC0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FSSRC0}. + */ + public String getVmFsSrc0() + { + return this.getArgument( CmdLnOption.VM_FSSRC0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FSTGT0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FSTGT0}. + */ + public String getVmFsTgt0() + { + return this.getArgument( CmdLnOption.VM_FSTGT0 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FSSRC1}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FSSRC1}. + */ + public String getVmFsSrc1() + { + return this.getArgument( CmdLnOption.VM_FSSRC1 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_FSTGT1}. + * + * @return argument of the command line option {@link CmdLnOption#VM_FSTGT1}. + */ + public String getVmFsTgt1() + { + return this.getArgument( CmdLnOption.VM_FSTGT1 ); + } + + /** + * Returns the argument of the command line option {@link CmdLnOption#VM_NVGPUIDS0}. + * + * @return argument of the command line option {@link CmdLnOption#VM_NVGPUIDS0}. + */ + public List<String> getVmNvGpuIds0() + { + final String[] nvidiaPciIdsRaw = this.getArguments( CmdLnOption.VM_NVGPUIDS0 ); + final ArrayList<String> nvidiaPciIds; + + if ( nvidiaPciIdsRaw == null || nvidiaPciIdsRaw.length <= 0 ) { + nvidiaPciIds = new ArrayList<String>(); + } else { + nvidiaPciIds = new ArrayList<String>( Arrays.asList( nvidiaPciIdsRaw ) ); + } + + return nvidiaPciIds; + } + + /** + * Returns the state whether a passthrough of a NVIDIA GPU is required. + * + * @return state whether a passthrough of a NVIDIA GPU is required. + */ + public boolean isNvidiaGpuPassthroughEnabled() + { + return this.getVmNvGpuIds0().size() > 0; + } + + /** + * Command line options for the run-virt QEMU plugin (command line tool). + * + * @author Manuel Bentele + * @version 1.0 + */ + public enum CmdLnOption + { + // @formatter:off + HELP ( 'h', "help", 0, "" ), + DEBUG ( 'b', "debug", 1, "Enable or disable debug mode" ), + VM_CFGINP ( 'i', "vmcfginp", 1, "File name of an existing and filtered Libvirt domain XML configuration file" ), + VM_CFGOUT ( 'o', "vmcfgout", 1, "File name to output a finalized Libvirt domain XML configuration file" ), + VM_NAME ( 'n', "vmname", 1, "Name for the virtual machine" ), + VM_UUID ( 'u', "vmuuid", 1, "UUID for the virtual machine" ), + VM_DSPLNAME ( 'd', "vmdsplname", 1, "Display name for the virtual machine" ), + VM_OS ( 's', "vmos", 1, "Operating system running in the virtual machine" ), + VM_NCPUS ( 'c', "vmncpus", 1, "Number of virtual CPUs for the virtual machine" ), + VM_MEM ( 'm', "vmmem", 1, "Amount of memory for the virtual machine" ), + VM_HDD0 ( 'r', "vmhdd0", 1, "Disk image for the first HDD device" ), + VM_FLOPPY0 ( 'f', "vmfloppy0", 1, "Disk image for the first floppy drive" ), + VM_FLOPPY1 ( 'g', "vmfloppy1", 1, "Disk image for the second floppy drive" ), + VM_CDROM0 ( 'k', "vmcdrom0", 1, "Disk image for the first CDROM drive" ), + VM_CDROM1 ( 'l', "vmcdrom1", 1, "Disk image for the second CDROM drive" ), + VM_PARALLEL0( 'p', "vmparallel0", 1, "Device for the first parallel port interface" ), + VM_SERIAL0 ( 'q', "vmserial0", 1, "Device for the first serial port interface" ), + VM_MAC0 ( 'a', "vmmac0", 1, "MAC address for the first network interface" ), + VM_FSSRC0 ( 't', "vmfssrc0", 1, "Source directory for first file system passthrough (shared folder)" ), + VM_FSTGT0 ( 'e', "vmfstgt0", 1, "Target directory for first file system passthrough (shared folder)" ), + VM_FSSRC1 ( 'v', "vmfssrc1", 1, "Source directory for second file system passthrough (shared folder)" ), + VM_FSTGT1 ( 'w', "vmfstgt1", 1, "Target directory for second file system passthrough (shared folder)" ), + VM_NVGPUIDS0( 'y', "vmnvgpuids0", 2, "PCI device description and address for passthrough of the first Nvidia GPU. " + + "The argument follow the pattern: " + + "\"<VENDOR ID>:<PRODUCT ID>,<PCI DOMAIN>:<PCI DEVICE>:<PCI DEVICE>.<PCI FUNCTION>\"" ); + // @formatter:on + + /** + * Stores the {@link Character} of the short command line option. + */ + private final char shortOption; + + /** + * Stores the {@link String} of the long command line option. + */ + private final String longOption; + + /** + * Stores the number of arguments for the command line option. + */ + private final int numArguments; + + /** + * Stores the textual description of the command line option. + */ + private final String description; + + /** + * Creates a new command line option for the run-virt QEMU plugin (command line tool). + * + * @param shortOption {@link Character} for the short command line option. + * @param longOption {@link String} for the long command line option. + * @param numArguments number of arguments for the command line option. + * @param description textual description of the command line option. + */ + CmdLnOption( char shortOption, String longOption, int numArguments, String description ) + { + this.shortOption = shortOption; + this.longOption = longOption; + this.numArguments = numArguments; + this.description = description; + } + + /** + * Returns the {@link Character} of the short command line option. + * + * @return {@link Character} of the short command line option. + */ + public String getShortOption() + { + return Character.toString( this.shortOption ); + } + + /** + * Returns the {@link String} of the long command line option. + * + * @return {@link String} of the long command line option. + */ + public String getLongOption() + { + return this.longOption; + } + + /** + * Returns the number of arguments for the command line option. + * + * @return number of arguments for the command line option. + */ + public int getNumArguments() + { + return this.numArguments; + } + + /** + * Returns the textual description of the command line option. + * + * @return textual description of the command line option. + */ + public String getDescription() + { + return this.description; + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgsException.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgsException.java new file mode 100644 index 00000000..a327b813 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/cmdln/CommandLineArgsException.java @@ -0,0 +1,25 @@ +package org.openslx.runvirt.plugin.qemu.cmdln; + +/** + * An exception during the parsing of command line arguments. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class CommandLineArgsException extends Exception +{ + /** + * Version number for serialization. + */ + private static final long serialVersionUID = 8371924151602194406L; + + /** + * Creates an command line argument parsing exception including an error message. + * + * @param errorMsg message to describe a specific parsing error. + */ + public CommandLineArgsException( String errorMsg ) + { + super( errorMsg ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericCpu.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericCpu.java new file mode 100644 index 00000000..9d9237c7 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericCpu.java @@ -0,0 +1,57 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.Domain.CpuCheck; +import org.openslx.libvirt.domain.Domain.CpuMode; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic CPU transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericCpu extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "CPU [number of cores, mode, ...]"; + + /** + * Creates a new generic CPU transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericCpu() + { + super( TransformationGenericCpu.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } else if ( args.getVmNumCpus() < 1 ) { + throw new TransformationException( "Invalid number of CPUs specified! Expected a number n > 0!" ); + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + config.setVCpu( args.getVmNumCpus() ); + config.setCpuMode( CpuMode.HOST_PASSTHROUGH ); + config.setCpuCheck( CpuCheck.PARTIAL ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskCdromDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskCdromDevices.java new file mode 100644 index 00000000..643c40ed --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskCdromDevices.java @@ -0,0 +1,110 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +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.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic CDROM drive transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericDiskCdromDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Disk CDROM devices"; + + /** + * Creates a new generic CDROM drive transformation for Libvirt/QEMU virtualization + * configurations. + */ + public TransformationGenericDiskCdromDevices() + { + super( TransformationGenericDiskCdromDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a CDROM drive in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param fileName name of the image file for the CDROM drive. + * @param index number of the CDROM drive in the virtualization configuration that is selected. + * @throws TransformationException transformation has failed. + */ + private void transformDiskCdromDevice( Domain config, String fileName, int index ) throws TransformationException + { + final ArrayList<DiskCdrom> devices = config.getDiskCdromDevices(); + final DiskCdrom disk = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( disk == null ) { + if ( fileName != null ) { + // CDROM drive does not exist, so create new CDROM drive + final DiskCdrom newDisk = config.addDiskCdromDevice(); + newDisk.setBusType( BusType.SATA ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "sd", index ); + newDisk.setTargetDevice( targetDevName ); + + if ( fileName.isEmpty() ) { + // remove storage source if empty string is specified to emulate an empty CDROM drive + newDisk.removeStorage(); + } else { + // set disk image file as storage source of the disk CDROM drive + newDisk.setStorage( StorageType.FILE, fileName ); + } + } + } else { + // CDROM drive exists, so update existing CDROM drive + if ( fileName == null ) { + // remove disk storage device if disk image file name is not set + disk.remove(); + } else if ( fileName.isEmpty() ) { + // remove storage source if empty string is specified to emulate an empty CDROM drive + disk.removeStorage(); + } else { + // set disk image file as storage source of the disk CDROM drive + disk.setStorage( StorageType.FILE, fileName ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter CDROM drives + this.transformDiskCdromDevice( config, args.getVmDiskFileNameCdrom0(), 0 ); + this.transformDiskCdromDevice( config, args.getVmDiskFileNameCdrom1(), 1 ); + + // remove all additional disk CDROM devices + final ArrayList<DiskCdrom> devices = config.getDiskCdromDevices(); + for ( int i = 2; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskFloppyDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskFloppyDevices.java new file mode 100644 index 00000000..fe3d3c34 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskFloppyDevices.java @@ -0,0 +1,104 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Disk.StorageType; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic floppy drive transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericDiskFloppyDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Disk floppy devices"; + + /** + * Creates a new floppy drive transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericDiskFloppyDevices() + { + super( TransformationGenericDiskFloppyDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a floppy drive in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param fileName name of the image file for the floppy drive. + * @param index number of the floppy drive in the virtualization configuration that is selected. + * @throws TransformationException transformation has failed. + */ + private void transformDiskFloppyDevice( Domain config, String fileName, int index ) throws TransformationException + { + final ArrayList<DiskFloppy> devices = config.getDiskFloppyDevices(); + final DiskFloppy disk = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( disk == null ) { + if ( fileName != null ) { + // floppy device does not exist, so create new floppy device + final DiskFloppy newDisk = config.addDiskFloppyDevice(); + newDisk.setBusType( BusType.FDC ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "fd", index ); + newDisk.setTargetDevice( targetDevName ); + + if ( fileName.isEmpty() ) { + newDisk.removeStorage(); + } else { + newDisk.setStorage( StorageType.FILE, fileName ); + } + } + } else { + // floppy device exists, so update existing floppy device + if ( fileName == null ) { + disk.remove(); + } else if ( fileName.isEmpty() ) { + disk.removeStorage(); + } else { + disk.setStorage( StorageType.FILE, fileName ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter floppy drives + this.transformDiskFloppyDevice( config, args.getVmDiskFileNameFloppy0(), 0 ); + this.transformDiskFloppyDevice( config, args.getVmDiskFileNameFloppy1(), 1 ); + + // remove all additional disk storage devices + final ArrayList<DiskFloppy> devices = config.getDiskFloppyDevices(); + for ( int i = 2; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskStorageDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskStorageDevices.java new file mode 100644 index 00000000..9bd1edbb --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericDiskStorageDevices.java @@ -0,0 +1,102 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Disk.StorageType; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.libvirt.domain.device.DiskStorage; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic storage device (HDD, SSD, ...) transformation for Libvirt/QEMU virtualization + * configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericDiskStorageDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Disk storage devices [HDD, SSD, ...]"; + + /** + * Creates a new storage device (HDD, SSD, ...) transformation for Libvirt/QEMU virtualization + * configurations. + */ + public TransformationGenericDiskStorageDevices() + { + super( TransformationGenericDiskStorageDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a storage device in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param fileName name of the image file for the storage device. + * @param index number of the storage device in the virtualization configuration that is + * selected. + * @throws TransformationException transformation has failed. + */ + private void transformDiskStorageDevice( Domain config, String fileName, int index ) throws TransformationException + { + final ArrayList<DiskStorage> devices = config.getDiskStorageDevices(); + final DiskStorage disk = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( disk == null ) { + if ( fileName != null && !fileName.isEmpty() ) { + // storage device does not exist, so create new storage device + final DiskFloppy newDisk = config.addDiskFloppyDevice(); + newDisk.setBusType( BusType.VIRTIO ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "vd", index ); + newDisk.setTargetDevice( targetDevName ); + newDisk.setStorage( StorageType.FILE, fileName ); + } + } else { + // storage device exists, so update existing storage device + if ( fileName == null || fileName.isEmpty() ) { + // remove disk storage device if disk image file name is not set + disk.remove(); + } else { + // set image file of disk storage if disk storage device is available + disk.setStorage( StorageType.FILE, fileName ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter storage device + this.transformDiskStorageDevice( config, args.getVmDiskFileNameHDD0(), 0 ); + + // remove all additional disk storage devices + final ArrayList<DiskStorage> devices = config.getDiskStorageDevices(); + for ( int i = 1; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericFileSystemDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericFileSystemDevices.java new file mode 100644 index 00000000..a4f77b0d --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericFileSystemDevices.java @@ -0,0 +1,107 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.FileSystem; +import org.openslx.libvirt.domain.device.FileSystem.AccessMode; +import org.openslx.libvirt.domain.device.FileSystem.Type; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic file system device (shared folder) transformation for Libvirt/QEMU virtualization + * configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericFileSystemDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "File system devices"; + + /** + * Creates a new file system device (shared folder) transformation for Libvirt/QEMU + * virtualization configurations. + */ + public TransformationGenericFileSystemDevices() + { + super( TransformationGenericFileSystemDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a storage device in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param source path of the file system source on a host system. + * @param target path of the file system destination in a virtualization guest. + * @param index number of the file system device in the virtualization configuration that is + * selected. + * @throws TransformationException transformation has failed. + */ + private void transformFileSystemDevice( Domain config, String source, String target, int index ) + throws TransformationException + { + final ArrayList<FileSystem> devices = config.getFileSystemDevices(); + final FileSystem fileSystem = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( fileSystem == null ) { + // check if file system device source directory is specified + if ( source != null && !source.isEmpty() && target != null && !target.isEmpty() ) { + // file system device does not exist, so create new file system device + final FileSystem newFileSystem = config.addFileSystemDevice(); + newFileSystem.setType( Type.MOUNT ); + newFileSystem.setAccessMode( AccessMode.MAPPED ); + newFileSystem.setSource( source ); + newFileSystem.setTarget( target ); + } + } else { + if ( source == null || source.isEmpty() || target == null || target.isEmpty() ) { + // remove file system device since device source or target is not specified + fileSystem.remove(); + } else { + // change type, access mode, source and target of existing file system device + fileSystem.setType( Type.MOUNT ); + fileSystem.setAccessMode( AccessMode.MAPPED ); + fileSystem.setSource( source ); + fileSystem.setTarget( target ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter file system devices + this.transformFileSystemDevice( config, args.getVmFsSrc0(), args.getVmFsTgt0(), 0 ); + this.transformFileSystemDevice( config, args.getVmFsSrc1(), args.getVmFsTgt1(), 1 ); + + // remove all additional file system devices + final ArrayList<FileSystem> devices = config.getFileSystemDevices(); + for ( int i = 2; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericInterfaceDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericInterfaceDevices.java new file mode 100644 index 00000000..6cf12ce2 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericInterfaceDevices.java @@ -0,0 +1,101 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.InterfaceBridge; +import org.openslx.libvirt.domain.device.Interface.Model; +import org.openslx.libvirt.domain.device.Interface.Type; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemu; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic network interface transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericInterfaceDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Network interface devices"; + + /** + * Creates a new network interface transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericInterfaceDevices() + { + super( TransformationGenericInterfaceDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a network interface in a virtualization configuration selected by its + * {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param macAddress MAC address for the network interface. + * @param index number of the network interface in the virtualization configuration that is + * selected. + * @throws TransformationException transformation has failed. + */ + private void transformInterfaceDevice( Domain config, String macAddress, int index ) throws TransformationException + { + final ArrayList<Interface> devices = config.getInterfaceDevices(); + final Interface device = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( device == null ) { + if ( macAddress != null && !macAddress.isEmpty() ) { + // create network interface if it does not exists + final InterfaceBridge newDevice = config.addInterfaceBridgeDevice(); + newDevice.setType( Type.BRIDGE ); + newDevice.setModel( Model.VIRTIO ); + newDevice.setMacAddress( macAddress ); + newDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_NAT_DEFAULT ); + } + } else { + if ( macAddress == null || macAddress.isEmpty() ) { + // remove network interface device if MAC address is not set + device.remove(); + } else { + // set MAC address of network interface device if network interface device is available + device.setMacAddress( macAddress ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter network interface + this.transformInterfaceDevice( config, args.getVmMacAddress0(), 0 ); + + // remove all additional disk storage devices + final ArrayList<Interface> devices = config.getInterfaceDevices(); + for ( int i = 1; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericMemory.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericMemory.java new file mode 100644 index 00000000..fce373f7 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericMemory.java @@ -0,0 +1,59 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.math.BigInteger; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.DomainUtils; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic memory transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericMemory extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Memory [normal, current (balloning)]"; + + /** + * Creates a new memory transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericMemory() + { + super( TransformationGenericMemory.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } else if ( args.getVmMemory() == null || args.getVmMemory().isEmpty() ) { + throw new TransformationException( "Amount of memory in MiB is not specified!" ); + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + BigInteger memory = DomainUtils.decodeMemory( args.getVmMemory(), "MiB" ); + + config.setMemory( memory ); + config.setCurrentMemory( memory ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericName.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericName.java new file mode 100644 index 00000000..b96793d5 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericName.java @@ -0,0 +1,57 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic name transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericName extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Name [(display) name]"; + + /** + * Creates a new name transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericName() + { + super( TransformationGenericName.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } else if ( args.getVmName() == null || args.getVmName().isEmpty() ) { + throw new TransformationException( "Name is not specified!" ); + } else if ( args.getVmDisplayName() == null || args.getVmDisplayName().isEmpty() ) { + throw new TransformationException( "Display name is not specified!" ); + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter names in the configuration + config.setName( args.getVmName() ); + config.setTitle( args.getVmDisplayName() ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericParallelDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericParallelDevices.java new file mode 100644 index 00000000..08d43ef0 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericParallelDevices.java @@ -0,0 +1,97 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.Parallel; +import org.openslx.libvirt.domain.device.Parallel.Type; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic parallel device transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericParallelDevices extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Parallel devices"; + + /** + * Creates a new parallel device transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericParallelDevices() + { + super( TransformationGenericParallelDevices.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Transforms a parallel device in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param fileName path to the parallel device file on the host system. + * @param index number of the parallel device in the virtualization configuration that is + * selected. + * @throws TransformationException transformation has failed. + */ + private void transformParallelDevice( Domain config, String fileName, int index ) throws TransformationException + { + final ArrayList<Parallel> devices = config.getParallelDevices(); + final Parallel device = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( device == null ) { + // check if device file name is specified + if ( fileName != null ) { + // parallel port device does not exist, so create new parallel port device + final Parallel newDevice = config.addParallelDevice(); + newDevice.setType( Type.DEV ); + newDevice.setSource( fileName ); + } + } else { + if ( fileName == null || fileName.isEmpty() ) { + // remove device since device file is not specified + device.remove(); + } else { + // change type and source of existing parallel port device + device.setType( Type.DEV ); + device.setSource( fileName ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter parallel device + this.transformParallelDevice( config, args.getVmDeviceParallel0(), 0 ); + + // remove all additional parallel devices + final ArrayList<Parallel> devices = config.getParallelDevices(); + for ( int i = 1; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericUuid.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericUuid.java new file mode 100644 index 00000000..43fb6412 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationGenericUuid.java @@ -0,0 +1,53 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationGeneric; + +/** + * Generic UUID transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationGenericUuid extends TransformationGeneric<Domain, CommandLineArgs> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "UUID"; + + /** + * Creates a new UUID transformation for Libvirt/QEMU virtualization configurations. + */ + public TransformationGenericUuid() + { + super( TransformationGenericUuid.NAME ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } else if ( args.getVmUuid() == null || args.getVmUuid().isEmpty() ) { + throw new TransformationException( "UUID is not specified!" ); + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + config.setUuid( args.getVmUuid() ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuArchitecture.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuArchitecture.java new file mode 100644 index 00000000..a51c829d --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuArchitecture.java @@ -0,0 +1,261 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; +import java.util.List; + +import org.openslx.libvirt.capabilities.Capabilities; +import org.openslx.libvirt.capabilities.guest.Guest; +import org.openslx.libvirt.capabilities.guest.Machine; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.Domain.OsType; +import org.openslx.libvirt.domain.Domain.Type; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationSpecific; + +/** + * Specific architecture transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationSpecificQemuArchitecture + extends TransformationSpecific<Domain, CommandLineArgs, LibvirtHypervisorQemu> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "QEMU Architecture [CPU architecture, machine type, ...]"; + + /** + * Creates a new architecture transformation for Libvirt/QEMU virtualization configurations. + * + * @param hypervisor Libvirt/QEMU hypervisor. + */ + public TransformationSpecificQemuArchitecture( LibvirtHypervisorQemu virtualizer ) + { + super( TransformationSpecificQemuArchitecture.NAME, virtualizer ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null ) { + throw new TransformationException( "Virtualization configuration is missing!" ); + } + } + + /** + * Queries and returns the capabilities of the Libvirt/QEMU hypervisor. + * + * @return capabilities of the Libvirt/QEMU hypervisor. + * @throws TransformationException failed to query and return the capabilities of the + * Libvirt/QEMU hypervisor. + */ + protected Capabilities getCapabilities() throws TransformationException + { + final Capabilities capabilities; + + try { + capabilities = this.getVirtualizer().getCapabilites(); + } catch ( LibvirtHypervisorException e ) { + final String errorMsg = new String( + "Failed to retrieve host capabilities from QEMU virtualizer: " + e.getLocalizedMessage() ); + throw new TransformationException( errorMsg ); + } + + return capabilities; + } + + /** + * Returns a guest capability of the hypervisor's host system based on a given target + * architecture name. + * + * @param architectureName target architecture of the guest that is returned + * @return guest capability of the hypervisor's host system with target architecture name. + * @throws TransformationException failed to return guest capability of the hypervisor's host. + */ + private Guest getTargetGuestFromArchName( String architectureName ) throws TransformationException + { + final List<Guest> guests = this.getCapabilities().getGuests(); + Guest targetGuest = null; + + if ( architectureName == null ) { + return targetGuest; + } + + for ( Guest guest : guests ) { + final String guestArchitectureName = guest.getArchName(); + if ( architectureName.equals( guestArchitectureName ) ) { + targetGuest = guest; + break; + } + } + + return targetGuest; + } + + /** + * Returns the target machine description of a host system's guest capability based on a given + * target machine name. + * + * @param guest guest capability of a host system. + * @param machineName name of the machine description. + * @return target machine description of a host system's guest capability. + * @throws TransformationException failed to return the target machine description of a host + * system's guest capabilities. + */ + private Machine getTargetMachineFromGuest( Guest guest, String machineName ) throws TransformationException + { + final List<Machine> machines = guest.getArchMachines(); + Machine targetMachine = null; + + if ( machineName == null ) { + return targetMachine; + } + + for ( Machine machine : machines ) { + if ( machineName.equals( machine.getName() ) ) { + targetMachine = machine; + break; + } + } + + return targetMachine; + } + + /** + * Returns the canonical names of a target machine description of a host system's guest + * capability. + * + * @param guest guest capability of a host system. + * @return canonical names of a target machine description of a host system's guest capability. + * @throws TransformationException failed to return the canonical names of a target machine + * description of a host system's guest capability + */ + private List<String> getCanonicalNamesFromTargetMachines( Guest guest ) throws TransformationException + { + final List<Machine> machines = guest.getArchMachines(); + final List<String> canonicalNames = new ArrayList<String>(); + + for ( Machine machine : machines ) { + final String canonicalName = machine.getCanonicalMachine(); + if ( canonicalName != null ) { + canonicalNames.add( canonicalName ); + } + } + + return canonicalNames; + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // get source architecture, machine- and OS type + final String sourceArchitectureName = config.getOsArch(); + final String sourceMachine = config.getOsMachine(); + final OsType sourceOsType = config.getOsType(); + final Type sourceDomainType = config.getType(); + + // check if source architecture is supported by one of the hypervisor's guests + Guest targetGuest = null; + if ( sourceArchitectureName == null ) { + final String errorMsg = new String( "Source architecture is not specified!" ); + throw new TransformationException( errorMsg ); + } else { + targetGuest = this.getTargetGuestFromArchName( sourceArchitectureName ); + if ( targetGuest == null ) { + final String errorMsg = new String( "Source architecture is not supported by the virtualizer!" ); + throw new TransformationException( errorMsg ); + } + } + + // check if source machine is supported by the hypervisor + Machine targetMachine = null; + if ( sourceMachine == null ) { + final String errorMsg = new String( "Source machine type is not specified!" ); + throw new TransformationException( errorMsg ); + } else { + // get all possible machine type for supported source architecture + targetMachine = this.getTargetMachineFromGuest( targetGuest, sourceMachine ); + + if ( targetMachine == null ) { + // source machine is not directly supported by the hypervisor + // check if up- or downgraded version of the chipset is supported by the hypervisor + List<String> targetMachineCanonicalNames = this.getCanonicalNamesFromTargetMachines( targetGuest ); + + // retrieve overwrite chipset name from canonical machine names + String sourceMachineOverwrite = null; + for ( String targetMachineCanonicalName : targetMachineCanonicalNames ) { + if ( sourceMachine.contains( targetMachineCanonicalName ) ) { + sourceMachineOverwrite = targetMachineCanonicalName; + break; + } + } + + // if overwrite available, patch the machine type + if ( sourceMachineOverwrite != null ) { + config.setOsMachine( sourceMachineOverwrite ); + } else { + final String errorMsg = new String( "Source machine type is not supported by the virtualizer!" ); + throw new TransformationException( errorMsg ); + } + } + } + + // check if source OS type is supported by the hypervisor's architecture + if ( sourceOsType == null ) { + final String errorMsg = new String( "OS type is not specified!" ); + throw new TransformationException( errorMsg ); + } else { + if ( !sourceOsType.toString().equals( targetGuest.getOsType().toString() ) ) { + final String errorMsg = new String( "OS type is not supported by the virtualizer!" ); + throw new TransformationException( errorMsg ); + } + } + + // check if source domain type is supported by the hypervisor's architecture + Type targetDomainType = null; + if ( sourceDomainType == null ) { + final String errorMsg = new String( "Source domain type is not specified!" ); + throw new TransformationException( errorMsg ); + } else { + final List<org.openslx.libvirt.capabilities.guest.Domain> targetDomains = targetGuest.getArchDomains(); + + // retrieve supported domain type + for ( org.openslx.libvirt.capabilities.guest.Domain domain : targetDomains ) { + final Type domainType = domain.getType(); + if ( domainType == sourceDomainType ) { + targetDomainType = domainType; + break; + } + } + + // check supported domain type + if ( targetDomainType == null ) { + final String errorMsg = new String( "Source domain type is not supported by the virtualizer!" ); + throw new TransformationException( errorMsg ); + } + } + + // patch path of QEMU emulator binary + final String archEmulator = targetGuest.getArchEmulator(); + if ( archEmulator == null ) { + final String errorMsg = new String( "Emulation of source architecture is not supported by the virtualizer!" ); + throw new TransformationException( errorMsg ); + } else { + config.setDevicesEmulator( targetGuest.getArchEmulator() ); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java new file mode 100644 index 00000000..a22bf027 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuGpuPassthroughNvidia.java @@ -0,0 +1,232 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import org.openslx.libvirt.capabilities.Capabilities; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.HostdevPci; +import org.openslx.libvirt.domain.device.HostdevPciDeviceAddress; +import org.openslx.libvirt.domain.device.HostdevPciDeviceDescription; +import org.openslx.libvirt.domain.device.Shmem; +import org.openslx.libvirt.domain.device.Video; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationSpecific; + +/** + * Specific Nvidia GPU passthrough transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationSpecificQemuGpuPassthroughNvidia + extends TransformationSpecific<Domain, CommandLineArgs, LibvirtHypervisorQemu> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "QEMU GPU passthrough [Nvidia]"; + + /** + * Vendor identifier of PCI devices from Nvidia. + */ + private static final int NVIDIA_PCI_VENDOR_ID = 0x10de; + + /** + * Vendor identifier of the Hyper-V enlightenment for hypervisor shadowing. + */ + public static final String HYPERV_VENDOR_ID = "62776c706277"; + + /** + * Maximum width in pixel of the GPU passthrough rendered display. + */ + private static final long MAX_DISPLAY_WIDTH = 2560; + + /** + * Maximum height in pixel of the GPU passthrough rendered display. + */ + private static final long MAX_DISPLAY_HEIGHT = 1440; + + /** + * Reserved memory for framebuffer meta data of the Looking Glass shared memory device in MiB. + */ + private static final long RESERVED_MEMORY_FRAMEBUFFER = 10; + + /** + * Creates a new Nvidia GPU passthrough transformation for Libvirt/QEMU virtualization + * configurations. + * + * @param hypervisor Libvirt/QEMU hypervisor. + */ + public TransformationSpecificQemuGpuPassthroughNvidia( LibvirtHypervisorQemu hypervisor ) + { + super( TransformationSpecificQemuGpuPassthroughNvidia.NAME, hypervisor ); + } + + /** + * Validates a PCI device description and address of a PCI device from a Nvidia GPU and parses + * the validated PCI device addresses. + * + * @param pciIds textual PCI device description and address to be validated. + * + * @return list of validated and parsed PCI device addresses for a NVIDIA GPU passthrough. + * + * @throws TransformationException validation of PCI device description and address failed. + */ + private static List<HostdevPciDeviceAddress> validateParseNvidiaPciIds( List<String> pciIds ) + throws TransformationException + { + final List<HostdevPciDeviceAddress> parsedPciAddresses = new ArrayList<HostdevPciDeviceAddress>(); + + if ( pciIds != null && pciIds.size() > 0 ) { + // abort if arguments do not follow the pattern: + // + // [0]: <VENDOR ID 0>:<DEVICE ID 0> + // [1]: <PCI DOMAIN 0>:<PCI BUS 0>:<PCI DEVICE 0>.<PCI FUNCTION 0> + // [2]: <VENDOR ID 1>:<DEVICE ID 1> + // [3]: <PCI DOMAIN 1>:<PCI BUS 1>:<PCI DEVICE 1>.<PCI FUNCTION 1> + // ... + // + if ( pciIds.size() % 2 != 0 ) { + throw new TransformationException( + "Arguments of PCI IDs are not follow the pattern for a GPU passthrough!" ); + } + + // parse PCI device description and PCI device address + for ( int i = 0; i < pciIds.size(); i += 2 ) { + // parse vendor and device ID + HostdevPciDeviceDescription deviceDescription = null; + try { + deviceDescription = HostdevPciDeviceDescription.valueOf( pciIds.get( i ) ); + } catch ( IllegalArgumentException e ) { + throw new TransformationException( "Invalid vendor or device ID of the PCI device description!" ); + } + + // validate vendor ID + final int vendorId = deviceDescription.getVendorId(); + if ( TransformationSpecificQemuGpuPassthroughNvidia.NVIDIA_PCI_VENDOR_ID != vendorId ) { + final String errorMsg = "Vendor ID '" + vendorId + "' of the PCI device is not from Nvidia!"; + throw new TransformationException( errorMsg ); + } + + // parse PCI domain, PCI bus, PCI device and PCI function + final HostdevPciDeviceAddress parsedPciAddress = HostdevPciDeviceAddress.valueOf( pciIds.get( i + 1 ) ); + if ( parsedPciAddress != null ) { + parsedPciAddresses.add( parsedPciAddress ); + } + } + } + + return parsedPciAddresses; + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + + TransformationSpecificQemuGpuPassthroughNvidia.validateParseNvidiaPciIds( args.getVmNvGpuIds0() ); + } + + /** + * Queries and returns the capabilities of the Libvirt/QEMU hypervisor. + * + * @return capabilities of the Libvirt/QEMU hypervisor. + * @throws TransformationException failed to query and return the capabilities of the + * Libvirt/QEMU hypervisor. + */ + protected Capabilities getCapabilities() throws TransformationException + { + Capabilities capabilities = null; + + try { + capabilities = this.getVirtualizer().getCapabilites(); + } catch ( LibvirtHypervisorException e ) { + final String errorMsg = new String( + "Failed to retrieve host capabilities from QEMU virtualizer: " + e.getLocalizedMessage() ); + throw new TransformationException( errorMsg ); + } + + return capabilities; + } + + private static BigInteger roundToNearestPowerOf2( BigInteger value ) + { + BigInteger k = BigInteger.valueOf( 1 ); + + while ( k.compareTo( value ) == -1 ) { + k = k.multiply( BigInteger.valueOf( 2 ) ); + } + + return k; + } + + /** + * Calculates the framebuffer memory size for the Looking Glass shared memory device. + * + * @return framebuffer memory size in bytes for the Looking Glass shared memory device. + */ + private static BigInteger calculateFramebufferSize() + { + final long totalBytesFramebuffer = MAX_DISPLAY_WIDTH * MAX_DISPLAY_HEIGHT * 4 * 2; + final long totalBytesReserved = RESERVED_MEMORY_FRAMEBUFFER * 1048576; + + // round sum of total memory in bytes to nearest power of two + return roundToNearestPowerOf2( BigInteger.valueOf( totalBytesFramebuffer + totalBytesReserved ) ); + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // check if passthrough of Nvidia GPU takes place + if ( args.isNvidiaGpuPassthroughEnabled() ) { + // validate submitted PCI IDs + final List<HostdevPciDeviceAddress> pciDeviceAddresses = TransformationSpecificQemuGpuPassthroughNvidia + .validateParseNvidiaPciIds( args.getVmNvGpuIds0() ); + + // check if IOMMU support is available on the host + if ( !this.getCapabilities().hasHostIommuSupport() ) { + final String errorMsg = "IOMMU support is not available on the hypervisor but required for GPU passthrough!"; + throw new TransformationException( errorMsg ); + } + + // passthrough PCI devices of the GPU + for ( final HostdevPciDeviceAddress pciDeviceAddress : pciDeviceAddresses ) { + final HostdevPci pciDevice = config.addHostdevPciDevice(); + pciDevice.setManaged( true ); + pciDevice.setSource( pciDeviceAddress ); + } + + // add shared memory device for Looking Glass + final Shmem shmemDevice = config.addShmemDevice(); + shmemDevice.setName( "looking-glass" ); + shmemDevice.setModel( Shmem.Model.IVSHMEM_PLAIN ); + shmemDevice.setSize( TransformationSpecificQemuGpuPassthroughNvidia.calculateFramebufferSize() ); + + // enable hypervisor shadowing to avoid error code 43 of Nvidia drivers in virtual machines + config.setFeatureHypervVendorIdValue( TransformationSpecificQemuGpuPassthroughNvidia.HYPERV_VENDOR_ID ); + config.setFeatureHypervVendorIdState( true ); + config.setFeatureKvmHiddenState( true ); + + // disable all software video devices by disable them + for ( Video videoDevice : config.getVideoDevices() ) { + videoDevice.disable(); + } + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuSerialDevices.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuSerialDevices.java new file mode 100644 index 00000000..30f60289 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuSerialDevices.java @@ -0,0 +1,117 @@ +package org.openslx.runvirt.plugin.qemu.configuration; + +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.Serial.Type; +import org.openslx.libvirt.domain.device.Serial; +import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs; +import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu; +import org.openslx.virtualization.configuration.VirtualizationConfigurationQemuUtils; +import org.openslx.virtualization.configuration.transformation.TransformationException; +import org.openslx.virtualization.configuration.transformation.TransformationSpecific; + +/** + * Specific serial device transformation for Libvirt/QEMU virtualization configurations. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class TransformationSpecificQemuSerialDevices + extends TransformationSpecific<Domain, CommandLineArgs, LibvirtHypervisorQemu> +{ + /** + * Name of the configuration transformation. + */ + private static final String NAME = "Serial devices"; + + /** + * Creates a new serial device transformation for Libvirt/QEMU virtualization configurations. + * + * @param hypervisor Libvirt/QEMU hypervisor. + */ + public TransformationSpecificQemuSerialDevices( LibvirtHypervisorQemu hypervisor ) + { + super( TransformationSpecificQemuSerialDevices.NAME, hypervisor ); + } + + /** + * Validates a virtualization configuration and input arguments for this transformation. + * + * @param config virtualization configuration for the validation. + * @param args input arguments for the validation. + * @throws TransformationException validation has failed. + */ + private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException + { + if ( config == null || args == null ) { + throw new TransformationException( "Virtualization configuration or input arguments are missing!" ); + } + } + + /** + * Returns all serial devices from a virtualization configuration that link to a host system's + * serial device. + * + * @param config virtualization configuration. + * @return all serial devices that link to a host system's serial device. + */ + private ArrayList<Serial> getSerialDevDevices( Domain config ) + { + final ArrayList<Serial> devices = config.getSerialDevices(); + final Predicate<Serial> byDeviceTypeDev = device -> device.getType() == Type.DEV; + + return devices.stream().filter( byDeviceTypeDev ).collect( Collectors.toCollection( ArrayList::new ) ); + } + + /** + * Transforms a serial device in a virtualization configuration selected by its {@code index}. + * + * @param config virtualization configuration for the transformation. + * @param fileName path to the serial device file on the host system. + * @param index number of the serial device in the virtualization configuration that is selected. + * @throws TransformationException transformation has failed. + */ + private void transformSerialDevice( Domain config, String fileName, int index ) throws TransformationException + { + final ArrayList<Serial> devices = this.getSerialDevDevices( config ); + final Serial device = VirtualizationConfigurationQemuUtils.getArrayIndex( devices, index ); + + if ( device == null ) { + // check if device file name is specified + if ( fileName != null && !fileName.isEmpty() ) { + // serial port device is not available, so create new serial port device + final Serial newDevice = config.addSerialDevice(); + newDevice.setType( Type.DEV ); + newDevice.setSource( fileName ); + } + } else { + if ( fileName == null || fileName.isEmpty() ) { + // remove serial port device if device file name is not set + device.remove(); + } else { + // set type and source of existing serial port device + device.setType( Type.DEV ); + device.setSource( fileName ); + } + } + } + + @Override + public void transform( Domain config, CommandLineArgs args ) throws TransformationException + { + // validate configuration and input arguments + this.validateInputs( config, args ); + + // alter serial device + this.transformSerialDevice( config, args.getVmDeviceSerial0(), 0 ); + + // remove all additional serial devices + final ArrayList<Serial> devices = this.getSerialDevDevices( config ); + for ( int i = 1; i < devices.size(); i++ ) { + devices.get( i ).remove(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/virtualization/LibvirtHypervisorQemu.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/virtualization/LibvirtHypervisorQemu.java new file mode 100644 index 00000000..34cf33cf --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/virtualization/LibvirtHypervisorQemu.java @@ -0,0 +1,63 @@ +package org.openslx.runvirt.plugin.qemu.virtualization; + +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; + +/** + * Representation of the Libvirt QEMU hypervisor backend. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class LibvirtHypervisorQemu extends LibvirtHypervisor +{ + /** + * Creates a new Libvirt QEMU hypervisor backend and connects to the specified backend. + * + * @param type session type of the connection to the Libvirt QEMU hypervisor backend. + * @throws LibvirtHypervisorException failed to connect to the Libvirt QEMU hypervisor backend. + */ + public LibvirtHypervisorQemu( QemuSessionType type ) throws LibvirtHypervisorException + { + super( type.getConnectionUri() ); + } + + /** + * Type of Libvirt QEMU hypervisor backend session. + * + * @author Manuel Bentele + * @version 1.0 + */ + public enum QemuSessionType + { + // @formatter:off + LOCAL_SYSTEM_SESSION( "qemu:///system" ), + LOCAL_USER_SESSION ( "qemu:///session" ); + // @formatter:on + + /** + * Connection URI of the QEMU session type. + */ + private final String connectionUri; + + /** + * Creates a new QEMU session type. + * + * @param connectionUri URI for the connection of the session. + */ + QemuSessionType( String connectionUri ) + { + this.connectionUri = connectionUri; + } + + /** + * Returns the URI of the connection for the session. + * + * @return URI of the connection for the session. + */ + public String getConnectionUri() + { + return this.connectionUri; + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/Viewer.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/Viewer.java new file mode 100644 index 00000000..d23a9e11 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/Viewer.java @@ -0,0 +1,118 @@ +package org.openslx.runvirt.viewer; + +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; +import org.openslx.virtualization.Version; + +/** + * Representation of an viewer for virtual machines running on a host system. + * + * @author Manuel Bentele + * @version 1.0 + */ +public abstract class Viewer +{ + /** + * Name of the viewer. + */ + private final String name; + + /** + * Number of supported displays by the viewer. + */ + private final int numSupportedDisplays; + + /** + * The virtual machine to display. + */ + private final LibvirtVirtualMachine machine; + + /** + * Remote (hypervisor) endpoint for the viewer to connect to. + */ + private final LibvirtHypervisor hypervisor; + + /** + * Creates a new viewer for a Libvirt virtual machine running on a Libvirt hypervisor. + * + * @param name textual name of the viewer. + * @param numSupportedDisplays number of supported displays by the viewer. + * @param machine virtual machine to display. + * @param hypervisor remote (hypervisor) endpoint for the viewer to connect to. + */ + public Viewer( String name, int numSupportedDisplays, LibvirtVirtualMachine machine, LibvirtHypervisor hypervisor ) + { + this.name = name; + this.numSupportedDisplays = numSupportedDisplays; + this.machine = machine; + this.hypervisor = hypervisor; + } + + /** + * Returns the name of the viewer. + * + * @return name of the viewer. + */ + public String getName() + { + return this.name; + } + + /** + * Returns the number of supported displays by the viewer. + * + * @return number of supported displays by the viewer. + */ + public int getNumberOfSupportedDisplays() + { + return this.numSupportedDisplays; + } + + /** + * Returns the virtual machine to display. + * + * @return virtual machine to display. + */ + public LibvirtVirtualMachine getMachine() + { + return this.machine; + } + + /** + * Returns the remote (hypervisor) endpoint for the viewer to connect to. + * + * @return remote (hypervisor) endpoint for the viewer to connect to. + */ + public LibvirtHypervisor getHypervisor() + { + return this.hypervisor; + } + + /** + * Displays all virtual machine's displays. + * + * @throws ViewerException failed to display all displays of a virtual machine. + * + * @apiNote A call to this method blocks until the implemented {@link #render()} process + * terminates. + */ + public void display() throws ViewerException + { + this.render(); + } + + /** + * Returns the version of the viewer. + * + * @return version of the viewer. + * @throws ViewerException failed to get version of the viewer. + */ + public abstract Version getVersion() throws ViewerException; + + /** + * Renders the content of all displays from the virtual machine. + * + * @throws ViewerException failed to render all displays of a virtual machine. + */ + protected abstract void render() throws ViewerException; +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerException.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerException.java new file mode 100644 index 00000000..0c178375 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerException.java @@ -0,0 +1,35 @@ +package org.openslx.runvirt.viewer; + +/** + * An exception of a viewer error during displaying all displays of a virtual machine. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class ViewerException extends Exception +{ + /** + * Version for serialization. + */ + private static final long serialVersionUID = 161091514643380414L; + + /** + * Creates a new viewer exception including an error message. + * + * @param errorMsg message to describe a specific viewer error. + */ + public ViewerException( String errorMsg ) + { + super( errorMsg ); + } + + /** + * Creates a new viewer exception by a copy of an existing viewer exception. + * + * @param e existing viewer exception. + */ + public ViewerException( ViewerException e ) + { + super( e ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerLookingGlassClient.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerLookingGlassClient.java new file mode 100644 index 00000000..cea9ccd8 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerLookingGlassClient.java @@ -0,0 +1,105 @@ +package org.openslx.runvirt.viewer; + +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; +import org.openslx.virtualization.Version; + +/** + * Looking Glass Client to view the exposed framebuffer (through a shared memory) of a virtual + * machine running the Looking Glass Host application. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class ViewerLookingGlassClient extends Viewer +{ + /** + * Name of the Looking Glass Client program. + */ + private final static String NAME = "looking-glass-client"; + + /** + * Maximum number of supported displays by the Looking Glass Client. + */ + private final static int NUM_SUPPORTED_DISPLAYS = 1; + + /** + * File name of the shared memory file to receive display content from the Looking Glass Host. + */ + private final static String SHARED_MEMORY_FILENAME = "/dev/shm/looking-glass"; + + /** + * State whether showing debug information during virtual machine rendering or not. + */ + private final boolean debug; + + /** + * Creates a new Looking Glass Client for a Libvirt virtual machine running on a Libvirt + * hypervisor. + * + * @param machine virtual machine to display. + * @param hypervisor remote (hypervisor) endpoint for the viewer to connect to. + */ + public ViewerLookingGlassClient( LibvirtVirtualMachine machine, LibvirtHypervisor hypervisor ) + { + this( machine, hypervisor, false ); + } + + /** + * Creates a new Looking Glass Client for a Libvirt virtual machine running on a Libvirt + * hypervisor. + * + * @param machine virtual machine to display. + * @param hypervisor remote (hypervisor) endpoint for the viewer to connect to. + * @param debug state whether showing debug information during virtual machine rendering or not. + */ + public ViewerLookingGlassClient( LibvirtVirtualMachine machine, LibvirtHypervisor hypervisor, boolean debug ) + { + super( ViewerLookingGlassClient.NAME, ViewerLookingGlassClient.NUM_SUPPORTED_DISPLAYS, machine, hypervisor ); + + this.debug = debug; + } + + /** + * Returns the state whether showing debug information during virtual machine rendering or not. + * + * @return state whether showing debug information during virtual machine rendering or not. + */ + public boolean isDebugEnabled() + { + return this.debug; + } + + @Override + public Version getVersion() throws ViewerException + { + return null; + } + + @Override + public void render() throws ViewerException + { + // execute viewer process with arguments: + // in non-debug mode: + // "looking-glass-client app:shmFile=<SHARED-MEM-FILE> win:fullScreen=yes spice:enable=yes win:alerts=no" + // in debug mode: + // "looking-glass-client app:shmFile=<SHARED-MEM-FILE> win:fullScreen=yes spice:enable=yes win:alerts=yes win:showFPS=yes" + final String[] viewerParameters; + if ( this.isDebugEnabled() ) { + viewerParameters = new String[] { + "app:shmFile=" + ViewerLookingGlassClient.SHARED_MEMORY_FILENAME, + "win:fullScreen=yes", + "spice:enable=yes", + "win:alerts=no" }; + } else { + viewerParameters = new String[] { + "app:shmFile=" + ViewerLookingGlassClient.SHARED_MEMORY_FILENAME, + "win:fullScreen=yes", + "spice:enable=yes", + "win:alerts=yes", + "win:showFPS=yes" }; + } + + ViewerUtils.executeViewer( ViewerLookingGlassClient.NAME, viewerParameters ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerUtils.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerUtils.java new file mode 100644 index 00000000..1ed5947a --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerUtils.java @@ -0,0 +1,65 @@ +package org.openslx.runvirt.viewer; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; + +/** + * Utils for viewing displays of virtual machines. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class ViewerUtils +{ + /** + * Synchronously executes a viewer program specified by a command line call. + * <p> + * The command line call of the viewer program consists of the program name and an optional list + * of submitted command line arguments for the viewer program. The result of the executed viewer + * program from the standard output is returned after the program has exited. + * + * @param viewerProgram name of the viewer program. + * @param viewerArguments optional command line arguments for the viewer program. + * @return result of the executed viewer program from the standard output. + * @throws ViewerException failed to execute the viewer program. + */ + @SuppressWarnings( "deprecation" ) + public static String executeViewer( String viewerProgram, String[] viewerArguments ) throws ViewerException + { + final CommandLine viewerCommandLine = new CommandLine( viewerProgram ); + final DefaultExecutor viewerExecutor = new DefaultExecutor(); + + // prepare viewer command to execute + viewerCommandLine.addArguments( viewerArguments ); + + // set up temporary working directory for the viewer process + viewerExecutor.setWorkingDirectory( FileUtils.getTempDirectory() ); + + // set expected exit value of the viewer process indicating a successful operation + viewerExecutor.setExitValue( 0 ); + + // set up output stream handler to retrieve the content from the viewer's standard output + final ByteArrayOutputStream viewerOutputStream = new ByteArrayOutputStream(); + final PumpStreamHandler viewerOutputStreamHandler = new PumpStreamHandler( viewerOutputStream ); + viewerExecutor.setStreamHandler( viewerOutputStreamHandler ); + + // execute the viewer command as blocking process + try { + viewerExecutor.execute( viewerCommandLine ); + } catch ( IOException e ) { + throw new ViewerException( "Failed to execute '" + viewerProgram + "': " + e.getLocalizedMessage() ); + } + + final String viewerOuput = viewerOutputStream.toString( StandardCharsets.UTF_8 ); + IOUtils.closeQuietly( viewerOutputStream ); + + return viewerOuput; + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtManager.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtManager.java new file mode 100644 index 00000000..1848d975 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtManager.java @@ -0,0 +1,75 @@ +package org.openslx.runvirt.viewer; + +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; +import org.openslx.virtualization.Version; + +/** + * Virtual Machine Manager (virt-manager) to control and view a display of a virtual machine. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class ViewerVirtManager extends Viewer +{ + /** + * Name of the Virtual Machine Manager program. + */ + private final static String NAME = "virt-manager"; + + /** + * Maximum number of supported displays by the Virtual Machine Manager. + */ + private final static int NUM_SUPPORTED_DISPLAYS = 1; + + /** + * Creates a new Virtual Machine Manager for a virtual machine running on a hypervisor. + * + * @param machine virtual machine to display. + * @param hypervisor remote (hypervisor) endpoint for the viewer to connect to. + */ + public ViewerVirtManager( LibvirtVirtualMachine machine, LibvirtHypervisor hypervisor ) + { + super( ViewerVirtManager.NAME, ViewerVirtManager.NUM_SUPPORTED_DISPLAYS, machine, hypervisor ); + } + + @Override + public Version getVersion() throws ViewerException + { + // execute viewer process with arguments: + // "virt-manager --version" + final String versionOutput = ViewerUtils.executeViewer( ViewerVirtManager.NAME, + new String[] { "--version" } ); + + return Version.valueOf( versionOutput ); + } + + @Override + public void render() throws ViewerException + { + String connectionUri = null; + String machineUuid = null; + + // get URI of the hypervisor connection and UUID of the machine + try { + connectionUri = this.getHypervisor().getConnectionUri(); + machineUuid = this.getMachine().getConfiguration().getUuid(); + } catch ( LibvirtHypervisorException e ) { + throw new ViewerException( + "Failed to retrieve the URI of the hypervisor backend or the UUID of the machine to display: " + + e.getLocalizedMessage() ); + } + + // check if URI of the hypervisor connection and UUID of the machine is specified, otherwise abort + if ( connectionUri == null || connectionUri.isEmpty() || machineUuid == null || machineUuid.isEmpty() ) { + throw new ViewerException( + "The URI of the hypervisor backend or the UUID of the machine to display is missing!" ); + } + + // execute viewer process with arguments: + // "virt-viewer --connect=<URI> --show-domain-console <DOMAIN-UUID>" + ViewerUtils.executeViewer( ViewerVirtManager.NAME, + new String[] { "--connect=" + connectionUri, "--show-domain-console", machineUuid } ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtViewer.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtViewer.java new file mode 100644 index 00000000..8f6e9481 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtViewer.java @@ -0,0 +1,97 @@ +package org.openslx.runvirt.viewer; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.openslx.runvirt.virtualization.LibvirtHypervisor; +import org.openslx.runvirt.virtualization.LibvirtHypervisorException; +import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; +import org.openslx.virtualization.Version; + +/** + * Virtual Viewer (virt-viewer) to view one or several displays of a virtual machine. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class ViewerVirtViewer extends Viewer +{ + /** + * Name of the Virtual Machine Manager program. + */ + private final static String NAME = "virt-viewer"; + + /** + * Maximum number of supported displays by the Virtual Viewer. + */ + private final static int NUM_SUPPORTED_DISPLAYS = Integer.MAX_VALUE; + + /** + * Creates a new Virtual Viewer for a Libvirt virtual machine running on a Libvirt hypervisor. + * + * @param machine virtual machine to display. + * @param hypervisor remote (hypervisor) endpoint for the viewer to connect to. + */ + public ViewerVirtViewer( LibvirtVirtualMachine machine, LibvirtHypervisor hypervisor ) + { + super( ViewerVirtViewer.NAME, ViewerVirtViewer.NUM_SUPPORTED_DISPLAYS, machine, hypervisor ); + } + + @Override + public Version getVersion() throws ViewerException + { + final Version version; + + // execute viewer process with arguments: + // "virt-viewer --version" + final String versionOutput = ViewerUtils.executeViewer( ViewerVirtViewer.NAME, + new String[] { "--version" } ); + + if ( versionOutput == null ) { + version = null; + } else { + // parse version from the viewer's process output + final Pattern viewerVersionPattern = Pattern.compile( "(\\d+).(\\d+)" ); + final Matcher viewerVersionMatcher = viewerVersionPattern.matcher( versionOutput ); + + // check if version pattern was found + if ( viewerVersionMatcher.find() ) { + final short major = Short.valueOf( viewerVersionMatcher.group( 1 ) ); + final short minor = Short.valueOf( viewerVersionMatcher.group( 2 ) ); + version = new Version( major, minor ); + } else { + version = null; + } + } + + return version; + } + + @Override + public void render() throws ViewerException + { + String connectionUri = null; + String machineUuid = null; + + // get URI of the hypervisor connection and UUID of the machine + try { + connectionUri = this.getHypervisor().getConnectionUri(); + machineUuid = this.getMachine().getConfiguration().getUuid(); + } catch ( LibvirtHypervisorException e ) { + throw new ViewerException( + "Failed to retrieve the URI of the hypervisor backend or the UUID of the machine to display: " + + e.getLocalizedMessage() ); + } + + // check if URI of the hypervisor connection and UUID of the machine is specified, otherwise abort + if ( connectionUri == null || connectionUri.isEmpty() || machineUuid == null || machineUuid.isEmpty() ) { + throw new ViewerException( + "The URI of the hypervisor backend or the UUID of the machine to display is missing!" ); + } + + // execute viewer process with arguments: + // "virt-viewer --full-screen --reconnect --wait --attach --connect=<URI> --domain-name -- <DOMAIN-UUID>" + ViewerUtils.executeViewer( ViewerVirtViewer.NAME, new String[] { "--full-screen", "--reconnect", "--wait", + "--attach", "--connect=" + connectionUri, "--uuid", "--", machineUuid } ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisor.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisor.java new file mode 100644 index 00000000..757fc706 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisor.java @@ -0,0 +1,209 @@ +package org.openslx.runvirt.virtualization; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.List; + +import org.libvirt.Connect; +import org.libvirt.LibvirtException; +import org.openslx.libvirt.capabilities.Capabilities; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; +import org.openslx.virtualization.Version; + +/** + * Representation of a Libvirt hypervisor backend (e.g. QEMU or VMware). + * <p> + * The representation allows to connect to a running Libvirt service and query the host system's + * capabilities or manage virtual machines. + * + * @implNote This class is the abstract representation to implement various Libvirt hypervisor + * backends using inheritance. + * + * @author Manuel Bentele + * @version 1.0 + */ +public abstract class LibvirtHypervisor implements Closeable +{ + /** + * Connection to a Libvirt hypervisor backend. + */ + protected Connect hypervisor = null; + + /** + * List of registered machines on the Libvirt hypervisor backend. + */ + private List<LibvirtVirtualMachine> machines; + + /** + * Creates a new Libvirt hypervisor backend specified by an URI and connects to the specified + * backend. + * + * @param connectionUri URI of a specific Libvirt hypervisor backend. + * @throws LibvirtHypervisorException failed to connect to the specified Libvirt hypervisor + * backend. + */ + public LibvirtHypervisor( String connectionUri ) throws LibvirtHypervisorException + { + this.connect( connectionUri ); + this.machines = new ArrayList<LibvirtVirtualMachine>(); + } + + /** + * Connects to the Libvirt hypervisor backend specified by an URI. + * + * @param connectionUri URI of a specific Libvirt hypervisor backend. + * @throws LibvirtHypervisorException failed to connect to the specified Libvirt hypervisor + * backend. + */ + protected void connect( String connectionUri ) throws LibvirtHypervisorException + { + try { + this.hypervisor = new Connect( connectionUri ); + } catch ( LibvirtException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + } + + /** + * Returns the URI of the connection to the Libvirt hypervisor backend. + * + * @return URI of the connection to the hypervisor. + * @throws LibvirtHypervisorException failed to return the connection URI of the Libvirt + * hypervisor backend. + */ + public String getConnectionUri() throws LibvirtHypervisorException + { + String connectionUri = null; + + try { + connectionUri = this.hypervisor.getURI(); + } catch ( LibvirtException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + + return connectionUri; + } + + /** + * Returns the queried Libvirt hypervisor's host system capabilities. + * + * @return queried Libvirt hypervisor's host system capabilities. + * @throws LibvirtHypervisorException failed to query and return the Libvirt hypervisor's host + * system capabilities. + */ + public Capabilities getCapabilites() throws LibvirtHypervisorException + { + Capabilities hypervisorCapabilities = null; + + try { + final String hypervisorCapabilitiesString = this.hypervisor.getCapabilities(); + hypervisorCapabilities = new Capabilities( hypervisorCapabilitiesString ); + } catch ( LibvirtException | LibvirtXmlDocumentException | LibvirtXmlSerializationException + | LibvirtXmlValidationException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + + return hypervisorCapabilities; + } + + /** + * Returns the version of the Libvirt hypervisor backend. + * + * @return version of the Libvirt hypervisor backend. + * @throws LibvirtHypervisorException failed to get the version of the Libvirt hypervisor + * backend. + */ + public Version getVersion() throws LibvirtHypervisorException + { + long hypervisorVersionRaw = 0; + Version hypervisorVersion = null; + + try { + hypervisorVersionRaw = this.hypervisor.getVersion(); + } catch ( LibvirtException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + + if ( hypervisorVersionRaw > 0 ) { + final short major = Long.valueOf( hypervisorVersionRaw / Long.valueOf( 1000000 ) ).shortValue(); + hypervisorVersionRaw %= Long.valueOf( 1000000 ); + final short minor = Long.valueOf( hypervisorVersionRaw / Long.valueOf( 1000 ) ).shortValue(); + hypervisorVersion = new Version( major, minor ); + } + + return hypervisorVersion; + } + + /** + * Register a virtual machine by the Libvirt hypervisor based on a virtualization configuration. + * + * @param vmConfiguration virtualization configuration for the virtual machine. + * @return instance of the registered and defined virtual machine. + * @throws LibvirtHypervisorException failed to register and define virtual machine. + */ + public LibvirtVirtualMachine registerVm( org.openslx.libvirt.domain.Domain vmConfiguration ) + throws LibvirtHypervisorException + { + final String xmlVmConfiguration = vmConfiguration.toString(); + org.libvirt.Domain internalConfiguration = null; + + try { + internalConfiguration = this.hypervisor.domainDefineXML( xmlVmConfiguration ); + } catch ( LibvirtException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + + final LibvirtVirtualMachine vm = new LibvirtVirtualMachine( internalConfiguration, vmConfiguration ); + this.machines.add( vm ); + + return vm; + } + + /** + * Deregisters an already registered virtual machine by the Libvirt hypervisor. + * + * @param vm virtual machine that should be deregistered + * @throws LibvirtHypervisorException failed to deregister virtual machine by the Libvirt + * hypervisor. + * @throws LibvirtVirtualMachineException failed to check and stop the virtual machine. + */ + public void deregisterVm( LibvirtVirtualMachine vm ) + throws LibvirtHypervisorException, LibvirtVirtualMachineException + { + // stop virtual machine if machine is running + if ( vm.isRunning() ) { + vm.stop(); + } + + // deregister and remove virtual machine from hypervisor + try { + vm.getLibvirtDomain().undefine(); + } catch ( LibvirtException e ) { + throw new LibvirtHypervisorException( e.getLocalizedMessage() ); + } + + this.machines.remove( vm ); + } + + @Override + public void close() + { + // deregister all VMs defined on the hypervisor + for ( LibvirtVirtualMachine vm : this.machines ) { + try { + this.deregisterVm( vm ); + } catch ( LibvirtHypervisorException | LibvirtVirtualMachineException e ) { + e.printStackTrace(); + } + } + + // close connection to the hypervisor + try { + this.hypervisor.close(); + } catch ( LibvirtException e ) { + e.printStackTrace(); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisorException.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisorException.java new file mode 100644 index 00000000..64ae6b20 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtHypervisorException.java @@ -0,0 +1,25 @@ +package org.openslx.runvirt.virtualization; + +/** + * An exception of a Libvirt hypervisor error during acquiring the hypervisor's functionality. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class LibvirtHypervisorException extends Exception +{ + /** + * Version for serialization. + */ + private static final long serialVersionUID = -3631452625806770209L; + + /** + * Creates a Libvirt hypervisor exception including an error message. + * + * @param errorMsg message to describe a specific Libvirt hypervisor error. + */ + LibvirtHypervisorException( String errorMsg ) + { + super( errorMsg ); + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachine.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachine.java new file mode 100644 index 00000000..3bcec2f8 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachine.java @@ -0,0 +1,150 @@ +package org.openslx.runvirt.virtualization; + +import org.libvirt.Domain; +import org.libvirt.LibvirtException; + +/** + * Representation of a Libvirt virtual machine. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class LibvirtVirtualMachine +{ + /** + * Internal Libvirt virtualization configuration of the virtual machine. + */ + private Domain domain; + + /** + * Libvirt virtualization configuration of the virtual machine. + */ + private org.openslx.libvirt.domain.Domain configuration; + + /** + * Creates a new Libvirt virtual machine specified by a virtualization configuration. + * + * @param internalConfiguration internal Libvirt virtualization configuration to specify the + * Libvirt virtual machine. + * @param configuration Libvirt virtualization configuration to specify the Libvirt virtual + * machine. + */ + LibvirtVirtualMachine( Domain internalConfiguration, org.openslx.libvirt.domain.Domain configuration ) + { + this.domain = internalConfiguration; + this.configuration = configuration; + } + + /** + * Returns the internal Libvirt virtualization configuration of the Libvirt virtual machine. + * + * @return internal Libvirt virtualization configuration of the Libvirt virtual machine. + */ + Domain getLibvirtDomain() + { + return this.domain; + } + + /** + * Returns the Libvirt virtualization configuration of the Libvirt virtual machine. + * + * @return Libvirt virtualization configuration of the Libvirt virtual machine. + */ + public org.openslx.libvirt.domain.Domain getConfiguration() + { + return this.configuration; + } + + /** + * Checks if the Libvirt virtual machine is running. + * + * @return state of the Libvirt virtual machine whether it is running or not. + * @throws LibvirtVirtualMachineException failed to check if Libvirt machine is running or not. + */ + public boolean isRunning() throws LibvirtVirtualMachineException + { + int state = 0; + + try { + state = this.domain.isActive(); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + + return ( state == 0 ) ? false : true; + } + + /** + * Starts the Libvirt virtual machine. + * + * @throws LibvirtVirtualMachineException failed to start the Libvirt virtual machine. + */ + public void start() throws LibvirtVirtualMachineException + { + if ( !this.isRunning() ) { + try { + this.domain.create(); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + } + } + + /** + * Stops the Libvirt virtual machine. + * + * @throws LibvirtVirtualMachineException failed to stop the Libvirt virtual machine. + */ + public void stop() throws LibvirtVirtualMachineException + { + if ( this.isRunning() ) { + try { + this.domain.shutdown(); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + } + } + + /** + * Suspends the Libvirt virtual machine. + * + * @throws LibvirtVirtualMachineException failed to suspend the Libvirt virtual machine. + */ + public void suspend() throws LibvirtVirtualMachineException + { + try { + this.domain.suspend(); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + } + + /** + * Resumes the Libvirt virtual machine. + * + * @throws LibvirtVirtualMachineException faild to resume the Libvirt virtual machine. + */ + public void resume() throws LibvirtVirtualMachineException + { + try { + this.domain.resume(); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + } + + /** + * Reboot the Libvirt virtual machine. + * + * @throws LibvirtVirtualMachineException failed to reboot the Libvirt virtual machine. + */ + public void reboot() throws LibvirtVirtualMachineException + { + try { + this.domain.reboot( 0 ); + } catch ( LibvirtException e ) { + throw new LibvirtVirtualMachineException( e.getLocalizedMessage() ); + } + } +} diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachineException.java b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachineException.java new file mode 100644 index 00000000..dd490be3 --- /dev/null +++ b/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/virtualization/LibvirtVirtualMachineException.java @@ -0,0 +1,25 @@ +package org.openslx.runvirt.virtualization; + +/** + * An exception of a Libvirt virtual machine error during controlling the virtual machine. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class LibvirtVirtualMachineException extends Exception +{ + /** + * Version for serialization. + */ + private static final long serialVersionUID = -5371327391243047616L; + + /** + * Creates a Libvirt virtual machine exception including an error message. + * + * @param errorMsg message to describe a specific Libvirt virtual machine error. + */ + public LibvirtVirtualMachineException( String errorMsg ) + { + super( errorMsg ); + } +} |