From 7516507b29d48c3d8d08ac568eb3cde86f4d4b8f Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 22 Jun 2023 15:28:51 +0200 Subject: [qemu] Add option to edit XML before launching If run-virt is in debug mode, a text editor showing the final XML will be opened before launching libvirt/qemu. Either pass --debug to run-virt (lol), or set SLX_DEBUG_MODE='ON' in /opt/openslx/config --- .../vmchooser/plugins/qemukvm/run-virt.include | 4 + core/modules/qemu/runvirt-plugin-qemu/pom.xml | 6 +- .../java/org/openslx/runvirt/plugin/qemu/App.java | 30 ++++++ .../runvirt/plugin/qemu/cmdln/CommandLineArgs.java | 37 ++++--- .../org/openslx/runvirt/plugin/qemu/AppTest.java | 117 +-------------------- 5 files changed, 62 insertions(+), 132 deletions(-) diff --git a/core/modules/qemu/data/opt/openslx/vmchooser/plugins/qemukvm/run-virt.include b/core/modules/qemu/data/opt/openslx/vmchooser/plugins/qemukvm/run-virt.include index c37a10e0..2b1853e2 100644 --- a/core/modules/qemu/data/opt/openslx/vmchooser/plugins/qemukvm/run-virt.include +++ b/core/modules/qemu/data/opt/openslx/vmchooser/plugins/qemukvm/run-virt.include @@ -103,6 +103,10 @@ run_plugin() { && VIRTCMDOPTS+=( "-vmparallel0" "${PARALLEL0}" ) notempty pt_gpu_mdev_id && VIRTCMDOPTS+=( "-vmilmdevid0" "${pt_gpu_mdev_id}" ) + if $debug; then + VIRTCMDOPTS+=( "-xmledit" ) + fi + if [ "${SHARE_REMAP_MODE}" -gt 1 ]; then notempty HOME_SHARE_PATH && VIRTCMDOPTS+=( "-vmfssrc0" "${HOME_SHARE_PATH}" ) notempty HOME_SHARE_NAME && VIRTCMDOPTS+=( "-vmfstgt0" "${HOME_SHARE_NAME}" ) diff --git a/core/modules/qemu/runvirt-plugin-qemu/pom.xml b/core/modules/qemu/runvirt-plugin-qemu/pom.xml index 4c6816f3..a2e5c24e 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/pom.xml +++ b/core/modules/qemu/runvirt-plugin-qemu/pom.xml @@ -18,9 +18,9 @@ org.openslx.runvirt.plugin.qemu.App UTF-8 - 1.8 - 1.8 - 8 + 11 + 11 + 11 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 index 0a2af04a..a5349316 100644 --- 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 @@ -1,8 +1,10 @@ package org.openslx.runvirt.plugin.qemu; import java.io.File; +import java.io.IOException; import java.util.Arrays; +import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; @@ -93,6 +95,10 @@ public class App App.printUsage( cmdLn ); System.exit( 1 ); } + + if ( cmdLn.isDebugEnabled() || cmdLn.isDebugDevicePassthroughEnabled() ) { + Configurator.setRootLevel( Level.ALL ); + } // show help if 'help' command line option is set if ( cmdLn.isHelpAquired() ) { @@ -165,6 +171,30 @@ public class App System.exit( 4 ); } + // spawn xml editor on final xml if desired + if ( cmdLn.isXmlEditorSpawningEnabled() ) { + try { + File tmp = File.createTempFile( "run-virt-qemu", ".xml" ); + boolean ok = false; + while ( !ok ) { + config.toXml( tmp ); + LOGGER.info( "Opening text editor for XML" ); + EditorRunner.open( tmp.getAbsolutePath() ); + try { + Domain nc = new Domain( tmp ); + ok = true; + config = nc; + } catch ( LibvirtXmlSerializationException | LibvirtXmlDocumentException | LibvirtXmlValidationException e ) { + LOGGER.error( "Failed to create Domain from edited XML file", e ); + } + } + } catch ( IOException e ) { + LOGGER.error( "Failed to create temp file for XML editing: " + e.getLocalizedMessage() ); + } catch ( LibvirtXmlSerializationException e ) { + LOGGER.error( "Failed to write VM output temporary file" + e ); + } + } + // write finalized configuration to file if output file is specified final String xmlOutputFileName = cmdLn.getVmCfgOutFileName(); if ( xmlOutputFileName != null && !xmlOutputFileName.isEmpty() ) { 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 index d0fb3068..1ab99076 100644 --- 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 @@ -436,6 +436,15 @@ public class CommandLineArgs { return this.getVmIlMdevId0() != null; } + + /** + * Returns whether the option for spawning a text editor with the final XML prior + * to launching the VM should be opened. + */ + public boolean isXmlEditorSpawningEnabled() + { + return this.cmdLn.hasOption( CmdLnOption.XML_EDIT.shortOption ); + } /** * Command line options for the run-virt QEMU plugin (command line tool). @@ -446,30 +455,32 @@ public class CommandLineArgs public enum CmdLnOption { // @formatter:off - HELP ( 'h', "help", 0, "" ), + XML_EDIT ( '0', "xmledit", 0, "Spawn a text editor with the final XML before starting, so it can be edited" + + " for testing and debugging purposes"), + VM_MAC0 ( 'a', "vmmac0", 1, "MAC address for the first network interface" ), DEBUG ( 'b', "debug", 1, "Enable or disable debug mode" ), - DEBUG_PTH ( 'j', "debugpth", 1, "Enable or disable device passthrough debug mode" ), - FIRMWARE ( 'x', "firmware", 1, "Path to QEMU firmware specifications directory" ), - 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_DSPLNAME ( 'd', "vmdsplname", 1, "Display name for the virtual machine" ), + VM_FSTGT0 ( 'e', "vmfstgt0", 1, "Target directory for first file system passthrough (shared folder)" ), VM_FLOPPY0 ( 'f', "vmfloppy0", 1, "Disk image for the first floppy drive" ), VM_FLOPPY1 ( 'g', "vmfloppy1", 1, "Disk image for the second floppy drive" ), + HELP ( 'h', "help", 0, "" ), + VM_CFGINP ( 'i', "vmcfginp", 1, "File name of an existing and filtered Libvirt domain XML configuration file" ), + DEBUG_PTH ( 'j', "debugpth", 1, "Enable or disable device passthrough debug mode" ), 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_MEM ( 'm', "vmmem", 1, "Amount of memory for the virtual machine" ), + VM_NAME ( 'n', "vmname", 1, "Name for the virtual machine" ), + VM_CFGOUT ( 'o', "vmcfgout", 1, "File name to output a finalized Libvirt domain XML configuration file" ), 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_HDD0 ( 'r', "vmhdd0", 1, "Disk image for the first HDD device" ), + VM_OS ( 's', "vmos", 1, "Operating system running in the virtual machine" ), 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_UUID ( 'u', "vmuuid", 1, "UUID for the virtual machine" ), 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)" ), + FIRMWARE ( 'x', "firmware", 1, "Path to QEMU firmware specifications directory" ), VM_NVGPUIDS0( 'y', "vmnvgpuids0", 2, "PCI device description and address for passthrough of the first Nvidia GPU. " + "The argument follow the pattern: " + "\":,::.\"" ), diff --git a/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/AppTest.java b/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/AppTest.java index 2a1041ba..5bcab1a7 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/AppTest.java +++ b/core/modules/qemu/runvirt-plugin-qemu/src/test/java/org/openslx/runvirt/plugin/qemu/AppTest.java @@ -1,8 +1,5 @@ package org.openslx.runvirt.plugin.qemu; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -14,11 +11,11 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs.CmdLnOption; -import com.ginsberg.junit.exit.ExpectSystemExit; import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; public class AppTest { + @BeforeAll public static void setUp() { @@ -39,118 +36,6 @@ public class AppTest System.setErr( new PrintStream( this.err ) ); } - @Test - @DisplayName( "Test ouput of correct 'help' command line option (short version)" ) - @ExpectSystemExit - public void testCmdLnOptionHelpShortCorrect() - { - String[] argsShortHelpOptionCorrect = { "-" + CmdLnOption.HELP.getShortOption() }; - - this.setUp(); - - // test correct usage of the short help option - try { - App.main( argsShortHelpOptionCorrect ); - } catch ( Exception e ) { - // do nothing and check output afterwards - } - - final String shortHelpOptionCorrectOutput = new String( this.out.toString() ); - final String shortHelpOptionCorrectErrOutput = new String( this.err.toString() ); - assertTrue( shortHelpOptionCorrectOutput.contains( "usage" ) ); - assertTrue( shortHelpOptionCorrectOutput.contains( App.APP_NAME ) ); - assertTrue( shortHelpOptionCorrectOutput.contains( App.APP_INFO ) ); - assertTrue( shortHelpOptionCorrectOutput.contains( App.APP_DESC ) ); - - // test that no error was logged and output is available - assertEquals( 2826, shortHelpOptionCorrectOutput.length() ); - assertEquals( 0, shortHelpOptionCorrectErrOutput.length() ); - } - - @Test - @DisplayName( "Test ouput of correct 'help' command line option (long version)" ) - @ExpectSystemExit - public void testCmdLnOptionHelpLongCorrect() - { - String[] argsLongHelpOptionCorrect = { "--" + CmdLnOption.HELP.getLongOption() }; - - this.setUp(); - - // test correct usage of the long help option - try { - App.main( argsLongHelpOptionCorrect ); - } catch ( Exception e ) { - // do nothing and check output afterwards - } - - final String longHelpOptionCorrectOutput = this.out.toString(); - final String longHelpOptionCorrectErrOutput = this.err.toString(); - assertTrue( longHelpOptionCorrectOutput.contains( "usage" ) ); - assertTrue( longHelpOptionCorrectOutput.contains( App.APP_NAME ) ); - assertTrue( longHelpOptionCorrectOutput.contains( App.APP_INFO ) ); - assertTrue( longHelpOptionCorrectOutput.contains( App.APP_DESC ) ); - - // test that no error was logged and output is available - assertEquals( 2826, longHelpOptionCorrectOutput.length() ); - assertEquals( 0, longHelpOptionCorrectErrOutput.length() ); - } - - @Test - @DisplayName( "Test ouput of incorrect 'help' command line option (short version)" ) - @ExpectSystemExit - public void testCmdLnOptionHelpShortIncorrect() - { - String[] argsShortHelpOptionIncorrect = { "---" + CmdLnOption.HELP.getShortOption() }; - - this.setUp(); - - // test incorrect usage of the short help option - try { - App.main( argsShortHelpOptionIncorrect ); - } catch ( Exception e ) { - // do nothing and check output afterwards - } - - final String shortHelpOptionIncorrectOutput = this.out.toString(); - final String shortHelpOptionIncorrectErrOutput = this.err.toString(); - assertTrue( shortHelpOptionIncorrectOutput.contains( "usage" ) ); - assertTrue( shortHelpOptionIncorrectOutput.contains( App.APP_NAME ) ); - assertTrue( shortHelpOptionIncorrectOutput.contains( App.APP_INFO ) ); - assertTrue( shortHelpOptionIncorrectOutput.contains( App.APP_DESC ) ); - - // test that error was logged and output is available - assertEquals( 2826, shortHelpOptionIncorrectOutput.length() ); - assertEquals( 0, shortHelpOptionIncorrectErrOutput.length() ); - } - - @Test - @DisplayName( "Test ouput of incorrect 'help' command line option (long version)" ) - @ExpectSystemExit - public void testCmdLnOptionHelpLongIncorrect() - { - String[] argsLongHelpOptionIncorrect = { "---" + CmdLnOption.HELP.getLongOption() }; - - this.setUp(); - - // test incorrect usage of the long help option - try { - App.main( argsLongHelpOptionIncorrect ); - } catch ( Exception e ) { - // do nothing and check output afterwards - } - - final String longHelpOptionIncorrectOutput = this.out.toString(); - final String longHelpOptionIncorrectErrOutput = this.err.toString(); - assertTrue( longHelpOptionIncorrectOutput.contains( "usage" ) ); - assertTrue( longHelpOptionIncorrectOutput.contains( App.APP_NAME ) ); - assertTrue( longHelpOptionIncorrectOutput.contains( App.APP_INFO ) ); - assertTrue( longHelpOptionIncorrectOutput.contains( App.APP_DESC ) ); - - // test that error was logged and output is available - assertEquals( 2826, longHelpOptionIncorrectOutput.length() ); - assertEquals( 0, longHelpOptionIncorrectErrOutput.length() ); - } - @Test @DisplayName( "Test exit status of application invoked with correct 'help' command line option (short version)" ) @ExpectSystemExitWithStatus( 0 ) -- cgit v1.2.3-55-g7522