From be40e979e03e41ddcd831d9c330902f76908ca64 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Thu, 25 Feb 2021 15:00:38 +0100 Subject: Refactor disk image representation and add unit tests --- src/main/java/org/openslx/util/ThriftUtil.java | 2 +- src/main/java/org/openslx/util/vm/DiskImage.java | 379 --------- .../org/openslx/util/vm/DockerMetaDataDummy.java | 216 ------ .../java/org/openslx/util/vm/KeyValuePair.java | 13 - .../java/org/openslx/util/vm/QemuMetaData.java | 854 -------------------- .../org/openslx/util/vm/QemuMetaDataUtils.java | 188 ----- .../vm/UnsupportedVirtualizerFormatException.java | 9 - src/main/java/org/openslx/util/vm/VboxConfig.java | 631 --------------- .../java/org/openslx/util/vm/VboxMetaData.java | 535 ------------- src/main/java/org/openslx/util/vm/VmMetaData.java | 418 ---------- .../java/org/openslx/util/vm/VmwareConfig.java | 276 ------- .../java/org/openslx/util/vm/VmwareMetaData.java | 683 ---------------- .../java/org/openslx/vm/DockerMetaDataDummy.java | 217 ++++++ src/main/java/org/openslx/vm/KeyValuePair.java | 13 + src/main/java/org/openslx/vm/QemuMetaData.java | 855 +++++++++++++++++++++ .../java/org/openslx/vm/QemuMetaDataUtils.java | 188 +++++ .../vm/UnsupportedVirtualizerFormatException.java | 9 + src/main/java/org/openslx/vm/VboxConfig.java | 631 +++++++++++++++ src/main/java/org/openslx/vm/VboxMetaData.java | 536 +++++++++++++ src/main/java/org/openslx/vm/VmMetaData.java | 419 ++++++++++ src/main/java/org/openslx/vm/VmwareConfig.java | 276 +++++++ src/main/java/org/openslx/vm/VmwareMetaData.java | 684 +++++++++++++++++ src/main/java/org/openslx/vm/disk/DiskImage.java | 251 ++++++ .../org/openslx/vm/disk/DiskImageException.java | 25 + .../java/org/openslx/vm/disk/DiskImageQcow2.java | 246 ++++++ .../java/org/openslx/vm/disk/DiskImageUtils.java | 154 ++++ .../java/org/openslx/vm/disk/DiskImageVdi.java | 119 +++ .../java/org/openslx/vm/disk/DiskImageVmdk.java | 298 +++++++ .../java/org/openslx/util/vm/DiskImageTest.java | 260 ------- .../openslx/util/vm/DiskImageTestResources.java | 16 - .../java/org/openslx/util/vm/QemuMetaDataTest.java | 466 ----------- src/test/java/org/openslx/vm/QemuMetaDataTest.java | 468 +++++++++++ .../org/openslx/vm/disk/DiskImageQcow2Test.java | 220 ++++++ .../java/org/openslx/vm/disk/DiskImageTest.java | 19 + .../openslx/vm/disk/DiskImageTestResources.java | 16 + .../java/org/openslx/vm/disk/DiskImageVdiTest.java | 43 ++ .../org/openslx/vm/disk/DiskImageVmdkTest.java | 110 +++ src/test/resources/disk/image-default_snapshot.vdi | Bin 0 -> 2097152 bytes src/test/resources/disk/image_t0.vmdk | Bin 0 -> 262144 bytes src/test/resources/disk/image_t1-s001.vmdk | Bin 0 -> 262144 bytes src/test/resources/disk/image_t1.vmdk | 21 + src/test/resources/disk/image_t2-flat.vmdk | Bin 0 -> 10485760 bytes src/test/resources/disk/image_t2.vmdk | 21 + src/test/resources/disk/image_t3-f001.vmdk | Bin 0 -> 10485760 bytes src/test/resources/disk/image_t3.vmdk | 21 + src/test/resources/disk/image_t4-flat.vmdk | Bin 0 -> 10485760 bytes src/test/resources/disk/image_t4.vmdk | 22 + src/test/resources/disk/image_t5.vmdk | Bin 0 -> 67584 bytes 48 files changed, 5883 insertions(+), 4945 deletions(-) delete mode 100644 src/main/java/org/openslx/util/vm/DiskImage.java delete mode 100644 src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java delete mode 100644 src/main/java/org/openslx/util/vm/KeyValuePair.java delete mode 100644 src/main/java/org/openslx/util/vm/QemuMetaData.java delete mode 100644 src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java delete mode 100644 src/main/java/org/openslx/util/vm/UnsupportedVirtualizerFormatException.java delete mode 100644 src/main/java/org/openslx/util/vm/VboxConfig.java delete mode 100644 src/main/java/org/openslx/util/vm/VboxMetaData.java delete mode 100644 src/main/java/org/openslx/util/vm/VmMetaData.java delete mode 100644 src/main/java/org/openslx/util/vm/VmwareConfig.java delete mode 100644 src/main/java/org/openslx/util/vm/VmwareMetaData.java create mode 100644 src/main/java/org/openslx/vm/DockerMetaDataDummy.java create mode 100644 src/main/java/org/openslx/vm/KeyValuePair.java create mode 100644 src/main/java/org/openslx/vm/QemuMetaData.java create mode 100644 src/main/java/org/openslx/vm/QemuMetaDataUtils.java create mode 100644 src/main/java/org/openslx/vm/UnsupportedVirtualizerFormatException.java create mode 100644 src/main/java/org/openslx/vm/VboxConfig.java create mode 100644 src/main/java/org/openslx/vm/VboxMetaData.java create mode 100644 src/main/java/org/openslx/vm/VmMetaData.java create mode 100644 src/main/java/org/openslx/vm/VmwareConfig.java create mode 100644 src/main/java/org/openslx/vm/VmwareMetaData.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImage.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageException.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageQcow2.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageUtils.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageVdi.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageVmdk.java delete mode 100644 src/test/java/org/openslx/util/vm/DiskImageTest.java delete mode 100644 src/test/java/org/openslx/util/vm/DiskImageTestResources.java delete mode 100644 src/test/java/org/openslx/util/vm/QemuMetaDataTest.java create mode 100644 src/test/java/org/openslx/vm/QemuMetaDataTest.java create mode 100644 src/test/java/org/openslx/vm/disk/DiskImageQcow2Test.java create mode 100644 src/test/java/org/openslx/vm/disk/DiskImageTest.java create mode 100644 src/test/java/org/openslx/vm/disk/DiskImageTestResources.java create mode 100644 src/test/java/org/openslx/vm/disk/DiskImageVdiTest.java create mode 100644 src/test/java/org/openslx/vm/disk/DiskImageVmdkTest.java create mode 100644 src/test/resources/disk/image-default_snapshot.vdi create mode 100644 src/test/resources/disk/image_t0.vmdk create mode 100644 src/test/resources/disk/image_t1-s001.vmdk create mode 100644 src/test/resources/disk/image_t1.vmdk create mode 100644 src/test/resources/disk/image_t2-flat.vmdk create mode 100644 src/test/resources/disk/image_t2.vmdk create mode 100644 src/test/resources/disk/image_t3-f001.vmdk create mode 100644 src/test/resources/disk/image_t3.vmdk create mode 100644 src/test/resources/disk/image_t4-flat.vmdk create mode 100644 src/test/resources/disk/image_t4.vmdk create mode 100644 src/test/resources/disk/image_t5.vmdk diff --git a/src/main/java/org/openslx/util/ThriftUtil.java b/src/main/java/org/openslx/util/ThriftUtil.java index 327f2d3..3c2c9ea 100644 --- a/src/main/java/org/openslx/util/ThriftUtil.java +++ b/src/main/java/org/openslx/util/ThriftUtil.java @@ -7,7 +7,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import org.openslx.util.vm.VmwareConfig; +import org.openslx.vm.VmwareConfig; public class ThriftUtil { diff --git a/src/main/java/org/openslx/util/vm/DiskImage.java b/src/main/java/org/openslx/util/vm/DiskImage.java deleted file mode 100644 index 617fadd..0000000 --- a/src/main/java/org/openslx/util/vm/DiskImage.java +++ /dev/null @@ -1,379 +0,0 @@ -package org.openslx.util.vm; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import org.apache.log4j.Logger; -import org.openslx.bwlp.thrift.iface.Virtualizer; -import org.openslx.thrifthelper.TConst; -import org.openslx.util.Util; - -public class DiskImage -{ - private static final Logger LOGGER = Logger.getLogger( DiskImage.class ); - /** - * Big endian representation of the 4 bytes 'KDMV' - */ - private static final int VMDK_MAGIC = 0x4b444d56; - private static final int VDI_MAGIC = 0x7f10dabe; - /** - * Big endian representation of the 4 bytes 'QFI\xFB' - */ - private static final int QEMU_MAGIC = 0x514649fb; - - public enum ImageFormat - { - VMDK( "vmdk" ), QCOW2( "qcow2" ), VDI( "vdi" ), DOCKER( "dockerfile" ); - - public final String extension; - - private ImageFormat( String extension ) - { - this.extension = extension; - } - - public static ImageFormat defaultForVirtualizer( Virtualizer virt ) - { - if ( virt == null ) - return null; - return defaultForVirtualizer( virt.virtId ); - } - - public static ImageFormat defaultForVirtualizer( String virtId ) - { - if ( virtId == null ) - return null; - if ( virtId.equals( TConst.VIRT_VMWARE ) ) - return VMDK; - if ( virtId.equals( TConst.VIRT_VIRTUALBOX ) ) - return VDI; - if ( virtId.equals( TConst.VIRT_QEMU ) ) - return QCOW2; - if ( virtId.equals( TConst.VIRT_DOCKER ) ) - return DOCKER; - return null; - } - } - - public final boolean isStandalone; - public final boolean isCompressed; - public final boolean isSnapshot; - public final ImageFormat format; - public final String subFormat; - public final int hwVersion; - public final String diskDescription; - - public ImageFormat getImageFormat() - { - return format; - } - - public DiskImage( File disk ) throws FileNotFoundException, IOException, UnknownImageFormatException - { - LOGGER.debug( "Validating disk file: " + disk.getAbsolutePath() ); - try ( RandomAccessFile file = new RandomAccessFile( disk, "r" ) ) { - // vmdk - boolean isVmdkMagic = ( file.readInt() == VMDK_MAGIC ); - if ( isVmdkMagic || file.length() < 4096 ) { - if ( isVmdkMagic ) { - file.seek( 512 ); - } else { - file.seek( 0 ); - } - byte[] buffer = new byte[ (int)Math.min( 2048, file.length() ) ]; - file.readFully( buffer ); - VmwareConfig config; - try { - config = new VmwareConfig( buffer, findNull( buffer ) ); - } catch ( UnsupportedVirtualizerFormatException e ) { - config = null; - } - if ( config != null ) { - String sf = config.get( "createType" ); - String parent = config.get( "parentCID" ); - if ( sf != null || parent != null ) { - subFormat = sf; - this.isStandalone = isStandaloneCreateType( subFormat, parent ); - this.isCompressed = subFormat != null && subFormat.equalsIgnoreCase( "streamOptimized" ); - this.isSnapshot = parent != null && !parent.equalsIgnoreCase( "ffffffff" ); - this.format = ImageFormat.VMDK; - String hwv = config.get( "ddb.virtualHWVersion" ); - if ( hwv == null ) { - this.hwVersion = 10; - } else { - this.hwVersion = Util.parseInt( hwv, 10 ); - } - this.diskDescription = null; - return; - } - } - } - // Format spec from: https://forums.virtualbox.org/viewtopic.php?t=8046 - // First 64 bytes are the opening tag: <<< .... >>> - // which we don't care about, then comes the VDI signature - file.seek( 64 ); - if ( file.readInt() == VDI_MAGIC ) { - // skip the next 4 ints as they don't interest us: - // - VDI version - // - size of header, strangely irrelevant? - // - image type, 1 for dynamic allocated storage, 2 for fixed size - // - image flags, seem to be always 00 00 00 00 - file.skipBytes( 4 * 4 ); - - // next 256 bytes are image description - byte[] imageDesc = new byte[ 256 ]; - file.readFully( imageDesc ); - // next sections are irrelevant (int if not specified): - // - offset blocks - // - offset data - // - cylinders - // - heads - // - sectors - // - sector size - // - - // - disk size (long = 8 bytes) - // - block size - // - block extra data - // - blocks in hdd - // - blocks allocated - file.skipBytes( 4 * 13 ); - - // now it gets interesting, UUID of VDI - byte[] diskUuid = new byte[ 16 ]; - file.readFully( diskUuid ); - // last snapshot uuid, mostly uninteresting since we don't support snapshots -> skip 16 bytes - // TODO: meaning of "uuid link"? for now, skip 16 - file.skipBytes( 32 ); - - // parent uuid, indicator if this VDI is a snapshot or not - byte[] parentUuid = new byte[ 16 ]; - file.readFully( parentUuid ); - byte[] zeroUuid = new byte[ 16 ]; - Arrays.fill( zeroUuid, (byte)0 ); - this.isSnapshot = !Arrays.equals( parentUuid, zeroUuid ); - // VDI does not seem to support split VDI files so always standalone - this.isStandalone = true; - // compression is done by sparsifying the disk files, there is no flag for it - this.isCompressed = false; - this.format = ImageFormat.VDI; - this.subFormat = null; - this.diskDescription = new String( imageDesc ); - this.hwVersion = 0; - return; - } - - // qcow2 disk image - file.seek( 0 ); - if ( file.readInt() == QEMU_MAGIC ) { - // - // qcow2 (version 2 and 3) header format: - // - // magic (4 byte) - // version (4 byte) - // backing_file_offset (8 byte) - // backing_file_size (4 byte) - // cluster_bits (4 byte) - // size (8 byte) - // crypt_method (4 byte) - // l1_size (4 byte) - // l1_table_offset (8 byte) - // refcount_table_offset (8 byte) - // refcount_table_clusters (4 byte) - // nb_snapshots (4 byte) - // snapshots_offset (8 byte) - // incompatible_features (8 byte) [*] - // compatible_features (8 byte) [*] - // autoclear_features (8 byte) [*] - // refcount_order (8 byte) [*] - // header_length (4 byte) [*] - // - // [*] these fields are only available in the qcow2 version 3 header format - // - - // - // check qcow2 file format version - // - file.seek( 4 ); - final int qcowVersion = file.readInt(); - if ( qcowVersion < 2 || qcowVersion > 3 ) { - // disk image format is not a qcow2 disk format - throw new UnknownImageFormatException(); - } else { - // disk image format is a valid qcow2 disk format - this.hwVersion = qcowVersion; - this.subFormat = null; - } - - // - // check if qcow2 image does not refer to any backing file - // - file.seek( 8 ); - this.isStandalone = ( file.readLong() == 0 ) ? true : false; - - // - // check if qcow2 image does not contain any snapshot - // - file.seek( 56 ); - this.isSnapshot = ( file.readInt() == 0 ) ? true : false; - - // - // check if qcow2 image uses extended L2 tables - // - boolean qcowUseExtendedL2 = false; - - // extended L2 tables are only possible in qcow2 version 3 header format - if ( qcowVersion == 3 ) { - // read incompatible feature bits - file.seek( 72 ); - final long qcowIncompatibleFeatures = file.readLong(); - - // support for extended L2 tables is enabled if bit 4 is set - qcowUseExtendedL2 = ( ( ( qcowIncompatibleFeatures & 0x000000000010 ) >>> 4 ) == 1 ); - } - - // - // check if qcow2 image contains compressed clusters - // - boolean qcowCompressed = false; - - // get number of entries in L1 table - file.seek( 36 ); - final int qcowL1TableSize = file.readInt(); - - // check if a valid L1 table is present - if ( qcowL1TableSize > 0 ) { - // qcow2 image contains active L1 table with more than 0 entries: l1_size > 0 - // search for first L2 table and its first entry to get compression bit - - // get cluster bits to calculate the cluster size - file.seek( 20 ); - final int qcowClusterBits = file.readInt(); - final int qcowClusterSize = ( 1 << qcowClusterBits ); - - // entries of a L1 table have always the size of 8 byte (64 bit) - final int qcowL1TableEntrySize = 8; - - // entries of a L2 table have either the size of 8 or 16 byte (64 or 128 bit) - final int qcowL2TableEntrySize = ( qcowUseExtendedL2 ) ? 16 : 8; - - // calculate number of L2 table entries - final int qcowL2TableSize = qcowClusterSize / qcowL2TableEntrySize; - - // get offset of L1 table - file.seek( 40 ); - long qcowL1TableOffset = file.readLong(); - - // check for each L2 table referenced from an L1 table its entries - // until a compressed cluster descriptor is found - for ( long i = 0; i < qcowL1TableSize; i++ ) { - // get offset of current L2 table from the current L1 table entry - long qcowL1TableEntryOffset = qcowL1TableOffset + ( i * qcowL1TableEntrySize ); - file.seek( qcowL1TableEntryOffset ); - long qcowL1TableEntry = file.readLong(); - - // extract offset (bits 9 - 55) from L1 table entry - long qcowL2TableOffset = ( qcowL1TableEntry & 0x00fffffffffffe00L ); - - if ( qcowL2TableOffset == 0 ) { - // L2 table and all clusters described by this L2 table are unallocated - continue; - } - - // get each L2 table entry and check if it is a compressed cluster descriptor - for ( long j = 0; j < qcowL2TableSize; j++ ) { - // get current L2 table entry - long qcowL2TableEntryOffset = qcowL2TableOffset + ( j * qcowL2TableEntrySize ); - file.seek( qcowL2TableEntryOffset ); - long qcowL2TableEntry = file.readLong(); - - // extract cluster type (standard or compressed) (bit 62) - boolean qcowClusterCompressed = ( ( ( qcowL2TableEntry & 0x4000000000000000L ) >>> 62 ) == 1 ); - - // check if qcow2 disk image contains at least one compressed cluster descriptor - if ( qcowClusterCompressed ) { - qcowCompressed = true; - break; - } - } - - // terminate if one compressed cluster descriptor is already found - if ( qcowCompressed ) { - break; - } - } - } else { - // qcow2 image does not contain an active L1 table with any entry: l1_size = 0 - qcowCompressed = false; - } - - this.isCompressed = qcowCompressed; - this.format = ImageFormat.QCOW2; - this.diskDescription = null; - - return; - } - } - throw new UnknownImageFormatException(); - } - - /** - * Creates new disk image and checks if image is supported by hypervisor's image formats. - * - * @param disk file to a disk storing the virtual machine content. - * @param supportedImageTypes list of supported image types of a hypervisor's image format. - * @throws FileNotFoundException cannot find virtual machine image file. - * @throws IOException cannot access the virtual machine image file. - * @throws UnknownImageFormatException virtual machine image file has an unknown image format. - */ - public DiskImage( File disk, List supportedImageTypes ) - throws FileNotFoundException, IOException, UnknownImageFormatException - { - this( disk ); - - if ( !this.isSupportedByHypervisor( supportedImageTypes ) ) { - throw new UnknownImageFormatException(); - } - } - - /** - * Checks if image format is supported by a list of supported hypervisor image formats. - * - * @param supportedImageTypes list of supported image types of a hypervisor. - * @return true if image type is supported by the hypervisor; otherwise - * false. - */ - public boolean isSupportedByHypervisor( List supportedImageTypes ) - { - Predicate matchDiskFormat = supportedImageType -> supportedImageType.toString() - .equalsIgnoreCase( this.getImageFormat().toString() ); - return supportedImageTypes.stream().anyMatch( matchDiskFormat ); - } - - private int findNull( byte[] buffer ) - { - for ( int i = 0; i < buffer.length; ++i ) { - if ( buffer[i] == 0 ) - return i; - } - return buffer.length; - } - - private boolean isStandaloneCreateType( String type, String parent ) - { - if ( type == null ) - return false; - if ( parent != null && !parent.equalsIgnoreCase( "ffffffff" ) ) - return false; - return type.equalsIgnoreCase( "streamOptimized" ) || type.equalsIgnoreCase( "monolithicSparse" ); - } - - public static class UnknownImageFormatException extends Exception - { - private static final long serialVersionUID = -6647935235475007171L; - } -} diff --git a/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java b/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java deleted file mode 100644 index 38388ce..0000000 --- a/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.openslx.util.vm; - -import org.apache.commons.io.IOUtils; -import org.apache.log4j.Logger; -import org.openslx.bwlp.thrift.iface.OperatingSystem; -import org.openslx.bwlp.thrift.iface.Virtualizer; -import org.openslx.thrifthelper.TConst; -import org.openslx.util.vm.DiskImage.ImageFormat; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -class DockerSoundCardMeta -{ -} - -class DockerDDAccelMeta -{ -} - -class DockerHWVersionMeta -{ -} - -class DockerEthernetDevTypeMeta -{ -} - -class DockerUsbSpeedMeta -{ -} - -public class DockerMetaDataDummy extends VmMetaData { - - /** - * List of supported image formats by the Docker hypervisor. - */ - private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( - Arrays.asList( ImageFormat.DOCKER ) ); - - private static final Logger LOGGER = Logger.getLogger( DockerMetaDataDummy.class); - - private final Virtualizer virtualizer = new Virtualizer(TConst.VIRT_DOCKER, "Docker"); - - /** - * 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. - *

- * See ContainerDefintion in tutor-module (bwsuite). - *

- * 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; - - @SuppressWarnings( "deprecation" ) - public DockerMetaDataDummy(List osList, File file) throws UnsupportedVirtualizerFormatException { - super(osList); - - BufferedInputStream bis = null; - - try { - bis = new BufferedInputStream(new FileInputStream(file)); - containerDefinition = new byte[(int) file.length()]; - bis.read(containerDefinition); - - checkIsTarGz(); - } catch (IOException | UnsupportedVirtualizerFormatException e) { - LOGGER.error("Couldn't read dockerfile", e); - } finally { - IOUtils.closeQuietly( bis ); - } - } - - public DockerMetaDataDummy(List osList, byte[] vmContent, int length) - throws UnsupportedVirtualizerFormatException { - super(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 UnsupportedVirtualizerFormatException - */ - private void checkIsTarGz() throws UnsupportedVirtualizerFormatException { - if (!((31 == containerDefinition[0]) && (-117 == containerDefinition[1]))) { - LOGGER.warn("Not Supported Content."); - throw new UnsupportedVirtualizerFormatException( - "DockerMetaDataDummy: Not tar.gz encoded content!"); - } - } - - @Override public byte[] getFilteredDefinitionArray() { - return containerDefinition; - } - - @Override - public List getSupportedImageFormats() - { - return DockerMetaDataDummy.SUPPORTED_IMAGE_FORMATS; - } - - @Override public void applySettingsForLocalEdit() { - - } - - @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 setHWVersion(HWVersion type) { - - } - - @Override public HWVersion getHWVersion() { - return HWVersion.DEFAULT; - } - - @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[] getDefinitionArray() { - return new byte[0]; - } - - @Override public boolean addEthernet(EtherType type) { - return false; - } - - @Override public Virtualizer getVirtualizer() { - return virtualizer; - } - - @Override public boolean tweakForNonPersistent() { - return false; - } - - @Override public void registerVirtualHW() { - - } -} diff --git a/src/main/java/org/openslx/util/vm/KeyValuePair.java b/src/main/java/org/openslx/util/vm/KeyValuePair.java deleted file mode 100644 index d89d51b..0000000 --- a/src/main/java/org/openslx/util/vm/KeyValuePair.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.openslx.util.vm; - -class KeyValuePair -{ - public final String key; - public final String value; - - public KeyValuePair( String key, String value ) - { - this.key = key; - this.value = value; - } -} \ No newline at end of file diff --git a/src/main/java/org/openslx/util/vm/QemuMetaData.java b/src/main/java/org/openslx/util/vm/QemuMetaData.java deleted file mode 100644 index d3b8451..0000000 --- a/src/main/java/org/openslx/util/vm/QemuMetaData.java +++ /dev/null @@ -1,854 +0,0 @@ -package org.openslx.util.vm; - -import java.io.File; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map.Entry; - -import org.openslx.bwlp.thrift.iface.OperatingSystem; -import org.openslx.bwlp.thrift.iface.Virtualizer; -import org.openslx.libvirt.domain.Domain; -import org.openslx.libvirt.domain.device.ControllerUsb; -import org.openslx.libvirt.domain.device.Disk.BusType; -import org.openslx.libvirt.domain.device.Disk.StorageType; -import org.openslx.libvirt.domain.device.DiskCdrom; -import org.openslx.libvirt.domain.device.DiskFloppy; -import org.openslx.libvirt.domain.device.DiskStorage; -import org.openslx.libvirt.domain.device.Graphics; -import org.openslx.libvirt.domain.device.GraphicsSpice; -import org.openslx.libvirt.domain.device.Interface; -import org.openslx.libvirt.domain.device.Sound; -import org.openslx.libvirt.domain.device.Video; -import org.openslx.libvirt.xml.LibvirtXmlDocumentException; -import org.openslx.libvirt.xml.LibvirtXmlSerializationException; -import org.openslx.libvirt.xml.LibvirtXmlValidationException; -import org.openslx.thrifthelper.TConst; -import org.openslx.util.vm.DiskImage.ImageFormat; - -/** - * 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 version of a QEMU virtual machine configuration. - * - * @author Manuel Bentele - * @version 1.0 - */ -class QemuHWVersionMeta -{ - /** - * Stores the version of a QEMU virtual machine configuration. - */ - private final int version; - - /** - * Creates metadata to describe the version of a QEMU virtual machine configuration. - * - * @param version version of the QEMU virtual machine configuration. - */ - public QemuHWVersionMeta( int version ) - { - this.version = version; - } - - /** - * Returns version of the QEMU virtual machine configuration. - * - * @return version of the QEMU virtual machine configuration. - */ - public int getVersion() - { - return this.version; - } -} - -/** - * 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 QemuMetaData extends - VmMetaData -{ - /** - * Default bridge name of the network bridge connected to the LAN. - */ - public static final String NETWORK_DEFAULT_BRIDGE = "brBwLehrpool"; - - /** - * Default network name of the isolated host network (host only). - */ - public static final String NETWORK_DEFAULT_HOST_ONLY = "host"; - - /** - * Default network name of the NAT network. - */ - public static final String NETWORK_DEFAULT_NAT = "nat"; - - /** - * Default physical CDROM drive of the hypervisor host. - */ - public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0"; - - /** - * List of supported image formats by the QEMU hypervisor. - */ - private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( - Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ); - - /** - * Representation of a QEMU hypervisor (managed by Libvirt). - */ - private static final Virtualizer VIRTUALIZER = new Virtualizer( TConst.VIRT_QEMU, "QEMU" ); - - /** - * Libvirt XML configuration file to modify configuration of virtual machine for QEMU. - */ - private Domain vmConfig = null; - - /** - * Stores current index of added HDD device to the Libvirt XML configuration file. - */ - private int vmDeviceIndexHddAdd = 0; - - /** - * Stores current index of added CDROM device to the Libvirt XML configuration file. - */ - private int vmDeviceIndexCdromAdd = 0; - - /** - * Stores current index of added ethernet device to the Libvirt XML configuration file. - */ - private int vmDeviceIndexEthernetAdd = 0; - - /** - * 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 UnsupportedVirtualizerFormatException Libvirt XML configuration cannot be processed. - */ - public QemuMetaData( List osList, File file ) throws UnsupportedVirtualizerFormatException - { - super( osList ); - - try { - // read and parse Libvirt domain XML configuration document - this.vmConfig = new Domain( file ); - } catch ( LibvirtXmlDocumentException e ) { - throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); - } catch ( LibvirtXmlSerializationException e ) { - throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); - } catch ( LibvirtXmlValidationException e ) { - throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); - } - - // register virtual hardware models for graphical editing of virtual devices (GPU, sound, USB, ...) - this.registerVirtualHW(); - - // 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 ); - } - } - - /** - * 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 = QemuMetaDataUtils.convertBusType( storageDiskDevice.getBusType() ); - String hddImagePath = storageDiskDevice.getStorageSource(); - - this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) ); - } - - @Override - public byte[] getFilteredDefinitionArray() - { - // remove UUID in Libvirt domain XML configuration - this.vmConfig.removeUuid(); - - // removes all specified boot order entries - this.vmConfig.removeBootOrder(); - - // removes all referenced storage files of all specified CDROMs, Floppy drives and HDDs - this.vmConfig.removeDiskDevicesStorage(); - - // removes all source networks of all specified network interfaces - this.vmConfig.removeInterfaceDevicesSource(); - - // output filtered Libvirt domain XML configuration - String configuration = this.vmConfig.toString(); - return configuration.getBytes( StandardCharsets.UTF_8 ); - } - - @Override - public List getSupportedImageFormats() - { - return QemuMetaData.SUPPORTED_IMAGE_FORMATS; - } - - @Override - public void applySettingsForLocalEdit() - { - // NOT implemented yet - } - - @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 ) - { - return this.addHddTemplate( this.vmDeviceIndexHddAdd++, 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 - * hddMode is set. - * @return result state of adding the HDD. - */ - public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir ) - { - ArrayList storageDiskDevices = this.vmConfig.getDiskStorageDevices(); - DiskStorage storageDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "vd", index ); - storageDiskDevice.setTargetDevice( targetDevName ); - 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 - storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); - } - - return false; - } - - @Override - public boolean addDefaultNat() - { - return this.addEthernet( EtherType.NAT ); - } - - @Override - public void setOs( String vendorOsId ) - { - this.setOs( vendorOsId ); - } - - @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 ) - { - BigInteger memory = BigInteger.valueOf( mem ); - - this.vmConfig.setMemory( memory ); - this.vmConfig.setCurrentMemory( memory ); - - final boolean isMemorySet = this.vmConfig.getMemory().toString().equals( memory.toString() ); - final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().toString().equals( memory.toString() ); - - return isMemorySet && isCurrentMemorySet; - } - - @Override - public void addFloppy( int index, String image, boolean readOnly ) - { - ArrayList floppyDiskDevices = this.vmConfig.getDiskFloppyDevices(); - DiskFloppy floppyDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "fd", index ); - floppyDiskDevice.setTargetDevice( targetDevName ); - floppyDiskDevice.setReadOnly( readOnly ); - floppyDiskDevice.setStorage( StorageType.FILE, image ); - } else { - // floppy device exists, so update existing floppy device - floppyDiskDevice.setReadOnly( readOnly ); - floppyDiskDevice.setStorage( StorageType.FILE, image ); - } - } - - @Override - public boolean addCdrom( String image ) - { - return this.addCdrom( this.vmDeviceIndexCdromAdd++, 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 cdromDiskDevices = this.vmConfig.getDiskCdromDevices(); - DiskCdrom cdromDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "sd", index ); - cdromDiskDevice.setTargetDevice( targetDevName ); - cdromDiskDevice.setReadOnly( true ); - - if ( image == null ) { - cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); - } 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 { - cdromDiskDevice.setStorage( StorageType.FILE, image ); - } - } - - return false; - } - - @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 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 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 = QemuMetaDataUtils.convertSoundDeviceModel( soundDeviceModel ); - } - - return soundDeviceType; - } - - @Override - public void setDDAcceleration( DDAcceleration type ) - { - QemuDDAccelMeta accelerationConfig = this.ddacc.get( type ); - ArrayList graphicDevices = this.vmConfig.getGraphicDevices(); - ArrayList

+ * See ContainerDefintion in tutor-module (bwsuite). + *

+ * 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; + + @SuppressWarnings( "deprecation" ) + public DockerMetaDataDummy(List osList, File file) throws UnsupportedVirtualizerFormatException { + super(osList); + + BufferedInputStream bis = null; + + try { + bis = new BufferedInputStream(new FileInputStream(file)); + containerDefinition = new byte[(int) file.length()]; + bis.read(containerDefinition); + + checkIsTarGz(); + } catch (IOException | UnsupportedVirtualizerFormatException e) { + LOGGER.error("Couldn't read dockerfile", e); + } finally { + IOUtils.closeQuietly( bis ); + } + } + + public DockerMetaDataDummy(List osList, byte[] vmContent, int length) + throws UnsupportedVirtualizerFormatException { + super(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 UnsupportedVirtualizerFormatException + */ + private void checkIsTarGz() throws UnsupportedVirtualizerFormatException { + if (!((31 == containerDefinition[0]) && (-117 == containerDefinition[1]))) { + LOGGER.warn("Not Supported Content."); + throw new UnsupportedVirtualizerFormatException( + "DockerMetaDataDummy: Not tar.gz encoded content!"); + } + } + + @Override public byte[] getFilteredDefinitionArray() { + return containerDefinition; + } + + @Override + public List getSupportedImageFormats() + { + return DockerMetaDataDummy.SUPPORTED_IMAGE_FORMATS; + } + + @Override public void applySettingsForLocalEdit() { + + } + + @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 setHWVersion(HWVersion type) { + + } + + @Override public HWVersion getHWVersion() { + return HWVersion.DEFAULT; + } + + @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[] getDefinitionArray() { + return new byte[0]; + } + + @Override public boolean addEthernet(EtherType type) { + return false; + } + + @Override public Virtualizer getVirtualizer() { + return virtualizer; + } + + @Override public boolean tweakForNonPersistent() { + return false; + } + + @Override public void registerVirtualHW() { + + } +} diff --git a/src/main/java/org/openslx/vm/KeyValuePair.java b/src/main/java/org/openslx/vm/KeyValuePair.java new file mode 100644 index 0000000..c5650ec --- /dev/null +++ b/src/main/java/org/openslx/vm/KeyValuePair.java @@ -0,0 +1,13 @@ +package org.openslx.vm; + +class KeyValuePair +{ + public final String key; + public final String value; + + public KeyValuePair( String key, String value ) + { + this.key = key; + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/org/openslx/vm/QemuMetaData.java b/src/main/java/org/openslx/vm/QemuMetaData.java new file mode 100644 index 0000000..c780429 --- /dev/null +++ b/src/main/java/org/openslx/vm/QemuMetaData.java @@ -0,0 +1,855 @@ +package org.openslx.vm; + +import java.io.File; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; + +import org.openslx.bwlp.thrift.iface.OperatingSystem; +import org.openslx.bwlp.thrift.iface.Virtualizer; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.ControllerUsb; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Disk.StorageType; +import org.openslx.libvirt.domain.device.DiskCdrom; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.libvirt.domain.device.DiskStorage; +import org.openslx.libvirt.domain.device.Graphics; +import org.openslx.libvirt.domain.device.GraphicsSpice; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.domain.device.Video; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; +import org.openslx.thrifthelper.TConst; +import org.openslx.vm.disk.DiskImage; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +/** + * 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 version of a QEMU virtual machine configuration. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuHWVersionMeta +{ + /** + * Stores the version of a QEMU virtual machine configuration. + */ + private final int version; + + /** + * Creates metadata to describe the version of a QEMU virtual machine configuration. + * + * @param version version of the QEMU virtual machine configuration. + */ + public QemuHWVersionMeta( int version ) + { + this.version = version; + } + + /** + * Returns version of the QEMU virtual machine configuration. + * + * @return version of the QEMU virtual machine configuration. + */ + public int getVersion() + { + return this.version; + } +} + +/** + * 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 QemuMetaData extends + VmMetaData +{ + /** + * Default bridge name of the network bridge connected to the LAN. + */ + public static final String NETWORK_DEFAULT_BRIDGE = "brBwLehrpool"; + + /** + * Default network name of the isolated host network (host only). + */ + public static final String NETWORK_DEFAULT_HOST_ONLY = "host"; + + /** + * Default network name of the NAT network. + */ + public static final String NETWORK_DEFAULT_NAT = "nat"; + + /** + * Default physical CDROM drive of the hypervisor host. + */ + public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0"; + + /** + * List of supported image formats by the QEMU hypervisor. + */ + private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ); + + /** + * Representation of a QEMU hypervisor (managed by Libvirt). + */ + private static final Virtualizer VIRTUALIZER = new Virtualizer( TConst.VIRT_QEMU, "QEMU" ); + + /** + * Libvirt XML configuration file to modify configuration of virtual machine for QEMU. + */ + private Domain vmConfig = null; + + /** + * Stores current index of added HDD device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexHddAdd = 0; + + /** + * Stores current index of added CDROM device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexCdromAdd = 0; + + /** + * Stores current index of added ethernet device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexEthernetAdd = 0; + + /** + * 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 UnsupportedVirtualizerFormatException Libvirt XML configuration cannot be processed. + */ + public QemuMetaData( List osList, File file ) throws UnsupportedVirtualizerFormatException + { + super( osList ); + + try { + // read and parse Libvirt domain XML configuration document + this.vmConfig = new Domain( file ); + } catch ( LibvirtXmlDocumentException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlSerializationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlValidationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } + + // register virtual hardware models for graphical editing of virtual devices (GPU, sound, USB, ...) + this.registerVirtualHW(); + + // 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 ); + } + } + + /** + * 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 = QemuMetaDataUtils.convertBusType( storageDiskDevice.getBusType() ); + String hddImagePath = storageDiskDevice.getStorageSource(); + + this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) ); + } + + @Override + public byte[] getFilteredDefinitionArray() + { + // remove UUID in Libvirt domain XML configuration + this.vmConfig.removeUuid(); + + // removes all specified boot order entries + this.vmConfig.removeBootOrder(); + + // removes all referenced storage files of all specified CDROMs, Floppy drives and HDDs + this.vmConfig.removeDiskDevicesStorage(); + + // removes all source networks of all specified network interfaces + this.vmConfig.removeInterfaceDevicesSource(); + + // output filtered Libvirt domain XML configuration + String configuration = this.vmConfig.toString(); + return configuration.getBytes( StandardCharsets.UTF_8 ); + } + + @Override + public List getSupportedImageFormats() + { + return QemuMetaData.SUPPORTED_IMAGE_FORMATS; + } + + @Override + public void applySettingsForLocalEdit() + { + // NOT implemented yet + } + + @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 ) + { + return this.addHddTemplate( this.vmDeviceIndexHddAdd++, 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 + * hddMode is set. + * @return result state of adding the HDD. + */ + public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir ) + { + ArrayList storageDiskDevices = this.vmConfig.getDiskStorageDevices(); + DiskStorage storageDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "vd", index ); + storageDiskDevice.setTargetDevice( targetDevName ); + 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 + storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); + } + + return false; + } + + @Override + public boolean addDefaultNat() + { + return this.addEthernet( EtherType.NAT ); + } + + @Override + public void setOs( String vendorOsId ) + { + this.setOs( vendorOsId ); + } + + @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 ) + { + BigInteger memory = BigInteger.valueOf( mem ); + + this.vmConfig.setMemory( memory ); + this.vmConfig.setCurrentMemory( memory ); + + final boolean isMemorySet = this.vmConfig.getMemory().toString().equals( memory.toString() ); + final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().toString().equals( memory.toString() ); + + return isMemorySet && isCurrentMemorySet; + } + + @Override + public void addFloppy( int index, String image, boolean readOnly ) + { + ArrayList floppyDiskDevices = this.vmConfig.getDiskFloppyDevices(); + DiskFloppy floppyDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "fd", index ); + floppyDiskDevice.setTargetDevice( targetDevName ); + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } else { + // floppy device exists, so update existing floppy device + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } + } + + @Override + public boolean addCdrom( String image ) + { + return this.addCdrom( this.vmDeviceIndexCdromAdd++, 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 cdromDiskDevices = this.vmConfig.getDiskCdromDevices(); + DiskCdrom cdromDiskDevice = QemuMetaDataUtils.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 = QemuMetaDataUtils.createAlphabeticalDeviceName( "sd", index ); + cdromDiskDevice.setTargetDevice( targetDevName ); + cdromDiskDevice.setReadOnly( true ); + + if ( image == null ) { + cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); + } 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 { + cdromDiskDevice.setStorage( StorageType.FILE, image ); + } + } + + return false; + } + + @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 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 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 = QemuMetaDataUtils.convertSoundDeviceModel( soundDeviceModel ); + } + + return soundDeviceType; + } + + @Override + public void setDDAcceleration( DDAcceleration type ) + { + QemuDDAccelMeta accelerationConfig = this.ddacc.get( type ); + ArrayList graphicDevices = this.vmConfig.getGraphicDevices(); + ArrayList

+ *   QCOW2 (version 2 and 3) header format:
+ *   
+ *   magic                   (4 byte)
+ *   version                 (4 byte)
+ *   backing_file_offset     (8 byte)
+ *   backing_file_size       (4 byte)
+ *   cluster_bits            (4 byte)
+ *   size                    (8 byte)
+ *   crypt_method            (4 byte)
+ *   l1_size                 (4 byte)
+ *   l1_table_offset         (8 byte)
+ *   refcount_table_offset   (8 byte)
+ *   refcount_table_clusters (4 byte)
+ *   nb_snapshots            (4 byte)
+ *   snapshots_offset        (8 byte)
+ *   incompatible_features   (8 byte)  [*]
+ *   compatible_features     (8 byte)  [*]
+ *   autoclear_features      (8 byte)  [*]
+ *   refcount_order          (8 byte)  [*]
+ *   header_length           (4 byte)  [*]
+ *
+ *   [*] these fields are only available in the QCOW2 version 3 header format
+ * 
+ * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageQcow2 extends DiskImage +{ + /** + * Big endian representation of the big endian QCOW2 magic bytes QFI\xFB. + */ + private static final int QCOW2_MAGIC = 0x514649fb; + + /** + * Creates a new QCOW2 disk image from an existing QCOW2 image file. + * + * @param diskImage file to a QCOW2 disk storing the image content. + * + * @throws FileNotFoundException cannot find specified QCOW2 disk image file. + * @throws IOException cannot access the content of the QCOW2 disk image file. + */ + public DiskImageQcow2( File disk ) throws FileNotFoundException, IOException + { + super( disk ); + } + + /** + * Creates a new QCOW2 disk image from an existing QCOW2 image file. + * + * @param diskImage file to a QCOW2 disk storing the image content. + */ + public DiskImageQcow2( RandomAccessFile disk ) + { + super( disk ); + } + + /** + * Probe specified file with unknown format to be a QCOW2 disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a QCOW2 disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isQcow2ImageFormat; + + // goto the beginning of the disk image to read the disk image + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageQcow2.QCOW2_MAGIC ) { + isQcow2ImageFormat = true; + } else { + isQcow2ImageFormat = false; + } + + return isQcow2ImageFormat; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final long qcowBackingFileOffset = DiskImageUtils.readLong( diskFile, 8 ); + final boolean qcowStandalone; + + // check if QCOW2 image does not refer to any backing file + if ( qcowBackingFileOffset == 0 ) { + qcowStandalone = true; + } else { + qcowStandalone = false; + } + + return qcowStandalone; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final boolean qcowUseExtendedL2; + boolean qcowCompressed = false; + + // check if QCOW2 image uses extended L2 tables + // extended L2 tables are only possible in QCOW2 version 3 header format + if ( this.getVersion() >= 3 ) { + // read incompatible feature bits + final long qcowIncompatibleFeatures = DiskImageUtils.readLong( diskFile, 72 ); + + // support for extended L2 tables is enabled if bit 4 is set + qcowUseExtendedL2 = ( ( ( qcowIncompatibleFeatures & 0x000000000010 ) >>> 4 ) == 1 ); + } else { + qcowUseExtendedL2 = false; + } + + // get number of entries in L1 table + final int qcowL1TableSize = DiskImageUtils.readInt( diskFile, 36 ); + + // check if a valid L1 table is present + if ( qcowL1TableSize > 0 ) { + // QCOW2 image contains active L1 table with more than 0 entries: l1_size > 0 + // search for first L2 table and its first entry to get compression bit + + // get cluster bits to calculate the cluster size + final int qcowClusterBits = DiskImageUtils.readInt( diskFile, 20 ); + final int qcowClusterSize = ( 1 << qcowClusterBits ); + + // entries of a L1 table have always the size of 8 byte (64 bit) + final int qcowL1TableEntrySize = 8; + + // entries of a L2 table have either the size of 8 or 16 byte (64 or 128 bit) + final int qcowL2TableEntrySize = ( qcowUseExtendedL2 ) ? 16 : 8; + + // calculate number of L2 table entries + final int qcowL2TableSize = qcowClusterSize / qcowL2TableEntrySize; + + // get offset of L1 table + final long qcowL1TableOffset = DiskImageUtils.readLong( diskFile, 40 ); + + // check for each L2 table referenced from an L1 table its entries + // until a compressed cluster descriptor is found + for ( long i = 0; i < qcowL1TableSize; i++ ) { + // get offset of current L2 table from the current L1 table entry + final long qcowL1TableEntryOffset = qcowL1TableOffset + ( i * qcowL1TableEntrySize ); + final long qcowL1TableEntry = DiskImageUtils.readLong( diskFile, qcowL1TableEntryOffset ); + + // extract offset (bits 9 - 55) from L1 table entry + final long qcowL2TableOffset = ( qcowL1TableEntry & 0x00fffffffffffe00L ); + + if ( qcowL2TableOffset == 0 ) { + // L2 table and all clusters described by this L2 table are unallocated + continue; + } + + // get each L2 table entry and check if it is a compressed cluster descriptor + for ( long j = 0; j < qcowL2TableSize; j++ ) { + // get current L2 table entry + final long qcowL2TableEntryOffset = qcowL2TableOffset + ( j * qcowL2TableEntrySize ); + final long qcowL2TableEntry = DiskImageUtils.readLong( diskFile, qcowL2TableEntryOffset ); + + // extract cluster type (standard or compressed) (bit 62) + boolean qcowClusterCompressed = ( ( ( qcowL2TableEntry & 0x4000000000000000L ) >>> 62 ) == 1 ); + + // check if QCOW2 disk image contains at least one compressed cluster descriptor + if ( qcowClusterCompressed ) { + qcowCompressed = true; + break; + } + } + + // terminate if one compressed cluster descriptor is already found + if ( qcowCompressed ) { + break; + } + } + } else { + // QCOW2 image does not contain an active L1 table with any entry: l1_size = 0 + qcowCompressed = false; + } + + return qcowCompressed; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int qcowNumSnapshots = DiskImageUtils.readInt( diskFile, 56 ); + final boolean qcowSnapshot; + + // check if QCOW2 image contains at least one snapshot + if ( qcowNumSnapshots == 0 ) { + qcowSnapshot = true; + } else { + qcowSnapshot = false; + } + + return qcowSnapshot; + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int qcowVersion = DiskImageUtils.readInt( diskFile, 4 ); + + // check QCOW2 file format version + if ( qcowVersion < 2 || qcowVersion > 3 ) { + // QCOW2 disk image does not contain a valid QCOW2 version + final String errorMsg = new String( "Invalid QCOW2 version in header found!" ); + throw new DiskImageException( errorMsg ); + } + + return DiskImageUtils.versionFromMajor( Integer.valueOf( qcowVersion ).shortValue() ); + } + + @Override + public String getDescription() throws DiskImageException + { + // QCOW2 disk image format does not support any disk description + return null; + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.QCOW2; + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageUtils.java b/src/main/java/org/openslx/vm/disk/DiskImageUtils.java new file mode 100644 index 0000000..fbed6f9 --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageUtils.java @@ -0,0 +1,154 @@ +package org.openslx.vm.disk; + +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * Utilities to parse disk image format elements and control versions of disk images. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageUtils +{ + /** + * Returns the size of a specified disk image file. + * + * @param diskImage file to a disk storing the image content. + * @return size of the disk image file in bytes. + * + * @throws DiskImageException unable to obtain the size of the disk image file. + */ + public static long getImageSize( RandomAccessFile diskImage ) throws DiskImageException + { + final long imageSize; + + try { + imageSize = diskImage.length(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + + return imageSize; + } + + /** + * Reads two bytes ({@link Short}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the two bytes. + * @return value of the two bytes from the disk image file as {@link Short}. + * + * @throws DiskImageException unable to read two bytes from the disk image file. + */ + public static short readShort( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + short value = 0; + + if ( imageSize >= ( offset + Short.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readShort(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads four bytes ({@link Integer}) at a given offset from the specified disk + * image file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the four bytes. + * @return value of the four bytes from the disk image file as {@link Integer}. + * + * @throws DiskImageException unable to read four bytes from the disk image file. + */ + public static int readInt( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + int value = 0; + + if ( imageSize >= ( offset + Integer.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readInt(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads eight bytes ({@link Long}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the eight bytes. + * @return value of the eight bytes from the disk image file as {@link Long}. + * + * @throws DiskImageException unable to read eight bytes from the disk image file. + */ + public static long readLong( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + long value = 0; + + if ( imageSize >= ( offset + Long.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readLong(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads two bytes ({@link Short}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the two bytes. + * @return value of the two bytes from the disk image file as {@link Short}. + * + * @throws DiskImageException unable to read two bytes from the disk image file. + */ + public static String readBytesAsString( RandomAccessFile diskImage, long offset, int numBytes ) + throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + byte values[] = {}; + + if ( imageSize >= ( offset + numBytes ) ) { + try { + diskImage.seek( offset ); + values = new byte[ numBytes ]; + diskImage.readFully( values ); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return new String( values ); + } + + public static int versionFromMajorMinor( final short major, final short minor ) + { + return ( ( Integer.valueOf( major ) << 16 ) | minor ); + } + + public static int versionFromMajor( final short major ) + { + return DiskImageUtils.versionFromMajorMinor( major, Short.valueOf( "0" ) ); + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageVdi.java b/src/main/java/org/openslx/vm/disk/DiskImageVdi.java new file mode 100644 index 0000000..1c34c1d --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageVdi.java @@ -0,0 +1,119 @@ +package org.openslx.vm.disk; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * VDI disk image for virtual machines. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageVdi extends DiskImage +{ + /** + * Big endian representation of the little endian VDI magic bytes (signature). + */ + private static final int VDI_MAGIC = 0x7f10dabe; + + /** + * Creates a new VDI disk image from an existing VDI image file. + * + * @param diskImage file to a VDI disk storing the image content. + * + * @throws FileNotFoundException cannot find specified VDI disk image file. + * @throws IOException cannot access the content of the VDI disk image file. + */ + public DiskImageVdi( File diskImage ) throws FileNotFoundException, IOException + { + super( diskImage ); + } + + /** + * Creates a new VDI disk image from an existing VDI image file. + * + * @param diskImage file to a VDI disk storing the image content. + */ + public DiskImageVdi( RandomAccessFile diskImage ) + { + super( diskImage ); + } + + /** + * Probe specified file with unknown format to be a VDI disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a VDI disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isVdiImageFormat; + + // goto the beginning of the disk image to read the magic bytes + // skip first 64 bytes (opening tag) + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 64 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageVdi.VDI_MAGIC ) { + isVdiImageFormat = true; + } else { + isVdiImageFormat = false; + } + + return isVdiImageFormat; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + // VDI does not seem to support split VDI files, so VDI files are always standalone + return true; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + // compression is done by sparsifying the disk files, there is no flag for it + return false; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + + // if parent UUID is set, the VDI file is a snapshot + final String parentUuid = DiskImageUtils.readBytesAsString( diskFile, 440, 16 ); + final String zeroUuid = new String( new byte[ 16 ] ); + + return !zeroUuid.equals( parentUuid ); + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + + final short vdiVersionMajor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 68 ) ); + final short vdiVersionMinor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 70 ) ); + + return DiskImageUtils.versionFromMajorMinor( vdiVersionMajor, vdiVersionMinor ); + } + + @Override + public String getDescription() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + return DiskImageUtils.readBytesAsString( diskFile, 84, 256 ); + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.VDI; + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java b/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java new file mode 100644 index 0000000..c9bfdbf --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java @@ -0,0 +1,298 @@ +package org.openslx.vm.disk; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.openslx.util.Util; +import org.openslx.vm.UnsupportedVirtualizerFormatException; +import org.openslx.vm.VmwareConfig; + +/** + * VMDK (sparse extent) disk image for virtual machines. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageVmdk extends DiskImage +{ + /** + * Big endian representation of the little endian magic bytes KDMV. + */ + private static final int VMDK_MAGIC = 0x4b444d56; + + /** + * Size of a VMDK disk image data cluster in bytes. + */ + private static final int VMDK_SECTOR_SIZE = 512; + + /** + * Default hardware version of a VMDK disk image. + */ + private static final int VMDK_DEFAULT_HW_VERSION = 10; + + /** + * Stores disk configuration if VMDK disk image contains an embedded descriptor file. + */ + private final VmwareConfig vmdkConfig; + + /** + * Creates a new VMDK disk image from an existing VMDK image file. + * + * @param diskImage file to a VMDK disk storing the image content. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + * @throws FileNotFoundException cannot find specified VMDK disk image file. + * @throws IOException cannot access the content of the VMDK disk image file. + */ + public DiskImageVmdk( File diskImage ) throws DiskImageException, FileNotFoundException, IOException + { + super( diskImage ); + + this.vmdkConfig = this.parseVmdkConfig(); + } + + /** + * Creates a new VMDK disk image from an existing VMDK image file. + * + * @param diskImage file to a VMDK disk storing the image content. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + */ + public DiskImageVmdk( RandomAccessFile diskImage ) throws DiskImageException + { + super( diskImage ); + + this.vmdkConfig = this.parseVmdkConfig(); + } + + /** + * Probe specified file with unknown format to be a VMDK disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a VMDK disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isVmdkImageFormat; + + // goto the beginning of the disk image to read the magic bytes + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageVmdk.VMDK_MAGIC ) { + isVmdkImageFormat = true; + } else { + isVmdkImageFormat = false; + } + + return isVmdkImageFormat; + } + + /** + * Returns the creation type from the VMDK's embedded descriptor file. + * + * @return creation type from the VMDK's embedded descriptor file. + */ + private String getCreationType() + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final String vmdkCreationType; + + if ( vmdkConfig == null ) { + // VMDK disk image does not contain any descriptor file + // assume that the file is not stand alone + vmdkCreationType = null; + } else { + // VMDK disk image contains a descriptor file + // get creation type from the content of the descriptor file + vmdkCreationType = this.vmdkConfig.get( "createType" ); + } + + return vmdkCreationType; + } + + /** + * Parse the configuration of the VMDK's embedded descriptor file. + * + * @return parsed configuration of the VMDK's embedded descriptor file. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + */ + protected VmwareConfig parseVmdkConfig() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final VmwareConfig vmdkConfig; + + // get offset and size of descriptor file embedded into the VMDK disk image + final long vmdkDescriptorSectorOffset = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 28 ) ); + final long vmdkDescriptorSectorSize = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 36 ) ); + + if ( vmdkDescriptorSectorOffset > 0 ) { + // get content of descriptor file embedded into the VMDK disk image + final long vmdkDescriptorOffset = vmdkDescriptorSectorOffset * DiskImageVmdk.VMDK_SECTOR_SIZE; + final long vmdkDescriptorSizeMax = vmdkDescriptorSectorSize * DiskImageVmdk.VMDK_SECTOR_SIZE; + final String descriptorStr = DiskImageUtils.readBytesAsString( diskFile, vmdkDescriptorOffset, + Long.valueOf( vmdkDescriptorSizeMax ).intValue() ); + + // get final length of the content within the sectors to be able to trim all 'zero' characters + final int vmdkDescriptorSize = descriptorStr.indexOf( 0 ); + + // if final length of the content is invalid, throw an exception + if ( vmdkDescriptorSize > vmdkDescriptorSizeMax || vmdkDescriptorSize < 0 ) { + final String errorMsg = new String( "Embedded descriptor size in VMDK disk image is invalid!" ); + throw new DiskImageException( errorMsg ); + } + + // trim all 'zero' characters at the end of the descriptor content to avoid errors during parsing + final String configStr = descriptorStr.substring( 0, vmdkDescriptorSize ); + + // create configuration instance from content of the descriptor file + try { + vmdkConfig = new VmwareConfig( configStr.getBytes(), vmdkDescriptorSize ); + } catch ( UnsupportedVirtualizerFormatException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } else { + // there is no descriptor file embedded into the VMDK disk image + vmdkConfig = null; + } + + return vmdkConfig; + } + + /** + * Returns parsed configuration of the VMDK's embedded descriptor file. + * + * @return parsed configuration of the VMDK's embedded descriptor file. + */ + protected VmwareConfig getVmdkConfig() + { + return this.vmdkConfig; + } + + /** + * Returns the hardware version from the VMDK's embedded descriptor file. + * + * If the VMDK's embedded descriptor file does not contain any hardware version configuration + * entry, the default hardware version (see {@link #VMDK_DEFAULT_HW_VERSION}) is returned. + * + * @return hardware version from the VMDK's embedded descriptor file. + * + * @throws DiskImageException + */ + public int getHwVersion() throws DiskImageException + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final int hwVersion; + + if ( vmdkConfig != null ) { + // VMDK image contains a hardware version, so return parsed hardware version + // if hardware version cannot be parsed, return default hardware version + final String hwVersionStr = vmdkConfig.get( "ddb.virtualHWVersion" ); + + final int hwVersionMajor = Util.parseInt( hwVersionStr, DiskImageVmdk.VMDK_DEFAULT_HW_VERSION ); + hwVersion = DiskImageUtils.versionFromMajor( Integer.valueOf( hwVersionMajor ).shortValue() ); + } else { + // VMDK image does not contain any hardware version, so return default hardware version + final int hwVersionMajor = DiskImageVmdk.VMDK_DEFAULT_HW_VERSION; + hwVersion = DiskImageUtils.versionFromMajor( Integer.valueOf( hwVersionMajor ).shortValue() ); + } + + return hwVersion; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + final String vmdkCreationType = this.getCreationType(); + final boolean vmdkStandalone; + + if ( vmdkCreationType != null ) { + // creation type is defined, so check if VMDK disk image is a snapshot + if ( this.isSnapshot() ) { + // VMDK disk image is a snapshot and not stand alone + vmdkStandalone = false; + } else { + // VMDK disk image is not a snapshot + // determine stand alone disk image property + vmdkStandalone = vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) || + vmdkCreationType.equalsIgnoreCase( "monolithicSparse" ); + } + } else { + // creation type is not defined + // assume that the file is not stand alone + vmdkStandalone = false; + } + + return vmdkStandalone; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + final String vmdkCreationType = this.getCreationType(); + final boolean vmdkCompressed; + + if ( vmdkCreationType != null && vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ) { + // creation type is defined, and VMDK disk image is compressed + vmdkCompressed = true; + } else { + // creation type for compression is not defined + // assume that the file is not compressed + vmdkCompressed = false; + } + + return vmdkCompressed; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final boolean vmdkSnapshot; + + if ( vmdkConfig == null ) { + // VMDK disk image does not contain any descriptor file + // assume that the file is not a snapshot + vmdkSnapshot = false; + } else { + // get parent CID to determine snapshot disk image property + final String parentCid = vmdkConfig.get( "parentCID" ); + + if ( parentCid != null && !parentCid.equalsIgnoreCase( "ffffffff" ) ) { + // link to parent content identifier is defined, so VMDK disk image is a snapshot + vmdkSnapshot = true; + } else { + // link to parent content identifier is not defined, so VMDK disk image is not a snapshot + vmdkSnapshot = false; + } + } + + return vmdkSnapshot; + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int vmdkVersion = Integer.reverseBytes( DiskImageUtils.readInt( diskFile, 4 ) ); + + return DiskImageUtils.versionFromMajor( Integer.valueOf( vmdkVersion ).shortValue() ); + } + + @Override + public String getDescription() throws DiskImageException + { + return null; + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.VMDK; + } +} diff --git a/src/test/java/org/openslx/util/vm/DiskImageTest.java b/src/test/java/org/openslx/util/vm/DiskImageTest.java deleted file mode 100644 index e1105d8..0000000 --- a/src/test/java/org/openslx/util/vm/DiskImageTest.java +++ /dev/null @@ -1,260 +0,0 @@ -package org.openslx.util.vm; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.apache.log4j.Level; -import org.apache.log4j.LogManager; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.openslx.util.vm.DiskImage.ImageFormat; -import org.openslx.util.vm.DiskImage.UnknownImageFormatException; - -public class DiskImageTest -{ - @BeforeAll - public static void setUp() - { - // disable logging with log4j - LogManager.getRootLogger().setLevel( Level.OFF ); - } - - @Test - @DisplayName( "Test detection of VMDK disk image" ) - public void testVmdkDiskImage() throws FileNotFoundException, IOException, UnknownImageFormatException - { - File file = DiskImageTestResources.getDiskFile( "image-default.vmdk" ); - DiskImage image = new DiskImage( file ); - - assertEquals( ImageFormat.VMDK.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 18, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of VDI disk image" ) - public void testVdiDiskImage() throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image-default.vdi" ) ); - - assertEquals( ImageFormat.VDI.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 0, image.hwVersion ); - assertNotNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of default QCOW2 disk image" ) - public void testQcow2DiskImage() throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image-default.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 16384 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2Compressed16384DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-on_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 16384 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2Compressed16384DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-on_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 16384 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2NonCompressed16384DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-off_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 16384 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2NonCompressed16384DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-off_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 65536 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2Compressed65536DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-on_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 65536 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2Compressed65536DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-on_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 65536 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2NonCompressed65536DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-off_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 65536 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2NonCompressed65536DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-off_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 2097152 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2Compressed2097152DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-on_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of compressed, 2097152 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2Compressed2097152DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-on_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( true, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 2097152 byte cluster QCOW2 disk image with extended L2 tables" ) - public void testQcow2DetectionL2NonCompressed2097152DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-off_l2-on.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test detection of non-compressed, 2097152 byte cluster QCOW2 disk image without extended L2 tables" ) - public void testQcow2DetectionNonL2NonCompressed2097152DiskImage() - throws FileNotFoundException, IOException, UnknownImageFormatException - { - DiskImage image = new DiskImage( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-off_l2-off.qcow2" ) ); - - assertEquals( ImageFormat.QCOW2.toString(), image.getImageFormat().toString() ); - assertEquals( true, image.isStandalone ); - assertEquals( false, image.isSnapshot ); - assertEquals( false, image.isCompressed ); - assertEquals( 3, image.hwVersion ); - assertNull( image.diskDescription ); - } - - @Test - @DisplayName( "Test of invalid disk image" ) - public void testInvalidDiskImage() throws FileNotFoundException, IOException, UnknownImageFormatException - { - Assertions.assertThrows( UnknownImageFormatException.class, () -> { - new DiskImage( DiskImageTestResources.getDiskFile( "image-default.invalid" ) ); - } ); - } -} diff --git a/src/test/java/org/openslx/util/vm/DiskImageTestResources.java b/src/test/java/org/openslx/util/vm/DiskImageTestResources.java deleted file mode 100644 index 1f164bd..0000000 --- a/src/test/java/org/openslx/util/vm/DiskImageTestResources.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.openslx.util.vm; - -import java.io.File; -import java.net.URL; - -public class DiskImageTestResources -{ - private static final String DISK_PREFIX_PATH = File.separator + "disk"; - - public static File getDiskFile( String diskFileName ) - { - String diskPath = DiskImageTestResources.DISK_PREFIX_PATH + File.separator + diskFileName; - URL disk = DiskImageTestResources.class.getResource( diskPath ); - return new File( disk.getFile() ); - } -} diff --git a/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java b/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java deleted file mode 100644 index f201a77..0000000 --- a/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java +++ /dev/null @@ -1,466 +0,0 @@ -package org.openslx.util.vm; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; - -import org.apache.commons.io.FileUtils; -import org.apache.log4j.Level; -import org.apache.log4j.LogManager; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.openslx.libvirt.domain.Domain; -import org.openslx.libvirt.domain.device.ControllerUsb; -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.Interface; -import org.openslx.libvirt.domain.device.Sound; -import org.openslx.libvirt.xml.LibvirtXmlTestResources; -import org.openslx.util.vm.DiskImage.ImageFormat; -import org.openslx.util.vm.VmMetaData.EtherType; -import org.openslx.util.vm.VmMetaData.EthernetDevType; -import org.openslx.util.vm.VmMetaData.SoundCardType; -import org.openslx.util.vm.VmMetaData.UsbSpeed; - -public class QemuMetaDataTest -{ - private static Domain getPrivateDomainFromQemuMetaData( QemuMetaData qemuMetadata ) - throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException - { - Field privateDomainField = QemuMetaData.class.getDeclaredField( "vmConfig" ); - privateDomainField.setAccessible( true ); - return Domain.class.cast( privateDomainField.get( qemuMetadata ) ); - } - - @BeforeAll - public static void setUp() - { - // disable logging with log4j - LogManager.getRootLogger().setLevel( Level.OFF ); - } - - @Test - @DisplayName( "Test display name from VM configuration" ) - public void testQemuMetaDataGetDisplayName() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final String displayName = vmConfig.getDisplayName(); - - assertEquals( "archlinux", displayName ); - } - - @Test - @DisplayName( "Test machine snapshot state from VM configuration" ) - public void testQemuMetaDataIsMachineSnapshot() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final boolean isVmSnapshot = vmConfig.isMachineSnapshot(); - - assertEquals( false, isVmSnapshot ); - } - - @Test - @DisplayName( "Test supported image formats from VM configuration" ) - public void testQemuMetaDataGetSupportedImageFormats() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final List supportedImageFormats = vmConfig.getSupportedImageFormats(); - - assertNotNull( supportedImageFormats ); - assertEquals( 3, supportedImageFormats.size() ); - assertEquals( true, supportedImageFormats - .containsAll( Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ) ); - } - - @Test - @DisplayName( "Test output of HDDs from VM configuration" ) - public void testQemuMetaDataGetHdds() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final List hdds = vmConfig.getHdds(); - - assertNotNull( hdds ); - assertEquals( 1, hdds.size() ); - assertEquals( "/var/lib/libvirt/images/archlinux.qcow2", hdds.get( 0 ).diskImage ); - } - - @Test - @DisplayName( "Test output of unfiltered VM configuration" ) - public void testQemuMetaDataGetDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final String unfilteredXmlConfig = new String( vmConfig.getDefinitionArray(), StandardCharsets.UTF_8 ); - final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); - - assertNotNull( unfilteredXmlConfig ); - - final int lengthUnfilteredXmlConfig = unfilteredXmlConfig.split( System.lineSeparator() ).length; - final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; - - assertEquals( lengthOriginalXmlConfig, lengthUnfilteredXmlConfig ); - } - - @Test - @DisplayName( "Test output of filtered VM configuration" ) - public void testQemuMetaDataGetFilteredDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - final int numberOfDeletedElements = 4; - - final String filteredXmlConfig = new String( vmConfig.getFilteredDefinitionArray(), StandardCharsets.UTF_8 ); - final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); - - assertNotNull( filteredXmlConfig ); - - final int lengthFilteredXmlConfig = filteredXmlConfig.split( System.lineSeparator() ).length; - final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; - - assertEquals( lengthOriginalXmlConfig, lengthFilteredXmlConfig + numberOfDeletedElements ); - } - - @ParameterizedTest - @DisplayName( "Test add HDD to VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-hdd.xml" } ) - public void testQemuMetaDataAddHdd( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numHddsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); - final int numHddsQemuMetaDataBeforeAdd = vmConfig.hdds.size(); - - vmConfig.addHddTemplate( diskFile, null, null ); - - final int numHddsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); - final int numHddsQemuMetaDataAfterAdd = vmConfig.hdds.size(); - - assertTrue( numHddsLibvirtDomainXmlBeforeAdd == numHddsQemuMetaDataBeforeAdd ); - assertTrue( numHddsLibvirtDomainXmlAfterAdd == numHddsQemuMetaDataAfterAdd ); - assertTrue( numHddsQemuMetaDataBeforeAdd >= 0 ); - assertTrue( numHddsQemuMetaDataAfterAdd > 0 ); - - if ( numHddsQemuMetaDataBeforeAdd >= 1 ) { - // update existing HDD in the Libvirt XML config, but do not add a new HDD - assertEquals( numHddsQemuMetaDataBeforeAdd, numHddsQemuMetaDataAfterAdd ); - } else { - // numHddsQemuMetaDataBeforeAdd == 0 - // add a HDD to the Libvirt XML config, since there was no HDD available - assertEquals( numHddsQemuMetaDataBeforeAdd + 1, numHddsQemuMetaDataAfterAdd ); - } - - DiskStorage addedStorageDevice = vmLibvirtDomainConfig.getDiskStorageDevices().get( 0 ); - assertEquals( diskFile.getAbsolutePath(), addedStorageDevice.getStorageSource() ); - } - - @ParameterizedTest - @DisplayName( "Test add CDROM to VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) - public void testQemuMetaDataAddCdrom( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); - - vmConfig.addCdrom( 0, diskFile.getAbsolutePath() ); - - final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); - - assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); - - DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); - assertEquals( diskFile.getAbsolutePath(), addedCdromDevice.getStorageSource() ); - } - - @ParameterizedTest - @DisplayName( "Test add physical CDROM drive to VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) - public void testQemuMetaDataAddPhysicalCdromDrive( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); - - vmConfig.addCdrom( 0, null ); - - final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); - - assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); - - DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); - assertEquals( QemuMetaData.CDROM_DEFAULT_PHYSICAL_DRIVE, addedCdromDevice.getStorageSource() ); - } - - @ParameterizedTest - @DisplayName( "Test add floppy to VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-floppy.xml" } ) - public void testQemuMetaDataAddFloppy( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numFloppiesLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); - - vmConfig.addFloppy( 0, diskFile.getAbsolutePath(), true ); - - final int numFloppiesLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); - - assertTrue( numFloppiesLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numFloppiesLibvirtDomainXmlAfterAdd > 0 ); - - DiskFloppy addedFloppyDevice = vmLibvirtDomainConfig.getDiskFloppyDevices().get( 0 ); - assertTrue( addedFloppyDevice.isReadOnly() ); - assertEquals( diskFile.getAbsolutePath(), addedFloppyDevice.getStorageSource() ); - } - - @ParameterizedTest - @DisplayName( "Test add CPU core count to VM configuration" ) - @ValueSource( ints = { 2, 4, 6, 8 } ) - public void testQemuMetaDataAddCpuCoreCount( int coreCount ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - vmConfig.addCpuCoreCount( coreCount ); - - assertEquals( coreCount, vmLibvirtDomainConfig.getVCpu() ); - } - - @ParameterizedTest - @DisplayName( "Test get sound card from VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) - public void testQemuMetaDataGetSoundCardType( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - SoundCardType soundCardType = vmConfig.getSoundCard(); - - if ( vmLibvirtDomainConfig.getSoundDevices().isEmpty() ) { - assertEquals( SoundCardType.NONE, soundCardType ); - } else { - assertEquals( SoundCardType.HD_AUDIO, soundCardType ); - } - } - - @ParameterizedTest - @DisplayName( "Test set sound card in VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) - public void testQemuMetaDataSetSoundCardType( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numSoundDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getSoundDevices().size(); - - vmConfig.setSoundCard( SoundCardType.SOUND_BLASTER ); - - final int numSoundDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getSoundDevices().size(); - - assertTrue( numSoundDevsLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numSoundDevsLibvirtDomainXmlAfterAdd > 0 ); - - Sound addedSoundDevice = vmLibvirtDomainConfig.getSoundDevices().get( 0 ); - assertEquals( Sound.Model.SB16, addedSoundDevice.getModel() ); - } - - @ParameterizedTest - @DisplayName( "Test get ethernet device type from VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) - public void testQemuMetaDataGetEthernetDevType( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - EthernetDevType ethernetDeviceType = vmConfig.getEthernetDevType( 0 ); - - if ( vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { - assertEquals( EthernetDevType.NONE, ethernetDeviceType ); - } else { - assertEquals( EthernetDevType.PARAVIRT, ethernetDeviceType ); - } - } - - @ParameterizedTest - @DisplayName( "Test set ethernet device type in VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) - public void testQemuMetaDataSetEthernetDevType( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - vmConfig.setEthernetDevType( 0, EthernetDevType.E1000E ); - - if ( !vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { - Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); - assertEquals( Interface.Model.E1000E, addedEthernetDevice.getModel() ); - } - } - - @ParameterizedTest - @DisplayName( "Test get maximal USB speed from VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) - public void testQemuMetaDataGetMaxUsbSpeed( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - UsbSpeed maxUsbSpeed = vmConfig.getMaxUsbSpeed(); - - if ( vmLibvirtDomainConfig.getUsbControllerDevices().isEmpty() ) { - assertEquals( UsbSpeed.NONE, maxUsbSpeed ); - } else { - assertEquals( UsbSpeed.USB3_0, maxUsbSpeed ); - } - } - - @ParameterizedTest - @DisplayName( "Test set maximal USB speed in VM configuration" ) - @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) - public void testQemuMetaDataSetMaxUsbSpeed( String xmlFileName ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numUsbControllersLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); - - vmConfig.setMaxUsbSpeed( UsbSpeed.USB2_0 ); - - final int numUsbControllersLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); - - assertTrue( numUsbControllersLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numUsbControllersLibvirtDomainXmlAfterAdd > 0 ); - - ControllerUsb addedUsbControllerDevice = vmLibvirtDomainConfig.getUsbControllerDevices().get( 0 ); - assertEquals( ControllerUsb.Model.ICH9_EHCI1, addedUsbControllerDevice.getModel() ); - } - - static Stream configAndEthernetTypeProvider() - { - return Stream.of( - arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.BRIDGED ), - arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.HOST_ONLY ), - arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.NAT ), - arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.BRIDGED ), - arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.HOST_ONLY ), - arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.NAT ) ); - } - - @ParameterizedTest - @DisplayName( "Test add ethernet device to VM configuration" ) - @MethodSource( "configAndEthernetTypeProvider" ) - public void testQemuMetaDataAddEthernet( String xmlFileName, EtherType ethernetType ) - throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, - IllegalArgumentException, IllegalAccessException - { - File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); - QemuMetaData vmConfig = new QemuMetaData( null, file ); - - Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); - - final int numEthernetDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); - - vmConfig.addEthernet( ethernetType ); - - final int numEthernetDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); - - assertTrue( numEthernetDevsLibvirtDomainXmlBeforeAdd >= 0 ); - assertTrue( numEthernetDevsLibvirtDomainXmlAfterAdd > 0 ); - - Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); - switch ( ethernetType ) { - case BRIDGED: - assertEquals( Interface.Type.BRIDGE, addedEthernetDevice.getType() ); - assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); - assertEquals( QemuMetaData.NETWORK_DEFAULT_BRIDGE, addedEthernetDevice.getSource() ); - break; - case HOST_ONLY: - assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); - assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); - assertEquals( QemuMetaData.NETWORK_DEFAULT_HOST_ONLY, addedEthernetDevice.getSource() ); - break; - case NAT: - assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); - assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); - assertEquals( QemuMetaData.NETWORK_DEFAULT_NAT, addedEthernetDevice.getSource() ); - break; - } - } -} diff --git a/src/test/java/org/openslx/vm/QemuMetaDataTest.java b/src/test/java/org/openslx/vm/QemuMetaDataTest.java new file mode 100644 index 0000000..3217fda --- /dev/null +++ b/src/test/java/org/openslx/vm/QemuMetaDataTest.java @@ -0,0 +1,468 @@ +package org.openslx.vm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.ControllerUsb; +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.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.xml.LibvirtXmlTestResources; +import org.openslx.vm.VmMetaData.EtherType; +import org.openslx.vm.VmMetaData.EthernetDevType; +import org.openslx.vm.VmMetaData.SoundCardType; +import org.openslx.vm.VmMetaData.UsbSpeed; +import org.openslx.vm.disk.DiskImage; +import org.openslx.vm.disk.DiskImageTestResources; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +public class QemuMetaDataTest +{ + private static Domain getPrivateDomainFromQemuMetaData( QemuMetaData qemuMetadata ) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException + { + Field privateDomainField = QemuMetaData.class.getDeclaredField( "vmConfig" ); + privateDomainField.setAccessible( true ); + return Domain.class.cast( privateDomainField.get( qemuMetadata ) ); + } + + @BeforeAll + public static void setUp() + { + // disable logging with log4j + LogManager.getRootLogger().setLevel( Level.OFF ); + } + + @Test + @DisplayName( "Test display name from VM configuration" ) + public void testQemuMetaDataGetDisplayName() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final String displayName = vmConfig.getDisplayName(); + + assertEquals( "archlinux", displayName ); + } + + @Test + @DisplayName( "Test machine snapshot state from VM configuration" ) + public void testQemuMetaDataIsMachineSnapshot() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final boolean isVmSnapshot = vmConfig.isMachineSnapshot(); + + assertEquals( false, isVmSnapshot ); + } + + @Test + @DisplayName( "Test supported image formats from VM configuration" ) + public void testQemuMetaDataGetSupportedImageFormats() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final List supportedImageFormats = vmConfig.getSupportedImageFormats(); + + assertNotNull( supportedImageFormats ); + assertEquals( 3, supportedImageFormats.size() ); + assertEquals( true, supportedImageFormats + .containsAll( Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ) ); + } + + @Test + @DisplayName( "Test output of HDDs from VM configuration" ) + public void testQemuMetaDataGetHdds() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final List hdds = vmConfig.getHdds(); + + assertNotNull( hdds ); + assertEquals( 1, hdds.size() ); + assertEquals( "/var/lib/libvirt/images/archlinux.qcow2", hdds.get( 0 ).diskImage ); + } + + @Test + @DisplayName( "Test output of unfiltered VM configuration" ) + public void testQemuMetaDataGetDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final String unfilteredXmlConfig = new String( vmConfig.getDefinitionArray(), StandardCharsets.UTF_8 ); + final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); + + assertNotNull( unfilteredXmlConfig ); + + final int lengthUnfilteredXmlConfig = unfilteredXmlConfig.split( System.lineSeparator() ).length; + final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; + + assertEquals( lengthOriginalXmlConfig, lengthUnfilteredXmlConfig ); + } + + @Test + @DisplayName( "Test output of filtered VM configuration" ) + public void testQemuMetaDataGetFilteredDefinitionArray() throws UnsupportedVirtualizerFormatException, IOException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + final int numberOfDeletedElements = 4; + + final String filteredXmlConfig = new String( vmConfig.getFilteredDefinitionArray(), StandardCharsets.UTF_8 ); + final String originalXmlConfig = FileUtils.readFileToString( file, StandardCharsets.UTF_8 ); + + assertNotNull( filteredXmlConfig ); + + final int lengthFilteredXmlConfig = filteredXmlConfig.split( System.lineSeparator() ).length; + final int lengthOriginalXmlConfig = originalXmlConfig.split( System.lineSeparator() ).length; + + assertEquals( lengthOriginalXmlConfig, lengthFilteredXmlConfig + numberOfDeletedElements ); + } + + @ParameterizedTest + @DisplayName( "Test add HDD to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-hdd.xml" } ) + public void testQemuMetaDataAddHdd( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numHddsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); + final int numHddsQemuMetaDataBeforeAdd = vmConfig.hdds.size(); + + vmConfig.addHddTemplate( diskFile, null, null ); + + final int numHddsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskStorageDevices().size(); + final int numHddsQemuMetaDataAfterAdd = vmConfig.hdds.size(); + + assertTrue( numHddsLibvirtDomainXmlBeforeAdd == numHddsQemuMetaDataBeforeAdd ); + assertTrue( numHddsLibvirtDomainXmlAfterAdd == numHddsQemuMetaDataAfterAdd ); + assertTrue( numHddsQemuMetaDataBeforeAdd >= 0 ); + assertTrue( numHddsQemuMetaDataAfterAdd > 0 ); + + if ( numHddsQemuMetaDataBeforeAdd >= 1 ) { + // update existing HDD in the Libvirt XML config, but do not add a new HDD + assertEquals( numHddsQemuMetaDataBeforeAdd, numHddsQemuMetaDataAfterAdd ); + } else { + // numHddsQemuMetaDataBeforeAdd == 0 + // add a HDD to the Libvirt XML config, since there was no HDD available + assertEquals( numHddsQemuMetaDataBeforeAdd + 1, numHddsQemuMetaDataAfterAdd ); + } + + DiskStorage addedStorageDevice = vmLibvirtDomainConfig.getDiskStorageDevices().get( 0 ); + assertEquals( diskFile.getAbsolutePath(), addedStorageDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add CDROM to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) + public void testQemuMetaDataAddCdrom( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + vmConfig.addCdrom( 0, diskFile.getAbsolutePath() ); + + final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); + + DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); + assertEquals( diskFile.getAbsolutePath(), addedCdromDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add physical CDROM drive to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-cdrom.xml" } ) + public void testQemuMetaDataAddPhysicalCdromDrive( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numCdromsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + vmConfig.addCdrom( 0, null ); + + final int numCdromsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskCdromDevices().size(); + + assertTrue( numCdromsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numCdromsLibvirtDomainXmlAfterAdd > 0 ); + + DiskCdrom addedCdromDevice = vmLibvirtDomainConfig.getDiskCdromDevices().get( 0 ); + assertEquals( QemuMetaData.CDROM_DEFAULT_PHYSICAL_DRIVE, addedCdromDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add floppy to VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-floppy.xml" } ) + public void testQemuMetaDataAddFloppy( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File diskFile = DiskImageTestResources.getDiskFile( "image-default.qcow2" ); + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numFloppiesLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); + + vmConfig.addFloppy( 0, diskFile.getAbsolutePath(), true ); + + final int numFloppiesLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getDiskFloppyDevices().size(); + + assertTrue( numFloppiesLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numFloppiesLibvirtDomainXmlAfterAdd > 0 ); + + DiskFloppy addedFloppyDevice = vmLibvirtDomainConfig.getDiskFloppyDevices().get( 0 ); + assertTrue( addedFloppyDevice.isReadOnly() ); + assertEquals( diskFile.getAbsolutePath(), addedFloppyDevice.getStorageSource() ); + } + + @ParameterizedTest + @DisplayName( "Test add CPU core count to VM configuration" ) + @ValueSource( ints = { 2, 4, 6, 8 } ) + public void testQemuMetaDataAddCpuCoreCount( int coreCount ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( "qemu-kvm_default-archlinux-vm.xml" ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + vmConfig.addCpuCoreCount( coreCount ); + + assertEquals( coreCount, vmLibvirtDomainConfig.getVCpu() ); + } + + @ParameterizedTest + @DisplayName( "Test get sound card from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) + public void testQemuMetaDataGetSoundCardType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + SoundCardType soundCardType = vmConfig.getSoundCard(); + + if ( vmLibvirtDomainConfig.getSoundDevices().isEmpty() ) { + assertEquals( SoundCardType.NONE, soundCardType ); + } else { + assertEquals( SoundCardType.HD_AUDIO, soundCardType ); + } + } + + @ParameterizedTest + @DisplayName( "Test set sound card in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-sound.xml" } ) + public void testQemuMetaDataSetSoundCardType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numSoundDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getSoundDevices().size(); + + vmConfig.setSoundCard( SoundCardType.SOUND_BLASTER ); + + final int numSoundDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getSoundDevices().size(); + + assertTrue( numSoundDevsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numSoundDevsLibvirtDomainXmlAfterAdd > 0 ); + + Sound addedSoundDevice = vmLibvirtDomainConfig.getSoundDevices().get( 0 ); + assertEquals( Sound.Model.SB16, addedSoundDevice.getModel() ); + } + + @ParameterizedTest + @DisplayName( "Test get ethernet device type from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) + public void testQemuMetaDataGetEthernetDevType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + EthernetDevType ethernetDeviceType = vmConfig.getEthernetDevType( 0 ); + + if ( vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { + assertEquals( EthernetDevType.NONE, ethernetDeviceType ); + } else { + assertEquals( EthernetDevType.PARAVIRT, ethernetDeviceType ); + } + } + + @ParameterizedTest + @DisplayName( "Test set ethernet device type in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-nic.xml" } ) + public void testQemuMetaDataSetEthernetDevType( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + vmConfig.setEthernetDevType( 0, EthernetDevType.E1000E ); + + if ( !vmLibvirtDomainConfig.getInterfaceDevices().isEmpty() ) { + Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); + assertEquals( Interface.Model.E1000E, addedEthernetDevice.getModel() ); + } + } + + @ParameterizedTest + @DisplayName( "Test get maximal USB speed from VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) + public void testQemuMetaDataGetMaxUsbSpeed( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + UsbSpeed maxUsbSpeed = vmConfig.getMaxUsbSpeed(); + + if ( vmLibvirtDomainConfig.getUsbControllerDevices().isEmpty() ) { + assertEquals( UsbSpeed.NONE, maxUsbSpeed ); + } else { + assertEquals( UsbSpeed.USB3_0, maxUsbSpeed ); + } + } + + @ParameterizedTest + @DisplayName( "Test set maximal USB speed in VM configuration" ) + @ValueSource( strings = { "qemu-kvm_default-archlinux-vm.xml", "qemu-kvm_default-archlinux-vm-no-usb.xml" } ) + public void testQemuMetaDataSetMaxUsbSpeed( String xmlFileName ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numUsbControllersLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); + + vmConfig.setMaxUsbSpeed( UsbSpeed.USB2_0 ); + + final int numUsbControllersLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getUsbControllerDevices().size(); + + assertTrue( numUsbControllersLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numUsbControllersLibvirtDomainXmlAfterAdd > 0 ); + + ControllerUsb addedUsbControllerDevice = vmLibvirtDomainConfig.getUsbControllerDevices().get( 0 ); + assertEquals( ControllerUsb.Model.ICH9_EHCI1, addedUsbControllerDevice.getModel() ); + } + + static Stream configAndEthernetTypeProvider() + { + return Stream.of( + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.BRIDGED ), + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.HOST_ONLY ), + arguments( "qemu-kvm_default-archlinux-vm.xml", EtherType.NAT ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.BRIDGED ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.HOST_ONLY ), + arguments( "qemu-kvm_default-archlinux-vm-no-usb.xml", EtherType.NAT ) ); + } + + @ParameterizedTest + @DisplayName( "Test add ethernet device to VM configuration" ) + @MethodSource( "configAndEthernetTypeProvider" ) + public void testQemuMetaDataAddEthernet( String xmlFileName, EtherType ethernetType ) + throws UnsupportedVirtualizerFormatException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException + { + File file = LibvirtXmlTestResources.getLibvirtXmlFile( xmlFileName ); + QemuMetaData vmConfig = new QemuMetaData( null, file ); + + Domain vmLibvirtDomainConfig = QemuMetaDataTest.getPrivateDomainFromQemuMetaData( vmConfig ); + + final int numEthernetDevsLibvirtDomainXmlBeforeAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); + + vmConfig.addEthernet( ethernetType ); + + final int numEthernetDevsLibvirtDomainXmlAfterAdd = vmLibvirtDomainConfig.getInterfaceDevices().size(); + + assertTrue( numEthernetDevsLibvirtDomainXmlBeforeAdd >= 0 ); + assertTrue( numEthernetDevsLibvirtDomainXmlAfterAdd > 0 ); + + Interface addedEthernetDevice = vmLibvirtDomainConfig.getInterfaceDevices().get( 0 ); + switch ( ethernetType ) { + case BRIDGED: + assertEquals( Interface.Type.BRIDGE, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_BRIDGE, addedEthernetDevice.getSource() ); + break; + case HOST_ONLY: + assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_HOST_ONLY, addedEthernetDevice.getSource() ); + break; + case NAT: + assertEquals( Interface.Type.NETWORK, addedEthernetDevice.getType() ); + assertEquals( Interface.Model.VIRTIO, addedEthernetDevice.getModel() ); + assertEquals( QemuMetaData.NETWORK_DEFAULT_NAT, addedEthernetDevice.getSource() ); + break; + } + } +} diff --git a/src/test/java/org/openslx/vm/disk/DiskImageQcow2Test.java b/src/test/java/org/openslx/vm/disk/DiskImageQcow2Test.java new file mode 100644 index 0000000..530cd60 --- /dev/null +++ b/src/test/java/org/openslx/vm/disk/DiskImageQcow2Test.java @@ -0,0 +1,220 @@ +package org.openslx.vm.disk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +public class DiskImageQcow2Test +{ + @Test + @DisplayName( "Test detection of default QCOW2 disk image" ) + public void testQcow2DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image-default.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 16384 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2Compressed16384DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-on_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 16384 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2Compressed16384DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-on_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 16384 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2NonCompressed16384DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-off_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 16384 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2NonCompressed16384DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-16384_cp-off_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 65536 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2Compressed65536DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-on_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 65536 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2Compressed65536DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-on_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 65536 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2NonCompressed65536DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-off_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 65536 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2NonCompressed65536DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-65536_cp-off_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 2097152 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2Compressed2097152DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-on_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of compressed, 2097152 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2Compressed2097152DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-on_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 2097152 byte cluster QCOW2 disk image with extended L2 tables" ) + public void testQcow2DetectionL2NonCompressed2097152DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-off_l2-on.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of non-compressed, 2097152 byte cluster QCOW2 disk image without extended L2 tables" ) + public void testQcow2DetectionNonL2NonCompressed2097152DiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage + .newInstance( DiskImageTestResources.getDiskFile( "image_cs-2097152_cp-off_l2-off.qcow2" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + + assertEquals( ImageFormat.QCOW2.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + } +} diff --git a/src/test/java/org/openslx/vm/disk/DiskImageTest.java b/src/test/java/org/openslx/vm/disk/DiskImageTest.java new file mode 100644 index 0000000..2572c58 --- /dev/null +++ b/src/test/java/org/openslx/vm/disk/DiskImageTest.java @@ -0,0 +1,19 @@ +package org.openslx.vm.disk; + +import java.io.IOException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class DiskImageTest +{ + @Test + @DisplayName( "Test of invalid disk image" ) + public void testInvalidDiskImage() throws IOException + { + Assertions.assertThrows( DiskImageException.class, () -> { + DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image-default.invalid" ) ); + } ); + } +} diff --git a/src/test/java/org/openslx/vm/disk/DiskImageTestResources.java b/src/test/java/org/openslx/vm/disk/DiskImageTestResources.java new file mode 100644 index 0000000..2ec2e05 --- /dev/null +++ b/src/test/java/org/openslx/vm/disk/DiskImageTestResources.java @@ -0,0 +1,16 @@ +package org.openslx.vm.disk; + +import java.io.File; +import java.net.URL; + +public class DiskImageTestResources +{ + private static final String DISK_PREFIX_PATH = File.separator + "disk"; + + public static File getDiskFile( String diskFileName ) + { + String diskPath = DiskImageTestResources.DISK_PREFIX_PATH + File.separator + diskFileName; + URL disk = DiskImageTestResources.class.getResource( diskPath ); + return new File( disk.getFile() ); + } +} diff --git a/src/test/java/org/openslx/vm/disk/DiskImageVdiTest.java b/src/test/java/org/openslx/vm/disk/DiskImageVdiTest.java new file mode 100644 index 0000000..492c6aa --- /dev/null +++ b/src/test/java/org/openslx/vm/disk/DiskImageVdiTest.java @@ -0,0 +1,43 @@ +package org.openslx.vm.disk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +public class DiskImageVdiTest +{ + @Test + @DisplayName( "Test detection of default VDI disk image" ) + public void testVdiDiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image-default.vdi" ) ); + final int imageVersion = DiskImageUtils.versionFromMajorMinor( Short.valueOf( "1" ), Short.valueOf( "1" ) ); + + assertEquals( ImageFormat.VDI.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNotNull( image.getDescription() ); + } + + @Test + @DisplayName( "Test detection of VDI disk image snapshot" ) + public void testVdiDiskImageSnapshot() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image-default_snapshot.vdi" ) ); + final int imageVersion = DiskImageUtils.versionFromMajorMinor( Short.valueOf( "1" ), Short.valueOf( "1" ) ); + + assertEquals( ImageFormat.VDI.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( true, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNotNull( image.getDescription() ); + } +} diff --git a/src/test/java/org/openslx/vm/disk/DiskImageVmdkTest.java b/src/test/java/org/openslx/vm/disk/DiskImageVmdkTest.java new file mode 100644 index 0000000..00cf561 --- /dev/null +++ b/src/test/java/org/openslx/vm/disk/DiskImageVmdkTest.java @@ -0,0 +1,110 @@ +package org.openslx.vm.disk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +public class DiskImageVmdkTest +{ + @Test + @DisplayName( "Test detection of default VMDK disk image" ) + public void testVmdkDiskImage() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image-default.vmdk" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "1" ) ); + final int imageHwVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "18" ) ); + + assertEquals( ImageFormat.VMDK.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + + // test special features of the VMDK disk image format + final DiskImageVmdk vmdkImage = DiskImageVmdk.class.cast( image ); + assertEquals( imageHwVersion, vmdkImage.getHwVersion() ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 0: single growable virtual disk)" ) + public void testVmdkDiskImageType0() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t0.vmdk" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "1" ) ); + final int imageHwVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "18" ) ); + + assertEquals( ImageFormat.VMDK.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( false, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + + // test special features of the VMDK disk image format + final DiskImageVmdk vmdkImage = DiskImageVmdk.class.cast( image ); + assertEquals( imageHwVersion, vmdkImage.getHwVersion() ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 1: growable virtual disk split into multiple files)" ) + public void testVmdkDiskImageType1() throws DiskImageException, IOException + { + Assertions.assertThrows( DiskImageException.class, () -> { + DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t1.vmdk" ) ); + } ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 2: preallocated virtual disk)" ) + public void testVmdkDiskImageType2() throws DiskImageException, IOException + { + Assertions.assertThrows( DiskImageException.class, () -> { + DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t2.vmdk" ) ); + } ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 3: preallocated virtual disk split into multiple files)" ) + public void testVmdkDiskImageType3() throws DiskImageException, IOException + { + Assertions.assertThrows( DiskImageException.class, () -> { + DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t3.vmdk" ) ); + } ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 4: preallocated ESX-type virtual disk)" ) + public void testVmdkDiskImageType4() throws DiskImageException, IOException + { + Assertions.assertThrows( DiskImageException.class, () -> { + DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t4.vmdk" ) ); + } ); + } + + @Test + @DisplayName( "Test detection of VMDK disk image (type 5: compressed disk optimized for streaming)" ) + public void testVmdkDiskImageType5() throws DiskImageException, IOException + { + final DiskImage image = DiskImage.newInstance( DiskImageTestResources.getDiskFile( "image_t5.vmdk" ) ); + final int imageVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "3" ) ); + final int imageHwVersion = DiskImageUtils.versionFromMajor( Short.valueOf( "18" ) ); + + assertEquals( ImageFormat.VMDK.toString(), image.getFormat().toString() ); + assertEquals( true, image.isStandalone() ); + assertEquals( false, image.isSnapshot() ); + assertEquals( true, image.isCompressed() ); + assertEquals( imageVersion, image.getVersion() ); + assertNull( image.getDescription() ); + + // test special features of the VMDK disk image format + final DiskImageVmdk vmdkImage = DiskImageVmdk.class.cast( image ); + assertEquals( imageHwVersion, vmdkImage.getHwVersion() ); + } +} diff --git a/src/test/resources/disk/image-default_snapshot.vdi b/src/test/resources/disk/image-default_snapshot.vdi new file mode 100644 index 0000000..f07502a Binary files /dev/null and b/src/test/resources/disk/image-default_snapshot.vdi differ diff --git a/src/test/resources/disk/image_t0.vmdk b/src/test/resources/disk/image_t0.vmdk new file mode 100644 index 0000000..08047a0 Binary files /dev/null and b/src/test/resources/disk/image_t0.vmdk differ diff --git a/src/test/resources/disk/image_t1-s001.vmdk b/src/test/resources/disk/image_t1-s001.vmdk new file mode 100644 index 0000000..a3ce425 Binary files /dev/null and b/src/test/resources/disk/image_t1-s001.vmdk differ diff --git a/src/test/resources/disk/image_t1.vmdk b/src/test/resources/disk/image_t1.vmdk new file mode 100644 index 0000000..7f780c2 --- /dev/null +++ b/src/test/resources/disk/image_t1.vmdk @@ -0,0 +1,21 @@ +# Disk DescriptorFile +version=1 +CID=f879aea6 +parentCID=ffffffff +createType="twoGbMaxExtentSparse" + +# Extent description +RW 20480 SPARSE "image_t1-s001.vmdk" + +# The Disk Data Base +#DDB + +ddb.adapterType = "ide" +ddb.deletable = "true" +ddb.encoding = "UTF-8" +ddb.geometry.cylinders = "20" +ddb.geometry.heads = "16" +ddb.geometry.sectors = "63" +ddb.longContentID = "a586da03e45ef91444741b5e03dd9850" +ddb.uuid = "60 00 C2 95 f2 fc 95 8b-59 71 f0 58 a4 63 3d d9" +ddb.virtualHWVersion = "18" diff --git a/src/test/resources/disk/image_t2-flat.vmdk b/src/test/resources/disk/image_t2-flat.vmdk new file mode 100644 index 0000000..3cfe668 Binary files /dev/null and b/src/test/resources/disk/image_t2-flat.vmdk differ diff --git a/src/test/resources/disk/image_t2.vmdk b/src/test/resources/disk/image_t2.vmdk new file mode 100644 index 0000000..2907c8d --- /dev/null +++ b/src/test/resources/disk/image_t2.vmdk @@ -0,0 +1,21 @@ +# Disk DescriptorFile +version=1 +CID=f879aea6 +parentCID=ffffffff +createType="monolithicFlat" + +# Extent description +RW 20480 FLAT "image_t2-flat.vmdk" 0 + +# The Disk Data Base +#DDB + +ddb.adapterType = "ide" +ddb.deletable = "true" +ddb.encoding = "UTF-8" +ddb.geometry.cylinders = "20" +ddb.geometry.heads = "16" +ddb.geometry.sectors = "63" +ddb.longContentID = "b104a6d8dbe1d6adfe5c3c2422159584" +ddb.uuid = "60 00 C2 91 13 fb 32 ae-b1 94 6c 90 7e a7 dc fd" +ddb.virtualHWVersion = "18" diff --git a/src/test/resources/disk/image_t3-f001.vmdk b/src/test/resources/disk/image_t3-f001.vmdk new file mode 100644 index 0000000..3cfe668 Binary files /dev/null and b/src/test/resources/disk/image_t3-f001.vmdk differ diff --git a/src/test/resources/disk/image_t3.vmdk b/src/test/resources/disk/image_t3.vmdk new file mode 100644 index 0000000..3a9755a --- /dev/null +++ b/src/test/resources/disk/image_t3.vmdk @@ -0,0 +1,21 @@ +# Disk DescriptorFile +version=1 +CID=f879aea6 +parentCID=ffffffff +createType="twoGbMaxExtentFlat" + +# Extent description +RW 20480 FLAT "image_t3-f001.vmdk" 0 + +# The Disk Data Base +#DDB + +ddb.adapterType = "ide" +ddb.deletable = "true" +ddb.encoding = "UTF-8" +ddb.geometry.cylinders = "20" +ddb.geometry.heads = "16" +ddb.geometry.sectors = "63" +ddb.longContentID = "1d2f0fc53996e4dd85d6348a9cb54a70" +ddb.uuid = "60 00 C2 92 6f 18 ff eb-66 28 54 8e f4 fb 0d e6" +ddb.virtualHWVersion = "18" diff --git a/src/test/resources/disk/image_t4-flat.vmdk b/src/test/resources/disk/image_t4-flat.vmdk new file mode 100644 index 0000000..3cfe668 Binary files /dev/null and b/src/test/resources/disk/image_t4-flat.vmdk differ diff --git a/src/test/resources/disk/image_t4.vmdk b/src/test/resources/disk/image_t4.vmdk new file mode 100644 index 0000000..c9b56db --- /dev/null +++ b/src/test/resources/disk/image_t4.vmdk @@ -0,0 +1,22 @@ +# Disk DescriptorFile +version=1 +CID=f879aea6 +parentCID=ffffffff +createType="vmfs" + +# Extent description +RW 20480 VMFS "image_t4-flat.vmdk" 0 + +# The Disk Data Base +#DDB + +ddb.adapterType = "ide" +ddb.deletable = "true" +ddb.encoding = "UTF-8" +ddb.geometry.cylinders = "20" +ddb.geometry.heads = "16" +ddb.geometry.sectors = "63" +ddb.longContentID = "c1c7f78748a6d9a7f9233c312a8362c6" +ddb.thinProvisioned = "1" +ddb.uuid = "60 00 C2 9a 0f 63 7a 30-b4 d5 1c 32 b3 3c e6 bb" +ddb.virtualHWVersion = "18" diff --git a/src/test/resources/disk/image_t5.vmdk b/src/test/resources/disk/image_t5.vmdk new file mode 100644 index 0000000..318ab09 Binary files /dev/null and b/src/test/resources/disk/image_t5.vmdk differ -- cgit v1.2.3-55-g7522