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/util/vm/VboxMetaData.java | 535 --------------------- 1 file changed, 535 deletions(-) delete mode 100644 src/main/java/org/openslx/util/vm/VboxMetaData.java (limited to 'src/main/java/org/openslx/util/vm/VboxMetaData.java') diff --git a/src/main/java/org/openslx/util/vm/VboxMetaData.java b/src/main/java/org/openslx/util/vm/VboxMetaData.java deleted file mode 100644 index 82936a7..0000000 --- a/src/main/java/org/openslx/util/vm/VboxMetaData.java +++ /dev/null @@ -1,535 +0,0 @@ -package org.openslx.util.vm; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map.Entry; -import java.util.UUID; - -import org.apache.log4j.Logger; -import org.openslx.bwlp.thrift.iface.OperatingSystem; -import org.openslx.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.w3c.dom.Attr; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -class VBoxSoundCardMeta -{ - public final boolean isPresent; - public final String value; - - public VBoxSoundCardMeta( boolean present, String val ) - { - isPresent = present; - value = val; - } -} - -class VBoxDDAccelMeta -{ - public final boolean isPresent; - - public VBoxDDAccelMeta( boolean present ) - { - isPresent = present; - } -} - -class VBoxHWVersionMeta -{ - public final int version; - - public VBoxHWVersionMeta( int vers ) - { - version = vers; - } -} - -class VBoxEthernetDevTypeMeta -{ - public final String value; - public final boolean isPresent; - - public VBoxEthernetDevTypeMeta( boolean present, String val ) - { - value = val; - isPresent = present; - } -} - -class VBoxUsbSpeedMeta -{ - public final String value; - public final int speed; - public VBoxUsbSpeedMeta( String value, int speed ) - { - this.value = value; - this.speed = speed; - } -} - -public class VboxMetaData extends VmMetaData -{ - /** - * List of supported image formats by the VirtualBox hypervisor. - */ - private static final List SUPPORTED_IMAGE_FORMATS = Collections.unmodifiableList( - Arrays.asList( ImageFormat.VDI ) ); - - private static final Logger LOGGER = Logger.getLogger( VboxMetaData.class ); - - private static final Virtualizer virtualizer = new Virtualizer( TConst.VIRT_VIRTUALBOX, "VirtualBox" ); - - private final VboxConfig config; - - public static enum EthernetType - { - NAT( "vboxnet1" ), BRIDGED( "vboxnet0" ), HOST_ONLY( "vboxnet2" ); - - public final String vnet; - - private EthernetType( String vnet ) - { - this.vnet = vnet; - } - } - - public VboxMetaData( List osList, File file ) throws IOException, UnsupportedVirtualizerFormatException - { - super( osList ); - this.config = new VboxConfig( file ); - init(); - } - - public VboxMetaData( List osList, byte[] vmContent, int length ) throws IOException, UnsupportedVirtualizerFormatException - { - super( osList ); - this.config = new VboxConfig( vmContent, length ); - init(); - } - - private void init() - { - registerVirtualHW(); - displayName = config.getDisplayName(); - setOs( config.getOsName() ); - this.isMachineSnapshot = config.isMachineSnapshot(); - for ( HardDisk hardDisk : config.getHdds() ) { - hdds.add( hardDisk ); - } - } - - @Override - public Virtualizer getVirtualizer() - { - return virtualizer; - } - - @Override - public List getSupportedImageFormats() - { - return VboxMetaData.SUPPORTED_IMAGE_FORMATS; - } - - @Override - public void applySettingsForLocalEdit() - { - // TODO Auto-generated method stub - } - - @Override - public byte[] getDefinitionArray() - { - return config.toString( false ).getBytes( StandardCharsets.UTF_8 ); - } - - @Override - public byte[] getFilteredDefinitionArray() - { - return config.toString( false ).getBytes( StandardCharsets.UTF_8 ); - } - - @Override - public boolean addHddTemplate( String diskImage, String hddMode, String redoDir ) - { - config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk[@location='" + PlaceHolder.HDDLOCATION.toString() + "']", "location", diskImage ); - config.changeAttribute( "/VirtualBox/Machine", "snapshotFolder", redoDir ); - return true; - } - - @Override - public boolean addHddTemplate( File diskImage, String hddMode, String redoDir ) - { - String diskImagePath = diskImage.getName(); - config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "location", diskImagePath ); - - UUID newhdduuid = UUID.randomUUID(); - - // patching the new uuid in the vbox config file here - String vboxUUid = "{" + newhdduuid.toString() + "}"; - config.changeAttribute( "/VirtualBox/Machine/MediaRegistry/HardDisks/HardDisk", "uuid", vboxUUid ); - config.changeAttribute( "/VirtualBox/Machine/StorageControllers/StorageController/AttachedDevice/Image", "uuid", vboxUUid ); - - // the order of the UUID is BIG_ENDIAN but we need to change the order of the first 8 Bytes - // to be able to write them to the vdi file... the PROBLEM here is that the first 8 - // are in LITTLE_ENDIAN order in pairs of 4-2-2 not the whole 8 so just changing the - // order when we are adding them to the bytebuffer won't help - // - // the following is a workaround that works - ByteBuffer buffer = ByteBuffer.wrap( new byte[ 16 ] ); - buffer.putLong( newhdduuid.getMostSignificantBits() ); - buffer.putLong( newhdduuid.getLeastSignificantBits() ); - byte[] oldOrder = buffer.array(); - // make a coppy here because the last 8 Bytes don't need to change position - byte[] bytesToWrite = Arrays.copyOf( oldOrder, oldOrder.length ); - // use an offset int[] to help with the shuffle - int[] offsets = { 3, 2, 1, 0, 5, 4, 7, 6 }; - for ( int index = 0; index < 8; index++ ) { - bytesToWrite[index] = oldOrder[offsets[index]]; - } - try ( RandomAccessFile file = new RandomAccessFile( diskImage, "rw" ) ) { - file.seek( 392 ); - file.write( bytesToWrite, 0, 16 ); - } catch ( Exception e ) { - LOGGER.warn( "could not patch new uuid in the vdi", e ); - } - - // we need a new machine uuid - UUID newMachineUuid = UUID.randomUUID(); - if ( newMachineUuid.equals( newhdduuid ) ) { - LOGGER.warn( "The new Machine UUID is the same as the new HDD UUID; tying again...this vm might not start" ); - newMachineUuid = UUID.randomUUID(); - } - String machineUUid = "{" + newMachineUuid.toString() + "}"; - return config.changeAttribute( "/VirtualBox/Machine", "uuid", machineUUid ); - } - - @Override - public boolean addDefaultNat() - { - if ( config.addNewNode( "/VirtualBox/Machine/Hardware/Network/Adapter", "NAT" ) == null ) { - LOGGER.error( "Failed to set network adapter to NAT." ); - return false; - } - return config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter", "MACAddress", "080027B86D12" ); - } - - @Override - public void setOs( String vendorOsId ) - { - config.changeAttribute( "/VirtualBox/Machine", "OSType", vendorOsId ); - setOs( TConst.VIRT_VIRTUALBOX, vendorOsId ); - } - - @Override - public boolean addDisplayName( String name ) - { - return config.changeAttribute( "/VirtualBox/Machine", "name", name ); - } - - @Override - public boolean addRam( int mem ) - { - return config.changeAttribute( "/VirtualBox/Machine/Hardware/Memory", "RAMSize", Integer.toString( mem ) ); - } - - @Override - public void addFloppy( int index, String image, boolean readOnly ) - { - Element floppyController = null; - NodeList matches = (NodeList)config.findNodes( "/VirtualBox/Machine/StorageControllers/StorageController[@name='Floppy']" ); - if ( matches == null || matches.getLength() == 0 ) { - floppyController = (Element)config.addNewNode( "/VirtualBox/Machine/StorageControllers", "StorageController" ); - if ( floppyController == null ) { - LOGGER.error( "Failed to add to floppy device." ); - return; - } - floppyController.setAttribute( "name", "Floppy" ); - floppyController.setAttribute( "type", "I82078" ); - floppyController.setAttribute( "PortCount", "1" ); - floppyController.setAttribute( "useHostIOCache", "true" ); - floppyController.setAttribute( "Bootable", "false" ); - } - // virtualbox only allows one controller per type - if ( matches.getLength() > 1 ) { - LOGGER.error( "Multiple floppy controllers detected, this should never happen! " ); - return; - } - // so if we had any matches, we know we have exactly one - if ( floppyController == null ) - floppyController = (Element)matches.item( 0 ); - - // add the floppy device - Element floppyDevice = (Element)config.addNewNode( floppyController, "AttachedDevice" ); - if ( floppyDevice == null ) { - LOGGER.error( "Failed to add to floppy device." ); - return; - } - floppyDevice.setAttribute( "type", "Floppy" ); - floppyDevice.setAttribute( "hotpluggable", "false" ); - floppyDevice.setAttribute( "port", "0" ); - floppyDevice.setAttribute( "device", Integer.toString( index ) ); - - // finally add the image to it, if one was given - if ( image != null ) { - Element floppyImage = (Element)config.addNewNode( floppyDevice, "Image" ); - if ( floppyImage == null ) { - LOGGER.error( "Failed to add to floppy device." ); - return; - } - floppyImage.setAttribute( "uuid", VboxConfig.PlaceHolder.FLOPPYUUID.toString() ); - // register the image in the media registry - Element floppyImages = (Element)config.addNewNode( "/VirtualBox/Machine/MediaRegistry", "FloppyImages" ); - if ( floppyImages == null ) { - LOGGER.error( "Failed to add to media registry." ); - return; - } - Element floppyImageReg = (Element)config.addNewNode( "/VirtualBox/Machine/MediaRegistry/FloppyImages", "Image" ); - if ( floppyImageReg == null ) { - LOGGER.error( "Failed to add to floppy images in the media registry." ); - return; - } - floppyImageReg.setAttribute( "uuid", VboxConfig.PlaceHolder.FLOPPYUUID.toString() ); - floppyImageReg.setAttribute( "location", VboxConfig.PlaceHolder.FLOPPYLOCATION.toString() ); - } - } - - @Override - public boolean addCdrom( String image ) - { - // TODO - done in run-virt currently - return false; - } - - @Override - public boolean addCpuCoreCount( int nrOfCores ) - { - return config.changeAttribute( "/VirtualBox/Machine/Hardware/CPU", "count", Integer.toString( nrOfCores ) ); - } - - @Override - public void setSoundCard( org.openslx.util.vm.VmMetaData.SoundCardType type ) - { - VBoxSoundCardMeta sound = soundCards.get( type ); - config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "enabled", Boolean.toString( sound.isPresent ) ); - config.changeAttribute( "/VirtualBox/Machine/Hardware/AudioAdapter", "controller", sound.value ); - } - - @Override - public VmMetaData.SoundCardType getSoundCard() - { - // initialize here to type None to avoid all null pointer exceptions thrown for unknown user written sound cards - VmMetaData.SoundCardType returnsct = VmMetaData.SoundCardType.NONE; - Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/AudioAdapter" ).item( 0 ); - if ( !x.hasAttribute( "enabled" ) || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) { - return returnsct; - } else { - // extra separate case for the non-existing argument} - if ( !x.hasAttribute( "controller" ) ) { - returnsct = VmMetaData.SoundCardType.AC; - } else { - String controller = x.getAttribute( "controller" ); - VBoxSoundCardMeta soundMeta = null; - for ( VmMetaData.SoundCardType type : VmMetaData.SoundCardType.values() ) { - soundMeta = soundCards.get( type ); - if ( soundMeta != null ) { - if ( controller.equals( soundMeta.value ) ) { - returnsct = type; - } - } - } - } - } - return returnsct; - } - - @Override - public void setDDAcceleration( VmMetaData.DDAcceleration type ) - { - VBoxDDAccelMeta accel = ddacc.get( type ); - config.changeAttribute( "/VirtualBox/Machine/Hardware/Display", "accelerate3D", Boolean.toString( accel.isPresent ) ); - } - - @Override - public VmMetaData.DDAcceleration getDDAcceleration() - { - VmMetaData.DDAcceleration returndda = null; - Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Display" ).item( 0 ); - if ( x.hasAttribute( "accelerate3D" ) ) { - if ( x.getAttribute( "accelerate3D" ).equals( "true" ) ) { - returndda = VmMetaData.DDAcceleration.ON; - } else { - returndda = VmMetaData.DDAcceleration.OFF; - } - } else { - returndda = VmMetaData.DDAcceleration.OFF; - } - return returndda; - } - - /** - * Function does nothing for Virtual Box; - * Virtual Box accepts per default only one hardware version and is hidden from the user - */ - @Override - public void setHWVersion( HWVersion type ) - { - } - - @Override - public VmMetaData.HWVersion getHWVersion() - { - // Virtual Box uses only one virtual hardware version and can't be changed - return VmMetaData.HWVersion.DEFAULT; - } - - @Override - public void setEthernetDevType( int cardIndex, EthernetDevType type ) - { - String index = "0"; - VBoxEthernetDevTypeMeta nic = networkCards.get( type ); - // cardIndex is not used yet...maybe later needed for different network cards - config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "enabled", Boolean.toString( nic.isPresent ) ); - config.changeAttribute( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='" + index + "']", "type", nic.value ); - } - - @Override - public VmMetaData.EthernetDevType getEthernetDevType( int cardIndex ) - { - VmMetaData.EthernetDevType returnedt = VmMetaData.EthernetDevType.NONE; - Element x = (Element)config.findNodes( "/VirtualBox/Machine/Hardware/Network/Adapter" ).item( 0 ); - if ( !x.hasAttribute( "enabled" ) || ( x.hasAttribute( "enabled" ) && x.getAttribute( "enabled" ).equals( "false" ) ) ) { - return returnedt; - } else { - // extra separate case for the non-existing argument} - if ( !x.hasAttribute( "type" ) ) { - returnedt = VmMetaData.EthernetDevType.PCNETFAST3; - } else { - String temp = x.getAttribute( "type" ); - VBoxEthernetDevTypeMeta etherMeta = null; - for ( VmMetaData.EthernetDevType type : VmMetaData.EthernetDevType.values() ) { - etherMeta = networkCards.get( type ); - if ( etherMeta != null ) { - if ( temp.equals( etherMeta.value ) ) { - returnedt = type; - } - } - } - } - } - return returnedt; - } - - public void registerVirtualHW() - { - // none type needs to have a valid value; it takes the value of AC97; if value is left null or empty vm will not start because value is not valid - // TODO: Maybe just remove the entire section from the XML? Same for ethernet... - soundCards.put( VmMetaData.SoundCardType.NONE, new VBoxSoundCardMeta( false, "AC97" ) ); - soundCards.put( VmMetaData.SoundCardType.SOUND_BLASTER, new VBoxSoundCardMeta( true, "SB16" ) ); - soundCards.put( VmMetaData.SoundCardType.HD_AUDIO, new VBoxSoundCardMeta( true, "HDA" ) ); - soundCards.put( VmMetaData.SoundCardType.AC, new VBoxSoundCardMeta( true, "AC97" ) ); - - ddacc.put( VmMetaData.DDAcceleration.OFF, new VBoxDDAccelMeta( false ) ); - ddacc.put( VmMetaData.DDAcceleration.ON, new VBoxDDAccelMeta( true ) ); - - hwversion.put( VmMetaData.HWVersion.DEFAULT, new VBoxHWVersionMeta( 0 ) ); - - // none type needs to have a valid value; it takes the value of pcnetcpi2; if value is left null or empty vm will not start because value is not valid - networkCards.put( VmMetaData.EthernetDevType.NONE, new VBoxEthernetDevTypeMeta( false, "Am79C970A" ) ); - networkCards.put( VmMetaData.EthernetDevType.PCNETPCI2, new VBoxEthernetDevTypeMeta( true, "Am79C970A" ) ); - networkCards.put( VmMetaData.EthernetDevType.PCNETFAST3, new VBoxEthernetDevTypeMeta( true, "Am79C973" ) ); - networkCards.put( VmMetaData.EthernetDevType.PRO1000MTD, new VBoxEthernetDevTypeMeta( true, "82540EM" ) ); - networkCards.put( VmMetaData.EthernetDevType.PRO1000TS, new VBoxEthernetDevTypeMeta( true, "82543GC" ) ); - networkCards.put( VmMetaData.EthernetDevType.PRO1000MTS, new VBoxEthernetDevTypeMeta( true, "82545EM" ) ); - networkCards.put( VmMetaData.EthernetDevType.PARAVIRT, new VBoxEthernetDevTypeMeta( true, "virtio" ) ); - - usbSpeeds.put( VmMetaData.UsbSpeed.NONE, new VBoxUsbSpeedMeta( null, 0 ) ); - usbSpeeds.put( VmMetaData.UsbSpeed.USB1_1, new VBoxUsbSpeedMeta( "OHCI", 1 ) ); - usbSpeeds.put( VmMetaData.UsbSpeed.USB2_0, new VBoxUsbSpeedMeta( "EHCI", 2 ) ); - usbSpeeds.put( VmMetaData.UsbSpeed.USB3_0, new VBoxUsbSpeedMeta( "XHCI", 3 ) ); - } - - @Override - public boolean addEthernet( VmMetaData.EtherType type ) - { - Node hostOnlyInterfaceNode = config.addNewNode( "/VirtualBox/Machine/Hardware/Network/Adapter[@slot='0']", "HostOnlyInterface" ); - if ( hostOnlyInterfaceNode == null ) { - LOGGER.error( "Failed to create node for HostOnlyInterface." ); - return false; - } - return config.addAttributeToNode( hostOnlyInterfaceNode, "name", EthernetType.valueOf( type.name() ).vnet ); - } - - @Override - public boolean tweakForNonPersistent() - { - // Cannot disable suspend - // https://forums.virtualbox.org/viewtopic.php?f=6&t=77169 - // https://forums.virtualbox.org/viewtopic.php?f=8&t=80338 - // But some other stuff that won't make sense in non-persistent mode - config.setExtraData( "GUI/LastCloseAction", "PowerOff" ); - // Could use "Default" instead of "Last" above, but you won't get any confirmation dialog in that case - config.setExtraData( "GUI/RestrictedRuntimeHelpMenuActions", "All" ); - config.setExtraData( "GUI/RestrictedRuntimeMachineMenuActions", "TakeSnapshot,Pause,SaveState" ); - config.setExtraData( "GUI/RestrictedRuntimeMenus", "Help" ); - config.setExtraData( "GUI/PreventSnapshotOperations", "true" ); - config.setExtraData( "GUI/PreventApplicationUpdate", "true" ); - config.setExtraData( "GUI/RestrictedCloseActions", "SaveState,PowerOffRestoringSnapshot,Detach" ); - return true; - } - - @Override - public void setMaxUsbSpeed( VmMetaData.UsbSpeed speed ) - { - // Wipe existing ones - config.removeNodes( "/VirtualBox/Machine/Hardware", "USB" ); - if ( speed == null || speed == VmMetaData.UsbSpeed.NONE ) { - // Add marker so we know it's not an old config and we really want no USB - Element node = config.createNodeRecursive( "/VirtualBox/OpenSLX/USB" ); - if ( node != null ) { - node.setAttribute( "disabled", "true" ); - } - return; // NO USB - } - Element node = config.createNodeRecursive( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller" ); - VBoxUsbSpeedMeta vboxSpeed = usbSpeeds.get( speed ); - node.setAttribute( "type", vboxSpeed.value ); - node.setAttribute( "name", vboxSpeed.value ); - if ( speed == UsbSpeed.USB2_0 ) { - // If EHCI (2.0) is selected, VBox adds an OHCI controller too... - node.setAttribute( "type", "OHCI" ); - node.setAttribute( "name", "OHCI" ); - } - } - - @Override - public VmMetaData.UsbSpeed getMaxUsbSpeed() - { - NodeList nodes = config.findNodes( "/VirtualBox/Machine/Hardware/USB/Controllers/Controller/@type" ); - int maxSpeed = 0; - VmMetaData.UsbSpeed maxItem = VmMetaData.UsbSpeed.NONE; - for ( int i = 0; i < nodes.getLength(); ++i ) { - if ( nodes.item( i ).getNodeType() != Node.ATTRIBUTE_NODE ) { - LOGGER.info( "Not ATTRIBUTE type" ); - continue; - } - String type = ((Attr)nodes.item( i )).getValue(); - for ( Entry s : usbSpeeds.entrySet() ) { - if ( s.getValue().speed > maxSpeed && type.equals( s.getValue().value ) ) { - maxSpeed = s.getValue().speed; - maxItem = s.getKey(); - } - } - } - return maxItem; - } -} -- cgit v1.2.3-55-g7522