Skip to content

Commit

Permalink
Fix OVF XML disk parsing (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schamper committed Oct 6, 2023
1 parent ac6628c commit 0f04ac2
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 5 deletions.
19 changes: 14 additions & 5 deletions dissect/hypervisor/descriptor/ovf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from xml.etree import ElementTree
from typing import Iterator, TextIO
from xml.etree.ElementTree import Element

from defusedxml import ElementTree


class OVF:
Expand All @@ -11,9 +14,9 @@ class OVF:
DISK_XPATH = "ovf:DiskSection/ovf:Disk"
DISK_DRIVE_XPATH = 'ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item/[rasd:ResourceType="17"]'

def __init__(self, fh):
def __init__(self, fh: TextIO):
self.fh = fh
self.xml = ElementTree.fromstring(fh.read())
self.xml: Element = ElementTree.fromstring(fh.read())

self.references = {}
for file_ in self.xml.findall(self.FILE_XPATH, self.NS):
Expand All @@ -27,12 +30,18 @@ def __init__(self, fh):
file_ref = disk.get("{{{ovf}}}fileRef".format(**self.NS))
self._disks[disk_id] = self.references[file_ref]

def disks(self):
def disks(self) -> Iterator[str]:
for disk in self.xml.findall(self.DISK_DRIVE_XPATH, self.NS):
resource = disk.find("{{{rasd}}}HostResource".format(**self.NS))
_, _, xpath = resource.text.partition(":")
xpath = resource.text
if xpath.startswith("ovf:"):
xpath = xpath[4:]

if xpath.startswith("/disk/"):
disk_ref = xpath.split("/")[-1]
yield self._disks[disk_ref]
elif xpath.startswith("/file/"):
file_ref = xpath.split("/")[-1]
yield self.references[file_ref]
else:
raise NotImplementedError(resource)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ classifiers = [
"Topic :: Utilities",
]
dependencies = [
"defusedxml",
"dissect.cstruct>=3.0.dev,<4.0.dev",
"dissect.util>=3.0.dev,<4.0.dev",
]
Expand Down
168 changes: 168 additions & 0 deletions tests/test_ovf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from io import StringIO

from dissect.hypervisor.descriptor.ovf import OVF

# Handcrafted to include some test cases
TEST_OVF = """<?xml version="1.0"?>
<Envelope ovf:version="1.0" xml:lang="en-US" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vbox="http://www.virtualbox.org/ovf/machine">
<References>
<File ovf:id="file1" ovf:href="disk1.vmdk"/>
<File ovf:id="file2" ovf:href="disk2.vmdk"/>
<File ovf:id="file3" ovf:href="disk3.vmdk"/>
</References>
<DiskSection>
<Info>List of the virtual disks used in the package</Info>
<Disk ovf:capacity="1024" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" vbox:uuid="d3f38cfc-d512-425c-9ca8-5c046492a9a8"/>
<Disk ovf:capacity="1024" ovf:diskId="vmdisk2" ovf:fileRef="file2" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" vbox:uuid="663d3136-5211-4d29-aacf-b4ef38be5fa2"/>
</DiskSection>
<NetworkSection>
<Info>Logical networks used in the package</Info>
<Network ovf:name="NAT">
<Description>Logical network used by this appliance.</Description>
</Network>
</NetworkSection>
<VirtualSystem ovf:id="Test OVF">
<Info>A virtual machine</Info>
<OperatingSystemSection ovf:id="100">
<Info>The kind of installed guest operating system</Info>
<Description>Linux26_64</Description>
<vbox:OSType ovf:required="false">Linux26_64</vbox:OSType>
</OperatingSystemSection>
<VirtualHardwareSection>
<Info>Virtual hardware requirements for a virtual machine</Info>
<System>
<vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
<vssd:InstanceID>0</vssd:InstanceID>
<vssd:VirtualSystemIdentifier>Test OVF</vssd:VirtualSystemIdentifier>
<vssd:VirtualSystemType>virtualbox-2.2</vssd:VirtualSystemType>
</System>
<Item>
<rasd:Caption>1 virtual CPU</rasd:Caption>
<rasd:Description>Number of virtual CPUs</rasd:Description>
<rasd:ElementName>1 virtual CPU</rasd:ElementName>
<rasd:InstanceID>1</rasd:InstanceID>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
<rasd:Caption>1536 MB of memory</rasd:Caption>
<rasd:Description>Memory Size</rasd:Description>
<rasd:ElementName>1536 MB of memory</rasd:ElementName>
<rasd:InstanceID>2</rasd:InstanceID>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:VirtualQuantity>1536</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Caption>sataController0</rasd:Caption>
<rasd:Description>SATA Controller</rasd:Description>
<rasd:ElementName>sataController0</rasd:ElementName>
<rasd:InstanceID>3</rasd:InstanceID>
<rasd:ResourceSubType>AHCI</rasd:ResourceSubType>
<rasd:ResourceType>20</rasd:ResourceType>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Caption>usb</rasd:Caption>
<rasd:Description>USB Controller</rasd:Description>
<rasd:ElementName>usb</rasd:ElementName>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:ResourceType>23</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>3</rasd:AddressOnParent>
<rasd:AutomaticAllocation>false</rasd:AutomaticAllocation>
<rasd:Caption>sound</rasd:Caption>
<rasd:Description>Sound Card</rasd:Description>
<rasd:ElementName>sound</rasd:ElementName>
<rasd:InstanceID>5</rasd:InstanceID>
<rasd:ResourceSubType>ensoniq1371</rasd:ResourceSubType>
<rasd:ResourceType>35</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:Caption>disk1</rasd:Caption>
<rasd:Description>Disk Image</rasd:Description>
<rasd:ElementName>disk1</rasd:ElementName>
<rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
<rasd:InstanceID>6</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>1</rasd:AddressOnParent>
<rasd:Caption>disk2</rasd:Caption>
<rasd:Description>Disk Image</rasd:Description>
<rasd:ElementName>disk2</rasd:ElementName>
<rasd:HostResource>ovf:/disk/vmdisk2</rasd:HostResource>
<rasd:InstanceID>8</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>2</rasd:AddressOnParent>
<rasd:Caption>disk3</rasd:Caption>
<rasd:Description>Disk Image</rasd:Description>
<rasd:ElementName>disk3</rasd:ElementName>
<rasd:HostResource>ovf:/file/file3</rasd:HostResource>
<rasd:InstanceID>9</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item>
<Item>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Caption>Ethernet adapter on 'NAT'</rasd:Caption>
<rasd:Connection>NAT</rasd:Connection>
<rasd:ElementName>Ethernet adapter on 'NAT'</rasd:ElementName>
<rasd:InstanceID>7</rasd:InstanceID>
<rasd:ResourceType>10</rasd:ResourceType>
</Item>
</VirtualHardwareSection>
<vbox:Machine ovf:required="false" version="1.19-linux" uuid="{68383d3a-9a23-4789-a580-4f0a79437fd6}" name="Test OVF" OSType="Linux26_64" snapshotFolder="Snapshots" lastStateChange="2023-09-20T12:15:55Z">
<ovf:Info>Complete VirtualBox machine configuration in VirtualBox format</ovf:Info>
<Hardware>
<CPU>
<PAE enabled="true"/>
<LongMode enabled="true"/>
<X2APIC enabled="true"/>
<HardwareVirtExLargePages enabled="true"/>
</CPU>
<Memory RAMSize="1536"/>
<HID Pointing="USBTablet"/>
<Display controller="VMSVGA" VRAMSize="32"/>
<BIOS>
<IOAPIC enabled="true"/>
<SmbiosUuidLittleEndian enabled="true"/>
</BIOS>
<USB>
<Controllers>
<Controller name="OHCI" type="OHCI"/>
<Controller name="EHCI" type="EHCI"/>
</Controllers>
</USB>
<Network>
<Adapter slot="0" enabled="true" MACAddress="FFFFFFFFFFFF" type="virtio">
<NAT/>
</Adapter>
</Network>
<AudioAdapter driver="ALSA" enabled="true" enabledOut="true"/>
<RTC localOrUTC="UTC"/>
<Clipboard/>
<StorageControllers>
<StorageController name="SATA" type="AHCI" PortCount="4" useHostIOCache="true" Bootable="true" IDE0MasterEmulationPort="0" IDE0SlaveEmulationPort="1" IDE1MasterEmulationPort="2" IDE1SlaveEmulationPort="3">
<AttachedDevice type="HardDisk" hotpluggable="false" port="0" device="0">
<Image uuid="{d3f38cfc-d512-425c-9ca8-5c046492a9a8}"/>
</AttachedDevice>
</StorageController>
</StorageControllers>
</Hardware>
</vbox:Machine>
</VirtualSystem>
</Envelope>
""" # noqa


def test_ovf():
ovf = OVF(StringIO(TEST_OVF))
assert list(ovf.disks()) == ["disk1.vmdk", "disk2.vmdk", "disk3.vmdk"]

0 comments on commit 0f04ac2

Please sign in to comment.