From be40e979e03e41ddcd831d9c330902f76908ca64 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Thu, 25 Feb 2021 15:00:38 +0100 Subject: Refactor disk image representation and add unit tests --- .../java/org/openslx/vm/DockerMetaDataDummy.java | 217 ++++++ src/main/java/org/openslx/vm/KeyValuePair.java | 13 + src/main/java/org/openslx/vm/QemuMetaData.java | 855 +++++++++++++++++++++ .../java/org/openslx/vm/QemuMetaDataUtils.java | 188 +++++ .../vm/UnsupportedVirtualizerFormatException.java | 9 + src/main/java/org/openslx/vm/VboxConfig.java | 631 +++++++++++++++ src/main/java/org/openslx/vm/VboxMetaData.java | 536 +++++++++++++ src/main/java/org/openslx/vm/VmMetaData.java | 419 ++++++++++ src/main/java/org/openslx/vm/VmwareConfig.java | 276 +++++++ src/main/java/org/openslx/vm/VmwareMetaData.java | 684 +++++++++++++++++ src/main/java/org/openslx/vm/disk/DiskImage.java | 251 ++++++ .../org/openslx/vm/disk/DiskImageException.java | 25 + .../java/org/openslx/vm/disk/DiskImageQcow2.java | 246 ++++++ .../java/org/openslx/vm/disk/DiskImageUtils.java | 154 ++++ .../java/org/openslx/vm/disk/DiskImageVdi.java | 119 +++ .../java/org/openslx/vm/disk/DiskImageVmdk.java | 298 +++++++ 16 files changed, 4921 insertions(+) create mode 100644 src/main/java/org/openslx/vm/DockerMetaDataDummy.java create mode 100644 src/main/java/org/openslx/vm/KeyValuePair.java create mode 100644 src/main/java/org/openslx/vm/QemuMetaData.java create mode 100644 src/main/java/org/openslx/vm/QemuMetaDataUtils.java create mode 100644 src/main/java/org/openslx/vm/UnsupportedVirtualizerFormatException.java create mode 100644 src/main/java/org/openslx/vm/VboxConfig.java create mode 100644 src/main/java/org/openslx/vm/VboxMetaData.java create mode 100644 src/main/java/org/openslx/vm/VmMetaData.java create mode 100644 src/main/java/org/openslx/vm/VmwareConfig.java create mode 100644 src/main/java/org/openslx/vm/VmwareMetaData.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImage.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageException.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageQcow2.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageUtils.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageVdi.java create mode 100644 src/main/java/org/openslx/vm/disk/DiskImageVmdk.java (limited to 'src/main/java/org/openslx/vm') diff --git a/src/main/java/org/openslx/vm/DockerMetaDataDummy.java b/src/main/java/org/openslx/vm/DockerMetaDataDummy.java new file mode 100644 index 0000000..73cd92e --- /dev/null +++ b/src/main/java/org/openslx/vm/DockerMetaDataDummy.java @@ -0,0 +1,217 @@ +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.vm.disk.DiskImage; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +class DockerSoundCardMeta +{ +} + +class DockerDDAccelMeta +{ +} + +class DockerHWVersionMeta +{ +} + +class DockerEthernetDevTypeMeta +{ +} + +class DockerUsbSpeedMeta +{ +} + +public class DockerMetaDataDummy extends VmMetaData { + + /** + * List of supported image formats by the Docker hypervisor. + */ + private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.NONE ) ); + + private static final Logger LOGGER = Logger.getLogger( DockerMetaDataDummy.class); + + private final Virtualizer virtualizer = new Virtualizer(TConst.VIRT_DOCKER, "Docker"); + + /** + * containerDefinition is a serialized tar.gz archive and represents a + * ContainerDefinition. This archive contains a serialized Container Recipe (e.g. Dockerfile) + * and a ContainerMeta witch is serialized as a json file. + *

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

+ * This field is in vm context the machine description e.g. vmware = vmx. + * This field will be stored in table imageversion.virtualizerconfig + */ + private byte[] containerDefinition; + + @SuppressWarnings( "deprecation" ) + public DockerMetaDataDummy(List osList, File file) throws UnsupportedVirtualizerFormatException { + super(osList); + + BufferedInputStream bis = null; + + try { + bis = new BufferedInputStream(new FileInputStream(file)); + containerDefinition = new byte[(int) file.length()]; + bis.read(containerDefinition); + + checkIsTarGz(); + } catch (IOException | UnsupportedVirtualizerFormatException e) { + LOGGER.error("Couldn't read dockerfile", e); + } finally { + IOUtils.closeQuietly( bis ); + } + } + + public DockerMetaDataDummy(List osList, byte[] vmContent, int length) + throws UnsupportedVirtualizerFormatException { + super(osList); + + containerDefinition = vmContent; + + checkIsTarGz(); + } + + /* + TODO This is just a simple check to prevent the workflow from considering any content as acceptable. + */ + /** + * Checks if the first two bytes of the content identifies a tar.gz archive. + * The first byte is 31 == 0x1f, the second byte has to be -117 == 0x8b. + * + * @throws UnsupportedVirtualizerFormatException + */ + private void checkIsTarGz() throws UnsupportedVirtualizerFormatException { + if (!((31 == containerDefinition[0]) && (-117 == containerDefinition[1]))) { + LOGGER.warn("Not Supported Content."); + throw new UnsupportedVirtualizerFormatException( + "DockerMetaDataDummy: Not tar.gz encoded content!"); + } + } + + @Override public byte[] getFilteredDefinitionArray() { + return containerDefinition; + } + + @Override + public List getSupportedImageFormats() + { + return DockerMetaDataDummy.SUPPORTED_IMAGE_FORMATS; + } + + @Override public void applySettingsForLocalEdit() { + + } + + @Override public boolean addHddTemplate(File diskImage, String hddMode, String redoDir) { + return false; + } + + @Override public boolean addHddTemplate(String diskImagePath, String hddMode, String redoDir) { + return false; + } + + @Override public boolean addDefaultNat() { + return false; + } + + @Override public void setOs(String vendorOsId) { + + } + + @Override public boolean addDisplayName(String name) { + return false; + } + + @Override public boolean addRam(int mem) { + return false; + } + + @Override public void addFloppy(int index, String image, boolean readOnly) { + + } + + @Override public boolean addCdrom(String image) { + return false; + } + + @Override public boolean addCpuCoreCount(int nrOfCores) { + return false; + } + + @Override public void setSoundCard(SoundCardType type) { + + } + + @Override public SoundCardType getSoundCard() { + return SoundCardType.NONE; + } + + @Override public void setDDAcceleration(DDAcceleration type) { + + } + + @Override public DDAcceleration getDDAcceleration() { + return DDAcceleration.OFF; + } + + @Override public void setHWVersion(HWVersion type) { + + } + + @Override public HWVersion getHWVersion() { + return HWVersion.DEFAULT; + } + + @Override public void setEthernetDevType(int cardIndex, EthernetDevType type) { + + } + + @Override public EthernetDevType getEthernetDevType(int cardIndex) { + return EthernetDevType.NONE; + } + + @Override public void setMaxUsbSpeed(UsbSpeed speed) { + + } + + @Override public UsbSpeed getMaxUsbSpeed() { + return UsbSpeed.NONE; + } + + @Override public byte[] getDefinitionArray() { + return new byte[0]; + } + + @Override public boolean addEthernet(EtherType type) { + return false; + } + + @Override public Virtualizer getVirtualizer() { + return virtualizer; + } + + @Override public boolean tweakForNonPersistent() { + return false; + } + + @Override public void registerVirtualHW() { + + } +} diff --git a/src/main/java/org/openslx/vm/KeyValuePair.java b/src/main/java/org/openslx/vm/KeyValuePair.java new file mode 100644 index 0000000..c5650ec --- /dev/null +++ b/src/main/java/org/openslx/vm/KeyValuePair.java @@ -0,0 +1,13 @@ +package org.openslx.vm; + +class KeyValuePair +{ + public final String key; + public final String value; + + public KeyValuePair( String key, String value ) + { + this.key = key; + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/org/openslx/vm/QemuMetaData.java b/src/main/java/org/openslx/vm/QemuMetaData.java new file mode 100644 index 0000000..c780429 --- /dev/null +++ b/src/main/java/org/openslx/vm/QemuMetaData.java @@ -0,0 +1,855 @@ +package org.openslx.vm; + +import java.io.File; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; + +import org.openslx.bwlp.thrift.iface.OperatingSystem; +import org.openslx.bwlp.thrift.iface.Virtualizer; +import org.openslx.libvirt.domain.Domain; +import org.openslx.libvirt.domain.device.ControllerUsb; +import org.openslx.libvirt.domain.device.Disk.BusType; +import org.openslx.libvirt.domain.device.Disk.StorageType; +import org.openslx.libvirt.domain.device.DiskCdrom; +import org.openslx.libvirt.domain.device.DiskFloppy; +import org.openslx.libvirt.domain.device.DiskStorage; +import org.openslx.libvirt.domain.device.Graphics; +import org.openslx.libvirt.domain.device.GraphicsSpice; +import org.openslx.libvirt.domain.device.Interface; +import org.openslx.libvirt.domain.device.Sound; +import org.openslx.libvirt.domain.device.Video; +import org.openslx.libvirt.xml.LibvirtXmlDocumentException; +import org.openslx.libvirt.xml.LibvirtXmlSerializationException; +import org.openslx.libvirt.xml.LibvirtXmlValidationException; +import org.openslx.thrifthelper.TConst; +import org.openslx.vm.disk.DiskImage; +import org.openslx.vm.disk.DiskImage.ImageFormat; + +/** + * Metadata to describe the hardware type of a QEMU sound card. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuSoundCardMeta +{ + /** + * Stores the hardware model of the QEMU sound card. + */ + private final Sound.Model model; + + /** + * Creates metadata to describe the hardware model of a QEMU sound card. + * + * @param model hardware model of the QEMU sound card. + */ + public QemuSoundCardMeta( Sound.Model model ) + { + this.model = model; + } + + /** + * Returns hardware model of the QEMU sound card. + * + * @return hardware model of the QEMU sound card. + */ + public Sound.Model getModel() + { + return this.model; + } +} + +/** + * Metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuDDAccelMeta +{ + /** + * Stores state of the hardware acceleration for QEMU virtual graphics. + */ + private final boolean enabled; + + /** + * Creates metadata to describe the hardware acceleration state of QEMU virtual graphics. + * + * @param enabled state of the hardware acceleration for QEMU virtual graphics. + */ + public QemuDDAccelMeta( boolean enabled ) + { + this.enabled = enabled; + } + + /** + * Returns state of the hardware acceleration of QEMU virtual graphics. + * + * @return state of the hardware acceleration for QEMU virtual graphics. + */ + public boolean isEnabled() + { + return this.enabled; + } +} + +/** + * Metadata to describe the version of a QEMU virtual machine configuration. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuHWVersionMeta +{ + /** + * Stores the version of a QEMU virtual machine configuration. + */ + private final int version; + + /** + * Creates metadata to describe the version of a QEMU virtual machine configuration. + * + * @param version version of the QEMU virtual machine configuration. + */ + public QemuHWVersionMeta( int version ) + { + this.version = version; + } + + /** + * Returns version of the QEMU virtual machine configuration. + * + * @return version of the QEMU virtual machine configuration. + */ + public int getVersion() + { + return this.version; + } +} + +/** + * Metadata to describe the hardware type of a QEMU ethernet device. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuEthernetDevTypeMeta +{ + /** + * Stores the hardware model of the QEMU ethernet device. + */ + private final Interface.Model model; + + /** + * Creates metadata to describe the hardware type of a QEMU ethernet device. + * + * @param model hardware type of the QEMU ethernet device. + */ + public QemuEthernetDevTypeMeta( Interface.Model model ) + { + this.model = model; + } + + /** + * Returns the hardware type of a QEMU ethernet device. + * + * @return hardware type of the QEMU ethernet device. + */ + public Interface.Model getModel() + { + return this.model; + } +} + +/** + * Metadata to describe a QEMU USB controller. + * + * @author Manuel Bentele + * @version 1.0 + */ +class QemuUsbSpeedMeta +{ + /** + * Stores the USB speed of the QEMU USB controller. + */ + private final int speed; + + /** + * Stores the QEMU hardware model of the USB controller. + */ + private final ControllerUsb.Model model; + + /** + * Creates metadata to describe a QEMU USB controller. + * + * @param speed USB speed of the QEMU USB controller. + * @param model QEMU hardware model of the USB controller. + */ + public QemuUsbSpeedMeta( int speed, ControllerUsb.Model model ) + { + this.speed = speed; + this.model = model; + } + + /** + * Returns the speed of the QEMU USB controller. + * + * @return speed of the QEMU USB controller. + */ + public int getSpeed() + { + return this.speed; + } + + /** + * Returns QEMU hardware model of the USB controller. + * + * @return hardware model of the QEMU USB controller. + */ + public ControllerUsb.Model getModel() + { + return this.model; + } +} + +/** + * Virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class QemuMetaData extends + VmMetaData +{ + /** + * Default bridge name of the network bridge connected to the LAN. + */ + public static final String NETWORK_DEFAULT_BRIDGE = "brBwLehrpool"; + + /** + * Default network name of the isolated host network (host only). + */ + public static final String NETWORK_DEFAULT_HOST_ONLY = "host"; + + /** + * Default network name of the NAT network. + */ + public static final String NETWORK_DEFAULT_NAT = "nat"; + + /** + * Default physical CDROM drive of the hypervisor host. + */ + public static final String CDROM_DEFAULT_PHYSICAL_DRIVE = "/dev/sr0"; + + /** + * List of supported image formats by the QEMU hypervisor. + */ + private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( + Arrays.asList( ImageFormat.QCOW2, ImageFormat.VMDK, ImageFormat.VDI ) ); + + /** + * Representation of a QEMU hypervisor (managed by Libvirt). + */ + private static final Virtualizer VIRTUALIZER = new Virtualizer( TConst.VIRT_QEMU, "QEMU" ); + + /** + * Libvirt XML configuration file to modify configuration of virtual machine for QEMU. + */ + private Domain vmConfig = null; + + /** + * Stores current index of added HDD device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexHddAdd = 0; + + /** + * Stores current index of added CDROM device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexCdromAdd = 0; + + /** + * Stores current index of added ethernet device to the Libvirt XML configuration file. + */ + private int vmDeviceIndexEthernetAdd = 0; + + /** + * Creates new virtual machine configuration (managed by Libvirt) for the QEMU hypervisor. + * + * @param osList list of operating systems. + * @param file image file for the QEMU hypervisor. + * @throws UnsupportedVirtualizerFormatException Libvirt XML configuration cannot be processed. + */ + public QemuMetaData( List osList, File file ) throws UnsupportedVirtualizerFormatException + { + super( osList ); + + try { + // read and parse Libvirt domain XML configuration document + this.vmConfig = new Domain( file ); + } catch ( LibvirtXmlDocumentException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlSerializationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } catch ( LibvirtXmlValidationException e ) { + throw new UnsupportedVirtualizerFormatException( e.getLocalizedMessage() ); + } + + // register virtual hardware models for graphical editing of virtual devices (GPU, sound, USB, ...) + this.registerVirtualHW(); + + // set display name of VM + this.displayName = vmConfig.getName(); + + // this property cannot be checked with the Libvirt domain XML configuration + // to check if machine is in a paused/suspended state, look in the QEMU qcow2 image for snapshots and machine states + this.isMachineSnapshot = false; + + // add HDDs, SSDs to QEMU metadata + for ( DiskStorage storageDiskDevice : this.vmConfig.getDiskStorageDevices() ) { + this.addHddMetaData( storageDiskDevice ); + } + } + + /** + * Adds an existing and observed storage disk device to the HDD metadata. + * + * @param storageDiskDevice existing and observed storage disk that should be added to the + * metadata. + */ + private void addHddMetaData( DiskStorage storageDiskDevice ) + { + String hddChipsetModel = null; + DriveBusType hddChipsetBus = QemuMetaDataUtils.convertBusType( storageDiskDevice.getBusType() ); + String hddImagePath = storageDiskDevice.getStorageSource(); + + this.hdds.add( new HardDisk( hddChipsetModel, hddChipsetBus, hddImagePath ) ); + } + + @Override + public byte[] getFilteredDefinitionArray() + { + // remove UUID in Libvirt domain XML configuration + this.vmConfig.removeUuid(); + + // removes all specified boot order entries + this.vmConfig.removeBootOrder(); + + // removes all referenced storage files of all specified CDROMs, Floppy drives and HDDs + this.vmConfig.removeDiskDevicesStorage(); + + // removes all source networks of all specified network interfaces + this.vmConfig.removeInterfaceDevicesSource(); + + // output filtered Libvirt domain XML configuration + String configuration = this.vmConfig.toString(); + return configuration.getBytes( StandardCharsets.UTF_8 ); + } + + @Override + public List getSupportedImageFormats() + { + return QemuMetaData.SUPPORTED_IMAGE_FORMATS; + } + + @Override + public void applySettingsForLocalEdit() + { + // NOT implemented yet + } + + @Override + public boolean addHddTemplate( File diskImage, String hddMode, String redoDir ) + { + return this.addHddTemplate( diskImage.getAbsolutePath(), hddMode, redoDir ); + } + + @Override + public boolean addHddTemplate( String diskImagePath, String hddMode, String redoDir ) + { + return this.addHddTemplate( this.vmDeviceIndexHddAdd++, diskImagePath, hddMode, redoDir ); + } + + /** + * Adds hard disk drive (HDD) to the QEMU virtual machine configuration. + * + * @param index current index of HDD to be added to the virtual machine configuration. + * @param diskImagePath path to the virtual disk image for the HDD. + * @param hddMode operation mode of the HDD. + * @param redoDir directory for the redo log if an independent non-persistent + * hddMode is set. + * @return result state of adding the HDD. + */ + public boolean addHddTemplate( int index, String diskImagePath, String hddMode, String redoDir ) + { + ArrayList storageDiskDevices = this.vmConfig.getDiskStorageDevices(); + DiskStorage storageDiskDevice = QemuMetaDataUtils.getArrayIndex( storageDiskDevices, index ); + + if ( storageDiskDevice == null ) { + // HDD does not exist, so create new storage (HDD) device + storageDiskDevice = this.vmConfig.addDiskStorageDevice(); + storageDiskDevice.setReadOnly( false ); + storageDiskDevice.setBusType( BusType.VIRTIO ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "vd", index ); + storageDiskDevice.setTargetDevice( targetDevName ); + storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); + + // add new created HDD to the metadata of the QemuMetaData object, too + this.addHddMetaData( storageDiskDevice ); + } else { + // HDD exists, so update existing storage (HDD) device + storageDiskDevice.setStorage( StorageType.FILE, diskImagePath ); + } + + return false; + } + + @Override + public boolean addDefaultNat() + { + return this.addEthernet( EtherType.NAT ); + } + + @Override + public void setOs( String vendorOsId ) + { + this.setOs( vendorOsId ); + } + + @Override + public boolean addDisplayName( String name ) + { + this.vmConfig.setName( name ); + + final boolean statusName = this.vmConfig.getName().equals( name ); + + return statusName; + } + + @Override + public boolean addRam( int mem ) + { + BigInteger memory = BigInteger.valueOf( mem ); + + this.vmConfig.setMemory( memory ); + this.vmConfig.setCurrentMemory( memory ); + + final boolean isMemorySet = this.vmConfig.getMemory().toString().equals( memory.toString() ); + final boolean isCurrentMemorySet = this.vmConfig.getCurrentMemory().toString().equals( memory.toString() ); + + return isMemorySet && isCurrentMemorySet; + } + + @Override + public void addFloppy( int index, String image, boolean readOnly ) + { + ArrayList floppyDiskDevices = this.vmConfig.getDiskFloppyDevices(); + DiskFloppy floppyDiskDevice = QemuMetaDataUtils.getArrayIndex( floppyDiskDevices, index ); + + if ( floppyDiskDevice == null ) { + // floppy device does not exist, so create new floppy device + floppyDiskDevice = this.vmConfig.addDiskFloppyDevice(); + floppyDiskDevice.setBusType( BusType.FDC ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "fd", index ); + floppyDiskDevice.setTargetDevice( targetDevName ); + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } else { + // floppy device exists, so update existing floppy device + floppyDiskDevice.setReadOnly( readOnly ); + floppyDiskDevice.setStorage( StorageType.FILE, image ); + } + } + + @Override + public boolean addCdrom( String image ) + { + return this.addCdrom( this.vmDeviceIndexCdromAdd++, image ); + } + + /** + * Adds CDROM drive to the QEMU virtual machine configuration. + * + * @param index current index of CDROM drive to be added to the virtual machine configuration. + * @param image path to a virtual image that will be inserted as CDROM into the drive. + * @return result state of adding the CDROM drive. + */ + public boolean addCdrom( int index, String image ) + { + ArrayList cdromDiskDevices = this.vmConfig.getDiskCdromDevices(); + DiskCdrom cdromDiskDevice = QemuMetaDataUtils.getArrayIndex( cdromDiskDevices, index ); + + if ( cdromDiskDevice == null ) { + // CDROM device does not exist, so create new CDROM device + cdromDiskDevice = this.vmConfig.addDiskCdromDevice(); + cdromDiskDevice.setBusType( BusType.SATA ); + String targetDevName = QemuMetaDataUtils.createAlphabeticalDeviceName( "sd", index ); + cdromDiskDevice.setTargetDevice( targetDevName ); + cdromDiskDevice.setReadOnly( true ); + + if ( image == null ) { + cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); + } else { + cdromDiskDevice.setStorage( StorageType.FILE, image ); + } + } else { + // CDROM device exists, so update existing CDROM device + cdromDiskDevice.setReadOnly( true ); + + if ( image == null ) { + cdromDiskDevice.setStorage( StorageType.BLOCK, CDROM_DEFAULT_PHYSICAL_DRIVE ); + } else { + cdromDiskDevice.setStorage( StorageType.FILE, image ); + } + } + + return false; + } + + @Override + public boolean addCpuCoreCount( int nrOfCores ) + { + this.vmConfig.setVCpu( nrOfCores ); + + boolean isVCpuSet = this.vmConfig.getVCpu() == nrOfCores; + + return isVCpuSet; + } + + @Override + public void setSoundCard( SoundCardType type ) + { + QemuSoundCardMeta soundDeviceConfig = this.soundCards.get( type ); + ArrayList soundDevices = this.vmConfig.getSoundDevices(); + Sound.Model soundDeviceModel = soundDeviceConfig.getModel(); + + if ( soundDevices.isEmpty() ) { + // create new sound device with 'soundDeviceModel' hardware + Sound soundDevice = this.vmConfig.addSoundDevice(); + soundDevice.setModel( soundDeviceModel ); + } else { + // update sound device model type of existing sound devices + for ( Sound soundDevice : soundDevices ) { + soundDevice.setModel( soundDeviceModel ); + } + } + } + + @Override + public SoundCardType getSoundCard() + { + ArrayList soundDevices = this.vmConfig.getSoundDevices(); + SoundCardType soundDeviceType = SoundCardType.DEFAULT; + + if ( soundDevices.isEmpty() ) { + // the VM configuration does not contain a sound card device + soundDeviceType = SoundCardType.NONE; + } else { + // the VM configuration at least one sound card device, so return the type of the first one + Sound.Model soundDeviceModel = soundDevices.get( 0 ).getModel(); + soundDeviceType = QemuMetaDataUtils.convertSoundDeviceModel( soundDeviceModel ); + } + + return soundDeviceType; + } + + @Override + public void setDDAcceleration( DDAcceleration type ) + { + QemuDDAccelMeta accelerationConfig = this.ddacc.get( type ); + ArrayList graphicDevices = this.vmConfig.getGraphicDevices(); + ArrayList

+ *   QCOW2 (version 2 and 3) header format:
+ *   
+ *   magic                   (4 byte)
+ *   version                 (4 byte)
+ *   backing_file_offset     (8 byte)
+ *   backing_file_size       (4 byte)
+ *   cluster_bits            (4 byte)
+ *   size                    (8 byte)
+ *   crypt_method            (4 byte)
+ *   l1_size                 (4 byte)
+ *   l1_table_offset         (8 byte)
+ *   refcount_table_offset   (8 byte)
+ *   refcount_table_clusters (4 byte)
+ *   nb_snapshots            (4 byte)
+ *   snapshots_offset        (8 byte)
+ *   incompatible_features   (8 byte)  [*]
+ *   compatible_features     (8 byte)  [*]
+ *   autoclear_features      (8 byte)  [*]
+ *   refcount_order          (8 byte)  [*]
+ *   header_length           (4 byte)  [*]
+ *
+ *   [*] these fields are only available in the QCOW2 version 3 header format
+ * 
+ * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageQcow2 extends DiskImage +{ + /** + * Big endian representation of the big endian QCOW2 magic bytes QFI\xFB. + */ + private static final int QCOW2_MAGIC = 0x514649fb; + + /** + * Creates a new QCOW2 disk image from an existing QCOW2 image file. + * + * @param diskImage file to a QCOW2 disk storing the image content. + * + * @throws FileNotFoundException cannot find specified QCOW2 disk image file. + * @throws IOException cannot access the content of the QCOW2 disk image file. + */ + public DiskImageQcow2( File disk ) throws FileNotFoundException, IOException + { + super( disk ); + } + + /** + * Creates a new QCOW2 disk image from an existing QCOW2 image file. + * + * @param diskImage file to a QCOW2 disk storing the image content. + */ + public DiskImageQcow2( RandomAccessFile disk ) + { + super( disk ); + } + + /** + * Probe specified file with unknown format to be a QCOW2 disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a QCOW2 disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isQcow2ImageFormat; + + // goto the beginning of the disk image to read the disk image + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageQcow2.QCOW2_MAGIC ) { + isQcow2ImageFormat = true; + } else { + isQcow2ImageFormat = false; + } + + return isQcow2ImageFormat; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final long qcowBackingFileOffset = DiskImageUtils.readLong( diskFile, 8 ); + final boolean qcowStandalone; + + // check if QCOW2 image does not refer to any backing file + if ( qcowBackingFileOffset == 0 ) { + qcowStandalone = true; + } else { + qcowStandalone = false; + } + + return qcowStandalone; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final boolean qcowUseExtendedL2; + boolean qcowCompressed = false; + + // check if QCOW2 image uses extended L2 tables + // extended L2 tables are only possible in QCOW2 version 3 header format + if ( this.getVersion() >= 3 ) { + // read incompatible feature bits + final long qcowIncompatibleFeatures = DiskImageUtils.readLong( diskFile, 72 ); + + // support for extended L2 tables is enabled if bit 4 is set + qcowUseExtendedL2 = ( ( ( qcowIncompatibleFeatures & 0x000000000010 ) >>> 4 ) == 1 ); + } else { + qcowUseExtendedL2 = false; + } + + // get number of entries in L1 table + final int qcowL1TableSize = DiskImageUtils.readInt( diskFile, 36 ); + + // check if a valid L1 table is present + if ( qcowL1TableSize > 0 ) { + // QCOW2 image contains active L1 table with more than 0 entries: l1_size > 0 + // search for first L2 table and its first entry to get compression bit + + // get cluster bits to calculate the cluster size + final int qcowClusterBits = DiskImageUtils.readInt( diskFile, 20 ); + final int qcowClusterSize = ( 1 << qcowClusterBits ); + + // entries of a L1 table have always the size of 8 byte (64 bit) + final int qcowL1TableEntrySize = 8; + + // entries of a L2 table have either the size of 8 or 16 byte (64 or 128 bit) + final int qcowL2TableEntrySize = ( qcowUseExtendedL2 ) ? 16 : 8; + + // calculate number of L2 table entries + final int qcowL2TableSize = qcowClusterSize / qcowL2TableEntrySize; + + // get offset of L1 table + final long qcowL1TableOffset = DiskImageUtils.readLong( diskFile, 40 ); + + // check for each L2 table referenced from an L1 table its entries + // until a compressed cluster descriptor is found + for ( long i = 0; i < qcowL1TableSize; i++ ) { + // get offset of current L2 table from the current L1 table entry + final long qcowL1TableEntryOffset = qcowL1TableOffset + ( i * qcowL1TableEntrySize ); + final long qcowL1TableEntry = DiskImageUtils.readLong( diskFile, qcowL1TableEntryOffset ); + + // extract offset (bits 9 - 55) from L1 table entry + final long qcowL2TableOffset = ( qcowL1TableEntry & 0x00fffffffffffe00L ); + + if ( qcowL2TableOffset == 0 ) { + // L2 table and all clusters described by this L2 table are unallocated + continue; + } + + // get each L2 table entry and check if it is a compressed cluster descriptor + for ( long j = 0; j < qcowL2TableSize; j++ ) { + // get current L2 table entry + final long qcowL2TableEntryOffset = qcowL2TableOffset + ( j * qcowL2TableEntrySize ); + final long qcowL2TableEntry = DiskImageUtils.readLong( diskFile, qcowL2TableEntryOffset ); + + // extract cluster type (standard or compressed) (bit 62) + boolean qcowClusterCompressed = ( ( ( qcowL2TableEntry & 0x4000000000000000L ) >>> 62 ) == 1 ); + + // check if QCOW2 disk image contains at least one compressed cluster descriptor + if ( qcowClusterCompressed ) { + qcowCompressed = true; + break; + } + } + + // terminate if one compressed cluster descriptor is already found + if ( qcowCompressed ) { + break; + } + } + } else { + // QCOW2 image does not contain an active L1 table with any entry: l1_size = 0 + qcowCompressed = false; + } + + return qcowCompressed; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int qcowNumSnapshots = DiskImageUtils.readInt( diskFile, 56 ); + final boolean qcowSnapshot; + + // check if QCOW2 image contains at least one snapshot + if ( qcowNumSnapshots == 0 ) { + qcowSnapshot = true; + } else { + qcowSnapshot = false; + } + + return qcowSnapshot; + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int qcowVersion = DiskImageUtils.readInt( diskFile, 4 ); + + // check QCOW2 file format version + if ( qcowVersion < 2 || qcowVersion > 3 ) { + // QCOW2 disk image does not contain a valid QCOW2 version + final String errorMsg = new String( "Invalid QCOW2 version in header found!" ); + throw new DiskImageException( errorMsg ); + } + + return DiskImageUtils.versionFromMajor( Integer.valueOf( qcowVersion ).shortValue() ); + } + + @Override + public String getDescription() throws DiskImageException + { + // QCOW2 disk image format does not support any disk description + return null; + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.QCOW2; + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageUtils.java b/src/main/java/org/openslx/vm/disk/DiskImageUtils.java new file mode 100644 index 0000000..fbed6f9 --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageUtils.java @@ -0,0 +1,154 @@ +package org.openslx.vm.disk; + +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * Utilities to parse disk image format elements and control versions of disk images. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageUtils +{ + /** + * Returns the size of a specified disk image file. + * + * @param diskImage file to a disk storing the image content. + * @return size of the disk image file in bytes. + * + * @throws DiskImageException unable to obtain the size of the disk image file. + */ + public static long getImageSize( RandomAccessFile diskImage ) throws DiskImageException + { + final long imageSize; + + try { + imageSize = diskImage.length(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + + return imageSize; + } + + /** + * Reads two bytes ({@link Short}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the two bytes. + * @return value of the two bytes from the disk image file as {@link Short}. + * + * @throws DiskImageException unable to read two bytes from the disk image file. + */ + public static short readShort( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + short value = 0; + + if ( imageSize >= ( offset + Short.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readShort(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads four bytes ({@link Integer}) at a given offset from the specified disk + * image file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the four bytes. + * @return value of the four bytes from the disk image file as {@link Integer}. + * + * @throws DiskImageException unable to read four bytes from the disk image file. + */ + public static int readInt( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + int value = 0; + + if ( imageSize >= ( offset + Integer.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readInt(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads eight bytes ({@link Long}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the eight bytes. + * @return value of the eight bytes from the disk image file as {@link Long}. + * + * @throws DiskImageException unable to read eight bytes from the disk image file. + */ + public static long readLong( RandomAccessFile diskImage, long offset ) throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + long value = 0; + + if ( imageSize >= ( offset + Long.BYTES ) ) { + try { + diskImage.seek( offset ); + value = diskImage.readLong(); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return value; + } + + /** + * Reads two bytes ({@link Short}) at a given offset from the specified disk image + * file. + * + * @param diskImage file to a disk storing the image content. + * @param offset offset in bytes for reading the two bytes. + * @return value of the two bytes from the disk image file as {@link Short}. + * + * @throws DiskImageException unable to read two bytes from the disk image file. + */ + public static String readBytesAsString( RandomAccessFile diskImage, long offset, int numBytes ) + throws DiskImageException + { + final long imageSize = DiskImageUtils.getImageSize( diskImage ); + byte values[] = {}; + + if ( imageSize >= ( offset + numBytes ) ) { + try { + diskImage.seek( offset ); + values = new byte[ numBytes ]; + diskImage.readFully( values ); + } catch ( IOException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } + + return new String( values ); + } + + public static int versionFromMajorMinor( final short major, final short minor ) + { + return ( ( Integer.valueOf( major ) << 16 ) | minor ); + } + + public static int versionFromMajor( final short major ) + { + return DiskImageUtils.versionFromMajorMinor( major, Short.valueOf( "0" ) ); + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageVdi.java b/src/main/java/org/openslx/vm/disk/DiskImageVdi.java new file mode 100644 index 0000000..1c34c1d --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageVdi.java @@ -0,0 +1,119 @@ +package org.openslx.vm.disk; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * VDI disk image for virtual machines. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageVdi extends DiskImage +{ + /** + * Big endian representation of the little endian VDI magic bytes (signature). + */ + private static final int VDI_MAGIC = 0x7f10dabe; + + /** + * Creates a new VDI disk image from an existing VDI image file. + * + * @param diskImage file to a VDI disk storing the image content. + * + * @throws FileNotFoundException cannot find specified VDI disk image file. + * @throws IOException cannot access the content of the VDI disk image file. + */ + public DiskImageVdi( File diskImage ) throws FileNotFoundException, IOException + { + super( diskImage ); + } + + /** + * Creates a new VDI disk image from an existing VDI image file. + * + * @param diskImage file to a VDI disk storing the image content. + */ + public DiskImageVdi( RandomAccessFile diskImage ) + { + super( diskImage ); + } + + /** + * Probe specified file with unknown format to be a VDI disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a VDI disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isVdiImageFormat; + + // goto the beginning of the disk image to read the magic bytes + // skip first 64 bytes (opening tag) + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 64 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageVdi.VDI_MAGIC ) { + isVdiImageFormat = true; + } else { + isVdiImageFormat = false; + } + + return isVdiImageFormat; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + // VDI does not seem to support split VDI files, so VDI files are always standalone + return true; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + // compression is done by sparsifying the disk files, there is no flag for it + return false; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + + // if parent UUID is set, the VDI file is a snapshot + final String parentUuid = DiskImageUtils.readBytesAsString( diskFile, 440, 16 ); + final String zeroUuid = new String( new byte[ 16 ] ); + + return !zeroUuid.equals( parentUuid ); + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + + final short vdiVersionMajor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 68 ) ); + final short vdiVersionMinor = Short.reverseBytes( DiskImageUtils.readShort( diskFile, 70 ) ); + + return DiskImageUtils.versionFromMajorMinor( vdiVersionMajor, vdiVersionMinor ); + } + + @Override + public String getDescription() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + return DiskImageUtils.readBytesAsString( diskFile, 84, 256 ); + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.VDI; + } +} diff --git a/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java b/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java new file mode 100644 index 0000000..c9bfdbf --- /dev/null +++ b/src/main/java/org/openslx/vm/disk/DiskImageVmdk.java @@ -0,0 +1,298 @@ +package org.openslx.vm.disk; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.openslx.util.Util; +import org.openslx.vm.UnsupportedVirtualizerFormatException; +import org.openslx.vm.VmwareConfig; + +/** + * VMDK (sparse extent) disk image for virtual machines. + * + * @author Manuel Bentele + * @version 1.0 + */ +public class DiskImageVmdk extends DiskImage +{ + /** + * Big endian representation of the little endian magic bytes KDMV. + */ + private static final int VMDK_MAGIC = 0x4b444d56; + + /** + * Size of a VMDK disk image data cluster in bytes. + */ + private static final int VMDK_SECTOR_SIZE = 512; + + /** + * Default hardware version of a VMDK disk image. + */ + private static final int VMDK_DEFAULT_HW_VERSION = 10; + + /** + * Stores disk configuration if VMDK disk image contains an embedded descriptor file. + */ + private final VmwareConfig vmdkConfig; + + /** + * Creates a new VMDK disk image from an existing VMDK image file. + * + * @param diskImage file to a VMDK disk storing the image content. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + * @throws FileNotFoundException cannot find specified VMDK disk image file. + * @throws IOException cannot access the content of the VMDK disk image file. + */ + public DiskImageVmdk( File diskImage ) throws DiskImageException, FileNotFoundException, IOException + { + super( diskImage ); + + this.vmdkConfig = this.parseVmdkConfig(); + } + + /** + * Creates a new VMDK disk image from an existing VMDK image file. + * + * @param diskImage file to a VMDK disk storing the image content. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + */ + public DiskImageVmdk( RandomAccessFile diskImage ) throws DiskImageException + { + super( diskImage ); + + this.vmdkConfig = this.parseVmdkConfig(); + } + + /** + * Probe specified file with unknown format to be a VMDK disk image file. + * + * @param diskImage file with unknown format that should be probed. + * @return state whether file is a VMDK disk image or not. + * + * @throws DiskImageException cannot probe specified file with unknown format. + */ + public static boolean probe( RandomAccessFile diskImage ) throws DiskImageException + { + final boolean isVmdkImageFormat; + + // goto the beginning of the disk image to read the magic bytes + final int diskImageMagic = DiskImageUtils.readInt( diskImage, 0 ); + + // check if disk image's magic bytes can be found + if ( diskImageMagic == DiskImageVmdk.VMDK_MAGIC ) { + isVmdkImageFormat = true; + } else { + isVmdkImageFormat = false; + } + + return isVmdkImageFormat; + } + + /** + * Returns the creation type from the VMDK's embedded descriptor file. + * + * @return creation type from the VMDK's embedded descriptor file. + */ + private String getCreationType() + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final String vmdkCreationType; + + if ( vmdkConfig == null ) { + // VMDK disk image does not contain any descriptor file + // assume that the file is not stand alone + vmdkCreationType = null; + } else { + // VMDK disk image contains a descriptor file + // get creation type from the content of the descriptor file + vmdkCreationType = this.vmdkConfig.get( "createType" ); + } + + return vmdkCreationType; + } + + /** + * Parse the configuration of the VMDK's embedded descriptor file. + * + * @return parsed configuration of the VMDK's embedded descriptor file. + * + * @throws DiskImageException parsing of the VMDK's embedded descriptor file failed. + */ + protected VmwareConfig parseVmdkConfig() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final VmwareConfig vmdkConfig; + + // get offset and size of descriptor file embedded into the VMDK disk image + final long vmdkDescriptorSectorOffset = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 28 ) ); + final long vmdkDescriptorSectorSize = Long.reverseBytes( DiskImageUtils.readLong( diskFile, 36 ) ); + + if ( vmdkDescriptorSectorOffset > 0 ) { + // get content of descriptor file embedded into the VMDK disk image + final long vmdkDescriptorOffset = vmdkDescriptorSectorOffset * DiskImageVmdk.VMDK_SECTOR_SIZE; + final long vmdkDescriptorSizeMax = vmdkDescriptorSectorSize * DiskImageVmdk.VMDK_SECTOR_SIZE; + final String descriptorStr = DiskImageUtils.readBytesAsString( diskFile, vmdkDescriptorOffset, + Long.valueOf( vmdkDescriptorSizeMax ).intValue() ); + + // get final length of the content within the sectors to be able to trim all 'zero' characters + final int vmdkDescriptorSize = descriptorStr.indexOf( 0 ); + + // if final length of the content is invalid, throw an exception + if ( vmdkDescriptorSize > vmdkDescriptorSizeMax || vmdkDescriptorSize < 0 ) { + final String errorMsg = new String( "Embedded descriptor size in VMDK disk image is invalid!" ); + throw new DiskImageException( errorMsg ); + } + + // trim all 'zero' characters at the end of the descriptor content to avoid errors during parsing + final String configStr = descriptorStr.substring( 0, vmdkDescriptorSize ); + + // create configuration instance from content of the descriptor file + try { + vmdkConfig = new VmwareConfig( configStr.getBytes(), vmdkDescriptorSize ); + } catch ( UnsupportedVirtualizerFormatException e ) { + throw new DiskImageException( e.getLocalizedMessage() ); + } + } else { + // there is no descriptor file embedded into the VMDK disk image + vmdkConfig = null; + } + + return vmdkConfig; + } + + /** + * Returns parsed configuration of the VMDK's embedded descriptor file. + * + * @return parsed configuration of the VMDK's embedded descriptor file. + */ + protected VmwareConfig getVmdkConfig() + { + return this.vmdkConfig; + } + + /** + * Returns the hardware version from the VMDK's embedded descriptor file. + * + * If the VMDK's embedded descriptor file does not contain any hardware version configuration + * entry, the default hardware version (see {@link #VMDK_DEFAULT_HW_VERSION}) is returned. + * + * @return hardware version from the VMDK's embedded descriptor file. + * + * @throws DiskImageException + */ + public int getHwVersion() throws DiskImageException + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final int hwVersion; + + if ( vmdkConfig != null ) { + // VMDK image contains a hardware version, so return parsed hardware version + // if hardware version cannot be parsed, return default hardware version + final String hwVersionStr = vmdkConfig.get( "ddb.virtualHWVersion" ); + + final int hwVersionMajor = Util.parseInt( hwVersionStr, DiskImageVmdk.VMDK_DEFAULT_HW_VERSION ); + hwVersion = DiskImageUtils.versionFromMajor( Integer.valueOf( hwVersionMajor ).shortValue() ); + } else { + // VMDK image does not contain any hardware version, so return default hardware version + final int hwVersionMajor = DiskImageVmdk.VMDK_DEFAULT_HW_VERSION; + hwVersion = DiskImageUtils.versionFromMajor( Integer.valueOf( hwVersionMajor ).shortValue() ); + } + + return hwVersion; + } + + @Override + public boolean isStandalone() throws DiskImageException + { + final String vmdkCreationType = this.getCreationType(); + final boolean vmdkStandalone; + + if ( vmdkCreationType != null ) { + // creation type is defined, so check if VMDK disk image is a snapshot + if ( this.isSnapshot() ) { + // VMDK disk image is a snapshot and not stand alone + vmdkStandalone = false; + } else { + // VMDK disk image is not a snapshot + // determine stand alone disk image property + vmdkStandalone = vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) || + vmdkCreationType.equalsIgnoreCase( "monolithicSparse" ); + } + } else { + // creation type is not defined + // assume that the file is not stand alone + vmdkStandalone = false; + } + + return vmdkStandalone; + } + + @Override + public boolean isCompressed() throws DiskImageException + { + final String vmdkCreationType = this.getCreationType(); + final boolean vmdkCompressed; + + if ( vmdkCreationType != null && vmdkCreationType.equalsIgnoreCase( "streamOptimized" ) ) { + // creation type is defined, and VMDK disk image is compressed + vmdkCompressed = true; + } else { + // creation type for compression is not defined + // assume that the file is not compressed + vmdkCompressed = false; + } + + return vmdkCompressed; + } + + @Override + public boolean isSnapshot() throws DiskImageException + { + final VmwareConfig vmdkConfig = this.getVmdkConfig(); + final boolean vmdkSnapshot; + + if ( vmdkConfig == null ) { + // VMDK disk image does not contain any descriptor file + // assume that the file is not a snapshot + vmdkSnapshot = false; + } else { + // get parent CID to determine snapshot disk image property + final String parentCid = vmdkConfig.get( "parentCID" ); + + if ( parentCid != null && !parentCid.equalsIgnoreCase( "ffffffff" ) ) { + // link to parent content identifier is defined, so VMDK disk image is a snapshot + vmdkSnapshot = true; + } else { + // link to parent content identifier is not defined, so VMDK disk image is not a snapshot + vmdkSnapshot = false; + } + } + + return vmdkSnapshot; + } + + @Override + public int getVersion() throws DiskImageException + { + final RandomAccessFile diskFile = this.getDiskImage(); + final int vmdkVersion = Integer.reverseBytes( DiskImageUtils.readInt( diskFile, 4 ) ); + + return DiskImageUtils.versionFromMajor( Integer.valueOf( vmdkVersion ).shortValue() ); + } + + @Override + public String getDescription() throws DiskImageException + { + return null; + } + + @Override + public ImageFormat getFormat() + { + return ImageFormat.VMDK; + } +} -- cgit v1.2.3-55-g7522