summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManuel Bentele2021-02-25 15:00:38 +0100
committerManuel Bentele2021-03-10 15:05:56 +0100
commitbe40e979e03e41ddcd831d9c330902f76908ca64 (patch)
tree0b5d66d2e01bfb7b96c76170db788b5f36fd8b2d
parent[vmware] Stop creating 'null.present = "TRUE"' entries (diff)
downloadmaster-sync-shared-be40e979e03e41ddcd831d9c330902f76908ca64.tar.gz
master-sync-shared-be40e979e03e41ddcd831d9c330902f76908ca64.tar.xz
master-sync-shared-be40e979e03e41ddcd831d9c330902f76908ca64.zip
Refactor disk image representation and add unit tests
-rw-r--r--src/main/java/org/openslx/util/ThriftUtil.java2
-rw-r--r--src/main/java/org/openslx/util/vm/DiskImage.java379
-rw-r--r--src/main/java/org/openslx/vm/DockerMetaDataDummy.java (renamed from src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java)7
-rw-r--r--src/main/java/org/openslx/vm/KeyValuePair.java (renamed from src/main/java/org/openslx/util/vm/KeyValuePair.java)2
-rw-r--r--src/main/java/org/openslx/vm/QemuMetaData.java (renamed from src/main/java/org/openslx/util/vm/QemuMetaData.java)5
-rw-r--r--src/main/java/org/openslx/vm/QemuMetaDataUtils.java (renamed from src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java)8
-rw-r--r--src/main/java/org/openslx/vm/UnsupportedVirtualizerFormatException.java (renamed from src/main/java/org/openslx/util/vm/UnsupportedVirtualizerFormatException.java)2
-rw-r--r--src/main/java/org/openslx/vm/VboxConfig.java (renamed from src/main/java/org/openslx/util/vm/VboxConfig.java)6
-rw-r--r--src/main/java/org/openslx/vm/VboxMetaData.java (renamed from src/main/java/org/openslx/util/vm/VboxMetaData.java)9
-rw-r--r--src/main/java/org/openslx/vm/VmMetaData.java (renamed from src/main/java/org/openslx/util/vm/VmMetaData.java)3
-rw-r--r--src/main/java/org/openslx/vm/VmwareConfig.java (renamed from src/main/java/org/openslx/util/vm/VmwareConfig.java)2
-rw-r--r--src/main/java/org/openslx/vm/VmwareMetaData.java (renamed from src/main/java/org/openslx/util/vm/VmwareMetaData.java)7
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImage.java251
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImageException.java25
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImageQcow2.java246
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImageUtils.java154
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImageVdi.java119
-rw-r--r--src/main/java/org/openslx/vm/disk/DiskImageVmdk.java298
-rw-r--r--src/test/java/org/openslx/util/vm/DiskImageTest.java260
-rw-r--r--src/test/java/org/openslx/vm/QemuMetaDataTest.java (renamed from src/test/java/org/openslx/util/vm/QemuMetaDataTest.java)14
-rw-r--r--src/test/java/org/openslx/vm/disk/DiskImageQcow2Test.java220
-rw-r--r--src/test/java/org/openslx/vm/disk/DiskImageTest.java19
-rw-r--r--src/test/java/org/openslx/vm/disk/DiskImageTestResources.java (renamed from src/test/java/org/openslx/util/vm/DiskImageTestResources.java)2
-rw-r--r--src/test/java/org/openslx/vm/disk/DiskImageVdiTest.java43
-rw-r--r--src/test/java/org/openslx/vm/disk/DiskImageVmdkTest.java110
-rw-r--r--src/test/resources/disk/image-default_snapshot.vdibin0 -> 2097152 bytes
-rw-r--r--src/test/resources/disk/image_t0.vmdkbin0 -> 262144 bytes
-rw-r--r--src/test/resources/disk/image_t1-s001.vmdkbin0 -> 262144 bytes
-rw-r--r--src/test/resources/disk/image_t1.vmdk21
-rw-r--r--src/test/resources/disk/image_t2-flat.vmdkbin0 -> 10485760 bytes
-rw-r--r--src/test/resources/disk/image_t2.vmdk21
-rw-r--r--src/test/resources/disk/image_t3-f001.vmdkbin0 -> 10485760 bytes
-rw-r--r--src/test/resources/disk/image_t3.vmdk21
-rw-r--r--src/test/resources/disk/image_t4-flat.vmdkbin0 -> 10485760 bytes
-rw-r--r--src/test/resources/disk/image_t4.vmdk22
-rw-r--r--src/test/resources/disk/image_t5.vmdkbin0 -> 67584 bytes
36 files changed, 1608 insertions, 670 deletions
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
- // - <unused>
- // - 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<ImageFormat> 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 <code>true</code> if image type is supported by the hypervisor; otherwise
- * <code>false</code>.
- */
- public boolean isSupportedByHypervisor( List<ImageFormat> supportedImageTypes )
- {
- Predicate<ImageFormat> 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/vm/DockerMetaDataDummy.java
index 38388ce..73cd92e 100644
--- a/src/main/java/org/openslx/util/vm/DockerMetaDataDummy.java
+++ b/src/main/java/org/openslx/vm/DockerMetaDataDummy.java
@@ -1,11 +1,12 @@
-package org.openslx.util.vm;
+package org.openslx.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 org.openslx.vm.disk.DiskImage;
+import org.openslx.vm.disk.DiskImage.ImageFormat;
import java.io.BufferedInputStream;
import java.io.File;
@@ -41,7 +42,7 @@ public class DockerMetaDataDummy extends VmMetaData<DockerSoundCardMeta, DockerD
* List of supported image formats by the Docker hypervisor.
*/
private static final List<DiskImage.ImageFormat> SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList(
- Arrays.asList( ImageFormat.DOCKER ) );
+ Arrays.asList( ImageFormat.NONE ) );
private static final Logger LOGGER = Logger.getLogger( DockerMetaDataDummy.class);
diff --git a/src/main/java/org/openslx/util/vm/KeyValuePair.java b/src/main/java/org/openslx/vm/KeyValuePair.java
index d89d51b..c5650ec 100644
--- a/src/main/java/org/openslx/util/vm/KeyValuePair.java
+++ b/src/main/java/org/openslx/vm/KeyValuePair.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
class KeyValuePair
{
diff --git a/src/main/java/org/openslx/util/vm/QemuMetaData.java b/src/main/java/org/openslx/vm/QemuMetaData.java
index d3b8451..c780429 100644
--- a/src/main/java/org/openslx/util/vm/QemuMetaData.java
+++ b/src/main/java/org/openslx/vm/QemuMetaData.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.File;
import java.math.BigInteger;
@@ -27,7 +27,8 @@ 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;
+import org.openslx.vm.disk.DiskImage;
+import org.openslx.vm.disk.DiskImage.ImageFormat;
/**
* Metadata to describe the hardware type of a QEMU sound card.
diff --git a/src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java b/src/main/java/org/openslx/vm/QemuMetaDataUtils.java
index 42c3fb6..b6ac92e 100644
--- a/src/main/java/org/openslx/util/vm/QemuMetaDataUtils.java
+++ b/src/main/java/org/openslx/vm/QemuMetaDataUtils.java
@@ -1,14 +1,14 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.util.ArrayList;
import org.openslx.libvirt.domain.device.Disk;
import org.openslx.libvirt.domain.device.Interface;
import org.openslx.libvirt.domain.device.Disk.BusType;
+import org.openslx.vm.VmMetaData.DriveBusType;
+import org.openslx.vm.VmMetaData.EthernetDevType;
+import org.openslx.vm.VmMetaData.SoundCardType;
import org.openslx.libvirt.domain.device.Sound;
-import org.openslx.util.vm.VmMetaData.DriveBusType;
-import org.openslx.util.vm.VmMetaData.EthernetDevType;
-import org.openslx.util.vm.VmMetaData.SoundCardType;
/**
* Collection of utils to convert data types from bwLehrpool to Libvirt and vice versa.
diff --git a/src/main/java/org/openslx/util/vm/UnsupportedVirtualizerFormatException.java b/src/main/java/org/openslx/vm/UnsupportedVirtualizerFormatException.java
index 08c9673..f19b1ff 100644
--- a/src/main/java/org/openslx/util/vm/UnsupportedVirtualizerFormatException.java
+++ b/src/main/java/org/openslx/vm/UnsupportedVirtualizerFormatException.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
@SuppressWarnings( "serial" )
public class UnsupportedVirtualizerFormatException extends Exception
diff --git a/src/main/java/org/openslx/util/vm/VboxConfig.java b/src/main/java/org/openslx/vm/VboxConfig.java
index f405991..9724b6a 100644
--- a/src/main/java/org/openslx/util/vm/VboxConfig.java
+++ b/src/main/java/org/openslx/vm/VboxConfig.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -20,8 +20,8 @@ import javax.xml.xpath.XPathExpressionException;
import org.apache.log4j.Logger;
import org.openslx.util.Util;
import org.openslx.util.XmlHelper;
-import org.openslx.util.vm.VmMetaData.DriveBusType;
-import org.openslx.util.vm.VmMetaData.HardDisk;
+import org.openslx.vm.VmMetaData.DriveBusType;
+import org.openslx.vm.VmMetaData.HardDisk;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/src/main/java/org/openslx/util/vm/VboxMetaData.java b/src/main/java/org/openslx/vm/VboxMetaData.java
index 82936a7..a6ac14d 100644
--- a/src/main/java/org/openslx/util/vm/VboxMetaData.java
+++ b/src/main/java/org/openslx/vm/VboxMetaData.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.File;
import java.io.IOException;
@@ -15,8 +15,9 @@ 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 org.openslx.util.vm.VboxConfig.PlaceHolder;
+import org.openslx.vm.VboxConfig.PlaceHolder;
+import org.openslx.vm.disk.DiskImage;
+import org.openslx.vm.disk.DiskImage.ImageFormat;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -317,7 +318,7 @@ public class VboxMetaData extends VmMetaData<VBoxSoundCardMeta, VBoxDDAccelMeta,
}
@Override
- public void setSoundCard( org.openslx.util.vm.VmMetaData.SoundCardType type )
+ public void setSoundCard( org.openslx.vm.VmMetaData.SoundCardType type )
{
VBoxSoundCardMeta sound = soundCards.get( type );
config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "enabled", Boolean.toString( sound.isPresent ) );
diff --git a/src/main/java/org/openslx/util/vm/VmMetaData.java b/src/main/java/org/openslx/vm/VmMetaData.java
index 4b754c3..0be07e4 100644
--- a/src/main/java/org/openslx/util/vm/VmMetaData.java
+++ b/src/main/java/org/openslx/vm/VmMetaData.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.File;
import java.io.IOException;
@@ -13,6 +13,7 @@ import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Virtualizer;
+import org.openslx.vm.disk.DiskImage;
/**
* Describes a configured virtual machine. This class is parsed from a machine
diff --git a/src/main/java/org/openslx/util/vm/VmwareConfig.java b/src/main/java/org/openslx/vm/VmwareConfig.java
index 62a014d..d98a1d4 100644
--- a/src/main/java/org/openslx/util/vm/VmwareConfig.java
+++ b/src/main/java/org/openslx/vm/VmwareConfig.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
diff --git a/src/main/java/org/openslx/util/vm/VmwareMetaData.java b/src/main/java/org/openslx/vm/VmwareMetaData.java
index a20e0a2..9bbe581 100644
--- a/src/main/java/org/openslx/util/vm/VmwareMetaData.java
+++ b/src/main/java/org/openslx/vm/VmwareMetaData.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import java.io.File;
import java.io.IOException;
@@ -17,8 +17,9 @@ import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.thrifthelper.TConst;
import org.openslx.util.Util;
-import org.openslx.util.vm.DiskImage.ImageFormat;
-import org.openslx.util.vm.VmwareConfig.ConfigEntry;
+import org.openslx.vm.VmwareConfig.ConfigEntry;
+import org.openslx.vm.disk.DiskImage;
+import org.openslx.vm.disk.DiskImage.ImageFormat;
class VmWareSoundCardMeta
{
diff --git a/src/main/java/org/openslx/vm/disk/DiskImage.java b/src/main/java/org/openslx/vm/disk/DiskImage.java
new file mode 100644
index 0000000..38964f4
--- /dev/null
+++ b/src/main/java/org/openslx/vm/disk/DiskImage.java
@@ -0,0 +1,251 @@
+package org.openslx.vm.disk;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.openslx.bwlp.thrift.iface.Virtualizer;
+import org.openslx.thrifthelper.TConst;
+
+/**
+ * Disk image for virtual machines.
+ *
+ * @implNote This class is the abstract base class to implement various specific disk images (like
+ * QCOW2 or VMDK).
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public abstract class DiskImage
+{
+ /**
+ * Stores the image file of the disk.
+ */
+ private RandomAccessFile diskImage = null;
+
+ /**
+ * Creates a new disk image from an existing image file with a known disk image format.
+ *
+ * @param diskImage file to a disk storing the image content.
+ *
+ * @throws FileNotFoundException cannot find specified disk image file.
+ * @throws IOException cannot access the content of the disk image file.
+ *
+ * @implNote Do not use this constructor to create a new disk image from an image file with
+ * unknown disk image format. Instead, use the factory method
+ * {@link #newInstance(File)} to probe unknown disk image files before creation.
+ */
+ public DiskImage( File diskImage ) throws FileNotFoundException, IOException
+ {
+ final RandomAccessFile diskFile = new RandomAccessFile( diskImage, "r" );
+ this.diskImage = diskFile;
+ }
+
+ /**
+ * Creates a new disk image from an existing image file with a known disk image format.
+ *
+ * @param diskImage file to a disk storing the image content.
+ *
+ * @implNote Do not use this constructor to create a new disk image from an image file with
+ * unknown disk image format. Instead, use the factory method
+ * {@link #newInstance(File)} to probe unknown disk image files before creation.
+ */
+ public DiskImage( RandomAccessFile diskImage )
+ {
+ this.diskImage = diskImage;
+ }
+
+ /**
+ * Returns the disk image file.
+ *
+ * @return the disk image file.
+ */
+ protected RandomAccessFile getDiskImage()
+ {
+ return this.diskImage;
+ }
+
+ /**
+ * Checks whether disk image is standalone and do not depend on other files (e.g. snapshot
+ * files).
+ *
+ * @return state whether disk image is standalone or not.
+ *
+ * @throws DiskImageException unable to check if disk image is standalone.
+ */
+ public abstract boolean isStandalone() throws DiskImageException;
+
+ /**
+ * Checks whether disk image is compressed.
+ *
+ * @return state whether disk image is compressed or not.
+ *
+ * @throws DiskImageException unable to check whether disk image is compressed.
+ */
+ public abstract boolean isCompressed() throws DiskImageException;
+
+ /**
+ * Checks whether disk image is a snapshot.
+ *
+ * @return state whether disk image is a snapshot or not.
+ *
+ * @throws DiskImageException unable to check whether disk image is a snapshot.
+ */
+ public abstract boolean isSnapshot() throws DiskImageException;
+
+ /**
+ * Returns the version of the disk image format.
+ *
+ * @return version of the disk image format.
+ *
+ * @throws DiskImageException unable to obtain version of the disk image format.
+ */
+ public abstract int getVersion() throws DiskImageException;
+
+ /**
+ * Returns the disk image description.
+ *
+ * @return description of the disk image.
+ *
+ * @throws DiskImageException unable to obtain description of the disk image.
+ */
+ public abstract String getDescription() throws DiskImageException;
+
+ /**
+ * Returns the format of the disk image.
+ *
+ * @return format of the disk image.
+ */
+ public abstract ImageFormat getFormat();
+
+ /**
+ * Creates a new disk image from an existing image file with an unknown disk image format.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @return concrete disk image instance.
+ *
+ * @throws FileNotFoundException cannot find specified disk image file.
+ * @throws IOException cannot access the content of the disk image file.
+ * @throws DiskImageException disk image file has an invalid and unknown disk image format.
+ */
+ public static DiskImage newInstance( File diskImage ) throws FileNotFoundException, IOException, DiskImageException
+ {
+ final RandomAccessFile diskFile = new RandomAccessFile( diskImage, "r" );
+ final DiskImage diskImageInstance;
+
+ if ( DiskImageQcow2.probe( diskFile ) ) {
+ diskImageInstance = new DiskImageQcow2( diskFile );
+ } else if ( DiskImageVdi.probe( diskFile ) ) {
+ diskImageInstance = new DiskImageVdi( diskFile );
+ } else if ( DiskImageVmdk.probe( diskFile ) ) {
+ diskImageInstance = new DiskImageVmdk( diskFile );
+ } else {
+ final String errorMsg = new String( "File '" + diskImage.getAbsolutePath() + "' is not a valid disk image!" );
+ throw new DiskImageException( errorMsg );
+ }
+
+ return diskImageInstance;
+ }
+
+ /**
+ * Format of a disk image.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+ public enum ImageFormat
+ {
+ // @formatter:off
+ NONE ( "none" ),
+ QCOW2( "qcow2" ),
+ VDI ( "vdi" ),
+ VMDK ( "vmdk" );
+ // @formatter:on
+
+ /**
+ * Stores filename extension of the disk image format.
+ */
+ public final String extension;
+
+ /**
+ * Create new disk image format.
+ *
+ * @param extension filename extension of the disk image format.
+ */
+ ImageFormat( String extension )
+ {
+ this.extension = extension;
+ }
+
+ /**
+ * Returns filename extension of the disk image.
+ *
+ * @return filename extension of the disk image.
+ */
+ public String getExtension()
+ {
+ return this.extension;
+ }
+
+ /**
+ * Checks if the disk image format is supported by a virtualizer.
+ *
+ * @param supportedImageTypes list of supported disk image formats of a virtualizer.
+ * @return <code>true</code> if image type is supported by the virtualizer; otherwise
+ * <code>false</code>.
+ */
+ public boolean isSupportedbyVirtualizer( List<ImageFormat> supportedImageFormats )
+ {
+ Predicate<ImageFormat> matchDiskFormat = supportedImageFormat -> supportedImageFormat.toString()
+ .equalsIgnoreCase( this.toString() );
+ return supportedImageFormats.stream().anyMatch( matchDiskFormat );
+ }
+
+ /**
+ * Returns default (preferred) disk image format for the specified virtualizer.
+ *
+ * @param virt virtualizer for that the default disk image should be determined.
+ * @return default (preferred) disk image format.
+ */
+ public static ImageFormat defaultForVirtualizer( Virtualizer virt )
+ {
+ if ( virt == null ) {
+ return null;
+ } else {
+ return ImageFormat.defaultForVirtualizer( virt.virtId );
+ }
+ }
+
+ /**
+ * Returns default (preferred) disk image format for the specified virtualizer.
+ *
+ * @param virtId ID of a virtualizer for that the default disk image should be determined.
+ * @return default (preferred) disk image format.
+ */
+ public static ImageFormat defaultForVirtualizer( String virtId )
+ {
+ ImageFormat imgFormat = null;
+
+ if ( TConst.VIRT_DOCKER.equals( virtId ) ) {
+ imgFormat = NONE;
+ } else if ( TConst.VIRT_QEMU.equals( virtId ) ) {
+ imgFormat = QCOW2;
+ } else if ( TConst.VIRT_VIRTUALBOX.equals( virtId ) ) {
+ imgFormat = VDI;
+ } else if ( TConst.VIRT_VMWARE.equals( virtId ) ) {
+ imgFormat = VMDK;
+ }
+
+ return imgFormat;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.getExtension();
+ }
+ }
+}
diff --git a/src/main/java/org/openslx/vm/disk/DiskImageException.java b/src/main/java/org/openslx/vm/disk/DiskImageException.java
new file mode 100644
index 0000000..a98f963
--- /dev/null
+++ b/src/main/java/org/openslx/vm/disk/DiskImageException.java
@@ -0,0 +1,25 @@
+package org.openslx.vm.disk;
+
+/**
+ * An exception for faulty disk image handling.
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageException extends Exception
+{
+ /**
+ * Version number for serialization.
+ */
+ private static final long serialVersionUID = 5464286488698331909L;
+
+ /**
+ * Creates a disk image exception including an error message.
+ *
+ * @param errorMsg message to describe a disk image error.
+ */
+ public DiskImageException( String errorMsg )
+ {
+ super( errorMsg );
+ }
+}
diff --git a/src/main/java/org/openslx/vm/disk/DiskImageQcow2.java b/src/main/java/org/openslx/vm/disk/DiskImageQcow2.java
new file mode 100644
index 0000000..5f72a00
--- /dev/null
+++ b/src/main/java/org/openslx/vm/disk/DiskImageQcow2.java
@@ -0,0 +1,246 @@
+package org.openslx.vm.disk;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * QCOW2 disk image for virtual machines.
+ *
+ * A QCOW2 disk image consists of a header, one L1 table and several L2 tables used for lookup data
+ * clusters in the file via a two-level lookup. The QCOW2 header contains the following fields:
+ *
+ * <pre>
+ * QCOW2 (version 2 and 3) header format:
+ *
+ * magic (4 byte)
+ * version (4 byte)
+ * backing_file_offset (8 byte)
+ * backing_file_size (4 byte)
+ * cluster_bits (4 byte)
+ * size (8 byte)
+ * crypt_method (4 byte)
+ * l1_size (4 byte)
+ * l1_table_offset (8 byte)
+ * refcount_table_offset (8 byte)
+ * refcount_table_clusters (4 byte)
+ * nb_snapshots (4 byte)
+ * snapshots_offset (8 byte)
+ * incompatible_features (8 byte) [*]
+ * compatible_features (8 byte) [*]
+ * autoclear_features (8 byte) [*]
+ * refcount_order (8 byte) [*]
+ * header_length (4 byte) [*]
+ *
+ * [*] these fields are only available in the QCOW2 version 3 header format
+ * </pre>
+ *
+ * @author Manuel Bentele
+ * @version 1.0
+ */
+public class DiskImageQcow2 extends DiskImage
+{
+ /**
+ * Big endian representation of the big endian QCOW2 magic bytes <code>QFI\xFB</code>.
+ */
+ private static final int QCOW2_MAGIC = 0x514649fb;
+
+ /**
+ * Creates a new QCOW2 disk image from an existing QCOW2 image file.
+ *
+ * @param diskImage file to a QCOW2 disk storing the image content.
+ *
+ * @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 <code>offset</code> from the specified disk image
+ * file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the two bytes.
+ * @return value of the two bytes from the disk image file as {@link Short}.
+ *
+ * @throws DiskImageException unable to read two bytes from the disk image file.
+ */
+ public static short readShort( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ short value = 0;
+
+ if ( imageSize >= ( offset + Short.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readShort();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads four bytes ({@link Integer}) at a given <code>offset</code> from the specified disk
+ * image file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the four bytes.
+ * @return value of the four bytes from the disk image file as {@link Integer}.
+ *
+ * @throws DiskImageException unable to read four bytes from the disk image file.
+ */
+ public static int readInt( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ int value = 0;
+
+ if ( imageSize >= ( offset + Integer.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readInt();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads eight bytes ({@link Long}) at a given <code>offset</code> from the specified disk image
+ * file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the eight bytes.
+ * @return value of the eight bytes from the disk image file as {@link Long}.
+ *
+ * @throws DiskImageException unable to read eight bytes from the disk image file.
+ */
+ public static long readLong( RandomAccessFile diskImage, long offset ) throws DiskImageException
+ {
+ final long imageSize = DiskImageUtils.getImageSize( diskImage );
+ long value = 0;
+
+ if ( imageSize >= ( offset + Long.BYTES ) ) {
+ try {
+ diskImage.seek( offset );
+ value = diskImage.readLong();
+ } catch ( IOException e ) {
+ throw new DiskImageException( e.getLocalizedMessage() );
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Reads two bytes ({@link Short}) at a given <code>offset</code> from the specified disk image
+ * file.
+ *
+ * @param diskImage file to a disk storing the image content.
+ * @param offset offset in bytes for reading the two bytes.
+ * @return value of the two bytes from the disk image file as {@link Short}.
+ *
+ * @throws DiskImageException unable to read two bytes from the disk image file.
+ */
+ public static 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 <code>KDMV</code>.
+ */
+ private static final int VMDK_MAGIC = 0x4b444d56;
+
+ /**
+ * Size of a VMDK disk image data cluster in bytes.
+ */
+ private static final int VMDK_SECTOR_SIZE = 512;
+
+ /**
+ * Default hardware version of a VMDK disk image.
+ */
+ private static final int VMDK_DEFAULT_HW_VERSION = 10;
+
+ /**
+ * Stores disk configuration if VMDK disk image contains an embedded descriptor file.
+ */
+ private final 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/QemuMetaDataTest.java b/src/test/java/org/openslx/vm/QemuMetaDataTest.java
index f201a77..3217fda 100644
--- a/src/test/java/org/openslx/util/vm/QemuMetaDataTest.java
+++ b/src/test/java/org/openslx/vm/QemuMetaDataTest.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -31,11 +31,13 @@ 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;
+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
{
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/util/vm/DiskImageTestResources.java b/src/test/java/org/openslx/vm/disk/DiskImageTestResources.java
index 1f164bd..2ec2e05 100644
--- a/src/test/java/org/openslx/util/vm/DiskImageTestResources.java
+++ b/src/test/java/org/openslx/vm/disk/DiskImageTestResources.java
@@ -1,4 +1,4 @@
-package org.openslx.util.vm;
+package org.openslx.vm.disk;
import java.io.File;
import java.net.URL;
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
--- /dev/null
+++ b/src/test/resources/disk/image-default_snapshot.vdi
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t0.vmdk
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t1-s001.vmdk
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t2-flat.vmdk
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t3-f001.vmdk
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t4-flat.vmdk
Binary files 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
--- /dev/null
+++ b/src/test/resources/disk/image_t5.vmdk
Binary files differ