summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/virtualization
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/openslx/virtualization')
-rw-r--r--src/main/java/org/openslx/virtualization/Version.java321
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfiguration.java378
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationDocker.java174
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationException.java13
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java903
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuUtils.java404
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationUtils.java52
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualBox.java601
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualboxFileFormat.java771
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmware.java741
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmwareFileFormat.java284
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/container/ContainerBindMount.java74
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/container/ContainerDefinition.java195
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/container/ContainerImageContext.java10
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/container/ContainerMeta.java166
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModClientToDozModServer.java21
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToDozModClient.java116
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToStatelessClient.java79
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogic.java28
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModClientToDozModServer.java76
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToDozModClient.java197
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java108
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/Transformation.java65
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/TransformationException.java25
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/TransformationFunction.java39
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/TransformationGeneric.java25
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/TransformationManager.java158
-rw-r--r--src/main/java/org/openslx/virtualization/configuration/transformation/TransformationSpecific.java44
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImage.java253
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImageException.java25
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImageQcow2.java232
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImageUtils.java144
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImageVdi.java112
-rw-r--r--src/main/java/org/openslx/virtualization/disk/DiskImageVmdk.java282
-rw-r--r--src/main/java/org/openslx/virtualization/hardware/ConfigurationGroups.java21
-rw-r--r--src/main/java/org/openslx/virtualization/hardware/Ethernet.java20
-rw-r--r--src/main/java/org/openslx/virtualization/hardware/SoundCard.java13
-rw-r--r--src/main/java/org/openslx/virtualization/hardware/Usb.java11
-rw-r--r--src/main/java/org/openslx/virtualization/hardware/VirtOptionValue.java42
-rw-r--r--src/main/java/org/openslx/virtualization/virtualizer/Virtualizer.java68
-rw-r--r--src/main/java/org/openslx/virtualization/virtualizer/VirtualizerDocker.java55
-rw-r--r--src/main/java/org/openslx/virtualization/virtualizer/VirtualizerQemu.java72
-rw-r--r--src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVirtualBox.java56
-rw-r--r--src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVmware.java73
44 files changed, 7547 insertions, 0 deletions
diff --git a/src/main/java/org/openslx/virtualization/Version.java b/src/main/java/org/openslx/virtualization/Version.java
new file mode 100644
index 0000000..c823324
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/Version.java
@@ -0,0 +1,321 @@
+package org.openslx.virtualization;
+
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openslx.util.Util;
+
+/**
+ * Represents a version information.
+ *
+ * The version information is used in the field of virtualization (for virtualizers, disk images,
+ * virtualization configuration files, ...).
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class Version implements Comparable<Version>
+{
+ /**
+ * Regular expression to parse a version from a {@link String}.
+ * <p>
+ * The regular expression matches a version if its textual version information is well-formed
+ * according to the following examples:
+ *
+ * <pre>
+ * 52
+ * 4.31
+ * 5.10.13
+ * </pre>
+ */
+ private static final Pattern VERSION_NUMBER_REGEX = Pattern.compile( "^(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?$" );
+
+ /**
+ * Major number of the version.
+ */
+ private final short major;
+
+ /**
+ * Minor number of the version.
+ */
+ private final short minor;
+
+ /**
+ * Name or description of the version.
+ */
+ private final String name;
+
+ /**
+ * Creates a new version.
+ *
+ * The version consists of a major version, whereas the minor version is set to the value
+ * <code>0</code> and the version name is undefined.
+ *
+ * @param major major version.
+ */
+ public Version( short major )
+ {
+ this( major, Short.valueOf( "0" ), null );
+ }
+
+ /**
+ * Creates a new version.
+ *
+ * The version consists of a major version labeled with a version name, whereas the minor version
+ * is set to the value <code>0</code>.
+ *
+ * @param major major version.
+ * @param name version name.
+ */
+ public Version( short major, String name )
+ {
+ this( major, Short.valueOf( "0" ), name );
+ }
+
+ /**
+ * Creates a new version.
+ *
+ * The version consists of a major and a minor version, whereas the version name is undefined.
+ *
+ * @param major major version.
+ * @param minor minor version.
+ */
+ public Version( short major, short minor )
+ {
+ this( major, minor, null );
+ }
+
+ /**
+ * Creates a new version.
+ *
+ * The version consists of a major and a minor version labeled with a version name.
+ *
+ * @param major major version.
+ * @param minor minor version.
+ * @param name version name.
+ */
+ public Version( short major, short minor, String name )
+ {
+ this.major = major;
+ this.minor = minor;
+ this.name = name;
+ }
+
+ /**
+ * Returns the major version.
+ *
+ * @return major version.
+ */
+ public short getMajor()
+ {
+ return this.major;
+ }
+
+ /**
+ * Returns the minor version.
+ *
+ * @return minor version.
+ */
+ public short getMinor()
+ {
+ return this.minor;
+ }
+
+ /**
+ * Returns the full version as {@link Integer}.
+ *
+ * The full version consists of the major and minor version where both are combined in one
+ * {@link Integer} value. The upper 16-bits of the value represent the major number, whereas
+ * the lower 16-bits represent the minor number.
+ *
+ * @return full version as {@link Integer}.
+ */
+ public int getVersion()
+ {
+ final int major = this.major;
+ final int minor = this.minor;
+
+ return ( major << Short.SIZE ) | minor;
+ }
+
+ /**
+ * Returns the name of the version.
+ *
+ * @return name of the version.
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+ /**
+ * Checks if version is supported by a version from a list of supported versions.
+ *
+ * @param supportedVersions list of supported versions.
+ * @return state whether version is supported by a version from the list of versions or not.
+ */
+ public boolean isSupported( List<Version> supportedVersions )
+ {
+ return supportedVersions.contains( this );
+ }
+
+ /**
+ * Returns a version from a list of supported versions filtered by its given filter predicate.
+ *
+ * @param byFilter filter predicate.
+ * @param supportedVersions list of supported versions.
+ * @return version from a list of supported versions filtered by its given filter predicate.
+ */
+ private static Version getInstanceByPredicateFromVersions( Predicate<Version> byFilter,
+ List<Version> supportedVersions )
+ {
+ return supportedVersions.stream().filter( byFilter ).findFirst().orElse( null );
+ }
+
+ /**
+ * Returns a version from a list of supported versions by its given major version.
+ *
+ * @param major version.
+ * @param supportedVersions list of supported versions.
+ * @return version from a list of supported versions by its given major version.
+ */
+ public static Version getInstanceByMajorFromVersions( short major, List<Version> supportedVersions )
+ {
+ final Predicate<Version> byMajor = version -> major == version.getMajor();
+ return Version.getInstanceByPredicateFromVersions( byMajor, supportedVersions );
+ }
+
+ /**
+ * Returns a version from a list of supported versions by its given major and minor version.
+ *
+ * @param major version.
+ * @param minor version.
+ * @param supportedVersions list of supported versions.
+ * @return version from a list of supported versions by its given major and minor version.
+ */
+ public static Version getInstanceByMajorMinorFromVersions( short major, short minor,
+ List<Version> supportedVersions )
+ {
+ final Predicate<Version> byMajorMinor = version -> major == version.getMajor() && minor == version.getMinor();
+ return supportedVersions.stream().filter( byMajorMinor ).findFirst().orElse( null );
+ }
+
+ /**
+ * Checks if this version is smaller than a specified {@code version}.
+ *
+ * @param version for comparison.
+ * @return state whether this version is smaller than the specified {@code version} or not.
+ */
+ public boolean isSmallerThan( Version version )
+ {
+ return ( this.compareTo( version ) < 0 ) ? true : false;
+ }
+
+ /**
+ * Checks if this version is greater than a specified {@code version}.
+ *
+ * @param version for comparison.
+ * @return state whether this version is greater than the specified {@code version} or not.
+ */
+ public boolean isGreaterThan( Version version )
+ {
+ return ( this.compareTo( version ) > 0 ) ? true : false;
+ }
+
+ /**
+ * Creates a new version parsed from a {@link String}.
+ *
+ * The version consists of a major and a minor version parsed from the specified {@link String}.
+ *
+ * @param version textual information containing a version as {@link String}. The textual
+ * version should be well-formed according to the defined regular expression
+ * {@link #VERSION_NUMBER_REGEX}.
+ * @return version instance.
+ */
+ public static Version valueOf( String version )
+ {
+ final Version parsedVersion;
+
+ if ( Util.isEmptyString( version ) ) {
+ parsedVersion = null;
+ } else {
+ final Matcher versionMatcher = VERSION_NUMBER_REGEX.matcher( version );
+
+ if ( versionMatcher.find() ) {
+ final String majorStr = versionMatcher.group( 1 );
+ final String minorStr = versionMatcher.group( 2 );
+
+ final short major = ( majorStr != null ) ? Short.valueOf( majorStr ) : 0;
+ final short minor = ( minorStr != null ) ? Short.valueOf( minorStr ) : 0;
+
+ parsedVersion = new Version( major, minor );
+
+ } else {
+ parsedVersion = null;
+ }
+ }
+
+ return parsedVersion;
+ }
+
+ @Override
+ public String toString()
+ {
+ if ( Util.isEmptyString( this.getName() ) ) {
+ return String.format( "%d.%d", this.getMajor(), this.getMinor() );
+ } else {
+ return String.format( "%d.%d %s", this.getMajor(), this.getMinor(), this.getName() );
+ }
+ }
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( obj == null ) {
+ return false;
+ } else if ( this.getClass() != obj.getClass() ) {
+ return false;
+ } else if ( this.compareTo( Version.class.cast( obj ) ) == 0 ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int compareTo( Version v )
+ {
+ // compare the current version to the specified version
+ if ( this.getMajor() < v.getMajor() ) {
+ // current major version is smaller than the given major version
+ return -1;
+ } else if ( this.getMajor() > v.getMajor() ) {
+ // current major version is larger than the given major version
+ return 1;
+ } else {
+ // current major version is equal to the given major version
+ // so compare the current minor version to the specified minor version
+ if ( this.getMinor() < v.getMinor() ) {
+ // current minor version is smaller than the given minor version
+ // so the entire version is smaller than the given version
+ return -1;
+ } else if ( this.getMinor() > v.getMinor() ) {
+ // current minor version is larger than the given minor version
+ // so the entire version is larger than the given version
+ return 1;
+ } else {
+ // current minor version is equal to the given minor version
+ // so the entire version is equal to the given version
+ return 0;
+ }
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return ( Short.valueOf( this.getMajor() ).hashCode() ) ^ ( Short.valueOf( this.getMinor() ).hashCode() );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfiguration.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfiguration.java
new file mode 100644
index 0000000..f548597
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfiguration.java
@@ -0,0 +1,378 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.hardware.VirtOptionValue;
+import org.openslx.virtualization.hardware.ConfigurationGroups;
+import org.openslx.virtualization.virtualizer.Virtualizer;
+
+/**
+ * Describes a configured virtual machine. This class is parsed from a machine
+ * description, like a *.vmx for VMware machines.
+ */
+public abstract class VirtualizationConfiguration
+{
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfiguration.class );
+
+ private final Virtualizer virtualizer;
+
+
+ public static enum DriveBusType
+ {
+ SCSI, IDE, SATA, NVME;
+ }
+
+ public static class HardDisk
+ {
+ public final String chipsetDriver;
+ public final DriveBusType bus;
+ public final String diskImage;
+
+ public HardDisk( String chipsetDriver, DriveBusType bus, String diskImage )
+ {
+ this.chipsetDriver = chipsetDriver;
+ this.bus = bus;
+ this.diskImage = diskImage;
+ }
+ }
+
+ public static enum EtherType
+ {
+ NAT, BRIDGED, HOST_ONLY;
+ }
+
+ public static class ConfigurableOptionGroup
+ {
+ public final ConfigurationGroups groupIdentifier;
+
+ public final List<VirtOptionValue> availableOptions;
+
+ public ConfigurableOptionGroup( ConfigurationGroups groupIdentifier, List<VirtOptionValue> availableOptions )
+ {
+ this.groupIdentifier = groupIdentifier;
+ this.availableOptions = Collections.unmodifiableList( availableOptions );
+ }
+
+ public VirtOptionValue getSelected()
+ {
+ for (VirtOptionValue hw : availableOptions) {
+ if ( hw.isActive() )
+ return hw;
+ }
+ return null;
+ }
+
+ }
+
+ /*
+ * Members
+ */
+
+ protected final List<HardDisk> hdds = new ArrayList<HardDisk>();
+
+ protected final List<OperatingSystem> osList;
+
+ private OperatingSystem os = null;
+
+ protected String displayName = null;
+
+ protected boolean isMachineSnapshot;
+
+ protected final List<ConfigurableOptionGroup> configurableOptions = new ArrayList<>();
+
+ /**
+ * Get operating system of this VM.
+ *
+ * @return operating system of the VM.
+ */
+ public OperatingSystem getOs()
+ {
+ return os;
+ }
+
+ /**
+ * Sets the operating system for the virtualization configuration.
+ *
+ * @param os operating system for the virtualization configuration.
+ */
+ public void setOs( OperatingSystem os )
+ {
+ this.os = os;
+ }
+
+ /**
+ * Get all hard disks of this VM.
+ *
+ * @return list of hard disks of the VM.
+ */
+ public List<HardDisk> getHdds()
+ {
+ return Collections.unmodifiableList( hdds );
+ }
+
+ /**
+ * Get display name of VM.
+ *
+ * @return display name of the VM.
+ */
+ public String getDisplayName()
+ {
+ return displayName;
+ }
+
+ public boolean isMachineSnapshot()
+ {
+ return isMachineSnapshot;
+ }
+
+ /**
+ * Return name of file that indicates this VM is currently suspended.
+ * The existence of this file means the VM is currently suspended.
+ * Can be null if this virtualizer doesn't support suspension, or if
+ * it's already known the VM is not in a suspended state.
+ */
+ public String getSuspendedFile()
+ {
+ return null;
+ }
+
+ private class VersionOption extends VirtOptionValue {
+
+ private final Version version;
+
+ public VersionOption( Version version )
+ {
+ super( Integer.toString( version.getVersion() ), version.getName() );
+ this.version = version;
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ try {
+ return getVirtualizerVersion().equals( version );
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void apply()
+ {
+ setVirtualizerVersion( version );
+ }
+ }
+
+ /*
+ * Methods
+ */
+
+ public VirtualizationConfiguration( Virtualizer virtualizer, List<OperatingSystem> osList )
+ {
+ this.virtualizer = virtualizer;
+
+ if ( osList == null ) {
+ // create empty operating system list if none is specified
+ this.osList = new ArrayList<OperatingSystem>();
+ } else {
+ this.osList = osList;
+ }
+
+ // register virtual hardware models for graphical editing of virtual devices (GPU, sound, USB, ...)
+ final List<Version> availables = this.getVirtualizer().getSupportedVersions();
+ //Collections.sort( availables ); // XXX WTF? How did this not break before? It's an unmodifiable collection
+ if ( availables != null ) {
+ // XXX List is null for qemu?
+ List<VirtOptionValue> list = new ArrayList<>();
+ for ( Version ver : availables ) {
+ list.add( new VersionOption( ver ) );
+ }
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.HW_VERSION, list ) );
+ }
+ this.registerVirtualHW();
+ }
+
+ /**
+ * Returns a VmMetaData instance of the given machine description given as file
+ *
+ * @param osList List of supported operating systems
+ * @param file VM's machine description file to get the metadata instance from
+ * @return VmMetaData object representing the relevant parts of the given machine description
+ * @throws IOException failed to read machine description from specified file.
+ */
+ public static VirtualizationConfiguration getInstance( List<OperatingSystem> osList, File file )
+ throws IOException
+ {
+ try {
+ return new VirtualizationConfigurationVmware( osList, file );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a VMware file", e );
+ }
+ try {
+ return new VirtualizationConfigurationVirtualBox( osList, file );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a VirtualBox file", e );
+ }
+ try {
+ return new VirtualizationConfigurationQemu( osList, file );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a Libvirt file", e );
+ }
+ try {
+ return new VirtualizationConfigurationDocker( osList, file );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a tar.gz file, for docker container", e );
+ }
+
+ LOGGER.error( "Could not detect any known virtualizer format" );
+ return null;
+ }
+
+ /**
+ * Returns a VmMetaData instance of the given machine description given as a byte array
+ *
+ * @param osList List of supported operating systems
+ * @param vmContent VM's machine description as byte array (e.g. stored in DB)
+ * @param length length of the byte array given as vmContent
+ * @return VmMetaData object representing the relevant parts of the given machine description
+ * @throws IOException failed to read machine description from specified byte stream.
+ * @throws VirtualizationConfigurationException
+ */
+ public static VirtualizationConfiguration getInstance( List<OperatingSystem> osList, byte[] vmContent,
+ int length )
+ throws IOException, VirtualizationConfigurationException
+ {
+ try {
+ return new VirtualizationConfigurationVmware( osList, vmContent, length );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a VMware file", e );
+ }
+ try {
+ return new VirtualizationConfigurationDocker( osList, vmContent, length );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a tar.gz file, for docker container", e );
+ }
+ try {
+ return new VirtualizationConfigurationVirtualBox( osList, vmContent, length );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a VirtualBox file", e );
+ }
+ try {
+ return new VirtualizationConfigurationQemu( osList, vmContent, length );
+ } catch ( VirtualizationConfigurationException e ) {
+ LOGGER.debug( "Not a Libvirt file", e );
+ }
+ throw new VirtualizationConfigurationException( "Unknown virtualizer config format" );
+ }
+
+ /**
+ * Returns the file name extension for the virtualization configuration file.
+ *
+ * @return file name extension for the virtualization configuration file.
+ */
+ public abstract String getFileNameExtension();
+
+ public abstract boolean addEmptyHddTemplate();
+
+ public abstract boolean addHddTemplate( File diskImage, String hddMode, String redoDir );
+
+ public abstract boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir );
+
+ public abstract boolean addDefaultNat();
+
+ public abstract void setOs( String vendorOsId );
+
+ public abstract boolean addDisplayName( String name );
+
+ public abstract boolean addRam( int mem );
+
+ public abstract void addFloppy( int index, String image, boolean readOnly );
+
+ public abstract boolean addCdrom( String image );
+
+ public abstract boolean addCpuCoreCount( int nrOfCores );
+
+ public abstract void disableUsb();
+
+ public abstract void setVirtualizerVersion( Version type );
+
+ public abstract Version getVirtualizerVersion();
+
+ public abstract byte[] getConfigurationAsByteArray();
+
+ public String getConfigurationAsString()
+ {
+ return new String( this.getConfigurationAsByteArray(), StandardCharsets.UTF_8 );
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getConfigurationAsString();
+ }
+
+ public abstract boolean addEthernet( EtherType type );
+
+ public Virtualizer getVirtualizer()
+ {
+ return this.virtualizer;
+ }
+
+ /**
+ * Validates the virtualization configuration and reports errors if its content is not a valid
+ * virtualization configuration.
+ *
+ * @throws VirtualizationConfigurationException validation of the virtualization configuration
+ * failed.
+ */
+ public abstract void validate() throws VirtualizationConfigurationException;
+
+ /**
+ * Transforms the virtualization configuration in terms of a privacy filter to filter out
+ * sensitive information like name of users in absolute paths.
+ *
+ * @throws VirtualizationConfigurationException transformation of the virtualization
+ * configuration failed.
+ */
+ public abstract void transformPrivacy() throws VirtualizationConfigurationException;
+
+ /**
+ * Transforms the virtualization configuration applying options that are desired when locally
+ * editing a virtualized system (e.g. disables automatic DPI scaling).
+ *
+ * @throws VirtualizationConfigurationException transformation of the virtualization
+ * configuration failed.
+ */
+ public abstract void transformEditable() throws VirtualizationConfigurationException;
+
+ /**
+ * Transforms the virtualization configuration applying options that are desired when running a
+ * virtualized system in a stateless manner.
+ *
+ * @throws VirtualizationConfigurationException transformation of the virtualization
+ * configuration failed.
+ */
+ public abstract void transformNonPersistent() throws VirtualizationConfigurationException;
+
+ /**
+ * Function used to register virtual devices.
+ */
+ public abstract void registerVirtualHW();
+
+ /**
+ * Get all config options this virtualizer supports, with all available options.
+ */
+ public List<ConfigurableOptionGroup> getConfigurableOptions()
+ {
+ return Collections.unmodifiableList( configurableOptions );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationDocker.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationDocker.java
new file mode 100644
index 0000000..3163ed2
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationDocker.java
@@ -0,0 +1,174 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.virtualizer.VirtualizerDocker;
+
+public class VirtualizationConfigurationDocker extends VirtualizationConfiguration {
+
+ /**
+ * File name extension for Docker virtualization configuration files.
+ */
+ private static final String FILE_NAME_EXTENSION = null;
+
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfigurationDocker.class);
+
+ /**
+ * containerDefinition is a serialized tar.gz archive and represents a
+ * ContainerDefinition. This archive contains a serialized Container Recipe (e.g. Dockerfile)
+ * and a ContainerMeta witch is serialized as a json file.
+ * <p>
+ * See ContainerDefintion in tutor-module (bwsuite).
+ * <p>
+ * This field is in vm context the machine description e.g. vmware = vmx.
+ * This field will be stored in table imageversion.virtualizerconfig
+ */
+ private byte[] containerDefinition;
+
+ public VirtualizationConfigurationDocker(List<OperatingSystem> osList, File file) throws VirtualizationConfigurationException {
+ super(new VirtualizerDocker(), osList);
+
+ BufferedInputStream bis = null;
+
+ try {
+ bis = new BufferedInputStream(new FileInputStream(file));
+ containerDefinition = new byte[(int) file.length()];
+ bis.read(containerDefinition);
+
+ checkIsTarGz();
+ } catch (IOException | VirtualizationConfigurationException e) {
+ LOGGER.error("Couldn't read dockerfile", e);
+ } finally {
+ try {
+ bis.close();
+ } catch ( IOException e ) {
+ LOGGER.warn( "Could not close the input stream!" );
+ }
+ }
+ }
+
+ public VirtualizationConfigurationDocker(List<OperatingSystem> osList, byte[] vmContent, int length)
+ throws VirtualizationConfigurationException {
+ super(new VirtualizerDocker(), osList);
+
+ containerDefinition = vmContent;
+
+ checkIsTarGz();
+ }
+
+ /*
+ TODO This is just a simple check to prevent the workflow from considering any content as acceptable.
+ */
+ /**
+ * Checks if the first two bytes of the content identifies a tar.gz archive.
+ * The first byte is 31 == 0x1f, the second byte has to be -117 == 0x8b.
+ *
+ * @throws VirtualizationConfigurationException
+ */
+ private void checkIsTarGz() throws VirtualizationConfigurationException {
+ if (!((31 == containerDefinition[0]) && (-117 == containerDefinition[1]))) {
+ LOGGER.debug("Not Supported Content.");
+ throw new VirtualizationConfigurationException(
+ "DockerMetaDataDummy: Not tar.gz encoded content!");
+ }
+ }
+
+ @Override public void transformEditable() throws VirtualizationConfigurationException {
+
+ }
+
+ @Override
+ public void transformPrivacy() throws VirtualizationConfigurationException {
+ }
+
+ @Override
+ public boolean addEmptyHddTemplate() {
+ return true;
+ }
+
+ @Override public boolean addHddTemplate(File diskImage, String hddMode, String redoDir) {
+ return true;
+ }
+
+ @Override public boolean addHddTemplate(String diskImagePath, String hddMode, String redoDir) {
+ return false;
+ }
+
+ @Override public boolean addDefaultNat() {
+ return true;
+ }
+
+ @Override public void setOs(String vendorOsId) {
+
+ }
+
+ @Override public boolean addDisplayName(String name) {
+ return true;
+ }
+
+ @Override public boolean addRam(int mem) {
+ return true;
+ }
+
+ @Override public void addFloppy(int index, String image, boolean readOnly) {
+
+ }
+
+ @Override public boolean addCdrom(String image) {
+ return true;
+ }
+
+ @Override public boolean addCpuCoreCount(int nrOfCores) {
+ return true;
+ }
+
+ @Override public void setVirtualizerVersion( Version type )
+ {
+ }
+
+ @Override public Version getVirtualizerVersion()
+ {
+ return null;
+ }
+
+ @Override public byte[] getConfigurationAsByteArray() {
+ return this.containerDefinition;
+ }
+
+ @Override public boolean addEthernet(EtherType type) {
+ return true;
+ }
+
+ @Override public void transformNonPersistent() throws VirtualizationConfigurationException {
+
+ }
+
+ @Override public void registerVirtualHW() {
+
+ }
+
+ @Override
+ public String getFileNameExtension() {
+ return VirtualizationConfigurationDocker.FILE_NAME_EXTENSION;
+ }
+
+ @Override
+ public void validate() throws VirtualizationConfigurationException
+ {
+ }
+
+ @Override
+ public void disableUsb()
+ {
+ // Not applicable, maybe some day via USBguard?
+ }
+
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationException.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationException.java
new file mode 100644
index 0000000..2d401b1
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationException.java
@@ -0,0 +1,13 @@
+package org.openslx.virtualization.configuration;
+
+public class VirtualizationConfigurationException extends Exception
+{
+ /**
+ * Version for serialization.
+ */
+ private static final long serialVersionUID = 5794121065945636839L;
+
+ public VirtualizationConfigurationException(String message) {
+ super(message);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java
new file mode 100644
index 0000000..6c62a96
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java
@@ -0,0 +1,903 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.firmware.FirmwareException;
+import org.openslx.firmware.QemuFirmwareUtil;
+import org.openslx.libvirt.domain.Domain;
+import org.openslx.libvirt.domain.DomainUtils;
+import org.openslx.libvirt.domain.device.ControllerUsb;
+import org.openslx.libvirt.domain.device.Disk.BusType;
+import org.openslx.libvirt.domain.device.Disk.StorageType;
+import org.openslx.libvirt.domain.device.DiskCdrom;
+import org.openslx.libvirt.domain.device.DiskFloppy;
+import org.openslx.libvirt.domain.device.DiskStorage;
+import org.openslx.libvirt.domain.device.Graphics;
+import org.openslx.libvirt.domain.device.GraphicsSpice;
+import org.openslx.libvirt.domain.device.Interface;
+import org.openslx.libvirt.domain.device.Sound;
+import org.openslx.libvirt.domain.device.Video;
+import org.openslx.libvirt.libosinfo.LibOsInfo;
+import org.openslx.libvirt.libosinfo.os.Os;
+import org.openslx.libvirt.xml.LibvirtXmlDocumentException;
+import org.openslx.libvirt.xml.LibvirtXmlSerializationException;
+import org.openslx.libvirt.xml.LibvirtXmlValidationException;
+import org.openslx.util.LevenshteinDistance;
+import org.openslx.util.Util;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.hardware.VirtOptionValue;
+import org.openslx.virtualization.hardware.ConfigurationGroups;
+import org.openslx.virtualization.hardware.Ethernet;
+import org.openslx.virtualization.hardware.SoundCard;
+import org.openslx.virtualization.hardware.Usb;
+import org.openslx.virtualization.virtualizer.VirtualizerQemu;
+
+/**
+ * Virtual machine configuration (managed by Libvirt) for the QEMU hypervisor.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizationConfigurationQemu extends VirtualizationConfiguration
+{
+ /**
+ * Name of the network bridge for the LAN.
+ */
+ public static final String NETWORK_BRIDGE_LAN_DEFAULT = "br0";
+
+ /**
+ * Name of the network bridge for the default NAT network.
+ */
+ public static final String NETWORK_BRIDGE_NAT_DEFAULT = "nat1";
+
+ /**
+ * Name of the network for the isolated host network (host only).
+ */
+ public static final String NETWORK_BRIDGE_HOST_ONLY_DEFAULT = "vsw2";
+
+ /**
+ * Default physical CDROM drive of the hypervisor host.
+ */
+ public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0";
+
+ /**
+ * File name extension for QEMU (Libvirt) virtualization configuration files.
+ */
+ public static final String FILE_NAME_EXTENSION = "xml";
+
+ /**
+ * Libvirt XML configuration file to modify configuration of virtual machine for QEMU.
+ */
+ private Domain vmConfig = null;
+
+ /**
+ * Creates new virtual machine configuration (managed by Libvirt) for the QEMU hypervisor.
+ *
+ * @param osList list of operating systems.
+ * @param file image file for the QEMU hypervisor.
+ *
+ * @throws VirtualizationConfigurationException Libvirt XML configuration cannot be processed.
+ */
+ public VirtualizationConfigurationQemu( List<OperatingSystem> osList, File file )
+ throws VirtualizationConfigurationException
+ {
+ super( new VirtualizerQemu(), osList );
+
+ try {
+ // read and parse Libvirt domain XML configuration document
+ this.vmConfig = new Domain( file );
+ } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) {
+ throw new VirtualizationConfigurationException( e.getLocalizedMessage() );
+ }
+
+ // parse VM config and initialize fields of QemuMetaData class
+ this.parseVmConfig();
+ }
+
+ /**
+ * Creates new virtual machine configuration (managed by Libvirt) for the QEMU hypervisor.
+ *
+ * @param osList list of operating systems.
+ * @param vmContent file content for the QEMU hypervisor.
+ * @param length number of bytes of the file content.
+ *
+ * @throws VirtualizationConfigurationException Libvirt XML configuration cannot be processed.
+ */
+ public VirtualizationConfigurationQemu( List<OperatingSystem> osList, byte[] vmContent, int length )
+ throws VirtualizationConfigurationException
+ {
+ super( new VirtualizerQemu(), osList );
+
+ try {
+ // read and parse Libvirt domain XML configuration document
+ this.vmConfig = new Domain( new String( vmContent, StandardCharsets.UTF_8 ) );
+ } catch ( LibvirtXmlDocumentException | LibvirtXmlSerializationException | LibvirtXmlValidationException e ) {
+ throw new VirtualizationConfigurationException( e.getLocalizedMessage() );
+ }
+
+ // parse VM config and initialize fields of QemuMetaData class
+ this.parseVmConfig();
+ }
+
+ /**
+ * Parses Libvirt domain XML configuration to initialize QEMU metadata.
+ */
+ private void parseVmConfig()
+ {
+ // set display name of VM
+ this.displayName = vmConfig.getName();
+
+ // this property cannot be checked with the Libvirt domain XML configuration
+ // to check if machine is in a paused/suspended state, look in the QEMU qcow2 image for snapshots and machine states
+ this.isMachineSnapshot = false;
+
+ // add HDDs, SSDs to QEMU metadata
+ for ( DiskStorage storageDiskDevice : this.vmConfig.getDiskStorageDevices() ) {
+ this.addHddMetaData( storageDiskDevice );
+ }
+
+ // detect the operating system from the optional embedded libosinfo metadata
+ this.setOs( this.vmConfig.getLibOsInfoOsId() );
+ }
+
+ /**
+ * Adds an existing and observed storage disk device to the HDD metadata.
+ *
+ * @param storageDiskDevice existing and observed storage disk that should be added to the
+ * metadata.
+ */
+ private void addHddMetaData( DiskStorage storageDiskDevice )
+ {
+ String hddChipsetModel = null;
+ DriveBusType hddChipsetBus = VirtualizationConfigurationQemuUtils
+ .convertBusType( storageDiskDevice.getBusType() );
+ String hddImagePath = storageDiskDevice.getStorageSource();
+
+ this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) );
+ }
+
+ /**
+ * Detects the operating system by the specified libosinfo operating system identifier.
+ *
+ * @param osId libosinfo operating system identifier.
+ */
+ private OperatingSystem detectOperatingSystem( String osId )
+ {
+ OperatingSystem os = null;
+
+ if ( osId != null && !osId.isEmpty() ) {
+ // lookup operating system identifier in the libosinfo database
+ final Os osLookup = LibOsInfo.lookupOs( osId );
+
+ // check if entry in the database was found
+ if ( osLookup != null ) {
+ // operating system entry was found
+ // so determine OpenSLX OS name with the smallest distance to the libosinfo OS name
+ final LevenshteinDistance distance = new LevenshteinDistance( 2, 1, 1 );
+ int smallestDistance = Integer.MAX_VALUE;
+
+ // get name of the OS and combine it with the optional available architecture
+ String osLookupOsName = osLookup.getName();
+ final int osArchSize = VirtualizationConfigurationQemuUtils.getOsArchSize( this.vmConfig.getOsArch() );
+
+ if ( osArchSize > 0 ) {
+ // append architecture size in bit if information is available from the specified architecture
+ osLookupOsName += " (" + osArchSize + " Bit)";
+ }
+
+ for ( final OperatingSystem osCandidate : this.osList ) {
+ final int currentDistance = distance.calculateDistance( osLookupOsName, osCandidate.getOsName() );
+
+ if ( currentDistance < smallestDistance ) {
+ // if the distance is smaller save the current distance and operating system as best candidate
+ smallestDistance = currentDistance;
+ os = osCandidate;
+ }
+ }
+ }
+ }
+
+ return os;
+ }
+
+ @Override
+ public void transformEditable() throws VirtualizationConfigurationException
+ {
+ }
+
+ public void transformOsLoader() throws VirtualizationConfigurationException
+ {
+ final String sourceOsLoader = this.vmConfig.getOsLoader();
+ final String sourceOsArch = this.vmConfig.getOsArch();
+ final String sourceOsMachine = this.vmConfig.getOsMachine();
+
+ // transform OS loader for local editing
+ // check if OS loader is specified
+ if ( sourceOsLoader != null && !sourceOsLoader.isEmpty() ) {
+ // OS loader is specified so transform path to specified firmware path
+ // First, lookup QEMU firmware loader for target
+ String targetOsLoader = null;
+ try {
+ targetOsLoader = QemuFirmwareUtil.lookupTargetOsLoaderDefaultFwSpecDir( sourceOsLoader, sourceOsArch,
+ sourceOsMachine );
+ } catch ( FirmwareException e ) {
+ throw new VirtualizationConfigurationException( e.getLocalizedMessage() );
+ }
+
+ // Second, set target QEMU firmware loader if specified
+ if ( targetOsLoader != null && !targetOsLoader.isEmpty() ) {
+ this.vmConfig.setOsLoader( targetOsLoader );
+ }
+ }
+ }
+
+ @Override
+ public boolean addEmptyHddTemplate()
+ {
+ return this.addHddTemplate( "", null, null );
+ }
+
+ @Override
+ public boolean addHddTemplate( File diskImage, String hddMode, String redoDir )
+ {
+ return this.addHddTemplate( diskImage.getAbsolutePath(), hddMode, redoDir );
+ }
+
+ @Override
+ public boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir )
+ {
+ int index = this.vmConfig.getDiskStorageDevices().size() - 1;
+ index = ( index > 0 ) ? index : 0;
+ return this.addHddTemplate( index, diskImagePath, hddMode, redoDir );
+ }
+
+ /**
+ * Adds hard disk drive (HDD) to the QEMU virtual machine configuration.
+ *
+ * @param index current index of HDD to be added to the virtual machine configuration.
+ * @param diskImagePath path to the virtual disk image for the HDD.
+ * @param hddMode operation mode of the HDD.
+ * @param redoDir directory for the redo log if an independent non-persistent
+ * <code>hddMode</code> is set.
+ * @return result state of adding the HDD.
+ */
+ public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir )
+ {
+ ArrayList<DiskStorage> storageDiskDevices = this.vmConfig.getDiskStorageDevices();
+ DiskStorage storageDiskDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( storageDiskDevices, index );
+
+ if ( storageDiskDevice == null ) {
+ // HDD does not exist, so create new storage (HDD) device
+ final BusType devBusType = BusType.VIRTIO;
+ final String targetDevName = VirtualizationConfigurationQemuUtils.createDeviceName( this.vmConfig,
+ devBusType );
+ storageDiskDevice = this.vmConfig.addDiskStorageDevice();
+ storageDiskDevice.setReadOnly( false );
+ storageDiskDevice.setBusType( devBusType );
+ storageDiskDevice.setTargetDevice( targetDevName );
+
+ if ( diskImagePath == null || diskImagePath.isEmpty() ) {
+ storageDiskDevice.removeStorage();
+ } else {
+ storageDiskDevice.setStorage( StorageType.FILE, diskImagePath );
+ }
+
+ // add new created HDD to the metadata of the QemuMetaData object, too
+ this.addHddMetaData( storageDiskDevice );
+ } else {
+ // HDD exists, so update existing storage (HDD) device
+ if ( diskImagePath == null || diskImagePath.isEmpty() ) {
+ storageDiskDevice.removeStorage();
+ } else {
+ storageDiskDevice.setStorage( StorageType.FILE, diskImagePath );
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean addDefaultNat()
+ {
+ // since network interface was not filtered during VM upload,
+ // do not add or configure any network interface here
+ return true;
+ }
+
+ @Override
+ public void setOs( String vendorOsId )
+ {
+ final OperatingSystem os = this.detectOperatingSystem( vendorOsId );
+ this.setOs( os );
+ }
+
+ @Override
+ public boolean addDisplayName( String name )
+ {
+ this.vmConfig.setName( name );
+
+ final boolean statusName = this.vmConfig.getName().equals( name );
+
+ return statusName;
+ }
+
+ @Override
+ public boolean addRam( int mem )
+ {
+ // convert given memory in MiB to memory in bytes for Libvirt XML Domain API functions
+ final BigInteger memory = DomainUtils.decodeMemory( Integer.toString( mem ), "MiB" );
+
+ this.vmConfig.setMemory( memory );
+ this.vmConfig.setCurrentMemory( memory );
+
+ final boolean isMemorySet = this.vmConfig.getMemory().equals( memory );
+ final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().equals( memory );
+
+ return isMemorySet && isCurrentMemorySet;
+ }
+
+ @Override
+ public void addFloppy( int index, String image, boolean readOnly )
+ {
+ ArrayList<DiskFloppy> floppyDiskDevices = this.vmConfig.getDiskFloppyDevices();
+ DiskFloppy floppyDiskDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( floppyDiskDevices, index );
+
+ if ( floppyDiskDevice == null ) {
+ // floppy device does not exist, so create new floppy device
+ final BusType devBusType = BusType.FDC;
+ final String targetDevName = VirtualizationConfigurationQemuUtils.createDeviceName( this.vmConfig,
+ devBusType );
+ floppyDiskDevice = this.vmConfig.addDiskFloppyDevice();
+ floppyDiskDevice.setBusType( devBusType );
+ floppyDiskDevice.setTargetDevice( targetDevName );
+ floppyDiskDevice.setReadOnly( readOnly );
+
+ if ( image == null || image.isEmpty() ) {
+ floppyDiskDevice.removeStorage();
+ } else {
+ floppyDiskDevice.setStorage( StorageType.FILE, image );
+ }
+ } else {
+ // floppy device exists, so update existing floppy device
+ floppyDiskDevice.setReadOnly( readOnly );
+
+ if ( image == null || image.isEmpty() ) {
+ floppyDiskDevice.removeStorage();
+ } else {
+ floppyDiskDevice.setStorage( StorageType.FILE, image );
+ }
+ }
+ }
+
+ @Override
+ public boolean addCdrom( String image )
+ {
+ int index = this.vmConfig.getDiskCdromDevices().size() - 1;
+ index = ( index > 0 ) ? index : 0;
+ return this.addCdrom( index, image );
+ }
+
+ /**
+ * Adds CDROM drive to the QEMU virtual machine configuration.
+ *
+ * @param index current index of CDROM drive to be added to the virtual machine configuration.
+ * @param image path to a virtual image that will be inserted as CDROM into the drive.
+ * @return result state of adding the CDROM drive.
+ */
+ public boolean addCdrom( int index, String image )
+ {
+ ArrayList<DiskCdrom> cdromDiskDevices = this.vmConfig.getDiskCdromDevices();
+ DiskCdrom cdromDiskDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( cdromDiskDevices, index );
+
+ if ( cdromDiskDevice == null ) {
+ // CDROM device does not exist, so create new CDROM device
+ final BusType devBusType = BusType.SATA;
+ final String targetDevName = VirtualizationConfigurationQemuUtils.createDeviceName( this.vmConfig,
+ devBusType );
+ cdromDiskDevice = this.vmConfig.addDiskCdromDevice();
+ cdromDiskDevice.setBusType( devBusType );
+ cdromDiskDevice.setTargetDevice( targetDevName );
+ cdromDiskDevice.setReadOnly( true );
+
+ if ( image == null ) {
+ cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE );
+ } else {
+ if ( image.isEmpty() ) {
+ cdromDiskDevice.removeStorage();
+ } else {
+ cdromDiskDevice.setStorage( StorageType.FILE, image );
+ }
+ }
+ } else {
+ // CDROM device exists, so update existing CDROM device
+ cdromDiskDevice.setReadOnly( true );
+
+ if ( image == null ) {
+ cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE );
+ } else {
+ if ( image.isEmpty() ) {
+ cdromDiskDevice.removeStorage();
+ } else {
+ cdromDiskDevice.setStorage( StorageType.FILE, image );
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean addCpuCoreCount( int nrOfCores )
+ {
+ this.vmConfig.setVCpu( nrOfCores );
+
+ boolean isVCpuSet = this.vmConfig.getVCpu() == nrOfCores;
+
+ return isVCpuSet;
+ }
+
+ class QemuGfxModel extends VirtOptionValue
+ {
+ public QemuGfxModel( Video.Model model, String displayName )
+ {
+ super( model.toString(), displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ final ArrayList<Video> videoDevices = vmConfig.getVideoDevices();
+
+ if ( videoDevices.isEmpty() ) {
+ // add new video device with disabled acceleration to VM configuration
+ final Video videoDevice = vmConfig.addVideoDevice();
+ videoDevice.setModel( Video.Model.fromString( this.getId() ) );
+ videoDevice.set2DAcceleration( false );
+ videoDevice.set3DAcceleration( false );
+ } else {
+ // change graphics model of existing video devices
+ for ( final Video videoDevice : videoDevices ) {
+ // remove all old model-related XML attributes
+ videoDevice.removeXmlElement( "model" );
+ // set new model
+ videoDevice.setModel( Video.Model.fromString( this.getId() ) );
+ }
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ final ArrayList<Video> videoDevices = vmConfig.getVideoDevices();
+ boolean isActive = true;
+
+ for ( final Video videoDevice : videoDevices ) {
+ if ( !videoDevice.getModel().toString().equals( this.getId() ) ) {
+ isActive = false;
+ break;
+ }
+ }
+
+ return isActive;
+ }
+ }
+
+ class QemuGfxType extends VirtOptionValue
+ {
+ public QemuGfxType( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ ArrayList<Graphics> graphicDevices = vmConfig.getGraphicDevices();
+ ArrayList<Video> videoDevices = vmConfig.getVideoDevices();
+ final boolean accelerationEnabled = this.id.equals( "true" );
+
+ boolean acceleratedGraphicsAvailable = false;
+
+ if ( graphicDevices.isEmpty() ) {
+ // add new graphics device with enabled acceleration to VM configuration
+ GraphicsSpice graphicSpiceDevice = vmConfig.addGraphicsSpiceDevice();
+ graphicSpiceDevice.setOpenGl( true );
+ acceleratedGraphicsAvailable = true;
+ } else {
+ // enable graphic acceleration of existing graphics devices
+ for ( Graphics graphicDevice : graphicDevices ) {
+ // set hardware acceleration for SPICE based graphics output
+ // other graphic devices do not support hardware acceleration
+ if ( graphicDevice instanceof GraphicsSpice ) {
+ GraphicsSpice.class.cast( graphicDevice ).setOpenGl( true );
+ acceleratedGraphicsAvailable = true;
+ }
+ }
+ }
+
+ // only configure hardware acceleration of video card(s) if graphics with hardware acceleration is available
+ if ( acceleratedGraphicsAvailable ) {
+ if ( videoDevices.isEmpty() ) {
+ // add new video device with enabled acceleration to VM configuration
+ Video videoDevice = vmConfig.addVideoDevice();
+ videoDevice.setModel( Video.Model.VIRTIO );
+ videoDevice.set2DAcceleration( true );
+ videoDevice.set3DAcceleration( true );
+ } else {
+ // enable graphic acceleration of existing graphics and video devices
+ for ( Video videoDevice : videoDevices ) {
+ // set hardware acceleration for Virtio GPUs
+ // other GPUs do not support hardware acceleration
+ if ( videoDevice.getModel() == Video.Model.VIRTIO ) {
+ videoDevice.set2DAcceleration( accelerationEnabled );
+ videoDevice.set3DAcceleration( accelerationEnabled );
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ ArrayList<Graphics> graphicsDevices = vmConfig.getGraphicDevices();
+ ArrayList<Video> videoDevices = vmConfig.getVideoDevices();
+
+ boolean acceleratedGraphicsAvailable = false;
+ boolean acceleratedVideoDevAvailable = false;
+
+ // search for hardware accelerated graphics
+ for ( Graphics graphicDevice : graphicsDevices ) {
+ // only SPICE based graphic devices support hardware acceleration
+ if ( graphicDevice instanceof GraphicsSpice ) {
+ acceleratedGraphicsAvailable = true;
+ break;
+ }
+ }
+
+ // search for hardware accelerated video devices
+ for ( Video videoDevice : videoDevices ) {
+ // only Virtio based video devices support hardware acceleration
+ if ( videoDevice.getModel() == Video.Model.VIRTIO ) {
+ acceleratedVideoDevAvailable = true;
+ break;
+ }
+ }
+
+ // hardware acceleration is available if at least one accelerated graphics and video device is available
+ if ( acceleratedGraphicsAvailable && acceleratedVideoDevAvailable ) {
+ return this.id.equals( "true" );
+ } else {
+ return this.id.equals( "false" );
+ }
+ }
+ }
+
+ @Override
+ public void setVirtualizerVersion( Version type )
+ {
+ if ( type != null ) {
+ final String osMachine = this.vmConfig.getOsMachine();
+ final String osMachineName = VirtualizationConfigurationQemuUtils.getOsMachineName( osMachine );
+
+ if ( osMachineName != null && !osMachineName.isEmpty() ) {
+ final String modifiedOsMachineVersion = VirtualizationConfigurationQemuUtils.getOsMachineVersion( type );
+ final String modifiedOsMachine = VirtualizationConfigurationQemuUtils.getOsMachine( osMachineName,
+ modifiedOsMachineVersion );
+ this.vmConfig.setOsMachine( modifiedOsMachine );
+ }
+ }
+ }
+
+ @Override
+ public Version getVirtualizerVersion()
+ {
+ final String osMachine = this.vmConfig.getOsMachine();
+ final Version uncheckedVersion = VirtualizationConfigurationQemuUtils.getOsMachineVersion( osMachine );
+ final Version checkedVersion;
+
+ if ( uncheckedVersion == null ) {
+ checkedVersion = null;
+ } else {
+ checkedVersion = Version.getInstanceByMajorMinorFromVersions( uncheckedVersion.getMajor(),
+ uncheckedVersion.getMinor(), this.getVirtualizer().getSupportedVersions() );
+ }
+
+ return checkedVersion;
+ }
+
+ class QemuNicModel extends VirtOptionValue
+ {
+ private final int cardIndex;
+
+ public QemuNicModel( int cardIndex, Interface.Model model, String displayName )
+ {
+ super( model.toString(), displayName ); // XXX: toString/fromString would disappear if
+ this.cardIndex = cardIndex; // this were AbstractConfigurableOption<T extends Enum<T>>
+ }
+
+ @Override
+ public void apply()
+ {
+ ArrayList<Interface> networkDevices = vmConfig.getInterfaceDevices();
+ Interface networkDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( networkDevices, cardIndex );
+
+ if ( networkDevice != null ) {
+ networkDevice.setModel( Interface.Model.fromString( id ) );
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ ArrayList<Interface> networkDevices = vmConfig.getInterfaceDevices();
+ Interface networkDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( networkDevices, cardIndex );
+
+ if ( networkDevice == null ) {
+ // network interface device is not present
+ return Util.isEmptyString( this.id ); // XXX: would be more explicit with enum.NONE
+ }
+ // get model of existing network interface device
+ Interface.Model networkDeviceModel = networkDevice.getModel();
+ if ( networkDeviceModel == null ) {
+ return Util.isEmptyString( this.id ); // see above
+ }
+ // Success
+ return networkDeviceModel.toString().equals( this.id ); // XXX: enum would allow simple ==
+ }
+ }
+
+ class QemuSoundCardModel extends VirtOptionValue
+ {
+ public QemuSoundCardModel( Sound.Model id, String displayName )
+ {
+ super( id.toString(), displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ ArrayList<Sound> soundDevices = vmConfig.getSoundDevices();
+ Sound.Model soundDeviceModel = Sound.Model.fromString( this.id );
+
+ if ( soundDevices.isEmpty() ) {
+ // create new sound device with 'soundDeviceModel' hardware
+ Sound soundDevice = vmConfig.addSoundDevice();
+ soundDevice.setModel( soundDeviceModel );
+ } else {
+ // update sound device model type of existing sound devices
+ for ( Sound soundDevice : soundDevices ) {
+ soundDevice.setModel( soundDeviceModel );
+ }
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ ArrayList<Sound> soundDevices = vmConfig.getSoundDevices();
+
+ if ( soundDevices.isEmpty() ) {
+ // the VM configuration does not contain a sound card device
+ return Util.isEmptyString( this.id );
+ }
+ // the VM configuration at least one sound card device, so return the type of the first one
+ Sound.Model soundDeviceModel = soundDevices.get( 0 ).getModel();
+ return soundDeviceModel != null && soundDeviceModel.toString().equals( this.id );
+ }
+
+ }
+
+ class QemuUsbSpeed extends VirtOptionValue
+ {
+ public QemuUsbSpeed( ControllerUsb.Model id, String displayName )
+ {
+ super( id.toString(), displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ ArrayList<ControllerUsb> usbControllerDevices = vmConfig.getUsbControllerDevices();
+ ControllerUsb.Model usbControllerModel = ControllerUsb.Model.fromString( this.id );
+
+ if ( usbControllerDevices.isEmpty() ) {
+ // add new USB controller with specified speed 'usbControllerModel'
+ ControllerUsb usbControllerDevice = vmConfig.addControllerUsbDevice();
+ usbControllerDevice.setModel( usbControllerModel );
+ } else {
+ // update model of all USB controller devices to support the maximum speed
+ for ( ControllerUsb usbControllerDevice : usbControllerDevices ) {
+ usbControllerDevice.setModel( usbControllerModel );
+ }
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ ArrayList<ControllerUsb> usbControllerDevices = vmConfig.getUsbControllerDevices();
+
+ for ( ControllerUsb usbControllerDevice : usbControllerDevices ) {
+ ControllerUsb.Model usbControllerModel = usbControllerDevice.getModel();
+
+ // TODO Need something to map from chip to usb speed. But this is conceptually broken anyways since
+ // it's modeled after vmware, where you only cannot configure different controllers at the same time
+ // anyways XXX
+ if ( usbControllerModel.toString().equals( this.id ) )
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ public byte[] getConfigurationAsByteArray()
+ {
+ String configuration = this.vmConfig.toString();
+
+ if ( configuration == null ) {
+ return null;
+ } else {
+ // append newline at the end of the XML content to match the structure of an original Libvirt XML file
+ //configuration += LibvirtXmlDocument.LINE_SEPARATOR;
+ return configuration.getBytes( StandardCharsets.UTF_8 );
+ }
+ }
+
+ @Override
+ public boolean addEthernet( EtherType type )
+ {
+ int index = this.vmConfig.getInterfaceDevices().size() - 1;
+ index = ( index > 0 ) ? index : 0;
+ return this.addEthernet( index, type );
+ }
+
+ /**
+ * Adds an ethernet card to the QEMU virtual machine configuration.
+ *
+ * @param index current index of the ethernet card to be added to the virtual machine
+ * configuration.
+ * @param type card model of the ethernet card.
+ * @return result state of adding the ethernet card.
+ */
+ public boolean addEthernet( int index, EtherType type )
+ {
+ ArrayList<Interface> interfaceDevices = this.vmConfig.getInterfaceDevices();
+ Interface interfaceDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( interfaceDevices, index );
+
+ final Interface.Model defaultNetworkDeviceModel = Interface.Model.VIRTIO_NET_PCI;
+
+ if ( interfaceDevice == null ) {
+ // network interface device does not exist, so create new network interface device
+ switch ( type ) {
+ case BRIDGED:
+ // add network bridge interface device
+ interfaceDevice = this.vmConfig.addInterfaceBridgeDevice();
+ interfaceDevice.setModel( defaultNetworkDeviceModel );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_LAN_DEFAULT );
+ break;
+ case HOST_ONLY:
+ // add network interface device with link to the isolated host network
+ interfaceDevice = this.vmConfig.addInterfaceBridgeDevice();
+ interfaceDevice.setModel( defaultNetworkDeviceModel );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_HOST_ONLY_DEFAULT );
+ break;
+ case NAT:
+ // add network interface device with link to the NAT network
+ interfaceDevice = this.vmConfig.addInterfaceBridgeDevice();
+ interfaceDevice.setModel( defaultNetworkDeviceModel );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_NAT_DEFAULT );
+ break;
+ }
+ } else {
+ // network interface device exists, so update existing network interface device
+ switch ( type ) {
+ case BRIDGED:
+ interfaceDevice.setType( Interface.Type.BRIDGE );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_LAN_DEFAULT );
+ break;
+ case HOST_ONLY:
+ interfaceDevice.setType( Interface.Type.BRIDGE );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_HOST_ONLY_DEFAULT );
+ break;
+ case NAT:
+ interfaceDevice.setType( Interface.Type.BRIDGE );
+ interfaceDevice.setSource( VirtualizationConfigurationQemu.NETWORK_BRIDGE_NAT_DEFAULT );
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void transformNonPersistent() throws VirtualizationConfigurationException
+ {
+ // NOT implemented yet
+ }
+
+ @Override
+ public void transformPrivacy() throws VirtualizationConfigurationException
+ {
+ // removes all referenced storage files of all specified CDROMs, Floppy drives and HDDs
+ this.vmConfig.removeDiskDevicesStorage();
+
+ // remove specified NVRAM file of OS loader (firmware)
+ this.vmConfig.removeOsNvram();
+ }
+
+ @Override
+ public void registerVirtualHW()
+ {
+ // XXX Add missing qemu-only types/models
+ List<VirtOptionValue> list;
+ // @formatter:off
+ list = new ArrayList<>();
+ //list.add( new QemuSoundCardModel( Sound.Model.NONE, SoundCard.NONE ) ); // XXX TODO
+ list.add( new QemuSoundCardModel( Sound.Model.ICH9, SoundCard.DEFAULT ) );
+ list.add( new QemuSoundCardModel( Sound.Model.SB16, SoundCard.SOUND_BLASTER ) );
+ list.add( new QemuSoundCardModel( Sound.Model.ES1370, SoundCard.ES ) );
+ list.add( new QemuSoundCardModel( Sound.Model.AC97, SoundCard.AC ) );
+ list.add( new QemuSoundCardModel( Sound.Model.ICH9, SoundCard.HD_AUDIO ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.SOUND_CARD_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new QemuGfxModel( Video.Model.VGA, "VGA" ) );
+ list.add( new QemuGfxModel( Video.Model.QXL, "QXL" ) );
+ list.add( new QemuGfxModel( Video.Model.VMVGA, "VMware VGA" ) );
+ list.add( new QemuGfxModel( Video.Model.VIRTIO, "virtio-gpu" ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.GFX_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new QemuGfxType( "false", "2D" ) );
+ list.add( new QemuGfxType( "true", "3D" ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.GFX_TYPE, list ) );
+
+ list = new ArrayList<>();
+ // XXX Represent NONE; can add missing models now with new approach (add human readable strings)
+ list.add( new QemuNicModel( 0, Interface.Model.VIRTIO, Ethernet.AUTO ) );
+ list.add( new QemuNicModel( 0, Interface.Model.PCNET, Ethernet.PCNETPCI2 ) );
+ list.add( new QemuNicModel( 0, Interface.Model.E1000, Ethernet.E1000 ) );
+ list.add( new QemuNicModel( 0, Interface.Model.E1000E, Ethernet.E1000E ) );
+ list.add( new QemuNicModel( 0, Interface.Model.VMXNET3, Ethernet.VMXNET3 ) );
+ list.add( new QemuNicModel( 0, Interface.Model.VIRTIO_NET_PCI, Ethernet.PARAVIRT ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.NIC_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new QemuUsbSpeed( ControllerUsb.Model.NONE, Usb.NONE ) );
+ list.add( new QemuUsbSpeed( ControllerUsb.Model.ICH9_UHCI1, Usb.USB1_1 ) );
+ list.add( new QemuUsbSpeed( ControllerUsb.Model.ICH9_EHCI1, Usb.USB2_0 ) );
+ list.add( new QemuUsbSpeed( ControllerUsb.Model.QEMU_XHCI, Usb.USB3_0 ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.USB_SPEED, list ) );
+ // @formatter:on
+ }
+
+ @Override
+ public String getFileNameExtension()
+ {
+ return VirtualizationConfigurationQemu.FILE_NAME_EXTENSION;
+ }
+
+ @Override
+ public void validate() throws VirtualizationConfigurationException
+ {
+ try {
+ this.vmConfig.validateXml();
+ } catch ( LibvirtXmlValidationException e ) {
+ throw new VirtualizationConfigurationException( e.getLocalizedMessage() );
+ }
+ }
+
+ @Override
+ public void disableUsb()
+ {
+ new QemuUsbSpeed( ControllerUsb.Model.NONE, Usb.NONE ).apply();
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuUtils.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuUtils.java
new file mode 100644
index 0000000..86216a0
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuUtils.java
@@ -0,0 +1,404 @@
+package org.openslx.virtualization.configuration;
+
+import java.util.ArrayList;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openslx.libvirt.domain.Domain;
+import org.openslx.libvirt.domain.device.Disk;
+import org.openslx.libvirt.domain.device.Disk.BusType;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.configuration.VirtualizationConfiguration.DriveBusType;
+
+/**
+ * Collection of utils to convert data types from bwLehrpool to Libvirt and vice versa.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizationConfigurationQemuUtils
+{
+ /**
+ * Separator symbol between Libvirt/QEMU machine name and machine version.
+ */
+ private static final String OS_MACHINE_NAME_VERSION_SEPARATOR = "-";
+
+ /**
+ * Converts a Libvirt disk device bus type to a VM metadata driver bus type.
+ *
+ * @param busType Libvirt disk device bus type.
+ * @return VM metadata bus type of the disk drive.
+ */
+ public static DriveBusType convertBusType( Disk.BusType busType )
+ {
+ DriveBusType type = null;
+
+ if ( busType == null )
+ return null;
+
+ switch ( busType ) {
+ case IDE:
+ type = DriveBusType.IDE;
+ break;
+ case SATA:
+ type = DriveBusType.SATA;
+ break;
+ case SCSI:
+ type = DriveBusType.SCSI;
+ break;
+ default:
+ type = null;
+ break;
+ }
+
+ return type;
+ }
+
+ /**
+ * Converts a VM metadata driver bus type to a Libvirt disk device bus type.
+ *
+ * @param busType VM metadata bus type of the disk drive.
+ * @return Libvirt disk device bus type.
+ */
+ public static Disk.BusType convertBusType( DriveBusType busType )
+ {
+ Disk.BusType type = null;
+
+ switch ( busType ) {
+ case IDE:
+ type = BusType.IDE;
+ break;
+ case NVME:
+ type = null;
+ break;
+ case SATA:
+ type = BusType.SATA;
+ break;
+ case SCSI:
+ type = BusType.SCSI;
+ break;
+ }
+
+ return type;
+ }
+
+ /**
+ * Returns an item from a given {@link ArrayList}.
+ *
+ * The item is selected by a given index. If the item is not available within the
+ * {@link ArrayList}, <code>null</code> is returned.
+ *
+ * @param <T> type of the {@link ArrayList}.
+ * @param array {@link ArrayList} of type <code>T</code>.
+ * @param index selects the item from the {@link ArrayList}.
+ * @return selected item of the {@link ArrayList}.
+ */
+ public static <T> T getArrayIndex( ArrayList<T> array, int index )
+ {
+ T ret;
+
+ try {
+ ret = array.get( index );
+ } catch ( IndexOutOfBoundsException e ) {
+ ret = null;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Creates an alphabetical device name constructed from a device prefix and a device number.
+ *
+ * @param devicePrefix prefix of the constructed device name.
+ * @param deviceNumber number of the device.
+ * @return alphabetical device name.
+ */
+ private static String createAlphabeticalDeviceName( String devicePrefix, int deviceNumber )
+ {
+ if ( deviceNumber < 0 || deviceNumber >= ( 'z' - 'a' ) ) {
+ String errorMsg = "Device number is out of range to be able to create a valid device name.";
+ throw new IllegalArgumentException( errorMsg );
+ }
+
+ return devicePrefix + Character.valueOf( (char) ( 'a' + deviceNumber ) ).toString();
+ }
+
+ /**
+ * Creates an alphabetical device name for a disk device with a bus <i>type</i> that is unique in
+ * a Libvirt domain XML configuration.
+ *
+ * @param config Libvirt domain XML configuration.
+ * @param type device type for device name.
+ * @return alphabetical device name.
+ */
+ public static String createDeviceName( final Domain config, final BusType type ) throws IllegalArgumentException
+ {
+ final String devicePrefix;
+ final int deviceNumber;
+
+ switch ( type ) {
+ case FDC:
+ devicePrefix = "fd";
+ break;
+ case IDE:
+ devicePrefix = "hd";
+ break;
+ case SATA:
+ devicePrefix = "sd";
+ break;
+ case VIRTIO:
+ devicePrefix = "vd";
+ break;
+ default:
+ return null;
+ }
+
+ final Predicate<Disk> bySpecifiedBusType = d -> d.getBusType() == type;
+ deviceNumber = Long.valueOf( config.getDiskDevices().stream().filter( bySpecifiedBusType ).count() ).intValue();
+
+ return VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( devicePrefix, deviceNumber );
+ }
+
+ /**
+ * Data container to store a Libvirt/QEMU machine name with version information.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+ static class OsMachineNameAndVersion
+ {
+ /**
+ * Stores the machine name.
+ */
+ final private String osMachineName;
+
+ /**
+ * Stores the machine version.
+ */
+ final private Version osMachineVersion;
+
+ /**
+ * Creates a data container for a machine name with version information.
+ *
+ * @param osMachineName name of the machine.
+ * @param osMachineVersion version of the machine.
+ */
+ public OsMachineNameAndVersion( String osMachineName, Version osMachineVersion )
+ {
+ this.osMachineName = osMachineName;
+ this.osMachineVersion = osMachineVersion;
+ }
+
+ /**
+ * Returns the machine name.
+ *
+ * @return machine name.
+ */
+ public String getOsMachineName()
+ {
+ return this.osMachineName;
+ }
+
+ /**
+ * Returns the version information.
+ *
+ * @return version information.
+ */
+ public Version getOsMachineVersion()
+ {
+ return this.osMachineVersion;
+ }
+ }
+
+ /**
+ * Parses a machine name with version information from a Libvirt/QEMU machine description.
+ *
+ * @param osMachine Libvirt/QEMU machine description as {@link String}.
+ * @return data container containing the parsed machine name with version information.
+ */
+ private static OsMachineNameAndVersion parseOsMachineNameAndVersion( String osMachine )
+ {
+ final String osMachineName;
+ final Version osMachineVersion;
+
+ if ( osMachine == null || osMachine.isEmpty() ) {
+ // there is no machine description given, so we can not parse anything
+ osMachineName = null;
+ osMachineVersion = null;
+ } else {
+ // create regular expression based matcher to extract machine name and version number
+ final Pattern osMachineNameAndVersionPattern = Pattern.compile( "^([a-z0-9\\-]+)"
+ + VirtualizationConfigurationQemuUtils.OS_MACHINE_NAME_VERSION_SEPARATOR + "([0-9]+).([0-9]+)$" );
+ final Matcher osMachineNameAndVersionMatcher = osMachineNameAndVersionPattern.matcher( osMachine );
+
+ final boolean matches = osMachineNameAndVersionMatcher.find();
+
+ if ( matches ) {
+ // get results of regular expression based matcher
+ osMachineName = osMachineNameAndVersionMatcher.group( 1 );
+ final String osMachineMajorString = osMachineNameAndVersionMatcher.group( 2 );
+ final String osMachineMinorString = osMachineNameAndVersionMatcher.group( 3 );
+
+ // create version representation
+ final short osMachineMajor = Short.valueOf( osMachineMajorString );
+ final short osMachineMinor = Short.valueOf( osMachineMinorString );
+ osMachineVersion = new Version( osMachineMajor, osMachineMinor );
+ } else {
+ osMachineName = null;
+ osMachineVersion = null;
+ }
+ }
+
+ return new OsMachineNameAndVersion( osMachineName, osMachineVersion );
+ }
+
+ /**
+ * Parses a machine name from a Libvirt/QEMU machine description.
+ *
+ * @param osMachine Libvirt/QEMU machine description as {@link String}.
+ * @return parsed machine name.
+ */
+ public static String getOsMachineName( String osMachine )
+ {
+ final OsMachineNameAndVersion machineNameAndVersion = VirtualizationConfigurationQemuUtils
+ .parseOsMachineNameAndVersion( osMachine );
+ return machineNameAndVersion.getOsMachineName();
+ }
+
+ /**
+ * Parses a machine version from a Libvirt/QEMU machine description.
+ *
+ * @param osMachine Libvirt/QEMU machine description as {@link String}.
+ * @return parsed machine version.
+ */
+ public static Version getOsMachineVersion( String osMachine )
+ {
+ final OsMachineNameAndVersion machineNameAndVersion = VirtualizationConfigurationQemuUtils
+ .parseOsMachineNameAndVersion( osMachine );
+ return machineNameAndVersion.getOsMachineVersion();
+ }
+
+ /**
+ * Combines a machine name with a machine version and returns a Libvirt/QEMU machine description.
+ *
+ * @param osMachineName name of the machine.
+ * @param osMachineVersion version of the machine.
+ * @return Libvirt/QEMU machine description.
+ */
+ public static String getOsMachine( String osMachineName, String osMachineVersion )
+ {
+ return osMachineName + VirtualizationConfigurationQemuUtils.OS_MACHINE_NAME_VERSION_SEPARATOR + osMachineVersion;
+ }
+
+ /**
+ * Converts a {@link Version} to a Libvirt/QEMU machine version.
+ *
+ * @param version Libvirt/QEMU machine version as {@link Version}.
+ * @return Libvirt/QEMU machine version.
+ */
+ public static String getOsMachineVersion( Version version )
+ {
+ return String.format( "%d.%d", version.getMajor(), version.getMinor() );
+ }
+
+ /**
+ * Returns the size of a given architecture {@link String}.
+ *
+ * @param osArch Libvirt/QEMU machine architecture as {@link String}.
+ * @return Size of the Libvirt/QEMU machine architecture.
+ */
+ public static int getOsArchSize( String osArch )
+ {
+ final int archSize;
+
+ if ( osArch == null || osArch.isEmpty() ) {
+ archSize = 0;
+ } else {
+ switch ( osArch ) {
+ case "alpha":
+ archSize = 64;
+ break;
+ case "armv6l":
+ archSize = 32;
+ break;
+ case "armv7l":
+ archSize = 32;
+ break;
+ case "aarch64":
+ archSize = 64;
+ break;
+ case "cris":
+ archSize = 32;
+ break;
+ case "i686":
+ archSize = 32;
+ break;
+ case "m68k":
+ archSize = 32;
+ break;
+ case "microblaze":
+ archSize = 32;
+ break;
+ case "microblazeel":
+ archSize = 32;
+ break;
+ case "mips":
+ archSize = 32;
+ break;
+ case "mipsel":
+ archSize = 32;
+ break;
+ case "mips64":
+ archSize = 64;
+ break;
+ case "mips64el":
+ archSize = 64;
+ break;
+ case "ppc":
+ archSize = 32;
+ break;
+ case "ppc64":
+ archSize = 64;
+ break;
+ case "ppc64le":
+ archSize = 64;
+ break;
+ case "riscv32":
+ archSize = 32;
+ break;
+ case "riscv64":
+ archSize = 64;
+ break;
+ case "s390x":
+ archSize = 64;
+ break;
+ case "sh4":
+ archSize = 32;
+ break;
+ case "sh4eb":
+ archSize = 64;
+ break;
+ case "sparc":
+ archSize = 32;
+ break;
+ case "sparc64":
+ archSize = 64;
+ break;
+ case "x86_64":
+ archSize = 64;
+ break;
+ case "xtensa":
+ archSize = 32;
+ break;
+ case "xtensaeb":
+ archSize = 32;
+ break;
+ default:
+ archSize = 0;
+ }
+ }
+
+ return archSize;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationUtils.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationUtils.java
new file mode 100644
index 0000000..2427001
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationUtils.java
@@ -0,0 +1,52 @@
+package org.openslx.virtualization.configuration;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+
+/**
+ * Utilities to set up and edit virtualization configurations.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public final class VirtualizationConfigurationUtils
+{
+ /**
+ * Returns an operating system from a given list of operating systems determined by the
+ * virtualizer specific operating system parameters.
+ *
+ * @param osList list of available operating systems.
+ * @param virtId virtualizer identifier, e.g. <code>vmware</code> for VMware
+ * @param virtOsId operating system identifier used by the virtualizer, eg.
+ * <code>windows7-64</code> for 64bit Windows 7 on VMware.
+ */
+ public static OperatingSystem getOsOfVirtualizerFromList( List<OperatingSystem> osList, String virtId,
+ String virtOsId )
+ {
+ OperatingSystem os = null;
+
+ for ( final OperatingSystem osCandidate : osList ) {
+ final Map<String, String> osVirtualizerMapping = osCandidate.getVirtualizerOsId();
+ if ( osVirtualizerMapping != null ) {
+ for ( final Entry<String, String> entry : osVirtualizerMapping.entrySet() ) {
+ // check if suitable OS has been found
+ if ( entry.getKey().equals( virtId ) && entry.getValue().equals( virtOsId ) ) {
+ // save OS and exit inner loop since OS has been found
+ os = osCandidate;
+ break;
+ }
+ }
+
+ // exit outer loop if OS has been found
+ if ( os != null ) {
+ break;
+ }
+ }
+ }
+
+ return os;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualBox.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualBox.java
new file mode 100644
index 0000000..153fffa
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualBox.java
@@ -0,0 +1,601 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.thrifthelper.TConst;
+import org.openslx.util.Util;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationVirtualboxFileFormat.MatchMode;
+import org.openslx.virtualization.hardware.ConfigurationGroups;
+import org.openslx.virtualization.hardware.Ethernet;
+import org.openslx.virtualization.hardware.SoundCard;
+import org.openslx.virtualization.hardware.Usb;
+import org.openslx.virtualization.hardware.VirtOptionValue;
+import org.openslx.virtualization.virtualizer.VirtualizerVirtualBox;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class VirtualizationConfigurationVirtualBox extends VirtualizationConfiguration
+{
+ /**
+ * File name extension for VirtualBox virtualization configuration files..
+ */
+ public static final String FILE_NAME_EXTENSION = "vbox";
+
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfigurationVirtualBox.class );
+
+ private final VirtualizationConfigurationVirtualboxFileFormat config;
+
+ public static enum EthernetType
+ {
+ NAT( "vboxnet1" ), BRIDGED( "vboxnet0" ), HOST_ONLY( "vboxnet2" );
+
+ public final String vnet;
+
+ private EthernetType( String vnet )
+ {
+ this.vnet = vnet;
+ }
+ }
+
+ public VirtualizationConfigurationVirtualBox( List<OperatingSystem> osList, File file )
+ throws IOException, VirtualizationConfigurationException
+ {
+ super( new VirtualizerVirtualBox(), osList );
+ this.config = new VirtualizationConfigurationVirtualboxFileFormat( file );
+ init();
+ }
+
+ public VirtualizationConfigurationVirtualBox( List<OperatingSystem> osList, byte[] vmContent, int length )
+ throws IOException, VirtualizationConfigurationException
+ {
+ super( new VirtualizerVirtualBox(), osList );
+ this.config = new VirtualizationConfigurationVirtualboxFileFormat( vmContent, length );
+ init();
+ }
+
+ private void init()
+ {
+ displayName = config.getDisplayName();
+ setOs( config.getOsName() );
+ this.isMachineSnapshot = config.isMachineSnapshot();
+ for ( HardDisk hardDisk : config.getHdds() ) {
+ hdds.add( hardDisk );
+ }
+ }
+
+ private void disableEnhancedNetworkAdapters()
+ {
+ final NodeList disableAdapters = this.config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter[not(@slot='0')]" );
+
+ if ( disableAdapters != null ) {
+ for ( int i = 0; i < disableAdapters.getLength(); i++ ) {
+ final Element disableAdapter = (Element)disableAdapters.item( i );
+ disableAdapter.setAttribute( "enabled", "false" );
+ }
+ }
+ }
+
+ private void removeEnhancedNetworkAdapters()
+ {
+ final NodeList removeAdapters = this.config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter[not(@slot='0')]" );
+
+ if ( removeAdapters != null ) {
+ for ( int i = 0; i < removeAdapters.getLength(); i++ ) {
+ final Node removeAdapter = removeAdapters.item( i );
+ removeAdapter.getParentNode().removeChild( removeAdapter );
+ }
+ }
+ }
+
+ @Override
+ public void transformEditable() throws VirtualizationConfigurationException
+ {
+ this.disableEnhancedNetworkAdapters();
+ }
+
+ @Override
+ public void transformPrivacy() throws VirtualizationConfigurationException
+ {
+ config.addPlaceHolders();
+ }
+
+ @Override
+ public byte[] getConfigurationAsByteArray()
+ {
+ return config.toString( true ).getBytes( StandardCharsets.UTF_8 );
+ }
+
+ @Override
+ public boolean addEmptyHddTemplate()
+ {
+ return this.addHddTemplate( VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE,
+ VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE,
+ VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE );
+ }
+
+ @Override
+ public boolean addHddTemplate( String diskImage, String hddMode, String redoDir )
+ {
+ config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "location", diskImage, MatchMode.FIRST_ONLY );
+ config.changeAttribute( "/VirtualBox/Machine", "snapshotFolder", redoDir, MatchMode.FIRST_ONLY );
+ return true;
+ }
+
+ @Override
+ public boolean addHddTemplate( File diskImage, String hddMode, String redoDir )
+ {
+ String diskImagePath = diskImage.getName();
+ config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "location", diskImagePath, MatchMode.FIRST_ONLY );
+
+ UUID newhdduuid = UUID.randomUUID();
+
+ // patching the new uuid in the vbox config file here
+ String vboxUUid = "{" + newhdduuid.toString() + "}";
+ config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "uuid", vboxUUid, MatchMode.FIRST_ONLY );
+ config.changeAttribute( config.storageControllersPath() + "/StorageController/AttachedDevice/Image", "uuid",
+ vboxUUid, MatchMode.FIRST_ONLY );
+
+ // the order of the UUID is BIG_ENDIAN but we need to change the order of the first 8 Bytes
+ // to be able to write them to the vdi file... the PROBLEM here is that the first 8
+ // are in LITTLE_ENDIAN order in pairs of 4-2-2 not the whole 8 so just changing the
+ // order when we are adding them to the bytebuffer won't help
+ //
+ // the following is a workaround that works
+ ByteBuffer buffer = ByteBuffer.wrap( new byte[ 16 ] );
+ buffer.putLong( newhdduuid.getMostSignificantBits() );
+ buffer.putLong( newhdduuid.getLeastSignificantBits() );
+ byte[] oldOrder = buffer.array();
+ // make a coppy here because the last 8 Bytes don't need to change position
+ byte[] bytesToWrite = Arrays.copyOf( oldOrder, oldOrder.length );
+ // use an offset int[] to help with the shuffle
+ int[] offsets = { 3, 2, 1, 0, 5, 4, 7, 6 };
+ for ( int index = 0; index < 8; index++ ) {
+ bytesToWrite[index] = oldOrder[offsets[index]];
+ }
+ try ( RandomAccessFile file = new RandomAccessFile( diskImage, "rw" ) ) {
+ file.seek( 392 );
+ file.write( bytesToWrite, 0, 16 );
+ } catch ( Exception e ) {
+ LOGGER.warn( "could not patch new uuid in the vdi", e );
+ }
+
+ // we need a new machine uuid
+ UUID newMachineUuid = UUID.randomUUID();
+ if ( newMachineUuid.equals( newhdduuid ) ) {
+ LOGGER.warn( "The new Machine UUID is the same as the new HDD UUID; tying again...this vm might not start" );
+ newMachineUuid = UUID.randomUUID();
+ }
+ String machineUUid = "{" + newMachineUuid.toString() + "}";
+ return config.changeAttribute( "/VirtualBox/Machine", "uuid", machineUUid, MatchMode.EXACTLY_ONE );
+ }
+
+ @Override
+ public boolean addDefaultNat()
+ {
+ final boolean status;
+
+ final Node adapterSlot0 = config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']" ).item( 0 );
+ if ( adapterSlot0 != null ) {
+ // remove all child node to wipe existing networking mode
+ final NodeList adapterSlot0SettingNodes = adapterSlot0.getChildNodes();
+ while ( adapterSlot0.getChildNodes().getLength() > 0 ) {
+ adapterSlot0.removeChild( adapterSlot0SettingNodes.item( 0 ) );
+ }
+
+ // add networking mode 'NAT'
+ if ( config.addNewNode( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']", "NAT" ) == null ) {
+ LOGGER.error( "Failed to set network adapter to NAT." );
+ status = false;
+ } else {
+ status = config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']", "MACAddress",
+ "080027B86D12", MatchMode.EXACTLY_ONE );
+ }
+ } else {
+ status = false;
+ }
+
+ return status;
+ }
+
+ @Override
+ public void setOs( String vendorOsId )
+ {
+ config.changeAttribute( "/VirtualBox/Machine", "OSType", vendorOsId, MatchMode.EXACTLY_ONE );
+
+ final OperatingSystem os = VirtualizationConfigurationUtils.getOsOfVirtualizerFromList( this.osList,
+ TConst.VIRT_VIRTUALBOX, vendorOsId );
+ this.setOs( os );
+ }
+
+ @Override
+ public boolean addDisplayName( String name )
+ {
+ return config.changeAttribute( "/VirtualBox/Machine", "name", name, MatchMode.EXACTLY_ONE );
+ }
+
+ @Override
+ public boolean addRam( int mem )
+ {
+ return config.changeAttribute( "/VirtualBox/Machine/Hardware/Memory", "RAMSize",
+ Integer.toString( mem ), MatchMode.EXACTLY_ONE );
+ }
+
+ @Override
+ public void addFloppy( int index, String image, boolean readOnly )
+ {
+ Element floppyController = null;
+ NodeList matches = config.findNodes( config.storageControllersPath() + "/StorageController[@name='Floppy']" );
+ if ( matches == null || matches.getLength() == 0 ) {
+ floppyController = (Element)config.addNewNode( config.storageControllersPath(), "StorageController" );
+ if ( floppyController == null ) {
+ LOGGER.error( "Failed to add <Image> to floppy device." );
+ return;
+ }
+ floppyController.setAttribute( "name", "Floppy" );
+ floppyController.setAttribute( "type", "I82078" );
+ floppyController.setAttribute( "PortCount", "1" );
+ floppyController.setAttribute( "useHostIOCache", "true" );
+ floppyController.setAttribute( "Bootable", "false" );
+ }
+ // virtualbox only allows one controller per type
+ if ( matches.getLength() > 1 ) {
+ LOGGER.error( "Multiple floppy controllers detected, this should never happen! " );
+ return;
+ }
+ // so if we had any matches, we know we have exactly one
+ if ( floppyController == null )
+ floppyController = (Element)matches.item( 0 );
+
+ // add the floppy device
+ Element floppyDevice = (Element)config.addNewNode( floppyController, "AttachedDevice" );
+ if ( floppyDevice == null ) {
+ LOGGER.error( "Failed to add <Image> to floppy device." );
+ return;
+ }
+ floppyDevice.setAttribute( "type", "Floppy" );
+ floppyDevice.setAttribute( "hotpluggable", "false" );
+ floppyDevice.setAttribute( "port", "0" );
+ floppyDevice.setAttribute( "device", Integer.toString( index ) );
+
+ // finally add the image to it, if one was given
+ if ( image != null ) {
+ Element floppyImage = (Element)config.addNewNode( floppyDevice, "Image" );
+ if ( floppyImage == null ) {
+ LOGGER.error( "Failed to add <Image> to floppy device." );
+ return;
+ }
+ floppyImage.setAttribute( "uuid",
+ VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE );
+ // register the image in the media registry
+ Element floppyImages = (Element)config.addNewNode( "/VirtualBox/Machine/MediaRegistry", "FloppyImages" );
+ if ( floppyImages == null ) {
+ LOGGER.error( "Failed to add <FloppyImages> to media registry." );
+ return;
+ }
+ Element floppyImageReg = (Element)config.addNewNode( "/VirtualBox/Machine/MediaRegistry/FloppyImages",
+ "Image" );
+ if ( floppyImageReg == null ) {
+ LOGGER.error( "Failed to add <Image> to floppy images in the media registry." );
+ return;
+ }
+ floppyImageReg.setAttribute( "uuid",
+ VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE );
+ floppyImageReg.setAttribute( "location",
+ VirtualizationConfigurationVirtualboxFileFormat.DUMMY_VALUE );
+ }
+ }
+
+ @Override
+ public boolean addCdrom( String image )
+ {
+ // TODO - done in run-virt currently
+ return false;
+ }
+
+ @Override
+ public boolean addCpuCoreCount( int nrOfCores )
+ {
+ return config.changeAttribute( "/VirtualBox/Machine/Hardware/CPU", "count",
+ Integer.toString( nrOfCores ), MatchMode.EXACTLY_ONE );
+ }
+
+ class VBoxSoundCardModel extends VirtOptionValue
+ {
+
+ public VBoxSoundCardModel( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ // XXX I guess this "present" hack will be nicer with enum too
+ if ( Util.isEmptyString( this.id ) ) {
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "enabled", "false", MatchMode.MULTIPLE );
+ return;
+ }
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "enabled", "true", MatchMode.FIRST_ONLY );
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "controller", this.id, MatchMode.FIRST_ONLY );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/AudioAdapter" ).item( 0 );
+ if ( !x.hasAttribute( "enabled" )
+ || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) {
+ return Util.isEmptyString( this.id ); // XXX enum
+ }
+ String val = "AC97";
+ if ( x.hasAttribute( "controller" ) ) {
+ val = x.getAttribute( "controller" );
+ }
+ return val.equals( this.id );
+ }
+
+ }
+
+ class VBoxAccel3D extends VirtOptionValue
+ {
+
+ public VBoxAccel3D( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/Display", "accelerate3D", this.id, MatchMode.EXACTLY_ONE );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Display" ).item( 0 );
+ String val = "false";
+ if ( x.hasAttribute( "accelerate3D" ) ) {
+ val = x.getAttribute( "accelerate3D" );
+ }
+ return val.equalsIgnoreCase( this.id );
+ }
+
+ }
+
+ /**
+ * Function does nothing for Virtual Box;
+ * Virtual Box accepts per default only one hardware version and is hidden from the user
+ */
+ @Override
+ public void setVirtualizerVersion( Version type )
+ {
+ }
+
+ public Version getConfigurationVersion()
+ {
+ return this.config.getVersion();
+ }
+
+ @Override
+ public Version getVirtualizerVersion()
+ {
+ // Virtual Box uses only one virtual hardware version and can't be changed
+ return null;
+ }
+
+ class VBoxNicModel extends VirtOptionValue
+ {
+
+ private final int cardIndex;
+
+ public VBoxNicModel( int cardIndex, String id, String displayName )
+ {
+ super( id, displayName );
+ this.cardIndex = cardIndex;
+ }
+
+ @Override
+ public void apply()
+ {
+ String index = Integer.toString( this.cardIndex );
+ String dev = this.id;
+ boolean present = true;
+ if ( "".equals( this.id ) ) {
+ // none type needs to have a valid value; it takes the value of pcnetcpi2;
+ // if value is left null or empty vm will not start because value is not valid
+ dev = "Am79C970A";
+ present = false;
+ }
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "enabled",
+ Boolean.toString( present ), MatchMode.EXACTLY_ONE );
+ config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "type",
+ dev, MatchMode.EXACTLY_ONE );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter" ).item( 0 );
+ if ( !x.hasAttribute( "enabled" )
+ || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equalsIgnoreCase( "false" ) ) ) {
+ return Util.isEmptyString( this.id );
+ }
+ // Has NIC
+ if ( !x.hasAttribute( "type" ) ) {
+ return "Am79C973".equals( this.id );
+ }
+ return x.getAttribute( "type" ).equals( this.id );
+ }
+
+ }
+
+ public void registerVirtualHW()
+ {
+ List<VirtOptionValue> list;
+ // none type needs to have a valid value; it takes the value of AC97; if value is left null or empty vm will not start because value is not valid
+ // TODO: Maybe just remove the entire section from the XML? Same for ethernet...
+ list = new ArrayList<>();
+ list.add( new VBoxSoundCardModel( "AC97", SoundCard.NONE ) );
+ list.add( new VBoxSoundCardModel( "SB16", SoundCard.SOUND_BLASTER ) );
+ list.add( new VBoxSoundCardModel( "HDA", SoundCard.HD_AUDIO ) );
+ list.add( new VBoxSoundCardModel( "AC97", SoundCard.AC ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.SOUND_CARD_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VBoxAccel3D( "true", "3D" ) );
+ list.add( new VBoxAccel3D( "false", "2D" ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.GFX_TYPE, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VBoxNicModel( 0, "", Ethernet.NONE ) );
+ list.add( new VBoxNicModel( 0, "Am79C970A", Ethernet.PCNETPCI2 ) );
+ list.add( new VBoxNicModel( 0, "Am79C973", Ethernet.PCNETFAST3 ) );
+ list.add( new VBoxNicModel( 0, "82540EM", Ethernet.PRO1000MTD ) );
+ list.add( new VBoxNicModel( 0, "82543GC", Ethernet.PRO1000TS ) );
+ list.add( new VBoxNicModel( 0, "82545EM", Ethernet.PRO1000MTS ) );
+ list.add( new VBoxNicModel( 0, "virtio", Ethernet.PARAVIRT ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.NIC_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VBoxUsbSpeed( null, Usb.NONE ) );
+ list.add( new VBoxUsbSpeed( "OHCI", Usb.USB1_1 ) );
+ list.add( new VBoxUsbSpeed( "EHCI", Usb.USB2_0 ) );
+ list.add( new VBoxUsbSpeed( "XHCI", Usb.USB3_0 ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.USB_SPEED, list ) );
+ }
+
+ @Override
+ public boolean addEthernet( VirtualizationConfiguration.EtherType type )
+ {
+ Node hostOnlyInterfaceNode = config.addNewNode( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']",
+ "HostOnlyInterface" );
+ if ( hostOnlyInterfaceNode == null ) {
+ LOGGER.error( "Failed to create node for HostOnlyInterface." );
+ return false;
+ }
+ return config.addAttributeToNode( hostOnlyInterfaceNode, "name", EthernetType.valueOf( type.name() ).vnet );
+ }
+
+ @Override
+ public void transformNonPersistent() throws VirtualizationConfigurationException
+ {
+ // Cannot disable suspend
+ // https://forums.virtualbox.org/viewtopic.php?f=6&t=77169
+ // https://forums.virtualbox.org/viewtopic.php?f=8&t=80338
+ // But some other stuff that won't make sense in non-persistent mode
+ config.setExtraData( "GUI/LastCloseAction", "PowerOff" );
+ // Could use "Default" instead of "Last" above, but you won't get any confirmation dialog in that case
+ config.setExtraData( "GUI/RestrictedRuntimeHelpMenuActions", "All" );
+ config.setExtraData( "GUI/RestrictedRuntimeMachineMenuActions", "TakeSnapshot,Pause,SaveState" );
+ config.setExtraData( "GUI/RestrictedRuntimeMenus", "Help" );
+ config.setExtraData( "GUI/PreventSnapshotOperations", "true" );
+ config.setExtraData( "GUI/PreventApplicationUpdate", "true" );
+ config.setExtraData( "GUI/RestrictedCloseActions", "SaveState,PowerOffRestoringSnapshot,Detach" );
+
+ this.removeEnhancedNetworkAdapters();
+ }
+
+ class VBoxUsbSpeed extends VirtOptionValue
+ {
+
+ public VBoxUsbSpeed( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ // Wipe existing ones
+ config.removeNodes( "/VirtualBox/Machine/Hardware", "USB" );
+ if ( Util.isEmptyString( this.id ) ) {
+ // Add marker so we know it's not an old config and we really want no USB
+ Element node = config.createNodeRecursive( "/VirtualBox/OpenSLX/USB" );
+ if ( node != null ) {
+ node.setAttribute( "disabled", "true" );
+ }
+ return; // NO USB
+ }
+ Element node = config.createNodeRecursive( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller" );
+ node.setAttribute( "type", this.id );
+ node.setAttribute( "name", this.id );
+ if ( this.id.equals( "EHCI" ) ) { // XXX "mechanically" ported, could make a special class for this special case
+ // If EHCI (2.0) is selected, VBox adds an OHCI controller too...
+ node = config.addNewNode( "/VirtualBox/Machine/Hardware/USB/Controllers", "Controller" );
+ node.setAttribute( "type", "OHCI" );
+ node.setAttribute( "name", "OHCI" );
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ NodeList nodes = config.findNodes( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller/@type" );
+ LOGGER.info( "Found USB attribute nodes:" + nodes.getLength() );
+ /* Again, we need an ugly special case here for USB 2.0, which creates two entries in the
+ * XML, one EHCI and one OHCI. If we simply look for "our" type and return true, both
+ * OHCI and EHCI would return true, and what ends up getting selected in the UI is more or
+ * less undefined. So we need to put more brains in here and special-case the whole EHCI/OHCI
+ * detection, and not return true if this.id is OHCI, and we have a controller node with type
+ * OHCI, but also another node with type EHCI...
+ */
+ boolean ohci = false, ehci = false;
+ for ( int i = 0; i < nodes.getLength(); ++i ) {
+ if ( nodes.item( i ).getNodeType() != Node.ATTRIBUTE_NODE ) {
+ LOGGER.info( "Not ATTRIBUTE type (" + nodes.item( i ).getClass().getSimpleName() + ")" );
+ continue;
+ }
+ String type = ( (Attr)nodes.item( i ) ).getValue();
+ LOGGER.info( "Found USB node with type " + type );
+ if ( type.equals( "EHCI" ) ) {
+ ehci = true;
+ } else if ( type.equals( "OHCI" ) ) {
+ ohci = true;
+ } else if ( type.equals( this.id ) )
+ return true;
+ }
+ if ( ehci && "EHCI".equals( this.id ) )
+ return true;
+ if ( ohci && !ehci && "OHCI".equals( this.id ) )
+ return true;
+ if ( config.findNodes( "/VirtualBox/OpenSLX/USB/@disabled" ).getLength() > 0 ) {
+ return Util.isEmptyString( this.id );
+ }
+ return false;
+ }
+
+ }
+
+ @Override
+ public String getFileNameExtension()
+ {
+ return VirtualizationConfigurationVirtualBox.FILE_NAME_EXTENSION;
+ }
+
+ @Override
+ public void validate() throws VirtualizationConfigurationException
+ {
+ this.config.validate();
+ }
+
+ @Override
+ public void disableUsb()
+ {
+ new VBoxUsbSpeed( null, Usb.NONE ).apply();
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualboxFileFormat.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualboxFileFormat.java
new file mode 100644
index 0000000..b5b3180
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualboxFileFormat.java
@@ -0,0 +1,771 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.util.Resources;
+import org.openslx.util.Util;
+import org.openslx.util.XmlHelper;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.configuration.VirtualizationConfiguration.DriveBusType;
+import org.openslx.virtualization.configuration.VirtualizationConfiguration.HardDisk;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Class handling the parsing of a .vbox machine description file
+ */
+public class VirtualizationConfigurationVirtualboxFileFormat
+{
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfigurationVirtualboxFileFormat.class );
+
+ // key information set during initial parsing of the XML file
+ private String osName = "";
+ private ArrayList<HardDisk> hddsArray = new ArrayList<HardDisk>();
+
+ // XPath and DOM parsing related members
+ private Document doc = null;
+
+ /**
+ * Version of the configuration file format.
+ */
+ private Version version = null;
+
+ /**
+ * File names of XML schema files for different file format versions.
+ */
+ private final static HashMap<Version, String> FILE_FORMAT_SCHEMA_VERSIONS = new HashMap<Version, String>() {
+
+ private static final long serialVersionUID = -3163681758191475625L;
+
+ {
+ put( Version.valueOf( "1.15" ), "VirtualBox-settings_v1-15.xsd" );
+ put( Version.valueOf( "1.16" ), "VirtualBox-settings_v1-16.xsd" );
+ put( Version.valueOf( "1.17" ), "VirtualBox-settings_v1-17.xsd" );
+ put( Version.valueOf( "1.18" ), "VirtualBox-settings_v1-18.xsd" );
+ }
+ };
+
+ /**
+ * Path to the VirtualBox file format schemas within the *.jar file.
+ */
+ private final static String FILE_FORMAT_SCHEMA_PREFIX_PATH = Resources.PATH_SEPARATOR + "virtualbox"
+ + Resources.PATH_SEPARATOR + "xsd";
+
+ public static final String DUMMY_VALUE = "[dummy]";
+
+ // list of nodes to automatically remove when reading the vbox file
+ private static final String[] BLACKLIST = {
+ "/VirtualBox/Machine/Hardware/GuestProperties",
+ "/VirtualBox/Machine/Hardware/VideoCapture",
+ "/VirtualBox/Machine/Hardware/HID",
+ "/VirtualBox/Machine/Hardware/LPT",
+ "/VirtualBox/Machine/Hardware/SharedFolders",
+ "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']/*",
+ "/VirtualBox/Machine/ExtraData",
+ "/VirtualBox/Machine/StorageControllers/StorageController/AttachedDevice[not(@type='HardDisk')]",
+ "/VirtualBox/Machine/Hardware/StorageControllers/StorageController/AttachedDevice[not(@type='HardDisk')]",
+ "/VirtualBox/Machine/MediaRegistry/FloppyImages",
+ "/VirtualBox/Machine/MediaRegistry/DVDImages" };
+
+ public static enum MatchMode
+ {
+ EXACTLY_ONE,
+ MULTIPLE,
+ FIRST_ONLY,
+ };
+
+ /**
+ * Creates a vbox configuration by constructing a DOM from the given VirtualBox machine
+ * configuration file.
+ * Will validate the given file against the VirtualBox XSD schema and only proceed if it is
+ * valid.
+ *
+ * @param file the VirtualBox machine configuration file
+ * @throws IOException if an error occurs while reading the file
+ * @throws VirtualizationConfigurationException if the given file is not a valid VirtualBox
+ * configuration file.
+ */
+ public VirtualizationConfigurationVirtualboxFileFormat( File file ) throws IOException, VirtualizationConfigurationException
+ {
+ doc = XmlHelper.parseDocumentFromStream( new FileInputStream( file ) );
+ doc = XmlHelper.removeFormattingNodes( doc );
+ if ( doc == null )
+ throw new VirtualizationConfigurationException( "Could not parse given VirtualBox machine configuration file!" );
+
+ this.parseConfigurationVersion();
+ this.init();
+ }
+
+ /**
+ * Creates an vbox configuration by constructing a DOM from the XML content given as a byte
+ * array.
+ *
+ * @param machineDescription content of the XML file saved as a byte array.
+ * @param length of the machine description byte array.
+ * @throws VirtualizationConfigurationException creation of VirtualBox configuration file representation failed.
+ */
+ public VirtualizationConfigurationVirtualboxFileFormat( byte[] machineDescription, int length ) throws VirtualizationConfigurationException
+ {
+ ByteArrayInputStream is = new ByteArrayInputStream( machineDescription );
+
+ doc = XmlHelper.parseDocumentFromStream( is );
+ if ( doc == null ) {
+ final String errorMsg = "Could not parse given VirtualBox machine description from byte array!";
+ LOGGER.debug( errorMsg );
+ throw new VirtualizationConfigurationException( errorMsg );
+ }
+
+ this.parseConfigurationVersion();
+ this.init();
+ }
+
+ public void validate() throws VirtualizationConfigurationException
+ {
+ this.validateFileFormatVersion( this.getVersion() );
+ }
+
+ public void validateFileFormatVersion( Version version ) throws VirtualizationConfigurationException
+ {
+ if ( this.getVersion() != null && this.doc != null ) {
+ // check if specified version is supported
+ final String fileName = FILE_FORMAT_SCHEMA_VERSIONS.get( version );
+
+ if ( fileName == null ) {
+ final String errorMsg = "File format version " + version.toString() + " is not supported!";
+ LOGGER.debug( errorMsg );
+ throw new VirtualizationConfigurationException( errorMsg );
+ } else {
+ // specified version is supported, so validate document with corresponding schema file
+ final InputStream schemaResource = VirtualizationConfigurationVirtualboxFileFormat
+ .getSchemaResource( fileName );
+
+ if ( schemaResource != null ) {
+ try {
+ final SchemaFactory factory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
+ final Schema schema = factory.newSchema( new StreamSource( schemaResource ) );
+ final Validator validator = schema.newValidator();
+ validator.validate( new DOMSource( this.doc ) );
+ } catch ( SAXException | IOException e ) {
+ final String errorMsg = "XML configuration is not a valid VirtualBox v" + version.toString()
+ + " configuration: " + e.getLocalizedMessage();
+ LOGGER.debug( errorMsg );
+ throw new VirtualizationConfigurationException( errorMsg );
+ }
+ }
+ }
+ }
+ }
+
+ private static InputStream getSchemaResource( String fileName )
+ {
+ final String schemaFilePath = FILE_FORMAT_SCHEMA_PREFIX_PATH + File.separator + fileName;
+ return VirtualizationConfigurationVirtualboxFileFormat.class.getResourceAsStream( schemaFilePath );
+ }
+
+ /**
+ * Main initialization functions parsing the document created during the constructor.
+ * @throws VirtualizationConfigurationException
+ */
+ private void init() throws VirtualizationConfigurationException
+ {
+ try {
+ this.validate();
+ } catch ( VirtualizationConfigurationException e ) {
+ // do not print output of failed validation if placeholders are available
+ // since those placeholder values violate the defined UUID pattern
+ if ( !this.checkForPlaceholders() ) {
+ final String errorMsg = "XML configuration is not a valid VirtualBox v" + version.toString()
+ + " configuration: " + e.getLocalizedMessage();
+ LOGGER.debug( errorMsg );
+ }
+ }
+
+ if ( Util.isEmptyString( getDisplayName() ) ) {
+ throw new VirtualizationConfigurationException( "Machine doesn't have a name" );
+ }
+ try {
+ ensureHardwareUuid();
+ setOsType();
+ fixUsb(); // Since we now support selecting specific speed
+ removeUnusedHdds();
+ if ( checkForPlaceholders() ) {
+ return;
+ }
+ setHdds();
+ removeBlacklistedElements();
+ addPlaceHolders();
+ } catch ( XPathExpressionException e ) {
+ LOGGER.debug( "Could not initialize VBoxConfig", e );
+ return;
+ }
+ }
+
+ private void parseConfigurationVersion() throws VirtualizationConfigurationException
+ {
+ String versionText;
+ try {
+ versionText = XmlHelper.compileXPath( "/VirtualBox/@version" ).evaluate( this.doc );
+ } catch ( XPathExpressionException e ) {
+ throw new VirtualizationConfigurationException(
+ "Failed to parse the version number of the configuration file" );
+ }
+
+ if ( versionText == null || versionText.isEmpty() ) {
+ throw new VirtualizationConfigurationException( "Configuration file does not contain any version number!" );
+ } else {
+ // parse version information from textual description
+ final Pattern versionPattern = Pattern.compile( "^(\\d+\\.\\d+).*$" );
+ final Matcher versionMatcher = versionPattern.matcher( versionText );
+
+ if ( versionMatcher.find() ) {
+ this.version = Version.valueOf( versionMatcher.group( 1 ) );
+ }
+
+ if ( this.version == null ) {
+ throw new VirtualizationConfigurationException( "Configuration file version number is not valid!" );
+ }
+ }
+ }
+
+ private void fixUsb()
+ {
+ NodeList list = findNodes( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller" );
+ if ( list != null && list.getLength() != 0 ) {
+ LOGGER.info( "USB present, not fixing anything" );
+ return;
+ }
+ // If there's no USB section, this can mean two things:
+ // 1) Old config that would always default to USB 2.0 for "USB enabled" or nothing for disabled
+ // 2) New config with USB disabled
+ list = findNodes( "/VirtualBox/OpenSLX/USB[@disabled]" );
+ if ( list != null && list.getLength() != 0 ) {
+ LOGGER.info( "USB explicitly disabled" );
+ return; // Explicitly marked as disabled, do nothing
+ }
+ // We assume case 1) and add USB 2.0
+ LOGGER.info( "Fixing USB: Adding USB 2.0" );
+ Element controller;
+ Element node = createNodeRecursive( "/VirtualBox/Machine/Hardware/USB/Controllers" );
+ controller = addNewNode( node, "Controller" );
+ controller.setAttribute( "name", "OHCI" );
+ controller.setAttribute( "type", "OHCI" );
+ controller = addNewNode( node, "Controller" );
+ controller.setAttribute( "name", "EHCI" );
+ controller.setAttribute( "type", "EHCI" );
+ }
+
+ /**
+ * Remove any HDDs from MediaRegistry that aren't being used
+ */
+ private void removeUnusedHdds()
+ {
+ Set<String> existing = new HashSet<>();
+ NodeList list = findNodes( storageControllersPath() + "/StorageController/AttachedDevice/Image" );
+ if ( list != null && list.getLength() != 0 ) {
+ for ( int i = 0; i < list.getLength(); ++i ) {
+ Node item = list.item( i );
+ if ( ! ( item instanceof Element ) )
+ continue;
+ Element e = (Element)item;
+ String uuid = e.getAttribute( "uuid" );
+ if ( Util.isEmptyString( uuid ) )
+ continue;
+ existing.add( uuid );
+ }
+ }
+ // Now check registry
+ list = findNodes( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk" );
+ if ( list != null && list.getLength() != 0 ) {
+ for ( int i = 0; i < list.getLength(); ++i ) {
+ Node item = list.item( i );
+ if ( ! ( item instanceof Element ) )
+ continue;
+ Element e = (Element)item;
+ String uuid = e.getAttribute( "uuid" );
+ if ( !existing.contains( uuid ) ) {
+ LOGGER.info( "Removing unused HDD " + uuid + " from MediaRegistry" );
+ e.getParentNode().removeChild( e );
+ }
+ }
+ }
+ }
+
+ public String storageControllersPath()
+ {
+ if ( this.getVersion().isSmallerThan( Version.valueOf( "1.17" ) ) )
+ return "/VirtualBox/Machine/StorageControllers";
+ return "/VirtualBox/Machine/Hardware/StorageControllers";
+ }
+
+ /**
+ * Saves the machine's uuid as hardware uuid to prevent VMs from
+ * believing in a hardware change.
+ *
+ * @throws XPathExpressionException
+ * @throws VirtualizationConfigurationException
+ */
+ private void ensureHardwareUuid() throws XPathExpressionException, VirtualizationConfigurationException
+ {
+ // we will need the machine uuid, so get it
+ String machineUuid = XmlHelper.compileXPath( "/VirtualBox/Machine/@uuid" ).evaluate( this.doc );
+ if ( machineUuid.isEmpty() ) {
+ LOGGER.error( "Machine UUID empty, should never happen!" );
+ throw new VirtualizationConfigurationException( "XML doesn't contain a machine uuid" );
+ }
+
+ NodeList hwNodes = findNodes( "/VirtualBox/Machine/Hardware" );
+ int count = hwNodes.getLength();
+ if ( count != 1 ) {
+ throw new VirtualizationConfigurationException( "Zero or > 1 '/VirtualBox/Machine/Hardware' node were found, should never happen!" );
+ }
+ Element hw = (Element)hwNodes.item( 0 );
+ String hwUuid = hw.getAttribute( "uuid" );
+ if ( !hwUuid.isEmpty() ) {
+ LOGGER.info( "Found hardware uuid: " + hwUuid );
+ return;
+ } else {
+ if ( !addAttributeToNode( hw, "uuid", machineUuid ) ) {
+ LOGGER.error( "Failed to set machine UUID '" + machineUuid + "' as hardware UUID." );
+ return;
+ }
+ LOGGER.info( "Saved machine UUID as hardware UUID." );
+ }
+ }
+
+ public Version getVersion()
+ {
+ return this.version;
+ }
+
+ /**
+ * Self-explanatory.
+ */
+ public void addPlaceHolders()
+ {
+ // placeholder for the location of the virtual hdd
+ changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "location", DUMMY_VALUE, MatchMode.MULTIPLE );
+ // in case it already has a snapshot
+ changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk/HardDisk", "location", DUMMY_VALUE, MatchMode.MULTIPLE );
+ changeAttribute( "/VirtualBox/Machine", "snapshotFolder", DUMMY_VALUE, MatchMode.FIRST_ONLY );
+ }
+
+ /**
+ * Function checks if the placeholders are present
+ *
+ * @return true if the placeholders are present, false otherwise
+ */
+ private boolean checkForPlaceholders()
+ {
+ // TODO this should be more robust...
+ NodeList hdds = findNodes( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk" );
+ for ( int i = 0; i < hdds.getLength(); i++ ) {
+ Element hdd = (Element)hdds.item( i );
+ if ( hdd == null )
+ continue;
+ if ( hdd.getAttribute( "location" ).equals( DUMMY_VALUE ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called during init(), prunes the DOM from the elements blacklisted defined
+ * in the member blacklist, a list of XPath expressions as String
+ *
+ * @throws XPathExpressionException
+ */
+ private void removeBlacklistedElements() throws XPathExpressionException
+ {
+ // iterate over the blackList
+ for ( String blackedTag : BLACKLIST ) {
+ XPathExpression blackedExpr = XmlHelper.compileXPath( blackedTag );
+ NodeList blackedNodes = (NodeList)blackedExpr.evaluate( this.doc, XPathConstants.NODESET );
+ for ( int i = 0; i < blackedNodes.getLength(); i++ ) {
+ // go through the child nodes of the blacklisted ones -> why?
+ Element child = (Element)blackedNodes.item( i );
+ removeNode( child );
+ }
+ }
+ }
+
+ /**
+ * Getter for the display name
+ *
+ * @return the display name of this VM
+ */
+ public String getDisplayName()
+ {
+ try {
+ return XmlHelper.compileXPath( "/VirtualBox/Machine/@name" ).evaluate( this.doc );
+ } catch ( XPathExpressionException e ) {
+ return "";
+ }
+ }
+
+ /**
+ * Function finds and saves the name of the guest OS
+ *
+ * @throws XPathExpressionException failed to find and retrieve name of the guest OS.
+ */
+ public void setOsType() throws XPathExpressionException
+ {
+ String os = XmlHelper.compileXPath( "/VirtualBox/Machine/@OSType" ).evaluate( this.doc );
+ if ( os != null && !os.isEmpty() ) {
+ osName = os;
+ }
+ }
+
+ /**
+ * Getter for the parsed guest OS name
+ *
+ * @return name of the guest OS
+ */
+ public String getOsName()
+ {
+ return osName;
+ }
+
+ /**
+ * Search for attached hard drives and determine their controller and their path.
+ *
+ * @throws XPathExpressionException failed to find attached hard drives and their controllers.
+ */
+ public void setHdds() throws XPathExpressionException
+ {
+ final XPathExpression hddsExpr = XmlHelper.compileXPath( storageControllersPath()
+ + "/StorageController/AttachedDevice[@type='HardDisk']/Image" );
+
+ NodeList nodes = (NodeList)hddsExpr.evaluate( this.doc, XPathConstants.NODESET );
+ if ( nodes == null ) {
+ LOGGER.error( "Failed to find attached hard drives." );
+ return;
+ }
+ for ( int i = 0; i < nodes.getLength(); i++ ) {
+ Element hddElement = (Element)nodes.item( i );
+ if ( hddElement == null )
+ continue;
+ String uuid = hddElement.getAttribute( "uuid" );
+ if ( uuid.isEmpty() )
+ continue;
+ // got uuid, check if it was registered
+ XPathExpression hddsRegistered = XmlHelper.compileXPath( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk[@uuid='" + uuid + "']" );
+ NodeList hddsRegisteredNodes = (NodeList)hddsRegistered.evaluate( this.doc, XPathConstants.NODESET );
+ if ( hddsRegisteredNodes == null || hddsRegisteredNodes.getLength() != 1 ) {
+ LOGGER.error( "Found hard disk with uuid '" + uuid + "' which does not appear (unique) in the Media Registry. Skipping." );
+ continue;
+ }
+ Element hddElementReg = (Element)hddsRegisteredNodes.item( 0 );
+ if ( hddElementReg == null )
+ continue;
+ String fileName = hddElementReg.getAttribute( "location" );
+ String type = hddElementReg.getAttribute( "type" );
+ if ( !type.equals( "Normal" ) && !type.equals( "Writethrough" ) ) {
+ LOGGER.warn( "Type of the disk file is neither 'Normal' nor 'Writethrough' but: " + type );
+ LOGGER.warn( "This makes the image not directly modificable, which might lead to problems when editing it locally." );
+ }
+ // search if it is also attached to a controller
+ Node hddDevice = hddElement.getParentNode();
+ if ( hddDevice == null ) {
+ LOGGER.error( "HDD node had a null parent, shouldn't happen" );
+ continue;
+ }
+ Element hddController = (Element)hddDevice.getParentNode();
+ if ( hddController == null ) {
+ LOGGER.error( "HDD node had a null parent, shouldn't happen" );
+ continue;
+ }
+ String controllerMode = hddController.getAttribute( "type" );
+ String controllerType = hddController.getAttribute( "name" );
+ DriveBusType busType;
+ if ( controllerType.equals( "NVMe" ) ) {
+ busType = DriveBusType.NVME;
+ } else {
+ try {
+ // This assumes the type in the xml matches our enum constants.
+ busType = DriveBusType.valueOf( controllerType );
+ } catch (Exception e) {
+ LOGGER.warn( "Skipping unknown HDD controller type '" + controllerType + "'" );
+ continue;
+ }
+ }
+ LOGGER.info( "Adding hard disk with controller: " + busType + " (" + controllerMode + ") from file '" + fileName + "'." );
+ hddsArray.add( new HardDisk( controllerMode, busType, fileName ) );
+ }
+ }
+
+ /**
+ * Getter for the list of detected hard drives.
+ *
+ * @return list of disk drives.
+ */
+ public ArrayList<HardDisk> getHdds()
+ {
+ return hddsArray;
+ }
+
+ /**
+ * Detect if the vbox file has any machine snapshot by looking at
+ * the existance of '/VirtualBox/Machine/Snapshot' elements.
+ *
+ * @return true if a machine snapshot is present, false otherwise.
+ */
+ public boolean isMachineSnapshot()
+ {
+ // check if the vbox configuration file contains some machine snapshots.
+ // by looking at the existance of /VirtualBox/Machine/Snapshot
+ NodeList machineSnapshots = findNodes( "/VirtualBox/Machine/Snapshot" );
+ return machineSnapshots != null && machineSnapshots.getLength() > 0;
+ }
+
+ /**
+ * Searches the DOM for the elements matching the given XPath expression.
+ *
+ * @param xpath expression to search the DOM with
+ * @return nodes found by evaluating given XPath expression
+ */
+ public NodeList findNodes( String xpath )
+ {
+ NodeList nodes = null;
+ try {
+ XPathExpression expr = XmlHelper.compileXPath( xpath );
+ Object nodesObject = expr.evaluate( this.doc, XPathConstants.NODESET );
+ nodes = (NodeList)nodesObject;
+ } catch ( XPathExpressionException e ) {
+ LOGGER.error( "Could not build path", e );
+ }
+ return nodes;
+ }
+
+ /**
+ * Function used to change the value of an attribute of given element(s).
+ *
+ * @param elementXPath given as an xpath expression
+ * @param attribute attribute to change
+ * @param value to set the attribute to
+ * @param mode what to do if multiple nodes match XPath
+ * @return state of the change operation whether the attribute was changed successfully or not.
+ */
+ public boolean changeAttribute( String elementXPath, String attribute, String value, MatchMode mode )
+ {
+ NodeList nodes = findNodes( elementXPath );
+ if ( nodes == null || nodes.getLength() == 0 ) {
+ if ( mode != MatchMode.MULTIPLE ) {
+ LOGGER.error( "No node could be found for: " + elementXPath );
+ }
+ return false;
+ }
+ if ( nodes.getLength() != 1 && ( mode == MatchMode.EXACTLY_ONE ) ) {
+ LOGGER.error( "Multiple nodes found for: " + elementXPath );
+ return false;
+ }
+ boolean ret = true;
+ for ( int i = 0; i < nodes.getLength(); ++i ) {
+ if ( !addAttributeToNode( nodes.item( i ), attribute, value ) ) {
+ ret = false;
+ }
+ if ( mode == MatchMode.FIRST_ONLY )
+ break;
+ }
+ return ret;
+ }
+
+ /**
+ * Add given attribute with given value to the given node.
+ * NOTE: this will overwrite the attribute of the node if it already exists.
+ *
+ * @param node to add the attribute to
+ * @param attribute attribute to add to the node
+ * @param value of the attribute
+ * @return true if successful, false otherwise
+ */
+ public boolean addAttributeToNode( Node node, String attribute, String value )
+ {
+ if ( node == null || node.getNodeType() != Node.ELEMENT_NODE ) {
+ LOGGER.error( "Trying to change attribute of a non element node!" );
+ return false;
+ }
+ try {
+ ( (Element)node ).setAttribute( attribute, value );
+ } catch ( DOMException e ) {
+ LOGGER.error( "Failed set '" + attribute + "' to '" + value + "' of xml node '" + node.getNodeName() + "': ", e );
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds a new node named nameOfNewNode to the given parent found by parentXPath.
+ *
+ * @param parentXPath XPath expression to the parent
+ * @param childName name of the node to be added
+ * @return the newly added Node
+ */
+ public Element addNewNode( String parentXPath, String childName )
+ {
+ NodeList possibleParents = findNodes( parentXPath );
+ if ( possibleParents == null || possibleParents.getLength() != 1 ) {
+ LOGGER.error( "Could not find unique parent node to add new node to: " + parentXPath );
+ return null;
+ }
+ return addNewNode( possibleParents.item( 0 ), childName );
+ }
+
+ public Element createNodeRecursive( String xPath )
+ {
+ String[] nodeNames = xPath.split( "/" );
+ Node parent = this.doc;
+ Element latest = null;
+ for ( int nodeIndex = 0; nodeIndex < nodeNames.length; ++nodeIndex ) {
+ if ( nodeNames[nodeIndex].length() == 0 )
+ continue;
+ Node node = skipNonElementNodes( parent.getFirstChild() );
+ while ( node != null ) {
+ if ( node.getNodeType() == Node.ELEMENT_NODE && nodeNames[nodeIndex].equals( node.getNodeName() ) )
+ break; // Found existing
+ // Check next on same level
+ node = skipNonElementNodes( node.getNextSibling() );
+ }
+ if ( node == null ) {
+ node = doc.createElement( nodeNames[nodeIndex] );
+ parent.appendChild( node );
+ }
+ parent = node;
+ latest = (Element)node;
+ }
+ return latest;
+ }
+
+ private Element skipNonElementNodes( Node nn )
+ {
+ while ( nn != null && nn.getNodeType() != Node.ELEMENT_NODE ) {
+ nn = nn.getNextSibling();
+ }
+ return (Element)nn;
+ }
+
+ public void setExtraData( String key, String value )
+ {
+ NodeList nl = findNodes( "/VirtualBox/Machine/ExtraData/ExtraDataItem" );
+ Element e = null;
+ if ( nl != null ) {
+ for ( int i = 0; i < nl.getLength(); ++i ) {
+ Node n = nl.item( i );
+ if ( n.getNodeType() == Node.ELEMENT_NODE ) {
+ final Element ne = (Element)n;
+ final String keyValue = ne.getAttribute( "name" );
+ if ( keyValue != null && keyValue.equals( key ) ) {
+ e = ne;
+ break;
+ }
+ }
+ }
+ }
+ if ( e == null ) {
+ Element p = createNodeRecursive( "/VirtualBox/Machine/ExtraData" );
+ e = addNewNode( p, "ExtraDataItem" );
+ e.setAttribute( "name", key );
+ }
+ e.setAttribute( "value", value );
+ }
+
+ /**
+ * Creates a new element to the given parent node.
+ *
+ * @param parent to add the new element to
+ * @param childName name of the new element to create
+ * @return the newly created node
+ */
+ public Element addNewNode( Node parent, String childName )
+ {
+ if ( parent == null || parent.getNodeType() != Node.ELEMENT_NODE ) {
+ return null;
+ }
+ Element newNode = null;
+ try {
+ newNode = doc.createElement( childName );
+ parent.appendChild( newNode );
+ } catch ( DOMException e ) {
+ LOGGER.error( "Failed to add '" + childName + "' to '" + parent.getNodeName() + "'." );
+ }
+ return newNode;
+ }
+
+ /**
+ * Helper to remove given node from the DOM.
+ *
+ * @param node Node object to remove.
+ */
+ private void removeNode( Node node )
+ {
+ if ( node == null )
+ return;
+ Node parent = node.getParentNode();
+ if ( parent != null )
+ parent.removeChild( node );
+ }
+
+ /**
+ * Helper to output the DOM as a String.
+ *
+ * @param prettyPrint sets whether to indent the output
+ * @return (un-)formatted XML
+ */
+ public String toString( boolean prettyPrint )
+ {
+ return XmlHelper.getXmlFromDocument( doc, prettyPrint );
+ }
+
+ /**
+ * Remove all nodes with name childName from parentPath
+ * @param parentPath XPath to parent node of where child nodes are to be deleted
+ * @param childName Name of nodes to delete
+ */
+ public void removeNodes( String parentPath, String childName )
+ {
+ NodeList parentNodes = findNodes( parentPath );
+ // XPath might match multiple nodes
+ for ( int i = 0; i < parentNodes.getLength(); ++i ) {
+ Node parent = parentNodes.item( i );
+ List<Node> delList = new ArrayList<>( 0 );
+ // Iterate over child nodes
+ for ( Node child = parent.getFirstChild(); child != null; child = child.getNextSibling() ) {
+ if ( childName.equals( child.getNodeName() ) ) {
+ // Remember all to be deleted (don't delete while iterating)
+ delList.add( child );
+ }
+ }
+ // Now delete them all
+ for ( Node child : delList ) {
+ parent.removeChild( child );
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmware.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmware.java
new file mode 100644
index 0000000..107014f
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmware.java
@@ -0,0 +1,741 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.thrifthelper.TConst;
+import org.openslx.util.Util;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationVmwareFileFormat.ConfigEntry;
+import org.openslx.virtualization.hardware.VirtOptionValue;
+import org.openslx.virtualization.hardware.ConfigurationGroups;
+import org.openslx.virtualization.hardware.Ethernet;
+import org.openslx.virtualization.hardware.SoundCard;
+import org.openslx.virtualization.hardware.Usb;
+import org.openslx.virtualization.virtualizer.VirtualizerVmware;
+
+public class VirtualizationConfigurationVmware extends VirtualizationConfiguration
+{
+ /**
+ * File name extension for VMware virtualization configuration files.
+ */
+ public static final String FILE_NAME_EXTENSION = "vmx";
+
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfigurationVmware.class );
+
+ private static final Pattern HDD_PATTERN = Pattern.compile( "^(ide\\d|scsi\\d|sata\\d|nvme\\d):?(\\d?)\\.(.*)",
+ Pattern.CASE_INSENSITIVE );
+
+ /** Lowercase regex of allowed settings for stateless execution */
+ private static final Pattern STATELESS_WHITELIST_PATTERN;
+
+ /** Lowercase regex of forbidden settings when uploading (privacy concerns) */
+ private static final Pattern PRIVACY_BLACKLIST_PATTERN;
+
+ private final VirtualizationConfigurationVmwareFileFormat config;
+
+ // Init static members
+ static {
+ // LOWERCASE - Client execution whitelist
+ String[] list1 = { "^guestos", "^uuid\\.bios", "^config\\.version", "^ehci[.:]", "^mks\\.enable3d",
+ "^virtualhw\\.",
+ "^sound[.:]", "\\.pcislotnumber$", "^pcibridge", "\\.virtualdev$", "^tools\\.syncTime$",
+ "^time\\.synchronize",
+ "^bios\\.bootDelay", "^rtc\\.", "^xhci[.:]", "^usb_xhci[.:]", "\\.deviceType$", "\\.port$", "\\.parent$",
+ "^usb[.:]",
+ "^firmware", "^hpet", "^vm\\.genid",
+ "^svga\\.graphicsMemoryKB$" };
+ STATELESS_WHITELIST_PATTERN = Pattern.compile( String.join( "|", list1 ), Pattern.CASE_INSENSITIVE );
+ // LOWERCASE - Upload privacy filter
+ String[] list2 = { "^displayname$", "^extendedconfigfile$", "^gui\\.", "^nvram$", "^memsize$" };
+ PRIVACY_BLACKLIST_PATTERN = Pattern.compile( String.join( "|", list2 ), Pattern.CASE_INSENSITIVE );
+ }
+
+ public static enum EthernetType
+ {
+ NAT( "vmnet1" ), BRIDGED( "vmnet0" ), HOST_ONLY( "vmnet2" );
+
+ public final String vmnet;
+
+ private EthernetType( String vnet )
+ {
+ this.vmnet = vnet;
+ }
+ }
+
+ public VirtualizationConfigurationVmware( List<OperatingSystem> osList, File file )
+ throws IOException, VirtualizationConfigurationException
+ {
+ super( new VirtualizerVmware(), osList );
+ this.config = new VirtualizationConfigurationVmwareFileFormat( file );
+ init();
+ }
+
+ public VirtualizationConfigurationVmware( List<OperatingSystem> osList, byte[] vmxContent, int length )
+ throws VirtualizationConfigurationException
+ {
+ super( new VirtualizerVmware(), osList );
+ this.config = new VirtualizationConfigurationVmwareFileFormat( vmxContent, length ); // still unfiltered
+ init(); // now filtered
+ }
+
+ private void init()
+ {
+ Map<String, Controller> disks = new HashMap<>();
+ for ( Entry<String, ConfigEntry> entry : config.entrySet() ) {
+ handleLoadEntry( entry, disks );
+ }
+ // Fix accidentally filtered USB config if we see EHCI is present
+ if ( isSetAndTrue( "ehci.present" ) && !isSetAndTrue( "usb.present" ) ) {
+ addFiltered( "usb.present", "TRUE" );
+ }
+ // if we find this tag, we already went through the hdd's - so we're done.
+ if ( config.get( "#SLX_HDD_BUS" ) != null ) {
+ try {
+ hdds.add( new HardDisk( config.get( "#SLX_HDD_CHIP" ),
+ DriveBusType.valueOf( config.get( "#SLX_HDD_BUS" ) ), "empty" ) );
+ } catch ( Exception e ) {
+ LOGGER.debug( "Error adding HDD object when parsing #SLX_HDD_BUS. Meta-data will be incorrect.", e );
+ }
+ return;
+ }
+ // Now find the HDDs and add to list
+ for ( Entry<String, Controller> cEntry : disks.entrySet() ) {
+ Controller controller = cEntry.getValue();
+ String controllerType = cEntry.getKey();
+ if ( !controller.present )
+ continue;
+ for ( Entry<String, Device> dEntry : controller.devices.entrySet() ) {
+ String deviceId = dEntry.getKey();
+ Device device = dEntry.getValue();
+ if ( !device.present )
+ continue; // Not present
+ if ( device.deviceType != null && !device.deviceType.toLowerCase().endsWith( "disk" ) )
+ continue; // Not a HDD
+ DriveBusType bus = null;
+ if ( controllerType.startsWith( "ide" ) ) {
+ bus = DriveBusType.IDE;
+ } else if ( controllerType.startsWith( "scsi" ) ) {
+ bus = DriveBusType.SCSI;
+ } else if ( controllerType.startsWith( "sata" ) ) {
+ bus = DriveBusType.SATA;
+ } else if ( controllerType.startsWith( "nvme" ) ) {
+ bus = DriveBusType.NVME;
+ }
+ hdds.add( new HardDisk( controller.virtualDev, bus, device.filename ) );
+ // Remove original entries from VMX
+ removeEntriesStartingWith( controllerType + ":" + deviceId + "." );
+ }
+ }
+ this.isMachineSnapshot = false;
+
+ // Add HDD to cleaned vmx
+ if ( !hdds.isEmpty() ) {
+ HardDisk hdd = hdds.get( 0 );
+ addFiltered( "#SLX_HDD_BUS", hdd.bus.toString() );
+ if ( hdd.chipsetDriver != null ) {
+ addFiltered( "#SLX_HDD_CHIP", hdd.chipsetDriver );
+ }
+ }
+ }
+
+ private void removeEntriesStartingWith( String start )
+ {
+ for ( Iterator<Entry<String, ConfigEntry>> it = config.entrySet().iterator(); it.hasNext(); ) {
+ Entry<String, ConfigEntry> entry = it.next();
+ if ( entry.getKey().startsWith( start ) ) {
+ it.remove();
+ }
+ }
+ }
+
+ private void addFiltered( String key, String value )
+ {
+ config.set( key, value );
+ }
+
+ private boolean isSetAndTrue( String key )
+ {
+ String value = config.get( key );
+ return value != null && value.equalsIgnoreCase( "true" );
+ }
+
+ private void handleLoadEntry( Entry<String, ConfigEntry> entry, Map<String, Controller> disks )
+ {
+ String lowerKey = entry.getKey().toLowerCase();
+ // Dig Usable meta data
+ String value = entry.getValue().getValue();
+ if ( lowerKey.equals( "guestos" ) ) {
+ setOs( value );
+ return;
+ }
+ if ( lowerKey.equals( "displayname" ) ) {
+ displayName = value;
+ return;
+ }
+ Matcher hdd = HDD_PATTERN.matcher( entry.getKey() );
+ if ( hdd.find() ) {
+ handleHddEntry( disks, hdd.group( 1 ).toLowerCase(), hdd.group( 2 ), hdd.group( 3 ), value );
+ }
+ }
+
+ private void handleHddEntry( Map<String, Controller> disks, String controllerStr, String deviceStr, String property, String value )
+ {
+ Controller controller = disks.get( controllerStr );
+ if ( controller == null ) {
+ controller = new Controller();
+ disks.put( controllerStr, controller );
+ }
+ if ( deviceStr == null || deviceStr.isEmpty() ) {
+ // Controller property
+ if ( property.equalsIgnoreCase( "present" ) ) {
+ controller.present = Boolean.parseBoolean( value );
+ } else if ( property.equalsIgnoreCase( "virtualDev" ) ) {
+ controller.virtualDev = value;
+ }
+ return;
+ }
+ // Device property
+ Device device = controller.devices.get( deviceStr );
+ if ( device == null ) {
+ device = new Device();
+ controller.devices.put( deviceStr, device );
+ }
+ if ( property.equalsIgnoreCase( "deviceType" ) ) {
+ device.deviceType = value;
+ } else if ( property.equalsIgnoreCase( "filename" ) ) {
+ device.filename = value;
+ } else if ( property.equalsIgnoreCase( "present" ) ) {
+ device.present = Boolean.parseBoolean( value );
+ }
+ }
+
+ @Override
+ public boolean addEmptyHddTemplate()
+ {
+ return this.addHddTemplate( "%VM_DISK_PATH%", "%VM_DISK_MODE%", "%VM_DISK_REDOLOGDIR%" );
+ }
+
+ @Override
+ public boolean addHddTemplate( File diskImage, String hddMode, String redoDir )
+ {
+ return addHddTemplate( diskImage.getName(), hddMode, redoDir );
+ }
+
+ @Override
+ public boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir )
+ {
+ if ( diskImagePath.isEmpty() ) {
+ LOGGER.error( "Empty disk image path given!" );
+ return false;
+ }
+
+ if ( hdds.isEmpty() ) {
+ LOGGER.warn( "No HDDs found in configuration" );
+ return false;
+ }
+
+ HardDisk hdd = hdds.get( 0 );
+
+ String chipset = hdd.chipsetDriver;
+ String prefix;
+ switch ( hdd.bus ) {
+ case SATA:
+ // Cannot happen?... use lsisas1068
+ prefix = "scsi0";
+ chipset = "lsisas1068";
+ break;
+ case IDE:
+ case SCSI:
+ case NVME:
+ prefix = hdd.bus.name().toLowerCase() + "0";
+ break;
+ default:
+ LOGGER.warn( "Unknown HDD bus type: " + hdd.bus.toString() );
+ return false;
+ }
+ // Gen
+ addFiltered( prefix + ".present", "TRUE" );
+ if ( chipset != null ) {
+ addFiltered( prefix + ".virtualDev", chipset );
+ }
+ addFiltered( prefix + ":0.present", "TRUE" );
+ addFiltered( prefix + ":0.deviceType", "disk" );
+ addFiltered( prefix + ":0.fileName", diskImagePath );
+ if ( hddMode != null ) {
+ addFiltered( prefix + ":0.mode", hddMode );
+ addFiltered( prefix + ":0.redo", "" );
+ addFiltered( prefix + ":0.redoLogDir", redoDir );
+ }
+ config.remove( "#SLX_HDD_BUS" );
+ config.remove( "#SLX_HDD_CHIP" );
+ return true;
+ }
+
+ public boolean addDefaultNat()
+ {
+ addFiltered( "ethernet0.present", "TRUE" );
+ addFiltered( "ethernet0.connectionType", "nat" );
+ return true;
+ }
+
+ public boolean addEthernet( VirtualizationConfiguration.EtherType type )
+ {
+ boolean ret = false;
+ int index = 0;
+ for ( ;; ++index ) {
+ if ( config.get( "ethernet" + index + ".present" ) == null )
+ break;
+ }
+ switch ( type ) {
+ case NAT:
+ ret = addEthernet( index, EthernetType.NAT );
+ break;
+ case BRIDGED:
+ ret = addEthernet( index, EthernetType.BRIDGED );
+ break;
+ case HOST_ONLY:
+ ret = addEthernet( index, EthernetType.HOST_ONLY );
+ break;
+ default:
+ // Should not come to this...
+ break;
+ }
+ return ret;
+ }
+
+ public boolean addEthernet( int index, EthernetType type )
+ {
+ String ether = "ethernet" + index;
+ addFiltered( ether + ".present", "TRUE" );
+ addFiltered( ether + ".connectionType", "custom" );
+ addFiltered( ether + ".vnet", type.vmnet );
+ if ( config.get( ether + ".virtualDev" ) == null ) {
+ String dev = config.get( "ethernet0.virtualDev" );
+ if ( dev != null ) {
+ addFiltered( ether + ".virtualDev", dev );
+ }
+ }
+ return true;
+ }
+
+ public void addFloppy( int index, String image, boolean readOnly )
+ {
+ String pre = "floppy" + index;
+ addFiltered( pre + ".present", "TRUE" );
+ if ( image == null ) {
+ addFiltered( pre + ".startConnected", "FALSE" );
+ addFiltered( pre + ".fileType", "device" );
+ config.remove( pre + ".fileName" );
+ config.remove( pre + ".readonly" );
+ addFiltered( pre + ".autodetect", "TRUE" );
+ } else {
+ addFiltered( pre + ".startConnected", "TRUE" );
+ addFiltered( pre + ".fileType", "file" );
+ addFiltered( pre + ".fileName", image );
+ addFiltered( pre + ".readonly", vmBoolean( readOnly ) );
+ config.remove( pre + ".autodetect" );
+ }
+ }
+
+ public boolean addCdrom( String image )
+ {
+ for ( String port : new String[] { "ide0:0", "ide0:1", "ide1:0", "ide1:1", "scsi0:1" } ) {
+ if ( !isSetAndTrue( port + ".present" ) ) {
+ addFiltered( port + ".present", "TRUE" );
+ if ( image == null ) {
+ addFiltered( port + ".autodetect", "TRUE" );
+ addFiltered( port + ".deviceType", "cdrom-raw" );
+ config.remove( port + ".fileName" );
+ } else {
+ config.remove( port + ".autodetect" );
+ addFiltered( port + ".deviceType", "cdrom-image" );
+ addFiltered( port + ".fileName", image );
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String vmBoolean( boolean var )
+ {
+ return Boolean.toString( var ).toUpperCase();
+ }
+
+ private static String vmInteger( int val )
+ {
+ return Integer.toString( val );
+ }
+
+ @Override
+ public void transformNonPersistent() throws VirtualizationConfigurationException
+ {
+ // Cleaned vmx construction
+ for ( Iterator<Entry<String, ConfigEntry>> it = config.entrySet().iterator(); it.hasNext(); ) {
+ Entry<String, ConfigEntry> elem = it.next();
+ if ( !STATELESS_WHITELIST_PATTERN.matcher( elem.getKey() ).find() ) {
+ it.remove();
+ }
+ }
+ addFiltered( "suspend.disabled", "TRUE" );
+ }
+
+ @Override
+ public void transformEditable() throws VirtualizationConfigurationException
+ {
+ addFiltered( "gui.applyHostDisplayScalingToGuest", "FALSE" );
+ // This is for a very old bug: Check we have at lerast USB 2.0, as
+ // a buggy dmsd removed all USB controllers
+ List<ConfigurableOptionGroup> groups = getConfigurableOptions();
+ for ( ConfigurableOptionGroup group : groups ) {
+ if ( group.groupIdentifier != ConfigurationGroups.USB_SPEED )
+ continue;
+ int currentSpeed = 0;
+ VirtOptionValue twoPointOh = null;
+ for ( VirtOptionValue option : group.availableOptions ) {
+ int s = Util.parseInt( option.getId(), 0 );
+ if ( option.isActive() && s > currentSpeed ) {
+ currentSpeed = s;
+ }
+ if ( s == 2 ) {
+ twoPointOh = option;
+ }
+ }
+ if ( currentSpeed < 3 && twoPointOh != null ) {
+ twoPointOh.apply();
+ }
+ }
+ }
+
+ @Override
+ public void transformPrivacy() throws VirtualizationConfigurationException
+ {
+ for ( Iterator<Entry<String, ConfigEntry>> it = config.entrySet().iterator(); it.hasNext(); ) {
+ Entry<String, ConfigEntry> elem = it.next();
+ String key = elem.getKey();
+ String value = elem.getValue().getValue();
+ if ( key.endsWith( ".fileName" ) && !value.startsWith( "-" )
+ && ( value.contains( "." ) || value.contains( "/" ) || value.contains( "\\" ) ) ) {
+ it.remove();
+ } else if ( PRIVACY_BLACKLIST_PATTERN.matcher( key ).find() ) {
+ it.remove();
+ }
+ }
+ }
+
+ @Override
+ public boolean addDisplayName( String name )
+ {
+ addFiltered( "displayName", name );
+ return true;
+ }
+
+ @Override
+ public boolean addRam( int mem )
+ {
+ addFiltered( "memsize", Integer.toString( mem ) );
+ return true;
+ }
+
+ public void setOs( String vendorOsId )
+ {
+ addFiltered( "guestOS", vendorOsId );
+
+ final OperatingSystem os = VirtualizationConfigurationUtils.getOsOfVirtualizerFromList( this.osList,
+ TConst.VIRT_VMWARE, vendorOsId );
+ this.setOs( os );
+ }
+
+ public byte[] getConfigurationAsByteArray()
+ {
+ return config.toString().getBytes( StandardCharsets.UTF_8 );
+ }
+
+ private static class Device
+ {
+ public boolean present = false;
+ public String deviceType = null;
+ public String filename = null;
+
+ @Override
+ public String toString()
+ {
+ return filename + " is " + deviceType + " (present: " + present + ")";
+ }
+ }
+
+ private static class Controller
+ {
+ public boolean present = true; // Seems to be implicit, seen at least for IDE...
+ public String virtualDev = null;
+ Map<String, Device> devices = new HashMap<>();
+
+ @Override
+ public String toString()
+ {
+ return virtualDev + " is (present: " + present + "): " + devices.toString();
+ }
+ }
+
+ public String getValue( String key )
+ {
+ return config.get( key );
+ }
+
+ class VmwareNoSoundCard extends VirtOptionValue
+ {
+
+ public VmwareNoSoundCard( String displayName )
+ {
+ super( "", displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ addFiltered( "sound.present", vmBoolean( false ) );
+ config.remove( "sound.virtualDev" );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return !isSetAndTrue( "sound.present" );
+ }
+
+ }
+
+ class VmWareSoundCardModelNone extends VirtOptionValue
+ {
+
+ public VmWareSoundCardModelNone( String displayName )
+ {
+ super( "none", displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ addFiltered( "sound.present", vmBoolean( false ) );
+ addFiltered( "sound.autodetect", vmBoolean( false ) );
+ config.remove( "sound.virtualDev" );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return !isSetAndTrue( "sound.present" );
+ }
+
+ }
+
+ class VmWareSoundCardModel extends VirtOptionValue
+ {
+
+ public VmWareSoundCardModel( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ addFiltered( "sound.present", vmBoolean( true ) );
+ addFiltered( "sound.autodetect", vmBoolean( true ) );
+ addFiltered( "sound.virtualDev", this.id );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return isSetAndTrue( "sound.present" ) && isSetAndTrue( "sound.autodetect" )
+ && this.id.equals( config.get( "sound.virtualDev" ) );
+ }
+
+ }
+
+ class VmWareAccel3D extends VirtOptionValue
+ {
+
+ public VmWareAccel3D( String id, String displayName )
+ {
+ super( id, displayName );
+ }
+
+ @Override
+ public void apply()
+ {
+ addFiltered( "mks.enable3d", this.id );
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ return Boolean.parseBoolean( this.id ) == isSetAndTrue( "mks.enable3d" );
+ }
+
+ }
+
+ public void setVirtualizerVersion( Version type )
+ {
+ addFiltered( "virtualHW.version", vmInteger( type.getMajor() ) );
+ }
+
+ public Version getVirtualizerVersion()
+ {
+ final short major = Integer.valueOf( Util.parseInt( config.get( "virtualHW.version" ), -1 ) ).shortValue();
+ return Version.getInstanceByMajorFromVersions( major, this.getVirtualizer().getSupportedVersions() );
+ }
+
+ class VmwareNicModel extends VirtOptionValue
+ {
+
+ private final int cardIndex;
+
+ public VmwareNicModel( int cardIndex, String id, String displayName )
+ {
+ super( id, displayName );
+ this.cardIndex = cardIndex;
+ }
+
+ @Override
+ public void apply()
+ {
+ if ( Util.isEmptyString( id ) ) {
+ config.remove( "ethernet" + cardIndex + ".virtualDev" );
+ } else {
+ addFiltered( "ethernet" + cardIndex + ".virtualDev", id );
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ String temp = config.get( "ethernet" + cardIndex + ".virtualDev" );
+ if ( temp == null )
+ return Util.isEmptyString( this.id );
+ return temp.equals( this.id );
+ }
+
+ }
+
+ class VmWareUsbSpeed extends VirtOptionValue
+ {
+ private final String[] SPEED = { null, "usb", "ehci", "usb_xhci" };
+ private final int speed;
+
+ public VmWareUsbSpeed( int speed, String displayName )
+ {
+ super( Integer.toString( speed ), displayName );
+ this.speed = speed;
+ }
+
+ @Override
+ public void apply()
+ {
+ // XXX TODO This sucks, qnd
+ for ( int i = 1; i < SPEED.length; ++i ) {
+ String key = SPEED[i] + ".present";
+ if ( i <= speed ) {
+ // Enable desired speed class, plus all lower ones
+ addFiltered( key, "TRUE" );
+ } else {
+ config.remove( key );
+ }
+ }
+ // VMware 14+ needs this to use USB 3.0 devices at USB 3.0 ports in VMs configured for < 3.0
+ if ( speed > 0 && speed < 3 ) {
+ addFiltered( "usb.mangleUsb3Speed", "TRUE" );
+ }
+ }
+
+ @Override
+ public boolean isActive()
+ {
+ int max = 0;
+ for ( int i = 1; i < SPEED.length; ++i ) {
+ if ( isSetAndTrue( SPEED[i] + ".present" ) ) {
+ max = i;
+ }
+ }
+ return speed == max;
+ }
+
+ }
+
+ @Override
+ public boolean addCpuCoreCount( int numCores )
+ {
+ addFiltered( "numvcpus", vmInteger( numCores ) );
+ return true;
+ }
+
+ public void registerVirtualHW()
+ {
+ List<VirtOptionValue> list;
+ list = new ArrayList<>();
+ list.add( new VmWareSoundCardModelNone( SoundCard.NONE ) );
+ list.add( new VmWareSoundCardModel( "", SoundCard.DEFAULT ) );
+ list.add( new VmWareSoundCardModel( "sb16", SoundCard.SOUND_BLASTER ) );
+ list.add( new VmWareSoundCardModel( "es1371", SoundCard.ES ) );
+ list.add( new VmWareSoundCardModel( "hdaudio", SoundCard.HD_AUDIO ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.SOUND_CARD_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VmWareAccel3D( "FALSE", "2D" ) );
+ list.add( new VmWareAccel3D( "TRUE", "3D" ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.GFX_TYPE, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VmwareNicModel( 0, "", Ethernet.AUTO ) );
+ list.add( new VmwareNicModel( 0, "vlance", Ethernet.PCNET32 ) );
+ list.add( new VmwareNicModel( 0, "e1000", Ethernet.E1000 ) );
+ list.add( new VmwareNicModel( 0, "e1000e", Ethernet.E1000E ) );
+ list.add( new VmwareNicModel( 0, "vmxnet", Ethernet.VMXNET ) );
+ list.add( new VmwareNicModel( 0, "vmxnet3", Ethernet.VMXNET3 ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.NIC_MODEL, list ) );
+
+ list = new ArrayList<>();
+ list.add( new VmWareUsbSpeed( 0, Usb.NONE ) );
+ list.add( new VmWareUsbSpeed( 1, Usb.USB1_1 ) );
+ list.add( new VmWareUsbSpeed( 2, Usb.USB2_0 ) );
+ list.add( new VmWareUsbSpeed( 3, Usb.USB3_0 ) );
+ configurableOptions.add( new ConfigurableOptionGroup( ConfigurationGroups.USB_SPEED, list ) );
+ }
+
+ @Override
+ public String getFileNameExtension()
+ {
+ return VirtualizationConfigurationVmware.FILE_NAME_EXTENSION;
+ }
+
+ @Override
+ public void validate() throws VirtualizationConfigurationException
+ {
+ }
+
+ @Override
+ public void disableUsb()
+ {
+ new VmWareUsbSpeed( 0, Usb.NONE ).apply();
+ }
+
+ @Override
+ public String getSuspendedFile()
+ {
+ return config.get( "checkpoint.vmState" );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmwareFileFormat.java b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmwareFileFormat.java
new file mode 100644
index 0000000..356a034
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmwareFileFormat.java
@@ -0,0 +1,284 @@
+package org.openslx.virtualization.configuration;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.util.Util;
+
+class KeyValuePair
+{
+ public final String key;
+ public final String value;
+
+ public KeyValuePair( String key, String value )
+ {
+ this.key = key;
+ this.value = value;
+ }
+}
+
+public class VirtualizationConfigurationVmwareFileFormat
+{
+
+ private static final Logger LOGGER = LogManager.getLogger( VirtualizationConfigurationVmwareFileFormat.class );
+
+ private Map<String, ConfigEntry> entries = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
+
+ public VirtualizationConfigurationVmwareFileFormat()
+ {
+ // (void)
+ }
+
+ public VirtualizationConfigurationVmwareFileFormat( File file ) throws IOException, VirtualizationConfigurationException
+ {
+ int todo = (int)Math.min( 100000, file.length() );
+ int offset = 0;
+ byte[] data = new byte[ todo ];
+ FileInputStream fr = null;
+ try {
+ fr = new FileInputStream( file );
+ while ( todo > 0 ) {
+ int ret = fr.read( data, offset, todo );
+ if ( ret <= 0 )
+ break;
+ todo -= ret;
+ offset += ret;
+ }
+ } finally {
+ Util.safeClose( fr );
+ }
+ init( data, offset );
+
+ }
+
+ public VirtualizationConfigurationVmwareFileFormat( InputStream is ) throws IOException, VirtualizationConfigurationException
+ {
+ int todo = Math.max( 4000, Math.min( 100000, is.available() ) );
+ int offset = 0;
+ byte[] data = new byte[ todo ];
+ while ( todo > 0 ) {
+ int ret = is.read( data, offset, todo );
+ if ( ret <= 0 )
+ break;
+ todo -= ret;
+ offset += ret;
+ }
+ init( data, offset );
+ }
+
+ public VirtualizationConfigurationVmwareFileFormat( byte[] vmxContent, int length ) throws VirtualizationConfigurationException
+ {
+ init( vmxContent, length );
+ }
+
+ // function is used for both .vmx and .vmdk files
+ private void init( byte[] vmxContent, int length ) throws VirtualizationConfigurationException
+ {
+ try {
+ boolean isValid = false;
+ BufferedReader reader = getVmxReader( vmxContent, length );
+ String line;
+ while ( ( line = reader.readLine() ) != null ) {
+ KeyValuePair entry = parse( line );
+
+ if ( entry != null ) {
+ // TODO: This is supposed to be case insensitive.
+ // Check if there are other consequences from lowercase entries in converted vmx files.
+ if ( entry.key.equals( "virtualHW.version" ) || entry.key.equals( "ddb.virtualHWVersion" )
+ || entry.key.equals( "virtualhw.version" ) ) {
+ isValid = true;
+ }
+ set( entry.key, unescape( entry.value ) );
+ }
+ }
+ if ( !isValid ) {
+ throw new VirtualizationConfigurationException( "Not in VMX format." );
+ }
+ } catch ( IOException e ) {
+ LOGGER.warn( "Exception when loading vmx from byte array (how!?)", e );
+ }
+ }
+
+ public static BufferedReader getVmxReader( byte[] vmxContent, int length ) throws IOException
+ {
+ Charset cs = getCharset( vmxContent, length );
+ if ( cs == null )
+ cs = StandardCharsets.UTF_8; // YES BECAUSE THIS IS NOT VMX AND EVERYTHING IS SHIT
+ return new BufferedReader( new InputStreamReader( new ByteArrayInputStream( vmxContent, 0, length ), cs ) );
+ }
+
+ /**
+ * Get charset of config. Returns null if input doesn't look like a vmx file.
+ * @param vmxContent
+ * @param length
+ * @return
+ */
+ public static Charset getCharset( byte[] vmxContent, int length )
+ {
+ String csName = detectCharset( new ByteArrayInputStream( vmxContent, 0, length ) );
+ if ( csName == null )
+ return null;
+ Charset cs = null;
+ try {
+ cs = Charset.forName( csName );
+ } catch ( Exception e ) {
+ LOGGER.warn( "Could not instantiate charset " + csName, e );
+ }
+ if ( cs == null )
+ cs = StandardCharsets.ISO_8859_1;
+ return cs;
+ }
+
+ private String unescape( String value )
+ {
+ String ret = value;
+ if ( ret.contains( "|22" ) ) {
+ ret = ret.replace( "|22", "\"" );
+ }
+ if ( ret.contains( "|7C" ) ) {
+ ret = ret.replace( "|7C", "|" );
+ }
+ return ret;
+ }
+
+ private static String detectCharset( InputStream is )
+ {
+ boolean isVmware = false;
+ try {
+ BufferedReader csDetectReader = new BufferedReader( new InputStreamReader( is, StandardCharsets.ISO_8859_1 ) );
+ String line;
+ while ( ( line = csDetectReader.readLine() ) != null ) {
+ KeyValuePair entry = parse( line );
+ if ( entry == null )
+ continue;
+ if ( entry.key.equals( ".encoding" ) || entry.key.equals( "encoding" ) ) {
+ return entry.value;
+ }
+ if ( entry.key.equals( "virtualHW.version" ) || entry.key.equals( "memsize" ) || entry.key.equals( "displayName") ) {
+ isVmware = true;
+ }
+ }
+ } catch ( Exception e ) {
+ LOGGER.warn( "Could not detect charset, fallback to latin1", e );
+ }
+ if ( !isVmware )
+ return null;
+ // Dumb fallback
+ return "ISO-8859-1";
+ }
+
+ public Set<Entry<String, ConfigEntry>> entrySet()
+ {
+ return entries.entrySet();
+ }
+
+ private static final Pattern settingMatcher1 = Pattern.compile( "^\\s*(#?[a-z0-9\\.\\:_]+)\\s*=\\s*\"(.*)\"\\s*$", Pattern.CASE_INSENSITIVE );
+ private static final Pattern settingMatcher2 = Pattern.compile( "^\\s*(#?[a-z0-9\\.\\:_]+)\\s*=\\s*([^\"]*)\\s*$", Pattern.CASE_INSENSITIVE );
+
+ private static KeyValuePair parse( String line )
+ {
+ Matcher matcher = settingMatcher1.matcher( line );
+ if ( !matcher.matches() ) {
+ matcher = settingMatcher2.matcher( line );
+ }
+ if ( !matcher.matches() ) {
+ return null;
+ }
+ return new KeyValuePair( matcher.group( 1 ), matcher.group( 2 ) );
+
+ }
+
+ public ConfigEntry set( String key, String value, boolean replace )
+ {
+ if ( !replace && entries.containsKey( key ) )
+ return null;
+ ConfigEntry ce = new ConfigEntry( value );
+ entries.put( key, ce );
+ return ce;
+ }
+
+ public ConfigEntry set( String key, String value )
+ {
+ return set( key, value, true );
+ }
+
+ public ConfigEntry set( KeyValuePair entry )
+ {
+ return set( entry.key, entry.value );
+ }
+
+ public void remove( String key )
+ {
+ entries.remove( key );
+ }
+
+ public String get( String key )
+ {
+ ConfigEntry ce = entries.get( key );
+ if ( ce == null )
+ return null;
+ return ce.value;
+ }
+
+ @Override
+ public String toString()
+ {
+ set( ".encoding", "UTF-8" );
+ StringBuilder sb = new StringBuilder( 300 );
+ for ( Entry<String, ConfigEntry> entry : entries.entrySet() ) {
+ ConfigEntry value = entry.getValue();
+ sb.append( entry.getKey() );
+ sb.append( " = \"" );
+ sb.append( value.getEscaped() );
+ sb.append( "\"\n" );
+ }
+ return sb.toString();
+ }
+
+ public static class ConfigEntry
+ {
+ private String value;
+
+ public ConfigEntry( String value )
+ {
+ this.value = value;
+ }
+
+ public String getEscaped()
+ {
+ String ret = value;
+ if ( ret.contains( "|" ) ) {
+ ret = ret.replace( "|", "|7C" );
+ }
+ if ( ret.contains( "\"" ) ) {
+ ret = ret.replace( "\"", "|22" );
+ }
+ return ret;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public void setValue( String value )
+ {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/container/ContainerBindMount.java b/src/main/java/org/openslx/virtualization/configuration/container/ContainerBindMount.java
new file mode 100644
index 0000000..0c1788e
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/container/ContainerBindMount.java
@@ -0,0 +1,74 @@
+package org.openslx.virtualization.configuration.container;
+
+import java.util.Objects;
+
+/**
+ * This class implements a model for a bind mount entry in the docker context
+ * (eg. docker run ... --mount type=bind,source=source,target=target,options ... ). A list of objects of this class is stored in
+ * {@link ContainerMeta}.
+ */
+public class ContainerBindMount {
+
+ public enum ContainerMountType {
+ DEFAULT,
+ CONTAINER_IMAGE
+ }
+
+ private ContainerMountType mount_type = ContainerMountType.DEFAULT;
+ private String source = "";
+ private String target = "";
+ private String options = "";
+
+ public ContainerBindMount() {
+ }
+
+ public ContainerBindMount(String source, String target, String options) {
+ this(ContainerMountType.DEFAULT,source,target,options);
+ }
+
+ public ContainerBindMount(ContainerMountType mount_type, String source, String target, String options) {
+ this.mount_type = mount_type;
+ this.source = source;
+ this.target = target;
+ this.options = options;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ public String getOptions() {
+ return options;
+ }
+
+ public void setOptions(String options) {
+ this.options = options;
+ }
+
+ public ContainerMountType getMountType() {
+ return this.mount_type;
+ }
+
+
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ContainerBindMount that = (ContainerBindMount) o;
+ return Objects.equals(source, that.source) && Objects.equals(mount_type, that.mount_type)
+ && Objects.equals(target, that.target) && Objects.equals(options, that.options);
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(source, target, options);
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/container/ContainerDefinition.java b/src/main/java/org/openslx/virtualization/configuration/container/ContainerDefinition.java
new file mode 100644
index 0000000..825d0c3
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/container/ContainerDefinition.java
@@ -0,0 +1,195 @@
+package org.openslx.virtualization.configuration.container;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.stream.JsonReader;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openslx.util.Util;
+import org.openslx.util.TarArchiveUtil.TarArchiveReader;
+import org.openslx.util.TarArchiveUtil.TarArchiveWriter;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+public class ContainerDefinition {
+
+ // TODO database needs a refactoring to store container details
+ // TODO refatoring: tar.gz of this object is not useful, for smaller dockerfiles it makes the package lager.
+ // remove the containerRecipe, ContainerMeta holds in build_context the dockerfile.
+
+ protected static final Logger LOGGER = LogManager.getLogger(ContainerDefinition.class);
+
+ protected static final String CONTAINER_FILE = "dockerfile";
+ protected static final String CONTAINER_META_FILE = "container_meta.json";
+
+ /**
+ * The file to construct a real container image, could be an dockerfile or a singularity recipe.
+ */
+ public String containerRecipe = "";
+
+ /**
+ * Further container information, see {@link ContainerMeta}.
+ */
+ public ContainerMeta containerMeta;
+
+ public ContainerDefinition() {
+ containerMeta = new ContainerMeta();
+ }
+
+ /**
+ * Copy Constructor
+ *
+ * @param containerDef {@link ContainerDefinition} from which to make a deep copy.
+ */
+ public ContainerDefinition(ContainerDefinition containerDef) {
+ containerRecipe = String.valueOf(containerDef.getContainerRecipe());
+ containerMeta = new ContainerMeta(containerDef.getContainerMeta());
+ }
+
+ /**
+ * Utility function to create a {@link ContainerDefinition} object for a byte array downloaded from the server.
+ *
+ * @param rawTarData Downloaded tar.gz file from the server as a byte array.
+ * @return New object of ContainerDefinition.
+ */
+ public static ContainerDefinition fromByteArray(byte[] rawTarData) {
+
+ ContainerDefinition containerDef = new ContainerDefinition();
+
+ try {
+ TarArchiveReader tarReader = new TarArchiveReader(new ByteArrayInputStream(rawTarData), true, true);
+
+ while (tarReader.hasNextEntry()) {
+ if (tarReader.getEntryName().equals(CONTAINER_FILE))
+ containerDef.setContainerRecipe(tarReader.readCurrentEntry());
+ if (tarReader.getEntryName().equals(CONTAINER_META_FILE))
+ containerDef.setContainerMeta(tarReader.readCurrentEntry());
+ }
+ tarReader.close();
+
+ } catch (IOException e) {
+ LOGGER.error("Could not create a ContainerDefinition Object for rawTarData", e);
+ }
+
+ return containerDef;
+ }
+
+ public String getContainerRecipe() {
+ return containerRecipe;
+ }
+
+ public void setContainerRecipe(String containerRecipe) {
+ this.containerRecipe = containerRecipe;
+ }
+
+ public void setContainerRecipe(File containerRecipeFile) {
+ this.containerRecipe = readContainerRecipe(containerRecipeFile);
+ }
+
+ public void setContainerRecipe(byte[] rawContainerRecipe) {
+ this.containerRecipe = new String(rawContainerRecipe, StandardCharsets.UTF_8);
+ }
+
+ public ContainerMeta getContainerMeta() {
+ return containerMeta;
+ }
+
+ public void setContainerMeta(byte[] containerMeta) {
+ Gson gson = new GsonBuilder().create();
+ this.containerMeta = gson.fromJson(new JsonReader(
+ new InputStreamReader(new ByteArrayInputStream(containerMeta), StandardCharsets.UTF_8)),
+ ContainerMeta.class);
+ }
+
+ /**
+ * Serializes the ContainerMeta and Container Description (e.g. dockerfile) into an tar.gz archive.
+ *
+ * @return A ByteBuffer object of the container definition. Can be uploaded so satellite server.
+ */
+ public ByteBuffer toByteBuffer() {
+
+ ByteBuffer containerDef = null;
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ TarArchiveWriter tarWriter = new TarArchiveWriter(baos);
+ tarWriter.writeFile(CONTAINER_META_FILE, gson.toJson(containerMeta));
+ tarWriter.writeFile(CONTAINER_FILE, containerRecipe);
+ Util.safeClose(tarWriter);
+
+ containerDef = ByteBuffer.wrap(baos.toByteArray());
+ } catch (IOException e) {
+ LOGGER.warn("Could not create a tar file", e);
+ }
+
+ return containerDef;
+ }
+
+ private String readContainerRecipe(File file) {
+ String recipe = null;
+ try {
+
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+ ByteArrayOutputStream rawFile = new ByteArrayOutputStream();
+ int count;
+ byte[] data = new byte[1024];
+ while ((count = bis.read(data)) != -1) {
+ rawFile.write(data, 0, count);
+ }
+
+ String rawRecipe = new String(rawFile.toByteArray(), StandardCharsets.UTF_8);
+
+ // replace windows by unix EOL
+ recipe = rawRecipe.replaceAll("\\r\\n", "\n");
+
+ bis.close();
+
+ } catch (IOException e) {
+ LOGGER.error("Could not read Container Recipe", e);
+ }
+ return recipe;
+ }
+
+ /**
+ * Saves containerRecipe and containerMeta at the provided location.
+ *
+ * @param destDir destination directory for containerRecipe and containerMeta.
+ */
+ public void saveLocal(File destDir) {
+ writeFile(destDir, containerRecipe, CONTAINER_FILE);
+ }
+
+ private void writeFile(File destDir, String fileContent, String filename) {
+ File output = new File(destDir, filename);
+ try {
+ FileWriter fw = new FileWriter(output);
+ fw.write(fileContent);
+ fw.flush();
+ fw.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ LOGGER.error("Could not write File", e);
+ }
+ }
+
+ public ContainerImageContext getContainerImageContext() {
+ return ContainerImageContext.fromInt(containerMeta.getContainerImageContext());
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ContainerDefinition that = (ContainerDefinition) o;
+ return containerRecipe.equals(that.containerRecipe) && containerMeta.equals(that.containerMeta);
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(containerRecipe, containerMeta);
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/container/ContainerImageContext.java b/src/main/java/org/openslx/virtualization/configuration/container/ContainerImageContext.java
new file mode 100644
index 0000000..f19d419
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/container/ContainerImageContext.java
@@ -0,0 +1,10 @@
+package org.openslx.virtualization.configuration.container;
+
+public enum ContainerImageContext {
+
+ DOCKERFILE, GIT_REPOSITORY, IMAGE_REPOSITORY, DOCKER_ARCHIVE;
+
+ public static ContainerImageContext fromInt(int index) {
+ return values()[index];
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/container/ContainerMeta.java b/src/main/java/org/openslx/virtualization/configuration/container/ContainerMeta.java
new file mode 100644
index 0000000..4dbf64b
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/container/ContainerMeta.java
@@ -0,0 +1,166 @@
+package org.openslx.virtualization.configuration.container;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * ContainerMeta is used to store container specific information. An object of
+ * this class will be serialized with gson to a json file.
+ * <p>
+ * TODO rename build_context_method to container_image_context, requires update
+ * in database(json)
+ * <p>
+ * TODO rename build_context_url to build_context TODO refactoring build_context
+ * is either a dockerfile or a git url with a dockerfile.
+ */
+public class ContainerMeta {
+
+ private int build_context_method;
+ private String image_repo;
+ private String build_context_url;
+ private String image_name;
+ private String run_options;
+ private String run_command;
+ private String image_type;
+ private List<ContainerBindMount> bind_mount_config = new ArrayList<>();
+
+ public ContainerMeta() {
+
+ image_repo = "";
+ build_context_method = ContainerImageContext.DOCKERFILE.ordinal();
+ build_context_url = "";
+ image_name = "";
+ run_options = "";
+ run_command = "";
+ image_type = ContainerImageType.LECTURE.toString();
+ bind_mount_config = new ArrayList<>();
+ }
+
+ public ContainerMeta(ContainerMeta containerMeta) {
+ build_context_method = containerMeta.build_context_method;
+ build_context_url = containerMeta.build_context_url;
+ image_name = containerMeta.image_name;
+ run_options = containerMeta.run_options;
+ run_command = containerMeta.run_command;
+ image_repo = containerMeta.image_repo;
+
+ for (ContainerBindMount bm : containerMeta.bind_mount_config)
+ bind_mount_config.add(new ContainerBindMount(bm.getSource(), bm.getTarget(), bm.getOptions()));
+
+ }
+
+ public int getContainerImageContext() {
+ return build_context_method;
+ }
+
+ public void setContainerImageContext(int buildContextMethod) {
+ this.build_context_method = buildContextMethod;
+ }
+
+ public String getBuildContextUrl() {
+ return build_context_url;
+ }
+
+ public void setBuildContextUrl(String buildContextUrl) {
+ this.build_context_url = buildContextUrl;
+ }
+
+ public String getRunOptions() {
+ return run_options;
+ }
+
+ public void setRunOptions(String run_options) {
+ this.run_options = run_options;
+ }
+
+ public String getRunCommand() {
+ return this.run_command;
+ }
+
+ public void setRunCommand(String run_command) {
+ this.run_command = run_command;
+ }
+
+ public String getImageName() {
+ return image_name;
+ }
+
+ public void setImageName(String image_name) {
+ this.image_name = image_name;
+ }
+
+ public List<ContainerBindMount> getBindMountConfig() {
+ return bind_mount_config;
+ }
+
+ public void setBindMountConfig(List<ContainerBindMount> bindMountConfig) {
+ this.bind_mount_config = bindMountConfig;
+ }
+
+ public String getImageRepo() {
+ return image_repo;
+ }
+
+ public void setImageRepo(String from_image) {
+ this.image_repo = from_image;
+ }
+
+ public ContainerImageType getImageType() {
+ if (image_type == null || image_type.length() == 0)
+ return ContainerImageType.LECTURE;
+
+ // turn string representation into enum-var 'LECTURE' ->
+ // ContainerImageType.LECTURE
+ return ContainerImageType.valueOf(image_type);
+ }
+
+ public void setImageType(ContainerImageType image_type) {
+ // set constant representation of the enum-var e.g. ContainerImageType.LECTURE
+ // -> 'LECTURE'
+ this.image_type = image_type.name();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ContainerMeta that = (ContainerMeta) o;
+ return Objects.equals(build_context_url, that.build_context_url) && Objects.equals(image_name, that.image_name)
+ && Objects.equals(run_options, that.run_options) && Objects.equals(run_command, that.run_command)
+ && Objects.equals(bind_mount_config, that.bind_mount_config)
+ && Objects.equals(image_repo, that.image_repo) && Objects.equals(image_type, that.image_type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(build_context_url, image_name, run_options, run_command, bind_mount_config, image_repo,
+ image_type);
+ }
+
+ public enum ContainerImageType implements org.apache.thrift.TEnum {
+ LECTURE("Lecture"), BATCH("Batch"), DATA("Data");
+
+ private final String displayLable;
+
+ ContainerImageType(String name) {
+ this.displayLable = name;
+ }
+
+ public boolean equalNames(String other) {
+ return displayLable.equals(other);
+ }
+
+ @Override
+ public String toString() {
+ return this.displayLable;
+ }
+
+ @Override
+ public int getValue() {
+ return this.ordinal();
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModClientToDozModServer.java b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModClientToDozModServer.java
new file mode 100644
index 0000000..8a08d05
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModClientToDozModServer.java
@@ -0,0 +1,21 @@
+package org.openslx.virtualization.configuration.data;
+
+/**
+ * Data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModClientToDozModServer}
+ * transformation.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationDataDozModClientToDozModServer
+{
+ /**
+ * Creates a new data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModClientToDozModServer}
+ * transformation.
+ */
+ public ConfigurationDataDozModClientToDozModServer()
+ {
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToDozModClient.java b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToDozModClient.java
new file mode 100644
index 0000000..4e18d48
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToDozModClient.java
@@ -0,0 +1,116 @@
+package org.openslx.virtualization.configuration.data;
+
+import java.io.File;
+
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+
+/**
+ * Data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModServerToDozModClient}
+ * transformation.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationDataDozModServerToDozModClient
+{
+ /**
+ * Display name for a transformation of a virtualization configuration.
+ */
+ private final String displayName;
+
+ /**
+ * Disk image file for a transformation of a virtualization configuration.
+ */
+ private final File diskImage;
+
+ /**
+ * Guest operating system for a transformation of a virtualization configuration.
+ */
+ private final OperatingSystem guestOs;
+
+ /**
+ * Virtualizer identifier for a transformation of a virtualization configuration.
+ */
+ private final String virtualizerId;
+
+ /**
+ * Total amount of available memory for a transformation of a virtualization configuration.
+ */
+ private final int totalMemory;
+
+ /**
+ * Creates a new data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModServerToDozModClient}
+ * transformation.
+ *
+ * @param displayName display name for a transformation of a virtualization configuration.
+ * @param diskImage disk image file for a transformation of a virtualization configuration.
+ * @param guestOs guest operating system for a transformation of a virtualization configuration.
+ * @param virtualizerId virtualizer identifier for a transformation of a virtualization
+ * configuration.
+ * @param totalMemory total amount of available memory for a transformation of a virtualization
+ * configuration.
+ */
+ public ConfigurationDataDozModServerToDozModClient( String displayName, File diskImage, OperatingSystem guestOs,
+ String virtualizerId, int totalMemory )
+ {
+ this.displayName = displayName;
+ this.diskImage = diskImage;
+ this.guestOs = guestOs;
+ this.virtualizerId = virtualizerId;
+ this.totalMemory = totalMemory;
+ }
+
+ /**
+ * Returns the display name for a transformation of a virtualization configuration.
+ *
+ * @return display name for a transformation of a virtualization configuration.
+ */
+ public String getDisplayName()
+ {
+ return this.displayName;
+ }
+
+ /**
+ * Returns the disk image file for a transformation of a virtualization configuration.
+ *
+ * @return disk image file for a transformation of a virtualization configuration.
+ */
+ public File getDiskImage()
+ {
+ return this.diskImage;
+ }
+
+ /**
+ * Returns the guest operating system for a transformation of a virtualization configuration.
+ *
+ * @return guest operating system for a transformation of a virtualization configuration.
+ */
+ public OperatingSystem getGuestOs()
+ {
+ return this.guestOs;
+ }
+
+ /**
+ * Returns the virtualizer identifier for a transformation of a virtualization configuration.
+ *
+ * @return virtualizer identifier for a transformation of a virtualization configuration.
+ */
+ public String getVirtualizerId()
+ {
+ return this.virtualizerId;
+ }
+
+ /**
+ * Returns the total amount of available memory for a transformation of a virtualization
+ * configuration.
+ *
+ * @return total amount of available memory for a transformation of a virtualization
+ * configuration.
+ */
+ public int getTotalMemory()
+ {
+ return this.totalMemory;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToStatelessClient.java b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToStatelessClient.java
new file mode 100644
index 0000000..65cc7ce
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/data/ConfigurationDataDozModServerToStatelessClient.java
@@ -0,0 +1,79 @@
+package org.openslx.virtualization.configuration.data;
+
+/**
+ * Data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModServerToStatelessClient}
+ * transformation.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationDataDozModServerToStatelessClient
+{
+ /**
+ * Display name for a transformation of a virtualization configuration.
+ */
+ private final String displayName;
+
+ /**
+ * Operating system identifier for a transformation of a virtualization configuration.
+ */
+ private final String osId;
+
+ /**
+ * State whether USB access is allowed or not for a transformation of a virtualization
+ * configuration.
+ */
+ private final boolean hasUsbAccess;
+
+ /**
+ * Creates a new data container to collect and store input arguments for a
+ * {@link org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModServerToStatelessClient}
+ * transformation.
+ *
+ * @param displayName display name for a transformation of a virtualization configuration.
+ * @param osId operating system identifier for a transformation of a virtualization
+ * configuration.
+ * @param hasUsbAccess state whether USB access is allowed or not for a transformation of a
+ * virtualization configuration.
+ */
+ public ConfigurationDataDozModServerToStatelessClient( String displayName, String osId, boolean hasUsbAccess )
+ {
+ this.displayName = displayName;
+ this.osId = osId;
+ this.hasUsbAccess = hasUsbAccess;
+ }
+
+ /**
+ * Returns the display name for a transformation of a virtualization configuration.
+ *
+ * @return display name for a transformation of a virtualization configuration.
+ */
+ public String getDisplayName()
+ {
+ return this.displayName;
+ }
+
+ /**
+ * Returns the operating system identifier for a transformation of a virtualization
+ * configuration.
+ *
+ * @return operating system identifier for a transformation of a virtualization configuration.
+ */
+ public String getOsId()
+ {
+ return this.osId;
+ }
+
+ /**
+ * Returns the state whether USB access is allowed or not for a transformation of a
+ * virtualization configuration.
+ *
+ * @return state whether USB access is allowed or not for a transformation of a virtualization
+ * configuration.
+ */
+ public boolean hasUsbAccess()
+ {
+ return this.hasUsbAccess;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogic.java b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogic.java
new file mode 100644
index 0000000..90319e2
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogic.java
@@ -0,0 +1,28 @@
+package org.openslx.virtualization.configuration.logic;
+
+import org.openslx.virtualization.configuration.VirtualizationConfiguration;
+import org.openslx.virtualization.configuration.transformation.TransformationGeneric;
+
+/**
+ * Generic transformation logic for virtualization configurations.
+ * <p>
+ * This transformation logic represents an encapsulated transformation logic to transform
+ * virtualization configurations as part of server or client implementations.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of configuration data used as input arguments for a transformation.
+ */
+public abstract class ConfigurationLogic<T> extends TransformationGeneric<VirtualizationConfiguration, T>
+{
+ /**
+ * Creates a new generic transformation logic for virtualization configurations.
+ *
+ * @param name generic transformation logic name.
+ */
+ public ConfigurationLogic( String name )
+ {
+ super( name );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModClientToDozModServer.java b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModClientToDozModServer.java
new file mode 100644
index 0000000..f5d9a76
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModClientToDozModServer.java
@@ -0,0 +1,76 @@
+package org.openslx.virtualization.configuration.logic;
+
+import org.openslx.virtualization.configuration.VirtualizationConfiguration;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationException;
+import org.openslx.virtualization.configuration.data.ConfigurationDataDozModClientToDozModServer;
+import org.openslx.virtualization.configuration.transformation.TransformationException;
+
+/**
+ * Transformation logic for virtualization configurations between a dozmod-client and a
+ * dozmod-server.
+ * <p>
+ * This transformation logic is applied while uploading a new virtualization configuration from a
+ * dozmod-client to a dozmod-server.
+ *
+ * <pre>
+ * +------------------------------+ DozModClientToDozModServer +------------------------------+
+ * | virtualization configuration | ----------------------------â–¶ | virtualization configuration |
+ * +---------------+--------------+ transformation logic +---------------+--------------+
+ * | dozmod-client | | dozmod-server |
+ * +---------------+ +---------------+
+ * </pre>
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationLogicDozModClientToDozModServer
+ extends ConfigurationLogic<ConfigurationDataDozModClientToDozModServer>
+{
+ /**
+ * Name of the transformation logic for virtualization configurations.
+ */
+ private static final String CONFIGURATION_LOGIC_NAME = "Transformation of virtualization configuration during upload from DozMod client to DozMod server";
+
+ /**
+ * Creates a new transformation logic for virtualization configurations between a dozmod-client
+ * and a dozmod-server.
+ */
+ public ConfigurationLogicDozModClientToDozModServer()
+ {
+ super( ConfigurationLogicDozModClientToDozModServer.CONFIGURATION_LOGIC_NAME );
+ }
+
+ /**
+ * Validates a virtualization configuration and input arguments for a transformation.
+ *
+ * @param config virtualization configuration for the validation.
+ * @param args input arguments for the validation.
+ * @throws TransformationException validation has failed.
+ */
+ private void validateInputs( VirtualizationConfiguration config,
+ ConfigurationDataDozModClientToDozModServer args )
+ throws TransformationException
+ {
+ if ( config == null || args == null ) {
+ throw new TransformationException( "Virtualization configuration or input arguments are missing!" );
+ } else if ( config.getDisplayName() == null ) {
+ throw new TransformationException( "Display name is missing in virtualization configuration!" );
+ }
+ }
+
+ @Override
+ public void transform( VirtualizationConfiguration config,
+ ConfigurationDataDozModClientToDozModServer args )
+ throws TransformationException
+ {
+ // check if input parameters for a transformation are valid
+ this.validateInputs( config, args );
+
+ // apply the privacy filter on the given virtualization configuration
+ try {
+ config.transformPrivacy();
+ } catch ( VirtualizationConfigurationException e ) {
+ throw new TransformationException( e.getLocalizedMessage() );
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToDozModClient.java b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToDozModClient.java
new file mode 100644
index 0000000..acbf4fc
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToDozModClient.java
@@ -0,0 +1,197 @@
+package org.openslx.virtualization.configuration.logic;
+
+import java.util.Map;
+
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.virtualization.configuration.VirtualizationConfiguration;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationException;
+import org.openslx.virtualization.configuration.data.ConfigurationDataDozModServerToDozModClient;
+import org.openslx.virtualization.configuration.transformation.TransformationException;
+
+/**
+ * Transformation logic for virtualization configurations between a dozmod-server and a
+ * dozmod-client.
+ * <p>
+ * This transformation logic is applied while downloading an existing virtualization configuration
+ * from a dozmod-server to a dozmod-client.
+ *
+ * <pre>
+ * +------------------------------+ DozModServerToDozModClient +------------------------------+
+ * | virtualization configuration | ----------------------------â–¶ | virtualization configuration |
+ * +---------------+--------------+ transformation logic +---------------+--------------+
+ * | dozmod-server | | dozmod-client |
+ * +---------------+ +---------------+
+ * </pre>
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationLogicDozModServerToDozModClient
+ extends ConfigurationLogic<ConfigurationDataDozModServerToDozModClient>
+{
+ /**
+ * Name of the transformation logic for virtualization configurations.
+ */
+ private static final String CONFIGURATION_LOGIC_NAME = "Transformation of virtualization configuration during download from DozMod server to DozMod client";
+
+ /**
+ * Default number of CPU cores set by the configuration logic for the virtualization
+ * configuration's virtualizer.
+ */
+ private static final int CONFIGURATION_LOGIC_NUM_CPU_CORES = 1;
+
+ /**
+ * Default memory in megabytes set by the configuration logic for the virtualization
+ * configuration's virtualizer.
+ */
+ private static final int CONFIGURATION_LOGIC_MEMORY_MIN = 1024;
+
+ /**
+ * Creates a new transformation logic for virtualization configurations between a dozmod-server
+ * and a dozmod-client.
+ */
+ public ConfigurationLogicDozModServerToDozModClient()
+ {
+ super( ConfigurationLogicDozModServerToDozModClient.CONFIGURATION_LOGIC_NAME );
+ }
+
+ /**
+ * Validates a virtualization configuration and input arguments for a transformation.
+ *
+ * @param config virtualization configuration for the validation.
+ * @param args input arguments for the validation.
+ * @throws TransformationException validation has failed.
+ */
+ private void validateInputs( VirtualizationConfiguration config,
+ ConfigurationDataDozModServerToDozModClient args )
+ throws TransformationException
+ {
+ if ( config == null || args == null ) {
+ throw new TransformationException( "Virtualization configuration or input arguments are missing!" );
+ } else if ( args.getDisplayName() == null || args.getDisplayName().isEmpty() ) {
+ throw new TransformationException( "Valid display name is not specified!" );
+ } else if ( args.getDiskImage() == null || !args.getDiskImage().exists() ) {
+ throw new TransformationException( "Valid disk image file is not specified!" );
+ } else if ( ! ( args.getTotalMemory() > 0 ) ) {
+ throw new TransformationException( "Total memory amount is not specified!" );
+ }
+ }
+
+ /**
+ * Rounds a given value to the nearest factor.
+ *
+ * @param value input value for the rounding.
+ * @param nearestFactor nearest factor for the rounding.
+ * @return rounded value as a multiple of the nearest factor.
+ *
+ * @apiNote This utility method rounds the given value to an integer value and no to a floating
+ * point value.
+ */
+ private static int roundToNearest( int value, int nearestFactor )
+ {
+ return ( value / nearestFactor ) * nearestFactor;
+ }
+
+ /**
+ * Calculates the amount of memory for the virtualization configuration depending on the
+ * available resources of the dozmod-client's host system.
+ *
+ * @param totalMemory maximum memory available on the dozmod-client's host system.
+ * @param osMaxMemory maximum memory supported by the defined operating system in the
+ * virtualization configuration.
+ * @return amount of memory for the virtualization configuration in megabytes
+ */
+ private static int calculateVirtualizationMemoryOnDozmodClient( int totalMemory, int osMaxMemory )
+ {
+ // calculate the amount of memory
+ int memory = totalMemory / 2 - 512;
+
+ // increase calculated memory if lower memory limit is undercut
+ if ( memory < ConfigurationLogicDozModServerToDozModClient.CONFIGURATION_LOGIC_MEMORY_MIN ) {
+ memory = ConfigurationLogicDozModServerToDozModClient.CONFIGURATION_LOGIC_MEMORY_MIN;
+ }
+
+ // limit virtualization memory if the available host's system memory amount is smaller
+ if ( osMaxMemory > 0 && memory > osMaxMemory ) {
+ memory = osMaxMemory;
+ }
+
+ // round to nearest factor of 4, otherwise VMware virtualization configuration files are invalid
+ return ConfigurationLogicDozModServerToDozModClient.roundToNearest( memory, 4 );
+ }
+
+ @Override
+ public void transform( VirtualizationConfiguration config,
+ ConfigurationDataDozModServerToDozModClient args )
+ throws TransformationException
+ {
+ // check if input parameters for a transformation are valid
+ this.validateInputs( config, args );
+
+ // set display name
+ if ( !config.addDisplayName( args.getDisplayName() ) ) {
+ throw new TransformationException( "Can not set display name in virtualization configuration!" );
+ }
+
+ // append hard disk drive
+ if ( !config.addHddTemplate( args.getDiskImage(), null, null ) ) {
+ throw new TransformationException( "Can not configure hard disk in virtualization configuration!" );
+ }
+
+ // append default NAT interface
+ if ( !config.addDefaultNat() ) {
+ throw new TransformationException( "Can not configure NAT interface in virtualization configuration!" );
+ }
+
+ // set the guest OS if specified
+ final OperatingSystem guestOs = args.getGuestOs();
+ final String virtualizerId = args.getVirtualizerId();
+ int osMaxMemory = 0;
+
+ if ( guestOs != null && virtualizerId != null ) {
+ final Map<String, String> virtOsIdMap = guestOs.getVirtualizerOsId();
+ if ( virtOsIdMap != null ) {
+ // set guest operating system if possible
+ final String virtOsId = virtOsIdMap.get( virtualizerId );
+ if ( virtOsId != null ) {
+ config.setOs( virtOsId );
+ }
+
+ // get maximum memory of editable host for guestOs if possible
+ final int maxMemMb = guestOs.getMaxMemMb();
+ if ( maxMemMb > 0 ) {
+ osMaxMemory = maxMemMb;
+ }
+ }
+ }
+
+ // set CPU core count
+ if ( !config.addCpuCoreCount( ConfigurationLogicDozModServerToDozModClient.CONFIGURATION_LOGIC_NUM_CPU_CORES ) ) {
+ throw new TransformationException( "Can not set CPU core count in virtualization configuration!" );
+ }
+
+ // calculate and set memory
+ final int virtualizationMemory = ConfigurationLogicDozModServerToDozModClient
+ .calculateVirtualizationMemoryOnDozmodClient( args.getTotalMemory(), osMaxMemory );
+ if ( !config.addRam( virtualizationMemory ) ) {
+ throw new TransformationException( "Can not set memory in virtualization configuration!" );
+ }
+
+ // append first empty floppy drive
+ config.addFloppy( 0, null, true );
+ // append second empty floppy drive
+ config.addFloppy( 1, null, true );
+
+ // append first empty (ISO-based) CDROM drive
+ config.addCdrom( "" );
+ // append second CDROM drive connected to the host's physical drive
+ config.addCdrom( null );
+
+ // apply settings to edit virtualized system locally
+ try {
+ config.transformEditable();
+ } catch ( VirtualizationConfigurationException e ) {
+ throw new TransformationException( e.getLocalizedMessage() );
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java
new file mode 100644
index 0000000..2d3e861
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java
@@ -0,0 +1,108 @@
+package org.openslx.virtualization.configuration.logic;
+
+import org.openslx.virtualization.configuration.VirtualizationConfiguration;
+import org.openslx.virtualization.configuration.VirtualizationConfiguration.EtherType;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationException;
+import org.openslx.virtualization.configuration.data.ConfigurationDataDozModServerToStatelessClient;
+import org.openslx.virtualization.configuration.transformation.TransformationException;
+
+/**
+ * Transformation logic for virtualization configurations between a dozmod-server and a stateless
+ * client.
+ * <p>
+ * This transformation logic is applied while downloading an existing virtualization configuration
+ * from a dozmod-server to a stateless client.
+ *
+ * <pre>
+ * +------------------------------+ DozModServerToStatelessClient +------------------------------+
+ * | virtualization configuration | -------------------------------â–¶ | virtualization configuration |
+ * +---------------+--------------+ transformation logic +------------------+-----------+
+ * | dozmod-server | | stateless client |
+ * +---------------+ +------------------+
+ * </pre>
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class ConfigurationLogicDozModServerToStatelessClient
+ extends ConfigurationLogic<ConfigurationDataDozModServerToStatelessClient>
+{
+ /**
+ * Name of the transformation logic for virtualization configurations.
+ */
+ private static final String CONFIGURATION_LOGIC_NAME = "Transformation of virtualization configuration during download from DozMod server to stateless client";
+
+ /**
+ * Default type for an ethernet interface in a virtualization configuration.
+ */
+ private static final EtherType CONFIGURATION_DEFAULT_ETHERNET_TYPE = EtherType.NAT;
+
+ /**
+ * Creates a new transformation logic for virtualization configurations between a dozmod-server
+ * and a stateless client.
+ */
+ public ConfigurationLogicDozModServerToStatelessClient()
+ {
+ super( ConfigurationLogicDozModServerToStatelessClient.CONFIGURATION_LOGIC_NAME );
+ }
+
+ /**
+ * Validates a virtualization configuration and input arguments for a transformation.
+ *
+ * @param config virtualization configuration for the validation.
+ * @param args input arguments for the validation.
+ * @throws TransformationException validation has failed.
+ */
+ private void validateInputs( VirtualizationConfiguration config,
+ ConfigurationDataDozModServerToStatelessClient args )
+ throws TransformationException
+ {
+ if ( config == null || args == null ) {
+ throw new TransformationException( "Virtualization configuration or input arguments are missing!" );
+ } else if ( args.getDisplayName() == null || args.getDisplayName().isEmpty() ) {
+ throw new TransformationException( "Valid display name is not specified!" );
+ }
+ }
+
+ @Override
+ public void transform( VirtualizationConfiguration config,
+ ConfigurationDataDozModServerToStatelessClient args )
+ throws TransformationException
+ {
+ // check if input parameters for a transformation are valid
+ this.validateInputs( config, args );
+
+ // apply settings to run virtualized system in a stateless manner
+ try {
+ config.transformNonPersistent();
+ } catch ( VirtualizationConfigurationException e ) {
+ throw new TransformationException( e.getLocalizedMessage() );
+ }
+
+ // set display name of lecture
+ if ( !config.addDisplayName( args.getDisplayName() ) ) {
+ throw new TransformationException( "Can not set display name in virtualization configuration!" );
+ }
+
+ // append hard disk drive (with no referenced image as content)
+ if ( !config.addEmptyHddTemplate() ) {
+ throw new TransformationException( "Can not configure hard disk in virtualization configuration!" );
+ }
+
+ // append default NAT interface
+ if ( !config.addEthernet(
+ ConfigurationLogicDozModServerToStatelessClient.CONFIGURATION_DEFAULT_ETHERNET_TYPE ) ) {
+ throw new TransformationException( "Can not configure NAT interface in virtualization configuration!" );
+ }
+
+ // set the guest OS if specified
+ if ( args.getOsId() != null ) {
+ config.setOs( args.getOsId() );
+ }
+
+ // disable USB if necessary
+ if ( !args.hasUsbAccess() ) {
+ config.disableUsb();
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/Transformation.java b/src/main/java/org/openslx/virtualization/configuration/transformation/Transformation.java
new file mode 100644
index 0000000..af0e181
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/Transformation.java
@@ -0,0 +1,65 @@
+package org.openslx.virtualization.configuration.transformation;
+
+/**
+ * Represents a transformation that transforms (alters) a given configuration with specified input
+ * arguments.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of the configuration which will be transformed.
+ * @param <R> type of input arguments for the transformation.
+ */
+public abstract class Transformation<T, R> implements TransformationFunction<T, R>
+{
+ /**
+ * Name of the transformation.
+ */
+ private final String name;
+
+ /**
+ * State of the transformation.
+ */
+ private boolean enabled;
+
+ /**
+ * Creates a transformation.
+ *
+ * @param name comprehensible name for the transformation.
+ */
+ public Transformation( String name )
+ {
+ this.name = name;
+ this.setEnabled( true );
+ }
+
+ /**
+ * Returns the name of the transformation.
+ *
+ * @return name of the transformation.
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+ /**
+ * Returns the state of the transformation.
+ *
+ * @return state of the transformation.
+ */
+ public boolean isEnabled()
+ {
+ return this.enabled;
+ }
+
+ /**
+ * Sets the state for the transformation.
+ *
+ * @param enabled state for the transformation.
+ */
+ public void setEnabled( boolean enabled )
+ {
+ this.enabled = enabled;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationException.java b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationException.java
new file mode 100644
index 0000000..72f320d
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationException.java
@@ -0,0 +1,25 @@
+package org.openslx.virtualization.configuration.transformation;
+
+/**
+ * An exception of a transformation error.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class TransformationException extends Exception
+{
+ /**
+ * Version for serialization.
+ */
+ private static final long serialVersionUID = 7293420658901349154L;
+
+ /**
+ * Creates a transformation exception including an error message.
+ *
+ * @param errorMsg message to describe the exception.
+ */
+ public TransformationException( String errorMsg )
+ {
+ super( errorMsg );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationFunction.java b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationFunction.java
new file mode 100644
index 0000000..b5be7a0
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationFunction.java
@@ -0,0 +1,39 @@
+package org.openslx.virtualization.configuration.transformation;
+
+/**
+ * Represents a transformation operation that transforms (alters) a given configuration with
+ * specified input arguments and returns no result.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of the configuration which will be transformed.
+ * @param <R> type of input arguments for the transformation.
+ */
+@FunctionalInterface
+public interface TransformationFunction<T, R>
+{
+ /**
+ * Transforms a given configuration with the specified input arguments.
+ *
+ * @param config configuration which will be transformed.
+ * @param args input arguments for the transformation.
+ *
+ * @throws TransformationException transformation of the configuration failed.
+ */
+ public void transform( T config, R args ) throws TransformationException;
+
+ /**
+ * Applies the transformation function {@link #transform(Object, Object)} to the given
+ * configuration and specified input arguments.
+ *
+ * @param config configuration which will be transformed.
+ * @param args input arguments for the transformation.
+ *
+ * @throws TransformationException transformation of the configuration failed.
+ */
+ public default void apply( T config, R args ) throws TransformationException
+ {
+ this.transform( config, args );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationGeneric.java b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationGeneric.java
new file mode 100644
index 0000000..4ad36c7
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationGeneric.java
@@ -0,0 +1,25 @@
+package org.openslx.virtualization.configuration.transformation;
+
+/**
+ * Represents a generic transformation that transforms (alters) a given configuration with specified
+ * input arguments. The generic transformation does not depend on any external states of a
+ * virtualizer.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of the configuration which will be transformed.
+ * @param <R> type of input arguments for the transformation.
+ */
+public abstract class TransformationGeneric<T, R> extends Transformation<T, R>
+{
+ /**
+ * Create a generic transformation.
+ *
+ * @param name comprehensible name for the transformation.
+ */
+ public TransformationGeneric( String name )
+ {
+ super( name );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationManager.java b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationManager.java
new file mode 100644
index 0000000..18d7e43
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationManager.java
@@ -0,0 +1,158 @@
+package org.openslx.virtualization.configuration.transformation;
+
+import java.util.ArrayList;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * A transformation manager is a class to manage several transformations and their application.
+ *
+ * Transformations can be registered at the transformation manager. The transformation manager has
+ * the ability to apply all registered transformations on a given configuration and specified input
+ * arguments.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of the configuration which will be transformed by all transformations.
+ * @param <R> type of input arguments for all transformations.
+ */
+public class TransformationManager<T, R>
+{
+ /**
+ * List of registered transformations.
+ */
+ private ArrayList<Transformation<T, R>> transformations;
+
+ /**
+ * Reference to the configuration that will be transformed (altered).
+ */
+ private T config;
+
+ /**
+ * Reference to the input arguments for all registered transformations.
+ */
+ private R args;
+
+ /**
+ * Logger instance to log messages.
+ */
+ private static final Logger LOGGER = LogManager.getLogger( TransformationManager.class );
+
+ /**
+ * Create a transformation manager.
+ *
+ * @param config configuration which will be transformed.
+ * @param args input arguments for all registered transformations.
+ */
+ public TransformationManager( T config, R args )
+ {
+ this.transformations = new ArrayList<Transformation<T, R>>();
+ this.config = config;
+ this.args = args;
+ }
+
+ /**
+ * Registers and enables a transformation.
+ *
+ * @param transformation existing transformation that will be registered and enabled.
+ */
+ public void register( Transformation<T, R> transformation )
+ {
+ this.register( transformation, true );
+ }
+
+ /**
+ * Registers a transformation and sets its state.
+ *
+ * @param transformation existing transformation that will be registered.
+ * @param enabled state for the existing transformation that will be set.
+ */
+ public void register( Transformation<T, R> transformation, boolean enabled )
+ {
+ LOGGER.debug( "Register transformation '" + transformation.getName() + "' and "
+ + ( enabled ? "enable" : "do not enable" ) + " it" );
+
+ transformation.setEnabled( enabled );
+ this.transformations.add( transformation );
+ }
+
+ /**
+ * Registers a transformation function as a new transformation and enables the registered
+ * transformation.
+ *
+ * @param name comprehensible name for the transformation.
+ * @param function transformation operation for the transformation.
+ */
+ public void register( String name, TransformationFunction<T, R> function )
+ {
+ this.register( name, function, true );
+ }
+
+ /**
+ * Registers a transformation function as a new transformation and sets the state of the
+ * registered transformation.
+ *
+ * @param name comprehensible name for the transformation.
+ * @param function transformation operation for the transformation.
+ * @param enabled state for the transformation.
+ */
+ public void register( String name, TransformationFunction<T, R> function, boolean enabled )
+ {
+ this.register( new Transformation<T, R>( name ) {
+ @Override
+ public void transform( T document, R args ) throws TransformationException
+ {
+ function.apply( document, args );
+ }
+ }, enabled );
+ }
+
+ /**
+ * Applies all registered transformations, whose state is set to <code>enabled</code>, to the
+ * referenced configuration and input arguments.
+ *
+ * @throws TransformationException transformation of the configuration failed.
+ */
+ public void transform() throws TransformationException
+ {
+ for ( Transformation<T, R> transformation : this.transformations ) {
+ LOGGER.debug( "Apply transformation '" + transformation.getName() + "'" );
+ try {
+ transformation.apply( this.config, this.args );
+ } catch ( TransformationException e ) {
+ final String errorMsg =
+ "Error in configuration filter '" + transformation.getName() + "': " + e.getLocalizedMessage();
+ throw new TransformationException( errorMsg );
+ }
+ }
+ }
+
+ /**
+ * Returns a human readable summary of all registered transformations.
+ *
+ * @return human readable summary of all registered transformations.
+ */
+ private String showTransformations()
+ {
+ String transformationSummary = "";
+ final int maxFilterNumCharacters = ( this.transformations.size() + 1 ) / 10;
+
+ for ( int i = 0; i < this.transformations.size(); i++ ) {
+ final Transformation<T, R> transformation = this.transformations.get( i );
+ final String paddedNumber = String.format( "%-" + maxFilterNumCharacters + "s", i + 1 );
+ final String transformationState = transformation.isEnabled() ? "[ active ]" : "[inactive]";
+ transformationSummary += paddedNumber + ": " + transformationState + " ";
+ transformationSummary += transformation.getName() + System.lineSeparator();
+ }
+
+ return transformationSummary;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.showTransformations();
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationSpecific.java b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationSpecific.java
new file mode 100644
index 0000000..7038abf
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationSpecific.java
@@ -0,0 +1,44 @@
+package org.openslx.virtualization.configuration.transformation;
+
+/**
+ * Represents a specific transformation that transforms (alters) a given configuration with
+ * specified input arguments. The specific transformation depends on external states of a
+ * specified virtualizer.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ *
+ * @param <T> type of the configuration which will be transformed.
+ * @param <R> type of input arguments for the transformation.
+ * @param <H> type of the external input source.
+ */
+public abstract class TransformationSpecific<T, R, H> extends Transformation<T, R>
+{
+ /**
+ * Reference to virtualizer to query external states.
+ */
+ private final H virtualizer;
+
+ /**
+ * Create a specific transformation.
+ *
+ * @param name comprehensible name for the transformation.
+ * @param virtualizer initialized virtualizer.
+ */
+ public TransformationSpecific( String name, H virtualizer )
+ {
+ super( name );
+
+ this.virtualizer = virtualizer;
+ }
+
+ /**
+ * Returns the referenced virtualizer of the transformation.
+ *
+ * @return referenced virtualizer of the transformation.
+ */
+ public H getVirtualizer()
+ {
+ return this.virtualizer;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImage.java b/src/main/java/org/openslx/virtualization/disk/DiskImage.java
new file mode 100644
index 0000000..dfb242a
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImage.java
@@ -0,0 +1,253 @@
+package org.openslx.virtualization.disk;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.openslx.bwlp.thrift.iface.Virtualizer;
+import org.openslx.thrifthelper.TConst;
+import org.openslx.util.Util;
+import org.openslx.virtualization.Version;
+
+/**
+ * Disk image for virtual machines.
+ *
+ * @implNote This class is the abstract base class to implement various specific disk images (like
+ * QCOW2 or VMDK).
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public abstract class DiskImage implements Closeable
+{
+ /**
+ * Stores the image file of the disk.
+ */
+ private RandomAccessFile diskImage = null;
+
+ /**
+ * Creates a new disk image from an existing image file with a known disk image format.
+ *
+ * @param diskImage file to a disk storing the image content.
+ *
+ * @implNote Do not use this constructor to create a new disk image from an image file with
+ * unknown disk image format. Instead, use the factory method
+ * {@link #newInstance(File)} to probe unknown disk image files before creation.
+ */
+ protected DiskImage( RandomAccessFile diskImage )
+ {
+ this.diskImage = diskImage;
+ }
+
+ /**
+ * Returns the disk image file.
+ *
+ * @return the disk image file.
+ */
+ protected RandomAccessFile getDiskImage()
+ {
+ return this.diskImage;
+ }
+
+ /**
+ * Checks whether disk image is standalone and do not depend on other files (e.g. snapshot
+ * files).
+ *
+ * @return state whether disk image is standalone or not.
+ *
+ * @throws DiskImageException unable to check if disk image is standalone.
+ */
+ public abstract boolean isStandalone() throws DiskImageException;
+
+ /**
+ * Checks whether disk image is compressed.
+ *
+ * @return state whether disk image is compressed or not.
+ *
+ * @throws DiskImageException unable to check whether disk image is compressed.
+ */
+ public abstract boolean isCompressed() throws DiskImageException;
+
+ /**
+ * Checks whether disk image is a snapshot.
+ *
+ * @return state whether disk image is a snapshot or not.
+ *
+ * @throws DiskImageException unable to check whether disk image is a snapshot.
+ */
+ public abstract boolean isSnapshot() throws DiskImageException;
+
+ /**
+ * Returns the version of the disk image format.
+ *
+ * @return version of the disk image format.
+ *
+ * @throws DiskImageException unable to obtain version of the disk image format.
+ */
+ public abstract Version getVersion() throws DiskImageException;
+
+ /**
+ * Returns the disk image description.
+ *
+ * @return description of the disk image.
+ *
+ * @throws DiskImageException unable to obtain description of the disk image.
+ */
+ public abstract String getDescription() throws DiskImageException;
+
+ /**
+ * Returns the format of the disk image.
+ *
+ * @return format of the disk image.
+ */
+ public abstract ImageFormat getFormat();
+
+ /**
+ * Creates a new disk image from an existing image file with an unknown disk image format.
+ *
+ * @param diskImagePath file to a disk storing the image content.
+ * @return concrete disk image instance.
+ *
+ * @throws FileNotFoundException cannot find specified disk image file.
+ * @throws IOException cannot access the content of the disk image file.
+ * @throws DiskImageException disk image file has an invalid and unknown disk image format.
+ */
+ public static DiskImage newInstance( File diskImagePath )
+ throws FileNotFoundException, IOException, DiskImageException
+ {
+ // Make sure this doesn't escape the scope, in case instantiation fails - we can't know when the GC
+ // would come along and close this file, which is problematic on Windows (blocking rename/delete)
+ final RandomAccessFile fileHandle = new RandomAccessFile( diskImagePath, "r" );
+
+ try {
+ if ( DiskImageQcow2.probe( fileHandle ) ) {
+ return new DiskImageQcow2( fileHandle );
+ } else if ( DiskImageVdi.probe( fileHandle ) ) {
+ return new DiskImageVdi( fileHandle );
+ } else if ( DiskImageVmdk.probe( fileHandle ) ) {
+ return new DiskImageVmdk( fileHandle );
+ }
+ } catch ( Exception e ) {
+ Util.safeClose( fileHandle );
+ throw e;
+ }
+ Util.safeClose( fileHandle );
+ final String errorMsg = "File '" + diskImagePath.getAbsolutePath() + "' is not a valid disk image!";
+ throw new DiskImageException( errorMsg );
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ Util.safeClose( diskImage );
+ }
+
+ @Override
+ protected void finalize() throws Throwable
+ {
+ close();
+ }
+
+ /**
+ * Format of a disk image.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+ public enum ImageFormat
+ {
+ // @formatter:off
+ NONE ( "none" ),
+ QCOW2( "qcow2" ),
+ VDI ( "vdi" ),
+ VMDK ( "vmdk" );
+ // @formatter:on
+
+ /**
+ * Stores filename extension of the disk image format.
+ */
+ public final String extension;
+
+ /**
+ * Create new disk image format.
+ *
+ * @param extension filename extension of the disk image format.
+ */
+ ImageFormat( String extension )
+ {
+ this.extension = extension;
+ }
+
+ /**
+ * Returns filename extension of the disk image.
+ *
+ * @return filename extension of the disk image.
+ */
+ public String getExtension()
+ {
+ return this.extension;
+ }
+
+ /**
+ * Checks if the disk image format is supported by a virtualizer.
+ *
+ * @param supportedImageFormats list of supported disk image formats of a virtualizer.
+ * @return <code>true</code> if image type is supported by the virtualizer; otherwise
+ * <code>false</code>.
+ */
+ public boolean isSupportedbyVirtualizer( List<ImageFormat> supportedImageFormats )
+ {
+ Predicate<ImageFormat> matchDiskFormat = supportedImageFormat -> supportedImageFormat.toString()
+ .equalsIgnoreCase( this.toString() );
+ return supportedImageFormats.stream().anyMatch( matchDiskFormat );
+ }
+
+ /**
+ * Returns default (preferred) disk image format for the specified virtualizer.
+ *
+ * @param virt virtualizer for that the default disk image should be determined.
+ * @return default (preferred) disk image format.
+ */
+ public static ImageFormat defaultForVirtualizer( Virtualizer virt )
+ {
+ if ( virt == null ) {
+ return null;
+ } else {
+ return ImageFormat.defaultForVirtualizer( virt.virtId );
+ }
+ }
+
+ /**
+ * Returns default (preferred) disk image format for the specified virtualizer.
+ *
+ * @param virtId ID of a virtualizer for that the default disk image should be determined.
+ * @return default (preferred) disk image format.
+ */
+ public static ImageFormat defaultForVirtualizer( String virtId )
+ {
+ ImageFormat imgFormat = null;
+
+ if ( TConst.VIRT_DOCKER.equals( virtId ) ) {
+ imgFormat = NONE;
+ } else if ( TConst.VIRT_QEMU.equals( virtId ) ) {
+ imgFormat = QCOW2;
+ } else if ( TConst.VIRT_VIRTUALBOX.equals( virtId ) ) {
+ imgFormat = VDI;
+ } else if ( TConst.VIRT_VMWARE.equals( virtId ) ) {
+ imgFormat = VMDK;
+ }
+
+ return imgFormat;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getExtension();
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImageException.java b/src/main/java/org/openslx/virtualization/disk/DiskImageException.java
new file mode 100644
index 0000000..db62917
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImageException.java
@@ -0,0 +1,25 @@
+package org.openslx.virtualization.disk;
+
+/**
+ * An exception for faulty disk image handling.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageException extends Exception
+{
+ /**
+ * Version number for serialization.
+ */
+ private static final long serialVersionUID = 5464286488698331909L;
+
+ /**
+ * Creates a disk image exception including an error message.
+ *
+ * @param errorMsg message to describe a disk image error.
+ */
+ public DiskImageException( String errorMsg )
+ {
+ super( errorMsg );
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImageQcow2.java b/src/main/java/org/openslx/virtualization/disk/DiskImageQcow2.java
new file mode 100644
index 0000000..a6b3b73
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImageQcow2.java
@@ -0,0 +1,232 @@
+package org.openslx.virtualization.disk;
+
+import java.io.RandomAccessFile;
+
+import org.openslx.virtualization.Version;
+
+/**
+ * QCOW2 disk image for virtual machines.
+ *
+ * A QCOW2 disk image consists of a header, one L1 table and several L2 tables used for lookup data
+ * clusters in the file via a two-level lookup. The QCOW2 header contains the following fields:
+ *
+ * <pre>
+ * QCOW2 (version 2 and 3) header format:
+ *
+ * magic (4 byte)
+ * version (4 byte)
+ * backing_file_offset (8 byte)
+ * backing_file_size (4 byte)
+ * cluster_bits (4 byte)
+ * size (8 byte)
+ * crypt_method (4 byte)
+ * l1_size (4 byte)
+ * l1_table_offset (8 byte)
+ * refcount_table_offset (8 byte)
+ * refcount_table_clusters (4 byte)
+ * nb_snapshots (4 byte)
+ * snapshots_offset (8 byte)
+ * incompatible_features (8 byte) [*]
+ * compatible_features (8 byte) [*]
+ * autoclear_features (8 byte) [*]
+ * refcount_order (8 byte) [*]
+ * header_length (4 byte) [*]
+ *
+ * [*] these fields are only available in the QCOW2 version 3 header format
+ * </pre>
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageQcow2 extends DiskImage
+{
+ /**
+ * Big endian representation of the big endian QCOW2 magic bytes <code>QFI\xFB</code>.
+ */
+ private static final int QCOW2_MAGIC = 0x514649fb;
+
+ /**
+ * Creates a new QCOW2 disk image from an existing QCOW2 image file.
+ *
+ * @param diskImage file to a QCOW2 disk storing the image content.
+ */
+ DiskImageQcow2( RandomAccessFile diskImage )
+ {
+ super( diskImage );
+ }
+
+ /**
+ * Probe specified file with unknown format to be a QCOW2 disk image file.
+ *
+ * @param diskImage file with unknown format that should be probed.
+ * @return state whether file is a QCOW2 disk image or not.
+ *
+ * @throws DiskImageException cannot probe specified file with unknown format.
+ */
+ public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException
+ {
+ final boolean isQcow2ImageFormat;
+
+ // goto the beginning of the disk image to read the disk image
+ final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 );
+
+ // check if disk image's magic bytes can be found
+ if ( diskImageMagic == DiskImageQcow2.QCOW2_MAGIC ) {
+ isQcow2ImageFormat = true;
+ } else {
+ isQcow2ImageFormat = false;
+ }
+
+ return isQcow2ImageFormat;
+ }
+
+ @Override
+ public boolean isStandalone() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final long qcowBackingFileOffset = DiskImageUtils.readLong( diskFile, 8 );
+ final boolean qcowStandalone;
+
+ // check if QCOW2 image does not refer to any backing file
+ if ( qcowBackingFileOffset == 0 ) {
+ qcowStandalone = true;
+ } else {
+ qcowStandalone = false;
+ }
+
+ return qcowStandalone;
+ }
+
+ @Override
+ public boolean isCompressed() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final boolean qcowUseExtendedL2;
+ boolean qcowCompressed = false;
+
+ // check if QCOW2 image uses extended L2 tables
+ // extended L2 tables are only possible in QCOW2 version 3 header format
+ if ( this.getVersion().getMajor() >= Short.valueOf( "3" ) ) {
+ // read incompatible feature bits
+ final long qcowIncompatibleFeatures = DiskImageUtils.readLong( diskFile, 72 );
+
+ // support for extended L2 tables is enabled if bit 4 is set
+ qcowUseExtendedL2 = ( ( ( qcowIncompatibleFeatures & 0x000000000010 ) >>> 4 ) == 1 );
+ } else {
+ qcowUseExtendedL2 = false;
+ }
+
+ // get number of entries in L1 table
+ final int qcowL1TableSize = DiskImageUtils.readInt( diskFile, 36 );
+
+ // check if a valid L1 table is present
+ if ( qcowL1TableSize > 0 ) {
+ // QCOW2 image contains active L1 table with more than 0 entries: l1_size > 0
+ // search for first L2 table and its first entry to get compression bit
+
+ // get cluster bits to calculate the cluster size
+ final int qcowClusterBits = DiskImageUtils.readInt( diskFile, 20 );
+ final int qcowClusterSize = ( 1 << qcowClusterBits );
+
+ // entries of a L1 table have always the size of 8 byte (64 bit)
+ final int qcowL1TableEntrySize = 8;
+
+ // entries of a L2 table have either the size of 8 or 16 byte (64 or 128 bit)
+ final int qcowL2TableEntrySize = ( qcowUseExtendedL2 ) ? 16 : 8;
+
+ // calculate number of L2 table entries
+ final int qcowL2TableSize = qcowClusterSize / qcowL2TableEntrySize;
+
+ // get offset of L1 table
+ final long qcowL1TableOffset = DiskImageUtils.readLong( diskFile, 40 );
+
+ // check for each L2 table referenced from an L1 table its entries
+ // until a compressed cluster descriptor is found
+ for ( long i = 0; i < qcowL1TableSize; i++ ) {
+ // get offset of current L2 table from the current L1 table entry
+ final long qcowL1TableEntryOffset = qcowL1TableOffset + ( i * qcowL1TableEntrySize );
+ final long qcowL1TableEntry = DiskImageUtils.readLong( diskFile, qcowL1TableEntryOffset );
+
+ // extract offset (bits 9 - 55) from L1 table entry
+ final long qcowL2TableOffset = ( qcowL1TableEntry & 0x00fffffffffffe00L );
+
+ if ( qcowL2TableOffset == 0 ) {
+ // L2 table and all clusters described by this L2 table are unallocated
+ continue;
+ }
+
+ // get each L2 table entry and check if it is a compressed cluster descriptor
+ for ( long j = 0; j < qcowL2TableSize; j++ ) {
+ // get current L2 table entry
+ final long qcowL2TableEntryOffset = qcowL2TableOffset + ( j * qcowL2TableEntrySize );
+ final long qcowL2TableEntry = DiskImageUtils.readLong( diskFile, qcowL2TableEntryOffset );
+
+ // extract cluster type (standard or compressed) (bit 62)
+ boolean qcowClusterCompressed = ( ( ( qcowL2TableEntry & 0x4000000000000000L ) >>> 62 ) == 1 );
+
+ // check if QCOW2 disk image contains at least one compressed cluster descriptor
+ if ( qcowClusterCompressed ) {
+ qcowCompressed = true;
+ break;
+ }
+ }
+
+ // terminate if one compressed cluster descriptor is already found
+ if ( qcowCompressed ) {
+ break;
+ }
+ }
+ } else {
+ // QCOW2 image does not contain an active L1 table with any entry: l1_size = 0
+ qcowCompressed = false;
+ }
+
+ return qcowCompressed;
+ }
+
+ @Override
+ public boolean isSnapshot() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final int qcowNumSnapshots = DiskImageUtils.readInt( diskFile, 56 );
+ final boolean qcowSnapshot;
+
+ // check if QCOW2 image contains at least one snapshot
+ if ( qcowNumSnapshots == 0 ) {
+ qcowSnapshot = true;
+ } else {
+ qcowSnapshot = false;
+ }
+
+ return qcowSnapshot;
+ }
+
+ @Override
+ public Version getVersion() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final int qcowVersion = DiskImageUtils.readInt( diskFile, 4 );
+
+ // check QCOW2 file format version
+ if ( qcowVersion < 2 || qcowVersion > 3 ) {
+ // QCOW2 disk image does not contain a valid QCOW2 version
+ final String errorMsg = "Invalid QCOW2 version in header found!";
+ throw new DiskImageException( errorMsg );
+ }
+
+ return new Version( Integer.valueOf( qcowVersion ).shortValue() );
+ }
+
+ @Override
+ public String getDescription() throws DiskImageException
+ {
+ // QCOW2 disk image format does not support any disk description
+ return null;
+ }
+
+ @Override
+ public ImageFormat getFormat()
+ {
+ return ImageFormat.QCOW2;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImageUtils.java b/src/main/java/org/openslx/virtualization/disk/DiskImageUtils.java
new file mode 100644
index 0000000..6bcef7f
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImageUtils.java
@@ -0,0 +1,144 @@
+package org.openslx.virtualization.disk;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Utilities to parse disk image format elements and control versions of disk images.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageUtils
+{
+ /**
+ * Returns the size of a specified disk image file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @return size of the disk image file in bytes.
+ *
+ * @throws DiskImageException unable to obtain the size of the disk image file.
+ */
+ public static long getImageSize( RandomAccessFile diskImage ) throws DiskImageException
+ {
+ final long imageSize;
+
+ try {
+ imageSize = diskImage.length();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+
+ return imageSize;
+ }
+
+ /**
+ * Reads two bytes ({@link Short}) at a given <code>offset</code> from the specified disk image
+ * file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the two bytes.
+ * @return value of the two bytes from the disk image file as {@link Short}.
+ *
+ * @throws DiskImageException unable to read two bytes from the disk image file.
+ */
+ public static short readShort( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ short value = 0;
+
+ if ( imageSize >= ( offset + Short.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readShort();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads four bytes ({@link Integer}) at a given <code>offset</code> from the specified disk
+ * image file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the four bytes.
+ * @return value of the four bytes from the disk image file as {@link Integer}.
+ *
+ * @throws DiskImageException unable to read four bytes from the disk image file.
+ */
+ public static int readInt( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ int value = 0;
+
+ if ( imageSize >= ( offset + Integer.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readInt();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads eight bytes ({@link Long}) at a given <code>offset</code> from the specified disk image
+ * file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the eight bytes.
+ * @return value of the eight bytes from the disk image file as {@link Long}.
+ *
+ * @throws DiskImageException unable to read eight bytes from the disk image file.
+ */
+ public static long readLong( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ long value = 0;
+
+ if ( imageSize >= ( offset + Long.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readLong();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads a variable number of bytes (<code>numBytes</code>) at a given <code>offset</code> from the specified disk image file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading <code>numBytes</code> bytes.
+ * @param numBytes number of bytes to read at <code>offset</code>.
+ * @return read bytes from the disk image file as {@link byte[]}.
+ *
+ * @throws DiskImageException unable to read bytes from the disk image file.
+ */
+ public static byte[] readBytesAsArray( RandomAccessFile diskImage, long offset, int numBytes )
+ throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ byte values[] = {};
+
+ if ( imageSize >= ( offset + numBytes ) ) {
+ try {
+ diskImage.seek( offset );
+ values = new byte[ numBytes ];
+ diskImage.readFully( values );
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return values;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImageVdi.java b/src/main/java/org/openslx/virtualization/disk/DiskImageVdi.java
new file mode 100644
index 0000000..1e5b20b
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImageVdi.java
@@ -0,0 +1,112 @@
+package org.openslx.virtualization.disk;
+
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.openslx.virtualization.Version;
+
+/**
+ * VDI disk image for virtual machines.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageVdi extends DiskImage
+{
+ /**
+ * Big endian representation of the little endian VDI magic bytes (signature).
+ */
+ private static final int VDI_MAGIC = 0x7f10dabe;
+ /**
+ * Just 16 null-bytes
+ */
+ private static final byte[] ZERO_UUID = new byte[16];
+
+ /**
+ * Creates a new VDI disk image from an existing VDI image file.
+ *
+ * @param diskImage file to a VDI disk storing the image content.
+ */
+ DiskImageVdi( RandomAccessFile diskImage )
+ {
+ super( diskImage );
+ }
+
+ /**
+ * Probe specified file with unknown format to be a VDI disk image file.
+ *
+ * @param diskImage file with unknown format that should be probed.
+ * @return state whether file is a VDI disk image or not.
+ *
+ * @throws DiskImageException cannot probe specified file with unknown format.
+ */
+ public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException
+ {
+ final boolean isVdiImageFormat;
+
+ // goto the beginning of the disk image to read the magic bytes
+ // skip first 64 bytes (opening tag)
+ final int diskImageMagic = DiskImageUtils.readInt( diskImage, 64 );
+
+ // check if disk image's magic bytes can be found
+ if ( diskImageMagic == DiskImageVdi.VDI_MAGIC ) {
+ isVdiImageFormat = true;
+ } else {
+ isVdiImageFormat = false;
+ }
+
+ return isVdiImageFormat;
+ }
+
+ @Override
+ public boolean isStandalone() throws DiskImageException
+ {
+ // VDI does not seem to support split VDI files, so VDI files are always standalone
+ return true;
+ }
+
+ @Override
+ public boolean isCompressed() throws DiskImageException
+ {
+ // compression is done by sparsifying the disk files, there is no flag for it
+ return false;
+ }
+
+ @Override
+ public boolean isSnapshot() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+
+ // if parent UUID is set, the VDI file is a snapshot
+ byte[] parentUuid = DiskImageUtils.readBytesAsArray( diskFile, 440, 16 );
+
+ return !Arrays.equals( ZERO_UUID, parentUuid );
+ }
+
+ @Override
+ public Version getVersion() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+
+ final short vdiVersionMajor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 68 ) );
+ final short vdiVersionMinor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 70 ) );
+
+ return new Version( vdiVersionMajor, vdiVersionMinor );
+ }
+
+ @Override
+ public String getDescription() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ byte[] data = DiskImageUtils.readBytesAsArray( diskFile, 84, 256 );
+ // This will replace invalid chars. Maybe use CharsetDecoder and fall back to latin1 on error
+ return new String( data, StandardCharsets.UTF_8 );
+ }
+
+ @Override
+ public ImageFormat getFormat()
+ {
+ return ImageFormat.VDI;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/disk/DiskImageVmdk.java b/src/main/java/org/openslx/virtualization/disk/DiskImageVmdk.java
new file mode 100644
index 0000000..77986ef
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/disk/DiskImageVmdk.java
@@ -0,0 +1,282 @@
+package org.openslx.virtualization.disk;
+
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+
+import org.openslx.util.Util;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationVmwareFileFormat;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.configuration.VirtualizationConfigurationException;
+
+/**
+ * VMDK (sparse extent) disk image for virtual machines.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageVmdk extends DiskImage
+{
+ /**
+ * Big endian representation of the little endian magic bytes <code>KDMV</code>.
+ */
+ private static final int VMDK_MAGIC = 0x4b444d56;
+
+ /**
+ * Size of a VMDK disk image data cluster in bytes.
+ */
+ private static final int VMDK_SECTOR_SIZE = 512;
+
+ /**
+ * Default hardware version of a VMDK disk image.
+ */
+ private static final int VMDK_DEFAULT_HW_VERSION = 10;
+
+ /**
+ * Stores disk configuration if VMDK disk image contains an embedded descriptor file.
+ */
+ private final VirtualizationConfigurationVmwareFileFormat vmdkConfig;
+
+ /**
+ * Creates a new VMDK disk image from an existing VMDK image file.
+ *
+ * @param diskImage file to a VMDK disk storing the image content.
+ *
+ * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed.
+ */
+ DiskImageVmdk( RandomAccessFile diskImage ) throws DiskImageException
+ {
+ super( diskImage );
+
+ this.vmdkConfig = this.parseVmdkConfig();
+ }
+
+ /**
+ * Probe specified file with unknown format to be a VMDK disk image file.
+ *
+ * @param diskImage file with unknown format that should be probed.
+ * @return state whether file is a VMDK disk image or not.
+ *
+ * @throws DiskImageException cannot probe specified file with unknown format.
+ */
+ public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException
+ {
+ final boolean isVmdkImageFormat;
+
+ // goto the beginning of the disk image to read the magic bytes
+ final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 );
+
+ // check if disk image's magic bytes can be found
+ if ( diskImageMagic == DiskImageVmdk.VMDK_MAGIC ) {
+ isVmdkImageFormat = true;
+ } else {
+ isVmdkImageFormat = false;
+ }
+
+ return isVmdkImageFormat;
+ }
+
+ /**
+ * Returns the creation type from the VMDK's embedded descriptor file.
+ *
+ * @return creation type from the VMDK's embedded descriptor file.
+ */
+ private String getCreationType()
+ {
+ final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
+ final String vmdkCreationType;
+
+ if ( vmdkConfig == null ) {
+ // VMDK disk image does not contain any descriptor file
+ // assume that the file is not stand alone
+ vmdkCreationType = null;
+ } else {
+ // VMDK disk image contains a descriptor file
+ // get creation type from the content of the descriptor file
+ vmdkCreationType = this.vmdkConfig.get( "createType" );
+ }
+
+ return vmdkCreationType;
+ }
+
+ /**
+ * Parse the configuration of the VMDK's embedded descriptor file.
+ *
+ * @return parsed configuration of the VMDK's embedded descriptor file.
+ *
+ * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed.
+ */
+ protected VirtualizationConfigurationVmwareFileFormat parseVmdkConfig() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final VirtualizationConfigurationVmwareFileFormat vmdkConfig;
+
+ // get offset and size of descriptor file embedded into the VMDK disk image
+ final long vmdkDescriptorSectorOffset = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 28 ) );
+ final long vmdkDescriptorSectorSize = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 36 ) );
+
+ if ( vmdkDescriptorSectorOffset > 0 ) {
+ // get content of descriptor file embedded into the VMDK disk image
+ final long vmdkDescriptorOffset = vmdkDescriptorSectorOffset * DiskImageVmdk.VMDK_SECTOR_SIZE;
+ final long vmdkDescriptorSizeMax = vmdkDescriptorSectorSize * DiskImageVmdk.VMDK_SECTOR_SIZE;
+ final String descriptorStr = new String ( DiskImageUtils.readBytesAsArray( diskFile, vmdkDescriptorOffset,
+ Long.valueOf( vmdkDescriptorSizeMax ).intValue() ), StandardCharsets.US_ASCII );
+
+ // get final length of the content within the sectors to be able to trim all 'zero' characters
+ final int vmdkDescriptorSize = descriptorStr.indexOf( 0 );
+
+ // if final length of the content is invalid, throw an exception
+ if ( vmdkDescriptorSize > vmdkDescriptorSizeMax || vmdkDescriptorSize < 0 ) {
+ final String errorMsg = "Embedded descriptor size in VMDK disk image is invalid!";
+ throw new DiskImageException( errorMsg );
+ }
+
+ // trim all 'zero' characters at the end of the descriptor content to avoid errors during parsing
+ final String configStr = descriptorStr.substring( 0, vmdkDescriptorSize );
+
+ // create configuration instance from content of the descriptor file
+ try {
+ vmdkConfig = new VirtualizationConfigurationVmwareFileFormat( configStr.getBytes(), vmdkDescriptorSize );
+ } catch ( VirtualizationConfigurationException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ } else {
+ // there is no descriptor file embedded into the VMDK disk image
+ vmdkConfig = null;
+ }
+
+ return vmdkConfig;
+ }
+
+ /**
+ * Returns parsed configuration of the VMDK's embedded descriptor file.
+ *
+ * @return parsed configuration of the VMDK's embedded descriptor file.
+ */
+ protected VirtualizationConfigurationVmwareFileFormat getVmdkConfig()
+ {
+ return this.vmdkConfig;
+ }
+
+ /**
+ * Returns the hardware version from the VMDK's embedded descriptor file.
+ *
+ * If the VMDK's embedded descriptor file does not contain any hardware version configuration
+ * entry, the default hardware version (see {@link #VMDK_DEFAULT_HW_VERSION}) is returned.
+ *
+ * @return hardware version from the VMDK's embedded descriptor file.
+ *
+ * @throws DiskImageException unable to obtain the VMDK's hardware version of the disk image
+ * format.
+ */
+ public Version getHwVersion() throws DiskImageException
+ {
+ final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
+ final Version hwVersion;
+
+ if ( vmdkConfig != null ) {
+ // VMDK image contains a hardware version, so return parsed hardware version
+ // if hardware version cannot be parsed, return default hardware version
+ final String hwVersionStr = vmdkConfig.get( "ddb.virtualHWVersion" );
+
+ final int hwVersionMajor = Util.parseInt( hwVersionStr, DiskImageVmdk.VMDK_DEFAULT_HW_VERSION );
+ hwVersion = new Version( Integer.valueOf( hwVersionMajor ).shortValue() );
+ } else {
+ // VMDK image does not contain any hardware version, so return default hardware version
+ final int hwVersionMajor = DiskImageVmdk.VMDK_DEFAULT_HW_VERSION;
+ hwVersion = new Version( Integer.valueOf( hwVersionMajor ).shortValue() );
+ }
+
+ return hwVersion;
+ }
+
+ @Override
+ public boolean isStandalone() throws DiskImageException
+ {
+ final String vmdkCreationType = this.getCreationType();
+ final boolean vmdkStandalone;
+
+ if ( vmdkCreationType != null ) {
+ // creation type is defined, so check if VMDK disk image is a snapshot
+ if ( this.isSnapshot() ) {
+ // VMDK disk image is a snapshot and not stand alone
+ vmdkStandalone = false;
+ } else {
+ // VMDK disk image is not a snapshot
+ // determine stand alone disk image property
+ vmdkStandalone = vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ||
+ vmdkCreationType.equalsIgnoreCase( "monolithicSparse" );
+ }
+ } else {
+ // creation type is not defined
+ // assume that the file is not stand alone
+ vmdkStandalone = false;
+ }
+
+ return vmdkStandalone;
+ }
+
+ @Override
+ public boolean isCompressed() throws DiskImageException
+ {
+ final String vmdkCreationType = this.getCreationType();
+ final boolean vmdkCompressed;
+
+ if ( vmdkCreationType != null && vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ) {
+ // creation type is defined, and VMDK disk image is compressed
+ vmdkCompressed = true;
+ } else {
+ // creation type for compression is not defined
+ // assume that the file is not compressed
+ vmdkCompressed = false;
+ }
+
+ return vmdkCompressed;
+ }
+
+ @Override
+ public boolean isSnapshot() throws DiskImageException
+ {
+ final VirtualizationConfigurationVmwareFileFormat vmdkConfig = this.getVmdkConfig();
+ final boolean vmdkSnapshot;
+
+ if ( vmdkConfig == null ) {
+ // VMDK disk image does not contain any descriptor file
+ // assume that the file is not a snapshot
+ vmdkSnapshot = false;
+ } else {
+ // get parent CID to determine snapshot disk image property
+ final String parentCid = vmdkConfig.get( "parentCID" );
+
+ if ( parentCid != null && !parentCid.equalsIgnoreCase( "ffffffff" ) ) {
+ // link to parent content identifier is defined, so VMDK disk image is a snapshot
+ vmdkSnapshot = true;
+ } else {
+ // link to parent content identifier is not defined, so VMDK disk image is not a snapshot
+ vmdkSnapshot = false;
+ }
+ }
+
+ return vmdkSnapshot;
+ }
+
+ @Override
+ public Version getVersion() throws DiskImageException
+ {
+ final RandomAccessFile diskFile = this.getDiskImage();
+ final int vmdkVersion = Integer.reverseBytes( DiskImageUtils.readInt( diskFile, 4 ) );
+
+ return new Version( Integer.valueOf( vmdkVersion ).shortValue() );
+ }
+
+ @Override
+ public String getDescription() throws DiskImageException
+ {
+ return null;
+ }
+
+ @Override
+ public ImageFormat getFormat()
+ {
+ return ImageFormat.VMDK;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/hardware/ConfigurationGroups.java b/src/main/java/org/openslx/virtualization/hardware/ConfigurationGroups.java
new file mode 100644
index 0000000..8a0ab13
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/hardware/ConfigurationGroups.java
@@ -0,0 +1,21 @@
+package org.openslx.virtualization.hardware;
+
+public enum ConfigurationGroups
+{
+
+ NIC_MODEL( "E0VirtDev" ),
+ USB_SPEED( "maxUSBSpeed" ),
+ SOUND_CARD_MODEL( "sound" ),
+ GFX_MODEL( "graphics" ),
+ GFX_TYPE( "3DAcceleration" ),
+ HW_VERSION( "HWVersion" );
+
+ /** Identifier to use when looking up translation for this group. Should never change */
+ public final String i18n;
+
+ private ConfigurationGroups( String i18n )
+ {
+ this.i18n = i18n;
+ }
+
+}
diff --git a/src/main/java/org/openslx/virtualization/hardware/Ethernet.java b/src/main/java/org/openslx/virtualization/hardware/Ethernet.java
new file mode 100644
index 0000000..744a71d
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/hardware/Ethernet.java
@@ -0,0 +1,20 @@
+package org.openslx.virtualization.hardware;
+
+public class Ethernet
+{
+
+ public static final String AUTO = "(default)";
+ public static final String PCNET32 = "AMD PCnet32";
+ public static final String E1000 = "Intel E1000 (PCI)";
+ public static final String E1000E = "Intel E1000e (PCI-Express)";
+ public static final String VMXNET = "VMXnet";
+ public static final String VMXNET3 = "VMXnet 3";
+ public static final String PCNETPCI2 = "PCnet-PCI II";
+ public static final String PCNETFAST3 = "PCnet-FAST III";
+ public static final String PRO1000MTD = "Intel PRO/1000 MT Desktop";
+ public static final String PRO1000TS = "Intel PRO/1000 T Server";
+ public static final String PRO1000MTS = "Intel PRO/1000 MT Server";
+ public static final String PARAVIRT = "Paravirtualized Network";
+ public static final String NONE = "No Network Card";
+
+}
diff --git a/src/main/java/org/openslx/virtualization/hardware/SoundCard.java b/src/main/java/org/openslx/virtualization/hardware/SoundCard.java
new file mode 100644
index 0000000..e4065e4
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/hardware/SoundCard.java
@@ -0,0 +1,13 @@
+package org.openslx.virtualization.hardware;
+
+public class SoundCard
+{
+
+ public static final String NONE = "None";
+ public static final String DEFAULT = "(default)";
+ public static final String SOUND_BLASTER = "Sound Blaster 16";
+ public static final String ES = "ES 1371";
+ public static final String HD_AUDIO = "Intel Integrated HD Audio";
+ public static final String AC = "Intel ICH Audio Codec 97";
+
+}
diff --git a/src/main/java/org/openslx/virtualization/hardware/Usb.java b/src/main/java/org/openslx/virtualization/hardware/Usb.java
new file mode 100644
index 0000000..200bf8e
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/hardware/Usb.java
@@ -0,0 +1,11 @@
+package org.openslx.virtualization.hardware;
+
+public class Usb
+{
+
+ public static final String NONE = "None";
+ public static final String USB1_1 = "USB 1.1";
+ public static final String USB2_0 = "USB 2.0";
+ public static final String USB3_0 = "USB 3.0";
+
+}
diff --git a/src/main/java/org/openslx/virtualization/hardware/VirtOptionValue.java b/src/main/java/org/openslx/virtualization/hardware/VirtOptionValue.java
new file mode 100644
index 0000000..effc51f
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/hardware/VirtOptionValue.java
@@ -0,0 +1,42 @@
+package org.openslx.virtualization.hardware;
+
+public abstract class VirtOptionValue
+{
+
+ protected final String id;
+
+ protected final String displayName;
+
+ public VirtOptionValue( String id, String displayName )
+ {
+ this.id = id;
+ this.displayName = displayName;
+ }
+
+ public String getId()
+ {
+ return this.id;
+ }
+
+ public String getDisplayName()
+ {
+ return this.displayName;
+ }
+
+ public abstract void apply();
+
+ public abstract boolean isActive();
+
+ @Override
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ return true;
+ if ( obj instanceof VirtOptionValue ) {
+ VirtOptionValue other = ( (VirtOptionValue)obj );
+ return other.id == this.id || ( other.id != null && other.id.equals( this.id ) );
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/openslx/virtualization/virtualizer/Virtualizer.java b/src/main/java/org/openslx/virtualization/virtualizer/Virtualizer.java
new file mode 100644
index 0000000..ff24862
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/virtualizer/Virtualizer.java
@@ -0,0 +1,68 @@
+package org.openslx.virtualization.virtualizer;
+
+import java.util.List;
+
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.disk.DiskImage.ImageFormat;
+
+/**
+ * Representation of a virtualization system.
+ *
+ * The virtualization system can be for example a virtual machine hypervisor (like VirtualBox), a
+ * container for operating systems or applications (like LXC or Docker), or a runtime environment
+ * (like Wine).
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public abstract class Virtualizer
+{
+ /**
+ * Internal data representation for the virtualizer.
+ */
+ protected final org.openslx.bwlp.thrift.iface.Virtualizer internalVirtualizer;
+
+ /**
+ * Creates a new virtualizer.
+ *
+ * @param internalVirtualizer internal data representation for the new virtualizer.
+ */
+ public Virtualizer( org.openslx.bwlp.thrift.iface.Virtualizer internalVirtualizer )
+ {
+ this.internalVirtualizer = internalVirtualizer;
+ }
+
+ /**
+ * Returns the identifier of the virtualizer.
+ *
+ * @return identifier of the virtualizer.
+ */
+ public String getId()
+ {
+ return this.internalVirtualizer.getVirtId();
+ }
+
+ /**
+ * Returns the name of the virtualizer.
+ *
+ * @return name of the virtualizer.
+ */
+ public String getName()
+ {
+ return this.internalVirtualizer.getVirtName();
+ }
+
+ /**
+ * Returns a list of supported disk image formats by the virtualizer.
+ *
+ * @return list of supported disk image formats by the virtualizer.
+ */
+ public abstract List<ImageFormat> getSupportedImageFormats();
+
+ /**
+ * Returns a list of supported versions of the virtualizer.
+ *
+ * @return list of supported versions of the virtualizer.
+ */
+ public abstract List<Version> getSupportedVersions();
+}
diff --git a/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerDocker.java b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerDocker.java
new file mode 100644
index 0000000..06ac24e
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerDocker.java
@@ -0,0 +1,55 @@
+package org.openslx.virtualization.virtualizer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.openslx.thrifthelper.TConst;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.disk.DiskImage;
+import org.openslx.virtualization.disk.DiskImage.ImageFormat;
+
+/**
+ * Representation of the Docker virtualizer for application containers.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizerDocker extends Virtualizer
+{
+ /**
+ * Name of the Docker virtualizer.
+ */
+ private static final String VIRTUALIZER_NAME = "Docker";
+
+ /**
+ * List of supported image formats by the Docker virtualizer.
+ */
+ private static final List<DiskImage.ImageFormat> VIRTUALIZER_SUPPORTED_IMAGE_FORMATS = Collections
+ .unmodifiableList( Arrays.asList( ImageFormat.NONE ) );
+
+ /**
+ * List of supported versions of the Docker virtualizer.
+ */
+ private static final List<Version> VIRTUALIZER_SUPPORTED_VERSIONS = null;
+
+ /**
+ * Creates a new Docker virtualizer.
+ */
+ public VirtualizerDocker()
+ {
+ super( new org.openslx.bwlp.thrift.iface.Virtualizer( TConst.VIRT_DOCKER, VirtualizerDocker.VIRTUALIZER_NAME ) );
+ }
+
+ @Override
+ public List<ImageFormat> getSupportedImageFormats()
+ {
+ return VirtualizerDocker.VIRTUALIZER_SUPPORTED_IMAGE_FORMATS;
+ }
+
+ @Override
+ public List<Version> getSupportedVersions()
+ {
+ return VirtualizerDocker.VIRTUALIZER_SUPPORTED_VERSIONS;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerQemu.java b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerQemu.java
new file mode 100644
index 0000000..5d933bb
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerQemu.java
@@ -0,0 +1,72 @@
+package org.openslx.virtualization.virtualizer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.openslx.thrifthelper.TConst;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.disk.DiskImage;
+import org.openslx.virtualization.disk.DiskImage.ImageFormat;
+
+/**
+ * Representation of the QEMU virtualizer for virtual machines.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizerQemu extends Virtualizer
+{
+ /**
+ * Name of the QEMU virtualizer.
+ */
+ private static final String VIRTUALIZER_NAME = "QEMU";
+
+ /**
+ * List of supported image formats by the QEMU virtualizer.
+ */
+ private static final List<DiskImage.ImageFormat> VIRTUALIZER_SUPPORTED_IMAGE_FORMATS = Collections
+ .unmodifiableList( Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) );
+
+ /**
+ * List of supported versions of the QEMU virtualizer.
+ */
+ private static final List<Version> VIRTUALIZER_SUPPORTED_VERSIONS = Collections.unmodifiableList(
+ Arrays.asList(
+ new Version( Short.valueOf( "2" ), Short.valueOf( "4" ), "QEMU 2.4" ),
+ new Version( Short.valueOf( "2" ), Short.valueOf( "5" ), "QEMU 2.5" ),
+ new Version( Short.valueOf( "2" ), Short.valueOf( "6" ), "QEMU 2.6" ),
+ new Version( Short.valueOf( "2" ), Short.valueOf( "7" ), "QEMU 2.7" ),
+ new Version( Short.valueOf( "2" ), Short.valueOf( "8" ), "QEMU 2.8" ),
+ new Version( Short.valueOf( "2" ), Short.valueOf( "9" ), "QEMU 2.9" ),
+ new Version( Short.valueOf( "3" ), Short.valueOf( "0" ), "QEMU 3.0" ),
+ new Version( Short.valueOf( "3" ), Short.valueOf( "1" ), "QEMU 3.1" ),
+ new Version( Short.valueOf( "4" ), Short.valueOf( "0" ), "QEMU 4.0" ),
+ new Version( Short.valueOf( "4" ), Short.valueOf( "1" ), "QEMU 4.1" ),
+ new Version( Short.valueOf( "4" ), Short.valueOf( "2" ), "QEMU 4.2" ),
+ new Version( Short.valueOf( "5" ), Short.valueOf( "0" ), "QEMU 5.0" ),
+ new Version( Short.valueOf( "5" ), Short.valueOf( "1" ), "QEMU 5.1" ),
+ new Version( Short.valueOf( "5" ), Short.valueOf( "2" ), "QEMU 5.2" ),
+ new Version( Short.valueOf( "6" ), Short.valueOf( "0" ), "QEMU 6.0" ),
+ new Version( Short.valueOf( "6" ), Short.valueOf( "1" ), "QEMU 6.1" ) ) );
+
+ /**
+ * Creates a new QEMU virtualizer.
+ */
+ public VirtualizerQemu()
+ {
+ super( new org.openslx.bwlp.thrift.iface.Virtualizer( TConst.VIRT_QEMU, VirtualizerQemu.VIRTUALIZER_NAME ) );
+ }
+
+ @Override
+ public List<ImageFormat> getSupportedImageFormats()
+ {
+ return VirtualizerQemu.VIRTUALIZER_SUPPORTED_IMAGE_FORMATS;
+ }
+
+ @Override
+ public List<Version> getSupportedVersions()
+ {
+ return VirtualizerQemu.VIRTUALIZER_SUPPORTED_VERSIONS;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVirtualBox.java b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVirtualBox.java
new file mode 100644
index 0000000..ebb3b3f
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVirtualBox.java
@@ -0,0 +1,56 @@
+package org.openslx.virtualization.virtualizer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.openslx.thrifthelper.TConst;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.disk.DiskImage;
+import org.openslx.virtualization.disk.DiskImage.ImageFormat;
+
+/**
+ * Representation of the VirtualBox virtualizer for virtual machines.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizerVirtualBox extends Virtualizer
+{
+ /**
+ * Name of the VirtualBox virtualizer.
+ */
+ private static final String VIRTUALIZER_NAME = "VirtualBox";
+
+ /**
+ * List of supported image formats by the VirtualBox virtualizer.
+ */
+ private static final List<DiskImage.ImageFormat> VIRTUALIZER_SUPPORTED_IMAGE_FORMATS = Collections
+ .unmodifiableList( Arrays.asList( ImageFormat.VDI ) );
+
+ /**
+ * List of supported version of the VirtualBox virtualizer.
+ */
+ private static final List<Version> VIRTUALIZER_SUPPORTED_VERSIONS = null;
+
+ /**
+ * Creates a new VirtualBox virtualizer.
+ */
+ public VirtualizerVirtualBox()
+ {
+ super( new org.openslx.bwlp.thrift.iface.Virtualizer( TConst.VIRT_VIRTUALBOX,
+ VirtualizerVirtualBox.VIRTUALIZER_NAME ) );
+ }
+
+ @Override
+ public List<ImageFormat> getSupportedImageFormats()
+ {
+ return VirtualizerVirtualBox.VIRTUALIZER_SUPPORTED_IMAGE_FORMATS;
+ }
+
+ @Override
+ public List<Version> getSupportedVersions()
+ {
+ return VirtualizerVirtualBox.VIRTUALIZER_SUPPORTED_VERSIONS;
+ }
+}
diff --git a/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVmware.java b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVmware.java
new file mode 100644
index 0000000..f94ef39
--- /dev/null
+++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVmware.java
@@ -0,0 +1,73 @@
+package org.openslx.virtualization.virtualizer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.openslx.thrifthelper.TConst;
+import org.openslx.virtualization.Version;
+import org.openslx.virtualization.disk.DiskImage;
+import org.openslx.virtualization.disk.DiskImage.ImageFormat;
+
+/**
+ * Representation of the VMware virtualizer for virtual machines.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class VirtualizerVmware extends Virtualizer
+{
+ /**
+ * Name of the VMware virtualizer.
+ */
+ private static final String VIRTUALIZER_NAME = "VMware";
+
+ /**
+ * List of supported image formats by the VMware virtualizer.
+ */
+ private static final List<DiskImage.ImageFormat> VIRTUALIZER_SUPPORTED_IMAGE_FORMATS = Collections
+ .unmodifiableList( Arrays.asList( ImageFormat.VMDK ) );
+
+ /**
+ * List of supported versions of the VMware virtualizer.
+ */
+ private static final List<Version> VIRTUALIZER_SUPPORTED_VERSIONS = Collections.unmodifiableList(
+ Arrays.asList(
+ new Version( Short.valueOf( "03" ), "Workstation 4/5, Player 1" ),
+ new Version( Short.valueOf( "04" ), "Workstation 4/5, Player 1/2, Fusion 1" ),
+ new Version( Short.valueOf( "06" ), "Workstation 6" ),
+ new Version( Short.valueOf( "07" ), "Workstation 6.5/7, Player 3, Fusion 2/3" ),
+ new Version( Short.valueOf( "08" ), "Workstation 8, Player/Fusion 4" ),
+ new Version( Short.valueOf( "09" ), "Workstation 9, Player/Fusion 5" ),
+ new Version( Short.valueOf( "10" ), "Workstation 10, Player/Fusion 6" ),
+ new Version( Short.valueOf( "11" ), "Workstation 11, Player/Fusion 7" ),
+ new Version( Short.valueOf( "12" ), "Workstation/Player 12, Fusion 8" ),
+ new Version( Short.valueOf( "14" ), "Workstation/Player 14, Fusion 10" ),
+ new Version( Short.valueOf( "15" ), "Workstation/Player 15, Fusion 11" ),
+ new Version( Short.valueOf( "16" ), "Workstation/Player 15.1, Fusion 11.1" ),
+ new Version( Short.valueOf( "17" ), "Workstation/Player 16, Fusion 12" ),
+ new Version( Short.valueOf( "18" ), "Workstation/Player 16.1, Fusion 12.1" ),
+ new Version( Short.valueOf( "19" ), "Workstation/Player 16.2, Fusion 12.2" ),
+ new Version( Short.valueOf( "20" ), "Workstation/Player 17, Fusion 13" ) ) );
+
+ /**
+ * Creates a new VMware virtualizer.
+ */
+ public VirtualizerVmware()
+ {
+ super( new org.openslx.bwlp.thrift.iface.Virtualizer( TConst.VIRT_VMWARE, VirtualizerVmware.VIRTUALIZER_NAME ) );
+ }
+
+ @Override
+ public List<ImageFormat> getSupportedImageFormats()
+ {
+ return VirtualizerVmware.VIRTUALIZER_SUPPORTED_IMAGE_FORMATS;
+ }
+
+ @Override
+ public List<Version> getSupportedVersions()
+ {
+ return VirtualizerVmware.VIRTUALIZER_SUPPORTED_VERSIONS;
+ }
+
+}