From 670ef8aabd4d997372554e79f8ca8930df56d2e6 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Mon, 17 May 2021 13:51:41 +0200 Subject: [qemu] Add viewer representation to render displays of the virtual machine --- core/modules/qemu/runvirt-plugin-qemu/pom.xml | 5 + .../java/org/openslx/runvirt/plugin/qemu/App.java | 33 +++++- .../java/org/openslx/runvirt/viewer/Viewer.java | 118 +++++++++++++++++++++ .../openslx/runvirt/viewer/ViewerException.java | 35 ++++++ .../org/openslx/runvirt/viewer/ViewerUtils.java | 65 ++++++++++++ .../openslx/runvirt/viewer/ViewerVirtManager.java | 75 +++++++++++++ .../openslx/runvirt/viewer/ViewerVirtViewer.java | 97 +++++++++++++++++ .../runvirt/virtualization/LibvirtHypervisor.java | 31 +++++- .../virtualization/LibvirtVirtualMachine.java | 31 ++++-- 9 files changed, 476 insertions(+), 14 deletions(-) create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/Viewer.java create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerException.java create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerUtils.java create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtManager.java create mode 100644 core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/viewer/ViewerVirtViewer.java diff --git a/core/modules/qemu/runvirt-plugin-qemu/pom.xml b/core/modules/qemu/runvirt-plugin-qemu/pom.xml index 197f3df3..f02be96d 100644 --- a/core/modules/qemu/runvirt-plugin-qemu/pom.xml +++ b/core/modules/qemu/runvirt-plugin-qemu/pom.xml @@ -61,6 +61,11 @@ 1.4 compile + + org.apache.commons + commons-exec + 1.3 + log4j log4j 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 b026a540..ecc97b12 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 @@ -27,6 +27,9 @@ import org.openslx.runvirt.plugin.qemu.configuration.TransformationSpecificQemuA 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.ViewerVirtViewer; import org.openslx.runvirt.virtualization.LibvirtHypervisor; import org.openslx.runvirt.virtualization.LibvirtHypervisorException; import org.openslx.runvirt.virtualization.LibvirtVirtualMachine; @@ -160,7 +163,7 @@ public class App } } - // define QEMU VM from finalized configuration + // define Libvirt VM from finalized configuration LibvirtVirtualMachine vm = null; try { vm = hypervisor.registerVm( config ); @@ -170,6 +173,7 @@ public class App System.exit( 6 ); } + // start defined Libvirt VM try { vm.start(); } catch ( LibvirtVirtualMachineException e ) { @@ -177,13 +181,36 @@ public class App try { hypervisor.deregisterVm( vm ); } catch ( LibvirtHypervisorException | LibvirtVirtualMachineException e1 ) { - LOGGER.error( "Failed to undefine VM: " + e.getLocalizedMessage() ); + LOGGER.error( "Failed to undefine VM in error state after failed start of VM: " + e.getLocalizedMessage() ); } hypervisor.close(); System.exit( 7 ); } - // close connection and let VM be running + // display Libvirt VM with a specific viewer on the screen + final Viewer vmViewer = new ViewerVirtViewer( vm, hypervisor ); + try { + vmViewer.display(); + } catch ( ViewerException e ) { + LOGGER.error( "Failed to display VM: " + e.getLocalizedMessage() ); + try { + hypervisor.deregisterVm( vm ); + } catch ( LibvirtHypervisorException | LibvirtVirtualMachineException e1 ) { + LOGGER.error( "Failed to undefine VM in error state after failed display: " + 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(); + } + + // close connection to hypervisor hypervisor.close(); // return with successful exit code 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/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. + *

+ * 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= --show-domain-console " + 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= --domain-name -- " + 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 index 3c654880..42e2d00f 100644 --- 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 @@ -58,6 +58,26 @@ public abstract class LibvirtHypervisor implements Closeable } } + /** + * 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. * @@ -119,23 +139,24 @@ public abstract class LibvirtHypervisor implements Closeable throws LibvirtHypervisorException { final String xmlVmConfiguration = vmConfiguration.toString(); - org.libvirt.Domain libvirtDomain = null; + org.libvirt.Domain internalConfiguration = null; try { - libvirtDomain = this.hypervisor.domainDefineXML( xmlVmConfiguration ); + internalConfiguration = this.hypervisor.domainDefineXML( xmlVmConfiguration ); } catch ( LibvirtException e ) { throw new LibvirtHypervisorException( e.getLocalizedMessage() ); } - return new LibvirtVirtualMachine( libvirtDomain ); + return new LibvirtVirtualMachine( internalConfiguration, vmConfiguration ); } /** * 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. + * @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 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 index 32b99d66..3bcec2f8 100644 --- 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 @@ -12,18 +12,37 @@ import org.libvirt.LibvirtException; public class LibvirtVirtualMachine { /** - * Libvirt virtualization configuration of the virtual machine. + * 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 vm Libvirt virtualization configuration to specify the Libvirt virtual machine. + * @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 vm ) + LibvirtVirtualMachine( Domain internalConfiguration, org.openslx.libvirt.domain.Domain configuration ) { - this.domain = vm; + 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; } /** @@ -31,9 +50,9 @@ public class LibvirtVirtualMachine * * @return Libvirt virtualization configuration of the Libvirt virtual machine. */ - public Domain getLibvirtDomain() + public org.openslx.libvirt.domain.Domain getConfiguration() { - return this.domain; + return this.configuration; } /** -- cgit v1.2.3-55-g7522