2014-02-06 04:09:26 +04:00
#
# Copyright 2009, 2013 Red Hat, Inc.
# Cole Robinson <crobinso@redhat.com>
#
2018-03-20 22:00:02 +03:00
# This work is licensed under the GNU GPLv2.
# See the COPYING file in the top-level directory.
2014-02-06 04:09:26 +04:00
#
import logging
import os
2018-02-13 23:53:07 +03:00
import xml . etree . ElementTree
2014-02-06 04:09:26 +04:00
import virtinst
2014-09-12 23:59:22 +04:00
from . formats import parser_class
2014-02-06 04:09:26 +04:00
# Mapping of ResourceType value to device type
# http://konkretcmpi.org/cim218/CIM_ResourceAllocationSettingData.html
#
# "Other" [1]
# "Computer System" [2]
# "Processor" [3]
# "Memory" [4]
# "IDE Controller" [5]
# "Parallel SCSI HBA" [6]
# "FC HBA" [7]
# "iSCSI HBA" [8]
# "IB HCA" [9]
# "Ethernet Adapter" [10]
# "Other Network Adapter" [11]
# "I/O Slot" [12]
# "I/O Device" [13]
# "Floppy Drive" [14]
# "CD Drive" [15]
# "DVD drive" [16]
# "Disk Drive" [17]
# "Tape Drive" [18]
# "Storage Extent" [19]
# "Other storage device" [20]
# "Serial port" [21]
# "Parallel port" [22]
# "USB Controller" [23]
# "Graphics controller" [24]
# "IEEE 1394 Controller" [25]
# "Partitionable Unit" [26]
# "Base Partitionable Unit" [27]
# "Power" [28]
# "Cooling Capacity" [29]
# "Ethernet Switch Port" [30]
DEVICE_CPU = " 3 "
DEVICE_MEMORY = " 4 "
DEVICE_IDE_BUS = " 5 "
DEVICE_SCSI_BUS = " 6 "
DEVICE_ETHERNET = " 10 "
DEVICE_DISK = " 17 "
DEVICE_GRAPHICS = " 24 "
# AllocationUnits mapping can be found in Appendix C here:
# http://www.dmtf.org/standards/documents/CIM/DSP0004.pdf
2018-02-13 23:53:07 +03:00
OVF_NAMESPACES = {
" ovf " : " http://schemas.dmtf.org/ovf/envelope/1 " ,
" ovfenv " : " http://schemas.dmtf.org/ovf/environment/1 " ,
" rasd " : " http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData " ,
" vssd " : " http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData " ,
" vmw " : " http://www.vmware.com/schema/ovf " ,
}
2014-02-06 04:09:26 +04:00
def _convert_alloc_val ( ignore , val ) :
# This is a hack, but should we really have to decode
# allocation units = "bytes * 2^20"?
val = float ( val )
if val > 100000000 :
# Assume bytes
return int ( round ( val / 1024.0 / 1024.0 ) )
elif val > 100000 :
# Assume kilobytes
return int ( round ( val / 1024.0 ) )
elif val < 32 :
2014-06-17 05:33:14 +04:00
# Assume GiB
2014-02-06 04:09:26 +04:00
return int ( val * 1024 )
return int ( val )
2018-02-13 23:53:07 +03:00
def _convert_bool_val ( val ) :
if str ( val ) . lower ( ) == " false " :
2014-02-06 04:09:26 +04:00
return False
2018-02-13 23:53:07 +03:00
elif str ( val ) . lower ( ) == " true " :
return True
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
return False
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
def _find ( _node , _xpath ) :
return _node . find ( _xpath , namespaces = OVF_NAMESPACES )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
def _findall ( _node , _xpath ) :
return _node . findall ( _xpath , namespaces = OVF_NAMESPACES )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
def _text ( _node ) :
if _node is not None :
return _node . text
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
def _lookup_disk_path ( root , path ) :
"""
Map the passed HostResource ID to the actual host disk path
"""
ref = None
def _path_has_prefix ( prefix ) :
if path . startswith ( prefix ) :
return path [ len ( prefix ) : ]
if path . startswith ( " ovf: " + prefix ) :
return path [ len ( " ovf: " + prefix ) : ]
return False
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
if _path_has_prefix ( " /disk/ " ) :
disk_ref = _path_has_prefix ( " /disk/ " )
xpath = " ./ovf:DiskSection/ovf:Disk[@ovf:diskId= ' %s ' ] " % disk_ref
dnode = _find ( root , xpath )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
if dnode is None :
raise ValueError ( _ ( " Unknown disk reference id ' %s ' "
" for path %s . " ) % ( path , disk_ref ) )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
ref = dnode . attrib [ " { %s }fileRef " % OVF_NAMESPACES [ " ovf " ] ]
elif _path_has_prefix ( " /file/ " ) :
ref = _path_has_prefix ( " /file/ " )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
else :
raise ValueError ( _ ( " Unknown storage path type %s . " ) % path )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
xpath = " ./ovf:References/ovf:File[@ovf:id= ' %s ' ] " % ref
refnode = _find ( root , xpath )
if refnode is None :
raise ValueError ( _ ( " Unknown reference id ' %s ' "
" for path %s . " ) % ( ref , path ) )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
return refnode . attrib [ " { %s }href " % OVF_NAMESPACES [ " ovf " ] ]
def _import_file ( conn , input_file ) :
"""
Parse the OVF file and generate a virtinst . Guest object from it
"""
root = xml . etree . ElementTree . parse ( input_file ) . getroot ( )
vsnode = _find ( root , " ./ovf:VirtualSystem " )
vhnode = _find ( vsnode , " ./ovf:VirtualHardwareSection " )
2014-02-06 04:09:26 +04:00
# General info
2018-02-13 23:53:07 +03:00
name = _text ( vsnode . find ( " ./ovf:Name " , OVF_NAMESPACES ) )
desc = _text ( vsnode . find ( " ./ovf:AnnotationSection/ovf:Annotation " ,
OVF_NAMESPACES ) )
2014-02-06 04:09:26 +04:00
if not desc :
2018-02-13 23:53:07 +03:00
desc = _text ( vsnode . find ( " ./ovf:Description " , OVF_NAMESPACES ) )
vhxpath = " ./ovf:Item[rasd:ResourceType= ' %s ' ] "
vcpus = _text ( _find ( vhnode ,
( vhxpath % DEVICE_CPU ) + " /rasd:VirtualQuantity " ) )
mem = _text ( _find ( vhnode ,
( vhxpath % DEVICE_MEMORY ) + " /rasd:VirtualQuantity " ) )
alloc_mem = _text ( _find ( vhnode ,
( vhxpath % DEVICE_MEMORY ) + " /rasd:AllocationUnits " ) )
2014-02-06 04:09:26 +04:00
# Sections that we handle
# NetworkSection is ignored, since I don't have an example of
# a valid section in the wild.
parsed_sections = [ " References " , " DiskSection " , " NetworkSection " ,
" VirtualSystem " ]
# Check for unhandled 'required' sections
2018-02-13 23:53:07 +03:00
for env_node in root . findall ( " ./ " ) :
if any ( [ p for p in parsed_sections if p in env_node . tag ] ) :
2014-02-06 04:09:26 +04:00
continue
logging . debug ( " Unhandled XML section ' %s ' " ,
2018-02-13 23:53:07 +03:00
env_node . tag )
2014-02-06 04:09:26 +04:00
2018-02-13 23:53:07 +03:00
if not _convert_bool_val ( env_node . attrib . get ( " required " ) ) :
2014-02-06 04:09:26 +04:00
continue
2017-10-11 14:36:01 +03:00
raise Exception ( _ ( " OVF section ' %s ' is listed as "
" required, but parser doesn ' t know "
" how to handle it. " ) % env_node . name )
2014-02-06 04:09:26 +04:00
disk_buses = { }
2018-02-13 23:53:07 +03:00
for node in _findall ( vhnode , vhxpath % DEVICE_IDE_BUS ) :
instance_id = _text ( _find ( node , " rasd:InstanceID " ) )
2014-02-06 04:09:26 +04:00
disk_buses [ instance_id ] = " ide "
2018-02-13 23:53:07 +03:00
for node in _findall ( vhnode , vhxpath % DEVICE_SCSI_BUS ) :
instance_id = _text ( _find ( node , " rasd:InstanceID " ) )
2014-02-06 04:09:26 +04:00
disk_buses [ instance_id ] = " scsi "
ifaces = [ ]
2018-02-13 23:53:07 +03:00
for node in _findall ( vhnode , vhxpath % DEVICE_ETHERNET ) :
2018-03-20 19:18:35 +03:00
iface = virtinst . DeviceInterface ( conn )
2018-02-13 23:53:07 +03:00
# Just ignore 'source' info for now and choose the default
net_model = _text ( _find ( node , " rasd:ResourceSubType " ) )
2014-02-06 04:09:26 +04:00
if net_model and not net_model . isdigit ( ) :
iface . model = net_model . lower ( )
iface . set_default_source ( )
ifaces . append ( iface )
disks = [ ]
2018-02-13 23:53:07 +03:00
for node in _findall ( vhnode , vhxpath % DEVICE_DISK ) :
bus_id = _text ( _find ( node , " rasd:Parent " ) )
path = _text ( _find ( node , " rasd:HostResource " ) )
2014-02-06 04:09:26 +04:00
bus = disk_buses . get ( bus_id , " ide " )
fmt = " raw "
if path :
2018-02-13 23:53:07 +03:00
path = _lookup_disk_path ( root , path )
fmt = " vmdk "
2014-02-06 04:09:26 +04:00
2018-03-20 19:18:35 +03:00
disk = virtinst . DeviceDisk ( conn )
2014-02-06 04:09:26 +04:00
disk . path = path
disk . driver_type = fmt
disk . bus = bus
disk . device = " disk "
disks . append ( disk )
2018-02-13 23:53:07 +03:00
# Generate the Guest
2015-04-04 00:24:32 +03:00
guest = conn . caps . lookup_virtinst_guest ( )
2014-02-06 04:09:26 +04:00
guest . installer = virtinst . ImportInstaller ( conn )
if not name :
name = os . path . basename ( input_file )
guest . name = name . replace ( " " , " _ " )
guest . description = desc or None
if vcpus :
guest . vcpus = int ( vcpus )
if mem :
guest . memory = _convert_alloc_val ( alloc_mem , mem ) * 1024
for dev in ifaces + disks :
guest . add_device ( dev )
return guest
class ovf_parser ( parser_class ) :
"""
Support for OVF appliance configurations .
Whitepaper : http : / / www . vmware . com / pdf / ovf_whitepaper_specification . pdf
Spec : http : / / www . dmtf . org / standards / published_documents / DSP0243_1 .0 .0 . pdf
"""
name = " ovf "
suffix = " .ovf "
@staticmethod
def identify_file ( input_file ) :
"""
Return True if the given file is of this format .
"""
2018-02-13 23:53:07 +03:00
# Small heuristic to ensure we aren't attempting to identify
# a large .zip archive or similar
2014-02-06 04:09:26 +04:00
if os . path . getsize ( input_file ) > ( 1024 * 1024 * 2 ) :
return
try :
2018-02-13 23:53:07 +03:00
root = xml . etree . ElementTree . parse ( input_file ) . getroot ( )
return root . tag == ( " { %s }Envelope " % OVF_NAMESPACES [ " ovf " ] )
except Exception :
logging . debug ( " Error parsing OVF XML " , exc_info = True )
2014-02-06 04:09:26 +04:00
return False
@staticmethod
def export_libvirt ( conn , input_file ) :
2018-02-13 23:53:07 +03:00
logging . debug ( " Importing OVF XML: \n %s " , open ( input_file ) . read ( ) )
return _import_file ( conn , input_file )