diff options
Diffstat (limited to 'src/main/java/org/openslx/virtualization')
29 files changed, 5948 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..51dc98b --- /dev/null +++ b/src/main/java/org/openslx/virtualization/Version.java @@ -0,0 +1,320 @@ +package org.openslx.virtualization; + +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 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 String VERSION_NUMBER_REGEX = "^(\\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 ( version == null || version.isEmpty() ) { + parsedVersion = null; + } else { + final Pattern versionPattern = Pattern.compile( Version.VERSION_NUMBER_REGEX ); + final Matcher versionMatcher = versionPattern.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 ( this.getName() == null || this.getName().isEmpty() ) { + 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..cd8af1e --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfiguration.java @@ -0,0 +1,421 @@ +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.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.OperatingSystem; +import org.openslx.virtualization.Version; +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<T, U, W, X> +{ + private static final Logger LOGGER = Logger.getLogger( VirtualizationConfiguration.class ); + + /* + * Helper types + */ + protected Map<SoundCardType, T> soundCards = new HashMap<>(); + protected Map<DDAcceleration, U> ddacc = new HashMap<>(); + protected Map<EthernetDevType, W> networkCards = new HashMap<>(); + protected Map<UsbSpeed, X> usbSpeeds = new HashMap<>(); + + private final Virtualizer virtualizer; + + /** + * Virtual sound cards types + */ + public static enum SoundCardType + { + NONE( "None" ), DEFAULT( "(default)" ), SOUND_BLASTER( "Sound Blaster 16" ), ES( "ES 1371" ), HD_AUDIO( + "Intel Integrated HD Audio" ), AC( "Intel ICH Audio Codec 97" ); + + public final String displayName; + + private SoundCardType( String dName ) + { + this.displayName = dName; + } + } + + /** + * 3D acceleration types + */ + public static enum DDAcceleration + { + OFF( "Off" ), ON( "On" ); + + public final String displayName; + + private DDAcceleration( String dName ) + { + this.displayName = dName; + } + } + + /** + * Virtual network cards + */ + public static enum EthernetDevType + { + AUTO( "(default)" ), PCNET32( "AMD PCnet32" ), E1000( "Intel E1000 (PCI)" ), E1000E( + "Intel E1000e (PCI-Express)" ), VMXNET( "VMXnet" ), VMXNET3( "VMXnet 3" ), PCNETPCI2( + "PCnet-PCI II" ), PCNETFAST3( "PCnet-FAST III" ), PRO1000MTD( + "Intel PRO/1000 MT Desktop" ), PRO1000TS( + "Intel PRO/1000 T Server" ), PRO1000MTS( "Intel PRO/1000 MT Server" ), PARAVIRT( + "Paravirtualized Network" ), NONE( "No Network Card" ); + + public final String displayName; + + private EthernetDevType( String dName ) + { + this.displayName = dName; + } + } + + public static enum UsbSpeed + { + NONE( "None" ), USB1_1( "USB 1.1" ), USB2_0( "USB 2.0" ), USB3_0( "USB 3.0" ); + + public final String displayName; + + private UsbSpeed( String dName ) + { + this.displayName = dName; + } + } + + 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; + } + /* + * 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; + + /* + * Getters for virtual hardware + */ + public List<SoundCardType> getSupportedSoundCards() + { + ArrayList<SoundCardType> availables = new ArrayList<SoundCardType>( soundCards.keySet() ); + Collections.sort( availables ); + return availables; + } + + public List<DDAcceleration> getSupportedDDAccs() + { + ArrayList<DDAcceleration> availables = new ArrayList<DDAcceleration>( ddacc.keySet() ); + Collections.sort( availables ); + return availables; + } + + public List<Version> getSupportedHWVersions() + { + final List<Version> availables = this.getVirtualizer().getSupportedVersions(); + Collections.sort( availables ); + return Collections.unmodifiableList( availables ); + } + + public List<EthernetDevType> getSupportedEthernetDevices() + { + ArrayList<EthernetDevType> availables = new ArrayList<EthernetDevType>( networkCards.keySet() ); + Collections.sort( availables ); + return availables; + } + + public List<UsbSpeed> getSupportedUsbSpeeds() + { + ArrayList<UsbSpeed> availables = new ArrayList<>( usbSpeeds.keySet() ); + Collections.sort( availables ); + return availables; + } + + /** + * 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; + } + + /* + * Getter for isMachineSnapshot + */ + public boolean isMachineSnapshot() + { + return isMachineSnapshot; + } + + /* + * 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, ...) + 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. + */ + public static VirtualizationConfiguration<?, ?, ?, ?> getInstance( List<OperatingSystem> osList, byte[] vmContent, + int length ) + throws IOException + { + try { + return new VirtualizationConfigurationVmware( osList, vmContent, length ); + } catch ( VirtualizationConfigurationException e ) { + LOGGER.debug( "Not a VMware file", 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 ); + } + try { + return new VirtualizationConfigurationDocker( osList, vmContent, length ); + } 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 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 setSoundCard( SoundCardType type ); + + public abstract SoundCardType getSoundCard(); + + public abstract void setDDAcceleration( DDAcceleration type ); + + public abstract DDAcceleration getDDAcceleration(); + + public abstract void setVirtualizerVersion( Version type ); + + public abstract Version getVirtualizerVersion(); + + public abstract void setEthernetDevType( int cardIndex, EthernetDevType type ); + + public abstract EthernetDevType getEthernetDevType( int cardIndex ); + + public abstract void setMaxUsbSpeed( UsbSpeed speed ); + + public abstract UsbSpeed getMaxUsbSpeed(); + + 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(); +} 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..d5e9abb --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationDocker.java @@ -0,0 +1,213 @@ +package org.openslx.virtualization.configuration; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.OperatingSystem; +import org.openslx.virtualization.Version; +import org.openslx.virtualization.virtualizer.VirtualizerDocker; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; + +class DockerSoundCardMeta +{ +} + +class DockerDDAccelMeta +{ +} + +class DockerEthernetDevTypeMeta +{ +} + +class DockerUsbSpeedMeta +{ +} + +public class VirtualizationConfigurationDocker extends VirtualizationConfiguration<DockerSoundCardMeta, DockerDDAccelMeta, DockerEthernetDevTypeMeta, DockerUsbSpeedMeta> { + + /** + * File name extension for Docker virtualization configuration files. + */ + private static final String FILE_NAME_EXTENSION = null; + + private static final Logger LOGGER = Logger.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.warn("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 false; + } + + @Override public boolean addHddTemplate(File diskImage, String hddMode, String redoDir) { + return false; + } + + @Override public boolean addHddTemplate(String diskImagePath, String hddMode, String redoDir) { + return false; + } + + @Override public boolean addDefaultNat() { + return false; + } + + @Override public void setOs(String vendorOsId) { + + } + + @Override public boolean addDisplayName(String name) { + return false; + } + + @Override public boolean addRam(int mem) { + return false; + } + + @Override public void addFloppy(int index, String image, boolean readOnly) { + + } + + @Override public boolean addCdrom(String image) { + return false; + } + + @Override public boolean addCpuCoreCount(int nrOfCores) { + return false; + } + + @Override public void setSoundCard(SoundCardType type) { + + } + + @Override public SoundCardType getSoundCard() { + return SoundCardType.NONE; + } + + @Override public void setDDAcceleration(DDAcceleration type) { + + } + + @Override public DDAcceleration getDDAcceleration() { + return DDAcceleration.OFF; + } + + @Override public void setVirtualizerVersion(Version type) { + + } + + @Override public Version getVirtualizerVersion() { + return null; + } + + @Override public void setEthernetDevType(int cardIndex, EthernetDevType type) { + + } + + @Override public EthernetDevType getEthernetDevType(int cardIndex) { + return EthernetDevType.NONE; + } + + @Override public void setMaxUsbSpeed(UsbSpeed speed) { + + } + + @Override public UsbSpeed getMaxUsbSpeed() { + return UsbSpeed.NONE; + } + + @Override public byte[] getConfigurationAsByteArray() { + return this.containerDefinition; + } + + @Override public boolean addEthernet(EtherType type) { + return false; + } + + @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 + { + } +} 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..b4bc0ba --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemu.java @@ -0,0 +1,930 @@ +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 java.util.Map.Entry; + +import org.openslx.bwlp.thrift.iface.OperatingSystem; +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.libosinfo.LibOsInfo; +import org.openslx.libvirt.libosinfo.os.Os; +import org.openslx.libvirt.domain.device.DiskCdrom; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.libvirt.domain.device.DiskStorage; +import org.openslx.libvirt.domain.device.Graphics; +import org.openslx.libvirt.domain.device.GraphicsSpice; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.domain.device.Video; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; +import org.openslx.util.LevenshteinDistance; +import org.openslx.virtualization.Version; +import org.openslx.virtualization.virtualizer.VirtualizerQemu; + +/** + * Metadata to describe the hardware type of a QEMU sound card. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuSoundCardMeta +{ + /** + * Stores the hardware model of the QEMU sound card. + */ + private final Sound.Model model; + + /** + * Creates metadata to describe the hardware model of a QEMU sound card. + * + * @param model hardware model of the QEMU sound card. + */ + public QemuSoundCardMeta( Sound.Model model ) + { + this.model = model; + } + + /** + * Returns hardware model of the QEMU sound card. + * + * @return hardware model of the QEMU sound card. + */ + public Sound.Model getModel() + { + return this.model; + } +} + +/** + * Metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuDDAccelMeta +{ + /** + * Stores state of the hardware acceleration for QEMU virtual graphics. + */ + private final boolean enabled; + + /** + * Creates metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @param enabled state of the hardware acceleration for QEMU virtual graphics. + */ + public QemuDDAccelMeta( boolean enabled ) + { + this.enabled = enabled; + } + + /** + * Returns state of the hardware acceleration of QEMU virtual graphics. + * + * @return state of the hardware acceleration for QEMU virtual graphics. + */ + public boolean isEnabled() + { + return this.enabled; + } +} + +/** + * Metadata to describe the hardware type of a QEMU ethernet device. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuEthernetDevTypeMeta +{ + /** + * Stores the hardware model of the QEMU ethernet device. + */ + private final Interface.Model model; + + /** + * Creates metadata to describe the hardware type of a QEMU ethernet device. + * + * @param model hardware type of the QEMU ethernet device. + */ + public QemuEthernetDevTypeMeta( Interface.Model model ) + { + this.model = model; + } + + /** + * Returns the hardware type of a QEMU ethernet device. + * + * @return hardware type of the QEMU ethernet device. + */ + public Interface.Model getModel() + { + return this.model; + } +} + +/** + * Metadata to describe a QEMU USB controller. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuUsbSpeedMeta +{ + /** + * Stores the USB speed of the QEMU USB controller. + */ + private final int speed; + + /** + * Stores the QEMU hardware model of the USB controller. + */ + private final ControllerUsb.Model model; + + /** + * Creates metadata to describe a QEMU USB controller. + * + * @param speed USB speed of the QEMU USB controller. + * @param model QEMU hardware model of the USB controller. + */ + public QemuUsbSpeedMeta( int speed, ControllerUsb.Model model ) + { + this.speed = speed; + this.model = model; + } + + /** + * Returns the speed of the QEMU USB controller. + * + * @return speed of the QEMU USB controller. + */ + public int getSpeed() + { + return this.speed; + } + + /** + * Returns QEMU hardware model of the USB controller. + * + * @return hardware model of the QEMU USB controller. + */ + public ControllerUsb.Model getModel() + { + return this.model; + } +} + +/** + * Virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class VirtualizationConfigurationQemu extends + VirtualizationConfiguration<QemuSoundCardMeta, QemuDDAccelMeta, QemuEthernetDevTypeMeta, QemuUsbSpeedMeta> +{ + /** + * 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 ) ); + } 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 + { + // removes all specified boot order entries + this.vmConfig.removeBootOrder(); + + // removes all source networks of all specified network interfaces + this.vmConfig.removeInterfaceDevicesSource(); + } + + @Override + public boolean addEmptyHddTemplate() + { + return this.addHddTemplate( new String(), 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 + storageDiskDevice = this.vmConfig.addDiskStorageDevice(); + storageDiskDevice.setReadOnly( false ); + storageDiskDevice.setBusType( BusType.VIRTIO ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "vd", index ); + 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() + { + return this.addEthernet( EtherType.NAT ); + } + + @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 + floppyDiskDevice = this.vmConfig.addDiskFloppyDevice(); + floppyDiskDevice.setBusType( BusType.FDC ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "fd", index ); + 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 + cdromDiskDevice = this.vmConfig.addDiskCdromDevice(); + cdromDiskDevice.setBusType( BusType.SATA ); + String targetDevName = VirtualizationConfigurationQemuUtils.createAlphabeticalDeviceName( "sd", index ); + 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; + } + + @Override + public void setSoundCard( SoundCardType type ) + { + QemuSoundCardMeta soundDeviceConfig = this.soundCards.get( type ); + ArrayList<Sound> soundDevices = this.vmConfig.getSoundDevices(); + Sound.Model soundDeviceModel = soundDeviceConfig.getModel(); + + if ( soundDevices.isEmpty() ) { + // create new sound device with 'soundDeviceModel' hardware + Sound soundDevice = this.vmConfig.addSoundDevice(); + soundDevice.setModel( soundDeviceModel ); + } else { + // update sound device model type of existing sound devices + for ( Sound soundDevice : soundDevices ) { + soundDevice.setModel( soundDeviceModel ); + } + } + } + + @Override + public SoundCardType getSoundCard() + { + ArrayList<Sound> soundDevices = this.vmConfig.getSoundDevices(); + SoundCardType soundDeviceType = SoundCardType.DEFAULT; + + if ( soundDevices.isEmpty() ) { + // the VM configuration does not contain a sound card device + soundDeviceType = SoundCardType.NONE; + } else { + // the VM configuration at least one sound card device, so return the type of the first one + Sound.Model soundDeviceModel = soundDevices.get( 0 ).getModel(); + soundDeviceType = VirtualizationConfigurationQemuUtils.convertSoundDeviceModel( soundDeviceModel ); + } + + return soundDeviceType; + } + + @Override + public void setDDAcceleration( DDAcceleration type ) + { + QemuDDAccelMeta accelerationConfig = this.ddacc.get( type ); + ArrayList<Graphics> graphicDevices = this.vmConfig.getGraphicDevices(); + ArrayList<Video> videoDevices = this.vmConfig.getVideoDevices(); + final boolean accelerationEnabled = accelerationConfig.isEnabled(); + + boolean acceleratedGraphicsAvailable = false; + + if ( graphicDevices.isEmpty() ) { + // add new graphics device with enabled acceleration to VM configuration + GraphicsSpice graphicSpiceDevice = this.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 = this.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 DDAcceleration getDDAcceleration() + { + ArrayList<Graphics> graphicsDevices = this.vmConfig.getGraphicDevices(); + ArrayList<Video> videoDevices = this.vmConfig.getVideoDevices(); + DDAcceleration accelerationState = DDAcceleration.OFF; + + 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 ) { + accelerationState = DDAcceleration.ON; + } else { + accelerationState = DDAcceleration.OFF; + } + + return accelerationState; + } + + @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; + } + + @Override + public void setEthernetDevType( int cardIndex, EthernetDevType type ) + { + QemuEthernetDevTypeMeta networkDeviceConfig = this.networkCards.get( type ); + ArrayList<Interface> networkDevices = this.vmConfig.getInterfaceDevices(); + Interface networkDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( networkDevices, cardIndex ); + Interface.Model networkDeviceModel = networkDeviceConfig.getModel(); + + if ( networkDevice != null ) { + networkDevice.setModel( networkDeviceModel ); + } + } + + @Override + public EthernetDevType getEthernetDevType( int cardIndex ) + { + ArrayList<Interface> networkDevices = this.vmConfig.getInterfaceDevices(); + Interface networkDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( networkDevices, cardIndex ); + EthernetDevType networkDeviceType = EthernetDevType.NONE; + + if ( networkDevice == null ) { + // network interface device is not present + networkDeviceType = EthernetDevType.NONE; + } else { + // get model of existing network interface device + Interface.Model networkDeviceModel = networkDevice.getModel(); + networkDeviceType = VirtualizationConfigurationQemuUtils.convertNetworkDeviceModel( networkDeviceModel ); + } + + return networkDeviceType; + } + + @Override + public void setMaxUsbSpeed( UsbSpeed speed ) + { + QemuUsbSpeedMeta usbControllerConfig = this.usbSpeeds.get( speed ); + ArrayList<ControllerUsb> usbControllerDevices = this.vmConfig.getUsbControllerDevices(); + ControllerUsb.Model usbControllerModel = usbControllerConfig.getModel(); + + if ( usbControllerDevices.isEmpty() ) { + // add new USB controller with specified speed 'usbControllerModel' + ControllerUsb usbControllerDevice = this.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 UsbSpeed getMaxUsbSpeed() + { + ArrayList<ControllerUsb> usbControllerDevices = this.vmConfig.getUsbControllerDevices(); + UsbSpeed maxUsbSpeed = VirtualizationConfiguration.UsbSpeed.NONE; + int maxUsbSpeedNumeric = 0; + + for ( ControllerUsb usbControllerDevice : usbControllerDevices ) { + ControllerUsb.Model usbControllerModel = usbControllerDevice.getModel(); + + for ( Entry<UsbSpeed, QemuUsbSpeedMeta> usbSpeedEntry : this.usbSpeeds.entrySet() ) { + QemuUsbSpeedMeta usbSpeed = usbSpeedEntry.getValue(); + if ( usbSpeed.getSpeed() > maxUsbSpeedNumeric && usbSpeed.getModel() == usbControllerModel ) { + maxUsbSpeed = usbSpeedEntry.getKey(); + maxUsbSpeedNumeric = usbSpeed.getSpeed(); + } + } + } + + return maxUsbSpeed; + } + + @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 += System.lineSeparator(); + 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 ) + { + QemuEthernetDevTypeMeta defaultNetworkDeviceConfig = this.networkCards.get( EthernetDevType.AUTO ); + ArrayList<Interface> interfaceDevices = this.vmConfig.getInterfaceDevices(); + Interface interfaceDevice = VirtualizationConfigurationQemuUtils.getArrayIndex( interfaceDevices, index ); + + final Interface.Model defaultNetworkDeviceModel = defaultNetworkDeviceConfig.getModel(); + + 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(); + } + + @Override + public void registerVirtualHW() + { + // @formatter:off + soundCards.put( VirtualizationConfiguration.SoundCardType.NONE, new QemuSoundCardMeta( null ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.DEFAULT, new QemuSoundCardMeta( Sound.Model.ICH9 ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.SOUND_BLASTER, new QemuSoundCardMeta( Sound.Model.SB16 ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.ES, new QemuSoundCardMeta( Sound.Model.ES1370 ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.AC, new QemuSoundCardMeta( Sound.Model.AC97 ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.HD_AUDIO, new QemuSoundCardMeta( Sound.Model.ICH9 ) ); + + ddacc.put( VirtualizationConfiguration.DDAcceleration.OFF, new QemuDDAccelMeta( false ) ); + ddacc.put( VirtualizationConfiguration.DDAcceleration.ON, new QemuDDAccelMeta( true ) ); + + networkCards.put( VirtualizationConfiguration.EthernetDevType.NONE, new QemuEthernetDevTypeMeta( null ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.AUTO, new QemuEthernetDevTypeMeta( Interface.Model.VIRTIO_NET_PCI ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PCNETPCI2, new QemuEthernetDevTypeMeta( Interface.Model.PCNET ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.E1000, new QemuEthernetDevTypeMeta( Interface.Model.E1000 ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.E1000E, new QemuEthernetDevTypeMeta( Interface.Model.E1000E ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.VMXNET3, new QemuEthernetDevTypeMeta( Interface.Model.VMXNET3 ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PARAVIRT, new QemuEthernetDevTypeMeta( Interface.Model.VIRTIO_NET_PCI ) ); + + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.NONE, new QemuUsbSpeedMeta( 0, ControllerUsb.Model.NONE ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB1_1, new QemuUsbSpeedMeta( 1, ControllerUsb.Model.ICH9_UHCI1 ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB2_0, new QemuUsbSpeedMeta( 2, ControllerUsb.Model.ICH9_EHCI1 ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB3_0, new QemuUsbSpeedMeta( 3, ControllerUsb.Model.QEMU_XHCI ) ); + // @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() ); + } + } +} 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..5d74d0d --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationQemuUtils.java @@ -0,0 +1,441 @@ +package org.openslx.virtualization.configuration; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.openslx.libvirt.domain.device.Disk; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.virtualization.Version; +import org.openslx.virtualization.configuration.VirtualizationConfiguration.DriveBusType; +import org.openslx.virtualization.configuration.VirtualizationConfiguration.EthernetDevType; +import org.openslx.virtualization.configuration.VirtualizationConfiguration.SoundCardType; +import org.openslx.libvirt.domain.device.Sound; + +/** + * 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; + + 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; + } + + /** + * Converts a Libvirt sound device model to a VM metadata sound card type. + * + * @param soundDeviceModel Libvirt sound device model. + * @return VM metadata sound card type. + */ + public static SoundCardType convertSoundDeviceModel( Sound.Model soundDeviceModel ) + { + SoundCardType type = SoundCardType.NONE; + + switch ( soundDeviceModel ) { + case AC97: + type = SoundCardType.AC; + break; + case ES1370: + type = SoundCardType.ES; + break; + case ICH6: + type = SoundCardType.HD_AUDIO; + break; + case ICH9: + type = SoundCardType.HD_AUDIO; + break; + case SB16: + type = SoundCardType.SOUND_BLASTER; + break; + } + + return type; + } + + /** + * Converts a Libvirt network device model to a VM metadata ethernet device type. + * + * @param networkDeviceModel Libvirt network device model. + * @return VM metadata ethernet device type. + */ + public static EthernetDevType convertNetworkDeviceModel( Interface.Model networkDeviceModel ) + { + EthernetDevType type = EthernetDevType.NONE; + + switch ( networkDeviceModel ) { + case E1000: + type = EthernetDevType.E1000; + break; + case E1000E: + type = EthernetDevType.E1000E; + break; + case PCNET: + type = EthernetDevType.PCNETPCI2; + break; + case VIRTIO: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI_NON_TRANSITIONAL: + type = EthernetDevType.PARAVIRT; + break; + case VIRTIO_NET_PCI_TRANSITIONAL: + type = EthernetDevType.PARAVIRT; + break; + case VMXNET3: + type = EthernetDevType.VMXNET3; + break; + default: + type = EthernetDevType.AUTO; + 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. + */ + public static String createAlphabeticalDeviceName( String devicePrefix, int deviceNumber ) + { + if ( deviceNumber < 0 || deviceNumber >= ( 'z' - 'a' ) ) { + String errorMsg = new String( "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(); + } + + /** + * 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..6922c8c --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualBox.java @@ -0,0 +1,558 @@ +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.Arrays; +import java.util.List; +import java.util.Map.Entry; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.OperatingSystem; +import org.openslx.thrifthelper.TConst; +import org.openslx.virtualization.Version; +import org.openslx.virtualization.configuration.VirtualizationConfigurationVirtualboxFileFormat.PlaceHolder; +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; + +class VBoxSoundCardMeta +{ + public final boolean isPresent; + public final String value; + + public VBoxSoundCardMeta( boolean present, String val ) + { + isPresent = present; + value = val; + } +} + +class VBoxDDAccelMeta +{ + public final boolean isPresent; + + public VBoxDDAccelMeta( boolean present ) + { + isPresent = present; + } +} + +class VBoxEthernetDevTypeMeta +{ + public final String value; + public final boolean isPresent; + + public VBoxEthernetDevTypeMeta( boolean present, String val ) + { + value = val; + isPresent = present; + } +} + +class VBoxUsbSpeedMeta +{ + public final String value; + public final int speed; + + public VBoxUsbSpeedMeta( String value, int speed ) + { + this.value = value; + this.speed = speed; + } +} + +public class VirtualizationConfigurationVirtualBox + extends VirtualizationConfiguration<VBoxSoundCardMeta, VBoxDDAccelMeta, VBoxEthernetDevTypeMeta, VBoxUsbSpeedMeta> +{ + /** + * File name extension for VirtualBox virtualization configuration files.. + */ + public static final String FILE_NAME_EXTENSION = "vbox"; + + private static final Logger LOGGER = Logger.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 ); + } + } + + @Override + public void transformEditable() throws VirtualizationConfigurationException + { + // TODO Auto-generated method stub + } + + @Override + public void transformPrivacy() throws VirtualizationConfigurationException + { + + } + + @Override + public byte[] getConfigurationAsByteArray() + { + return config.toString( true ).getBytes( StandardCharsets.UTF_8 ); + } + + @Override + public boolean addEmptyHddTemplate() + { + return this.addHddTemplate( "%VM_DISK_PATH%", "%VM_DISK_MODE%", "%VM_DISK_REDOLOGDIR%" ); + } + + @Override + public boolean addHddTemplate( String diskImage, String hddMode, String redoDir ) + { + config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk[@location='" + + PlaceHolder.HDDLOCATION.toString() + "']", "location", diskImage ); + config.changeAttribute( "/VirtualBox/Machine", "snapshotFolder", redoDir ); + 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 ); + + 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 ); + config.changeAttribute( "/VirtualBox/Machine/StorageControllers/StorageController/AttachedDevice/Image", "uuid", + vboxUUid ); + + // 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 ); + } + + @Override + public boolean addDefaultNat() + { + if ( config.addNewNode( "/VirtualBox/Machine/Hardware/Network/Adapter", "NAT" ) == null ) { + LOGGER.error( "Failed to set network adapter to NAT." ); + return false; + } + return config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter", "MACAddress", "080027B86D12" ); + } + + @Override + public void setOs( String vendorOsId ) + { + config.changeAttribute( "/VirtualBox/Machine", "OSType", vendorOsId ); + + 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 ); + } + + @Override + public boolean addRam( int mem ) + { + return config.changeAttribute( "/VirtualBox/Machine/Hardware/Memory", "RAMSize", Integer.toString( mem ) ); + } + + @Override + public void addFloppy( int index, String image, boolean readOnly ) + { + Element floppyController = null; + NodeList matches = (NodeList)config + .findNodes( "/VirtualBox/Machine/StorageControllers/StorageController[@name='Floppy']" ); + if ( matches == null || matches.getLength() == 0 ) { + floppyController = (Element)config.addNewNode( "/VirtualBox/Machine/StorageControllers", "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.PlaceHolder.FLOPPYUUID.toString() ); + // 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.PlaceHolder.FLOPPYUUID.toString() ); + floppyImageReg.setAttribute( "location", + VirtualizationConfigurationVirtualboxFileFormat.PlaceHolder.FLOPPYLOCATION.toString() ); + } + } + + @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 ) ); + } + + @Override + public void setSoundCard( org.openslx.virtualization.configuration.VirtualizationConfiguration.SoundCardType type ) + { + VBoxSoundCardMeta sound = soundCards.get( type ); + config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "enabled", + Boolean.toString( sound.isPresent ) ); + config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "controller", sound.value ); + } + + @Override + public VirtualizationConfiguration.SoundCardType getSoundCard() + { + // initialize here to type None to avoid all null pointer exceptions thrown for unknown user written sound cards + VirtualizationConfiguration.SoundCardType returnsct = VirtualizationConfiguration.SoundCardType.NONE; + Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/AudioAdapter" ).item( 0 ); + if ( !x.hasAttribute( "enabled" ) + || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) { + return returnsct; + } else { + // extra separate case for the non-existing argument} + if ( !x.hasAttribute( "controller" ) ) { + returnsct = VirtualizationConfiguration.SoundCardType.AC; + } else { + String controller = x.getAttribute( "controller" ); + VBoxSoundCardMeta soundMeta = null; + for ( VirtualizationConfiguration.SoundCardType type : VirtualizationConfiguration.SoundCardType + .values() ) { + soundMeta = soundCards.get( type ); + if ( soundMeta != null ) { + if ( controller.equals( soundMeta.value ) ) { + returnsct = type; + } + } + } + } + } + return returnsct; + } + + @Override + public void setDDAcceleration( VirtualizationConfiguration.DDAcceleration type ) + { + VBoxDDAccelMeta accel = ddacc.get( type ); + config.changeAttribute( "/VirtualBox/Machine/Hardware/Display", "accelerate3D", + Boolean.toString( accel.isPresent ) ); + } + + @Override + public VirtualizationConfiguration.DDAcceleration getDDAcceleration() + { + VirtualizationConfiguration.DDAcceleration returndda = null; + Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Display" ).item( 0 ); + if ( x.hasAttribute( "accelerate3D" ) ) { + if ( x.getAttribute( "accelerate3D" ).equals( "true" ) ) { + returndda = VirtualizationConfiguration.DDAcceleration.ON; + } else { + returndda = VirtualizationConfiguration.DDAcceleration.OFF; + } + } else { + returndda = VirtualizationConfiguration.DDAcceleration.OFF; + } + return returndda; + } + + /** + * 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; + } + + @Override + public void setEthernetDevType( int cardIndex, EthernetDevType type ) + { + String index = "0"; + VBoxEthernetDevTypeMeta nic = networkCards.get( type ); + // cardIndex is not used yet...maybe later needed for different network cards + config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "enabled", + Boolean.toString( nic.isPresent ) ); + config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "type", + nic.value ); + } + + @Override + public VirtualizationConfiguration.EthernetDevType getEthernetDevType( int cardIndex ) + { + VirtualizationConfiguration.EthernetDevType returnedt = VirtualizationConfiguration.EthernetDevType.NONE; + Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter" ).item( 0 ); + if ( !x.hasAttribute( "enabled" ) + || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) { + return returnedt; + } else { + // extra separate case for the non-existing argument} + if ( !x.hasAttribute( "type" ) ) { + returnedt = VirtualizationConfiguration.EthernetDevType.PCNETFAST3; + } else { + String temp = x.getAttribute( "type" ); + VBoxEthernetDevTypeMeta etherMeta = null; + for ( VirtualizationConfiguration.EthernetDevType type : VirtualizationConfiguration.EthernetDevType + .values() ) { + etherMeta = networkCards.get( type ); + if ( etherMeta != null ) { + if ( temp.equals( etherMeta.value ) ) { + returnedt = type; + } + } + } + } + } + return returnedt; + } + + public void registerVirtualHW() + { + // 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... + soundCards.put( VirtualizationConfiguration.SoundCardType.NONE, new VBoxSoundCardMeta( false, "AC97" ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.SOUND_BLASTER, new VBoxSoundCardMeta( true, "SB16" ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.HD_AUDIO, new VBoxSoundCardMeta( true, "HDA" ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.AC, new VBoxSoundCardMeta( true, "AC97" ) ); + + ddacc.put( VirtualizationConfiguration.DDAcceleration.OFF, new VBoxDDAccelMeta( false ) ); + ddacc.put( VirtualizationConfiguration.DDAcceleration.ON, new VBoxDDAccelMeta( true ) ); + + // 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 + networkCards.put( VirtualizationConfiguration.EthernetDevType.NONE, + new VBoxEthernetDevTypeMeta( false, "Am79C970A" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PCNETPCI2, + new VBoxEthernetDevTypeMeta( true, "Am79C970A" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PCNETFAST3, + new VBoxEthernetDevTypeMeta( true, "Am79C973" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PRO1000MTD, + new VBoxEthernetDevTypeMeta( true, "82540EM" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PRO1000TS, + new VBoxEthernetDevTypeMeta( true, "82543GC" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PRO1000MTS, + new VBoxEthernetDevTypeMeta( true, "82545EM" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PARAVIRT, + new VBoxEthernetDevTypeMeta( true, "virtio" ) ); + + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.NONE, new VBoxUsbSpeedMeta( null, 0 ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB1_1, new VBoxUsbSpeedMeta( "OHCI", 1 ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB2_0, new VBoxUsbSpeedMeta( "EHCI", 2 ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB3_0, new VBoxUsbSpeedMeta( "XHCI", 3 ) ); + } + + @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" ); + } + + @Override + public void setMaxUsbSpeed( VirtualizationConfiguration.UsbSpeed speed ) + { + // Wipe existing ones + config.removeNodes( "/VirtualBox/Machine/Hardware", "USB" ); + if ( speed == null || speed == VirtualizationConfiguration.UsbSpeed.NONE ) { + // 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" ); + VBoxUsbSpeedMeta vboxSpeed = usbSpeeds.get( speed ); + node.setAttribute( "type", vboxSpeed.value ); + node.setAttribute( "name", vboxSpeed.value ); + if ( speed == UsbSpeed.USB2_0 ) { + // If EHCI (2.0) is selected, VBox adds an OHCI controller too... + node.setAttribute( "type", "OHCI" ); + node.setAttribute( "name", "OHCI" ); + } + } + + @Override + public VirtualizationConfiguration.UsbSpeed getMaxUsbSpeed() + { + NodeList nodes = config.findNodes( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller/@type" ); + int maxSpeed = 0; + VirtualizationConfiguration.UsbSpeed maxItem = VirtualizationConfiguration.UsbSpeed.NONE; + for ( int i = 0; i < nodes.getLength(); ++i ) { + if ( nodes.item( i ).getNodeType() != Node.ATTRIBUTE_NODE ) { + LOGGER.info( "Not ATTRIBUTE type" ); + continue; + } + String type = ( (Attr)nodes.item( i ) ).getValue(); + for ( Entry<VirtualizationConfiguration.UsbSpeed, VBoxUsbSpeedMeta> s : usbSpeeds.entrySet() ) { + if ( s.getValue().speed > maxSpeed && type.equals( s.getValue().value ) ) { + maxSpeed = s.getValue().speed; + maxItem = s.getKey(); + } + } + } + return maxItem; + } + + @Override + public String getFileNameExtension() + { + return VirtualizationConfigurationVirtualBox.FILE_NAME_EXTENSION; + } + + @Override + public void validate() throws VirtualizationConfigurationException + { + this.config.validate(); + } +} 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..b1c940a --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVirtualboxFileFormat.java @@ -0,0 +1,762 @@ +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.List; +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.log4j.Logger; +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 = Logger.getLogger( VirtualizationConfigurationVirtualboxFileFormat.class ); + + // key information set during initial parsing of the XML file + private String osName = new String(); + 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 = File.separator + "virtualbox" + File.separator + "xsd"; + + // list of nodes to automatically remove when reading the vbox file + private static 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[@enabled='true']/*", + "/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 PlaceHolder + { + FLOPPYUUID( "%VM_FLOPPY_UUID%" ), FLOPPYLOCATION( "%VM_FLOPPY_LOCATION%" ), CPU( "%VM_CPU_CORES%" ), MEMORY( "%VM_RAM%" ), MACHINEUUID( "%VM_MACHINE_UUID%" ), NETWORKMAC( + "%VM_NIC_MAC%" ), HDDLOCATION( "%VM_HDD_LOCATION%" ), HDDUUID( "%VM_HDD_UUID_" ); + + private final String holderName; + + private PlaceHolder( String name ) + { + this.holderName = name; + } + + @Override + public String toString() + { + return holderName; + } + } + + /** + * 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 thoses 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 + 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" ); + } + + /** + * 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 more '/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 machine uuid + changeAttribute( "/VirtualBox/Machine", "uuid", PlaceHolder.MACHINEUUID.toString() ); + + // placeholder for the location of the virtual hdd + changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "location", PlaceHolder.HDDLOCATION.toString() ); + + // placeholder for the memory + changeAttribute( "/VirtualBox/Machine/Hardware/Memory", "RAMSize", PlaceHolder.MEMORY.toString() ); + + // placeholder for the CPU + changeAttribute( "/VirtualBox/Machine/Hardware/CPU", "count", PlaceHolder.CPU.toString() ); + + // placeholder for the MACAddress + changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter", "MACAddress", PlaceHolder.NETWORKMAC.toString() ); + + 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; + String hddUuid = hdd.getAttribute( "uuid" ); + hdd.setAttribute( "uuid", PlaceHolder.HDDUUID.toString() + i + "%" ); + final NodeList images; + if ( this.getVersion().isSmallerThan( Version.valueOf( "1.17" ) ) ) { + images = findNodes( "/VirtualBox/Machine/StorageControllers/StorageController/AttachedDevice/Image" ); + } else { + images = findNodes( + "/VirtualBox/Machine/Hardware/StorageControllers/StorageController/AttachedDevice/Image" ); + } + + for ( int j = 0; j < images.getLength(); j++ ) { + Element image = (Element)images.item( j ); + if ( image == null ) + continue; + if ( hddUuid.equals( image.getAttribute( "uuid" ) ) ) { + image.setAttribute( "uuid", PlaceHolder.HDDUUID.toString() + i + "%" ); + break; + } + } + } + } + + /** + * 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( PlaceHolder.HDDLOCATION.toString() ) ) { + 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; + if ( this.getVersion().isSmallerThan( Version.valueOf( "1.17" ) ) ) { + hddsExpr = XmlHelper.compileXPath( + "/VirtualBox/Machine/StorageControllers/StorageController/AttachedDevice[@type='HardDisk']/Image" ); + } else { + hddsExpr = XmlHelper.compileXPath( + "/VirtualBox/Machine/Hardware/StorageControllers/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. + * The given xpath to the element needs to find a single node, or this function will return + * false. If only one element was found, it will return the result of calling addAttributeToNode. + * Note that due to the way setAttribute() works, this function to create the attribute if it + * doesn't exists. + * + * @param elementXPath given as an xpath expression + * @param attribute attribute to change + * @param value to set the attribute to + * @return state of the change operation whether the attribute was changed successful or not. + */ + public boolean changeAttribute( String elementXPath, String attribute, String value ) + { + NodeList nodes = findNodes( elementXPath ); + if ( nodes == null || nodes.getLength() != 1 ) { + LOGGER.error( "No unique node could be found for: " + elementXPath ); + return false; + } + return addAttributeToNode( nodes.item( 0 ), attribute, value ); + } + + /** + * 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 Node 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..39aaae5 --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmware.java @@ -0,0 +1,656 @@ +package org.openslx.virtualization.configuration; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +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.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.virtualizer.VirtualizerVmware; + +class VmWareSoundCardMeta +{ + public final boolean isPresent; + public final String value; + + public VmWareSoundCardMeta( boolean present, String val ) + { + isPresent = present; + value = val; + } +} + +class VmWareDDAccelMeta +{ + public final boolean isPresent; + + public VmWareDDAccelMeta( boolean present ) + { + isPresent = present; + } +} + +class VmWareEthernetDevTypeMeta +{ + public final String value; + + public VmWareEthernetDevTypeMeta( String val ) + { + value = val; + } +} + +class VmwareUsbSpeed +{ + public final String keyName; + public final int speedNumeric; + + public VmwareUsbSpeed( int speed, String key ) + { + this.keyName = key == null ? null : ( key + ".present" ); + this.speedNumeric = speed; + } +} + +public class VirtualizationConfigurationVmware extends + VirtualizationConfiguration<VmWareSoundCardMeta, VmWareDDAccelMeta, VmWareEthernetDevTypeMeta, VmwareUsbSpeed> +{ + /** + * File name extension for VMware virtualization configuration files. + */ + public static final String FILE_NAME_EXTENSION = "vmx"; + + private static final Logger LOGGER = Logger.getLogger( VirtualizationConfigurationVmware.class ); + + private static final Pattern hddKey = Pattern.compile( "^(ide\\d|scsi\\d|sata\\d|nvme\\d):?(\\d)?\\.(.*)", + Pattern.CASE_INSENSITIVE ); + + // Lowercase list of allowed settings for upload (as regex) + private static final Pattern[] whitelist; + + private final VirtualizationConfigurationVmwareFileFormat config; + + // Init static members + static { + String[] list = { "^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" }; + whitelist = new Pattern[ list.length ]; + for ( int i = 0; i < list.length; ++i ) { + whitelist[i] = Pattern.compile( list[i].toLowerCase() ); + } + } + + public static enum EthernetType + { + NAT( "vmnet1" ), BRIDGED( "vmnet0" ), HOST_ONLY( "vmnet2" ); + + public final String vmnet; + + private EthernetType( String vnet ) + { + this.vmnet = vnet; + } + } + + private final Map<String, Controller> disks = new HashMap<>(); + + 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() + { + for ( Entry<String, ConfigEntry> entry : config.entrySet() ) { + handleLoadEntry( entry ); + } + // 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 ) { + 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() ) { + 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 ) ); + } + } + // TODO check if this machine is in a paused/suspended state + 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 addFiltered( String key, String value ) + { + config.set( key, value ).filtered( true ); + } + + private boolean isSetAndTrue( String key ) + { + String value = config.get( key ); + return value != null && value.equalsIgnoreCase( "true" ); + } + + private void handleLoadEntry( Entry<String, ConfigEntry> entry ) + { + String lowerKey = entry.getKey().toLowerCase(); + // Cleaned vmx construction + for ( Pattern exp : whitelist ) { + if ( exp.matcher( lowerKey ).find() ) { + entry.getValue().filtered( true ); + break; + } + } + // + // 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 = hddKey.matcher( entry.getKey() ); + if ( hdd.find() ) { + handleHddEntry( hdd.group( 1 ).toLowerCase(), hdd.group( 2 ), hdd.group( 3 ), value ); + } + } + + private void handleHddEntry( 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; + } + + DriveBusType bus; + try { + bus = DriveBusType.valueOf( config.get( "#SLX_HDD_BUS" ) ); + } catch ( Exception e ) { + LOGGER.warn( "Unknown bus type: " + config.get( "#SLX_HDD_BUS" ) + ". Cannot add hdd config." ); + return false; + } + String chipset = config.get( "#SLX_HDD_CHIP" ); + String prefix; + switch ( bus ) { + case SATA: + // Cannot happen?... use lsisas1068 + prefix = "scsi0"; + chipset = "lsisas1068"; + break; + case IDE: + case SCSI: + case NVME: + prefix = bus.name().toLowerCase() + "0"; + break; + default: + LOGGER.warn( "Unknown HDD bus type: " + 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 + { + addFiltered( "suspend.disabled", "TRUE" ); + } + + @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( true, false ).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(); + } + } + + @Override + public void transformEditable() throws VirtualizationConfigurationException + { + addFiltered( "gui.applyHostDisplayScalingToGuest", "FALSE" ); + } + + public String getValue( String key ) + { + return config.get( key ); + } + + public void setSoundCard( VirtualizationConfiguration.SoundCardType type ) + { + VmWareSoundCardMeta soundCardMeta = soundCards.get( type ); + addFiltered( "sound.present", vmBoolean( soundCardMeta.isPresent ) ); + if ( soundCardMeta.value != null ) { + addFiltered( "sound.virtualDev", soundCardMeta.value ); + } else { + config.remove( "sound.virtualDev" ); + } + } + + public VirtualizationConfiguration.SoundCardType getSoundCard() + { + if ( !isSetAndTrue( "sound.present" ) || !isSetAndTrue( "sound.autodetect" ) ) { + return VirtualizationConfiguration.SoundCardType.NONE; + } + String current = config.get( "sound.virtualDev" ); + if ( current != null ) { + VmWareSoundCardMeta soundCardMeta = null; + for ( VirtualizationConfiguration.SoundCardType type : VirtualizationConfiguration.SoundCardType.values() ) { + soundCardMeta = soundCards.get( type ); + if ( soundCardMeta != null ) { + if ( current.equals( soundCardMeta.value ) ) { + return type; + } + } + } + } + return VirtualizationConfiguration.SoundCardType.DEFAULT; + } + + public void setDDAcceleration( VirtualizationConfiguration.DDAcceleration type ) + { + VmWareDDAccelMeta ddaMeta = ddacc.get( type ); + addFiltered( "mks.enable3d", vmBoolean( ddaMeta.isPresent ) ); + } + + public VirtualizationConfiguration.DDAcceleration getDDAcceleration() + { + if ( isSetAndTrue( "mks.enable3d" ) ) { + return VirtualizationConfiguration.DDAcceleration.ON; + } else { + return VirtualizationConfiguration.DDAcceleration.OFF; + } + } + + 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() ); + } + + public void setEthernetDevType( int cardIndex, VirtualizationConfiguration.EthernetDevType type ) + { + VmWareEthernetDevTypeMeta ethernetDevTypeMeta = networkCards.get( type ); + if ( ethernetDevTypeMeta.value != null ) { + addFiltered( "ethernet" + cardIndex + ".virtualDev", ethernetDevTypeMeta.value ); + } else { + config.remove( "ethernet" + cardIndex + ".virtualDev" ); + } + } + + public VirtualizationConfiguration.EthernetDevType getEthernetDevType( int cardIndex ) + { + String temp = config.get( "ethernet" + cardIndex + ".virtualDev" ); + if ( temp != null ) { + VmWareEthernetDevTypeMeta ethernetDevTypeMeta = null; + for ( VirtualizationConfiguration.EthernetDevType type : VirtualizationConfiguration.EthernetDevType + .values() ) { + ethernetDevTypeMeta = networkCards.get( type ); + if ( ethernetDevTypeMeta == null ) { + continue; + } + if ( temp.equals( ethernetDevTypeMeta.value ) ) { + return type; + } + } + } + return VirtualizationConfiguration.EthernetDevType.AUTO; + } + + @Override + public void setMaxUsbSpeed( VirtualizationConfiguration.UsbSpeed newSpeed ) + { + if ( newSpeed == null ) { + newSpeed = VirtualizationConfiguration.UsbSpeed.NONE; + } + VmwareUsbSpeed newSpeedMeta = usbSpeeds.get( newSpeed ); + if ( newSpeedMeta == null ) { + throw new RuntimeException( "USB Speed " + newSpeed.name() + " not registered with VMware" ); + } + for ( VmwareUsbSpeed meta : usbSpeeds.values() ) { + if ( meta == null ) + continue; // Should not happen + if ( meta.keyName == null ) + continue; // "No USB" has no config entry, obviously + if ( meta.speedNumeric <= newSpeedMeta.speedNumeric ) { + // Enable desired speed class, plus all lower ones + addFiltered( meta.keyName, "TRUE" ); + } else { + // This one is higher – remove + config.remove( meta.keyName ); + } + } + // VMware 14+ needs this to use USB 3.0 devices at USB 3.0 ports in VMs configured for < 3.0 + if ( newSpeedMeta.speedNumeric > 0 && newSpeedMeta.speedNumeric < 3 ) { + addFiltered( "usb.mangleUsb3Speed", "TRUE" ); + } + } + + @Override + public VirtualizationConfiguration.UsbSpeed getMaxUsbSpeed() + { + int max = 0; + VirtualizationConfiguration.UsbSpeed maxEnum = VirtualizationConfiguration.UsbSpeed.NONE; + for ( Entry<VirtualizationConfiguration.UsbSpeed, VmwareUsbSpeed> entry : usbSpeeds.entrySet() ) { + VmwareUsbSpeed v = entry.getValue(); + if ( v.speedNumeric > max && isSetAndTrue( v.keyName ) ) { + max = v.speedNumeric; + maxEnum = entry.getKey(); + } + } + return maxEnum; + } + + @Override + public boolean addCpuCoreCount( int numCores ) + { + // TODO actually add the cpu core count to the machine description + return true; + } + + @Override + public void transformPrivacy() throws VirtualizationConfigurationException + { + } + + public void registerVirtualHW() + { + soundCards.put( VirtualizationConfiguration.SoundCardType.NONE, new VmWareSoundCardMeta( false, null ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.DEFAULT, new VmWareSoundCardMeta( true, null ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.SOUND_BLASTER, + new VmWareSoundCardMeta( true, "sb16" ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.ES, new VmWareSoundCardMeta( true, "es1371" ) ); + soundCards.put( VirtualizationConfiguration.SoundCardType.HD_AUDIO, new VmWareSoundCardMeta( true, "hdaudio" ) ); + + ddacc.put( VirtualizationConfiguration.DDAcceleration.OFF, new VmWareDDAccelMeta( false ) ); + ddacc.put( VirtualizationConfiguration.DDAcceleration.ON, new VmWareDDAccelMeta( true ) ); + + networkCards.put( VirtualizationConfiguration.EthernetDevType.AUTO, new VmWareEthernetDevTypeMeta( null ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.PCNET32, + new VmWareEthernetDevTypeMeta( "vlance" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.E1000, new VmWareEthernetDevTypeMeta( "e1000" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.E1000E, new VmWareEthernetDevTypeMeta( "e1000e" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.VMXNET, new VmWareEthernetDevTypeMeta( "vmxnet" ) ); + networkCards.put( VirtualizationConfiguration.EthernetDevType.VMXNET3, + new VmWareEthernetDevTypeMeta( "vmxnet3" ) ); + + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.NONE, new VmwareUsbSpeed( 0, null ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB1_1, new VmwareUsbSpeed( 1, "usb" ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB2_0, new VmwareUsbSpeed( 2, "ehci" ) ); + usbSpeeds.put( VirtualizationConfiguration.UsbSpeed.USB3_0, new VmwareUsbSpeed( 3, "usb_xhci" ) ); + } + + @Override + public String getFileNameExtension() + { + return VirtualizationConfigurationVmware.FILE_NAME_EXTENSION; + } + + @Override + public void validate() throws VirtualizationConfigurationException + { + } +} 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..24df02f --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/VirtualizationConfigurationVmwareFileFormat.java @@ -0,0 +1,288 @@ +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.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 = Logger.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 ) { + if ( entry.key.equals( "virtualHW.version" ) || entry.key.equals( "ddb.virtualHWVersion" ) + // TODO: This is supposed to be case insensitive. + // Check if there are other consequences from lowercase entries in converted vmx files. + || 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 ); + return new BufferedReader( new InputStreamReader( new ByteArrayInputStream( vmxContent, 0, length ), cs ) ); + } + + public static Charset getCharset( byte[] vmxContent, int length ) + { + String csName = detectCharset( new ByteArrayInputStream( vmxContent, 0, length ) ); + 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; + } + + public static String detectCharset( InputStream is ) + { + 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; + } + } + } catch ( Exception e ) { + LOGGER.warn( "Could not detect charset, fallback to latin1", e ); + } + // 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; + } + + public String toString( boolean filteredRequired, boolean generatedRequired ) + { + set( ".encoding", "UTF-8" ).filtered( true ).generated( true ); + StringBuilder sb = new StringBuilder( 300 ); + for ( Entry<String, ConfigEntry> entry : entries.entrySet() ) { + ConfigEntry value = entry.getValue(); + if ( ( !filteredRequired || value.forFiltered ) && ( !generatedRequired || value.forGenerated ) ) { + sb.append( entry.getKey() ); + sb.append( " = \"" ); + sb.append( value.getEscaped() ); + sb.append( "\"\n" ); + } + } + return sb.toString(); + } + + @Override + public String toString() + { + return toString( false, false ); + } + + public static class ConfigEntry + { + private String value; + private boolean forFiltered; + private boolean forGenerated; + + public ConfigEntry( String value ) + { + this.value = value; + } + + public ConfigEntry filtered( boolean set ) + { + this.forFiltered = set; + return this; + } + + public ConfigEntry generated( boolean set ) + { + this.forGenerated = set; + return this; + } + + 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/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..018d046 --- /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..f375693 --- /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..06c8ad1 --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToDozModClient.java @@ -0,0 +1,203 @@ +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.VirtualizationConfiguration.UsbSpeed; +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 ); + + // set maximum USB speed + if ( config.getMaxUsbSpeed() != UsbSpeed.USB3_0 ) { + config.setMaxUsbSpeed( UsbSpeed.USB2_0 ); + } + + // 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..30d690a --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/logic/ConfigurationLogicDozModServerToStatelessClient.java @@ -0,0 +1,109 @@ +package org.openslx.virtualization.configuration.logic; + +import org.openslx.virtualization.configuration.VirtualizationConfiguration; +import org.openslx.virtualization.configuration.VirtualizationConfiguration.EtherType; +import org.openslx.virtualization.configuration.VirtualizationConfiguration.UsbSpeed; +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 ); + + // 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.setMaxUsbSpeed( UsbSpeed.NONE ); + } + + // apply settings to run virtualized system in a stateless manner + try { + config.transformNonPersistent(); + } catch ( VirtualizationConfigurationException e ) { + throw new TransformationException( e.getLocalizedMessage() ); + } + } +} 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..ce0ad96 --- /dev/null +++ b/src/main/java/org/openslx/virtualization/configuration/transformation/TransformationManager.java @@ -0,0 +1,146 @@ +package org.openslx.virtualization.configuration.transformation; + +import java.util.ArrayList; + +/** + * 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; + + /** + * 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 ) + { + 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 ) { + try { + transformation.apply( this.config, this.args ); + } catch ( TransformationException e ) { + final String errorMsg = new String( + "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 = new String(); + 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/virtualizer/Virtualizer.java b/src/main/java/org/openslx/virtualization/virtualizer/Virtualizer.java new file mode 100644 index 0000000..ac3a4ec --- /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.vm.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..673447b --- /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.vm.disk.DiskImage; +import org.openslx.vm.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..fcce392 --- /dev/null +++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerQemu.java @@ -0,0 +1,68 @@ +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.vm.disk.DiskImage; +import org.openslx.vm.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( "1" ), "QEMU 2.1" ), + 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" ) ) ); + + /** + * 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..6be7cbf --- /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.vm.disk.DiskImage; +import org.openslx.vm.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..6e676f3 --- /dev/null +++ b/src/main/java/org/openslx/virtualization/virtualizer/VirtualizerVmware.java @@ -0,0 +1,71 @@ +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.vm.disk.DiskImage; +import org.openslx.vm.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" ) ) ); + + /** + * 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; + } + +} |