virt-manager/virtconv/parsers/ovf.py
Giuseppe Scrivano 0df75c7603 headers: update "Red Hat, Inc." copyright for the year 2013
Ensure that any file touched by a @redhat.com author in 2013 has an
updated copyright header.

The files were updated using the build-aux/update-copyright gnulib
script and manually added where the copyright line wasn't present.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2013-10-28 17:22:26 +01:00

440 lines
14 KiB
Python

#
# Copyright 2009, 2013 Red Hat, Inc.
# Cole Robinson <crobinso@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import logging
from virtinst import util
import virtconv.formats as formats
import virtconv.vmcfg as vmcfg
import virtconv.diskcfg as diskcfg
import virtconv.netdevcfg as netdevcfg
# 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
def ovf_register_namespace(ctx):
ctx.xpathRegisterNs("ovf", "http://schemas.dmtf.org/ovf/envelope/1")
ctx.xpathRegisterNs("ovfenv", "http://schemas.dmtf.org/ovf/environment/1")
ctx.xpathRegisterNs("rasd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData")
ctx.xpathRegisterNs("vssd", "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData")
ctx.xpathRegisterNs("vmw", "http://www.vmware.com/schema/ovf")
def node_list(node):
child_list = []
child = node.children
while child:
child_list.append(child)
child = child.next
return child_list
def get_child_content(parent_node, child_name):
for node in node_list(parent_node):
if node.name == child_name:
return node.content
return None
def _xpath(xml, path=None, func=None, return_list=False,
register_namespace=None):
"""
Return the content from the passed xml xpath, or return the result
of a passed function (receives xpathContext as its only arg)
"""
def _getter(doc, ctx, path):
ignore = doc
if func:
return func(ctx)
if not path:
raise ValueError("'path' or 'func' is required.")
ret = ctx.xpathEval(path)
if type(ret) is list:
if len(ret) >= 1:
if return_list:
return ret
else:
return ret[0].content
else:
ret = None
return ret
return util.xml_parse_wrapper(xml, _getter, path,
register_namespace=register_namespace)
def convert_alloc_val(ignore, val):
# XXX: 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:
# Assume GB
return int(val * 1024)
return int(val)
def _parse_hw_section(vm, nodes, file_refs, disk_section):
vm.nr_vcpus = 0
disk_buses = {}
for device_node in nodes:
if device_node.name != "Item":
continue
devtype = None
for item_node in node_list(device_node):
if item_node.name == "ResourceType":
devtype = item_node.content
if devtype == DEVICE_CPU:
cpus = get_child_content(device_node, "VirtualQuantity")
if cpus:
vm.nr_vcpus += int(cpus)
elif devtype == DEVICE_MEMORY:
mem = get_child_content(device_node, "VirtualQuantity")
alloc_str = get_child_content(device_node, "AllocationUnits")
if mem:
vm.memory = convert_alloc_val(alloc_str, mem)
elif devtype == DEVICE_ETHERNET:
net_model = get_child_content(device_node, "ResourceSubType")
if net_model:
net_model = net_model.lower()
netdev = netdevcfg.netdev(driver=net_model)
vm.netdevs[len(vm.netdevs)] = netdev
elif devtype == DEVICE_IDE_BUS:
instance_id = get_child_content(device_node, "InstanceID")
disk_buses[instance_id] = "ide"
elif devtype == DEVICE_SCSI_BUS:
instance_id = get_child_content(device_node, "InstanceID")
disk_buses[instance_id] = "scsi"
elif devtype in [DEVICE_DISK]:
bus_id = get_child_content(device_node, "Parent")
path = get_child_content(device_node, "HostResource")
dev_num = int(get_child_content(device_node, "AddressOnParent"))
if bus_id and bus_id not in disk_buses:
raise ValueError(_("Didn't find parent bus for disk '%s'" %
path))
bus = (bus_id and disk_buses[bus_id]) or "ide"
fmt = diskcfg.DISK_FORMAT_RAW
if path:
ref = None
fmt = diskcfg.DISK_FORMAT_VMDK
if path.startswith("ovf:/disk/"):
disk_ref = path[len("ovf:/disk/"):]
if disk_ref not in disk_section:
raise ValueError(_("Unknown reference id '%s' "
"for path %s.") % (path, ref))
ref, fmt = disk_section[disk_ref]
elif path.startswith("ovf:/file/"):
ref = path[len("ovf:/file/"):]
else:
raise ValueError(_("Unknown storage path type %s." % path))
if not ref:
# XXX: This means allocate the disk.
pass
if ref not in file_refs:
raise ValueError(_("Unknown reference id '%s' "
"for path %s.") % (path, ref))
path = file_refs[ref]
disk = diskcfg.disk(path=path, fmt=fmt, bus=bus,
typ=diskcfg.DISK_TYPE_DISK)
vm.disks[(bus, dev_num)] = disk
else:
desc = get_child_content(device_node, "Description")
logging.debug("Unhandled device type=%s desc=%s", devtype, desc)
class ovf_parser(formats.parser):
"""
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"
can_import = True
can_export = False
can_identify = True
@staticmethod
def identify_file(input_file):
"""
Return True if the given file is of this format.
"""
infile = open(input_file, "r")
xml = infile.read()
infile.close()
res = False
try:
if xml.count("</Envelope>"):
res = bool(_xpath(xml, "/ovf:Envelope", return_list=True,
register_namespace=ovf_register_namespace))
except Exception, e:
logging.debug("Error parsing OVF XML: %s", str(e))
return res
@staticmethod
def import_file(input_file):
"""
Import a configuration file. Raises if the file couldn't be
opened, or parsing otherwise failed.
"""
infile = open(input_file, "r")
xml = infile.read()
infile.close()
logging.debug("Importing OVF XML:\n%s", xml)
return util.xml_parse_wrapper(xml, ovf_parser._import_file,
register_namespace=ovf_register_namespace)
@staticmethod
def _import_file(doc, ctx):
ignore = doc
def xpath_str(path):
ret = ctx.xpathEval(path)
result = None
if ret is not None:
if type(ret) == list:
if len(ret) >= 1:
result = ret[0].content
else:
result = ret
return result
def bool_val(val):
if str(val).lower() == "false":
return False
elif str(val).lower() == "true":
return True
return False
def xpath_nodes(path):
return ctx.xpathEval(path)
vm = vmcfg.vm()
file_refs = {}
disk_section = {}
net_section = {}
name = None
desc = None
os_id_ignore = None
os_ver_ignore = None
os_type_ignore = None
# XXX: Can have multiple machines nested as VirtualSystemCollection
# XXX: Need to check all Envelope
# General info
name = xpath_str("/ovf:Envelope/ovf:VirtualSystem/ovf:Name")
# Map files in <References> to actual filename
ens = xpath_nodes("/ovf:Envelope[1]")[0]
envelope_node = ens.children
for envelope_node in node_list(ens):
if envelope_node.name == "References":
for reference_node in envelope_node.children:
if reference_node.name != "File":
continue
file_id = reference_node.prop("id")
path = reference_node.prop("href")
# XXX: Should we validate the path exists? This can
# be http.
if file_id and path:
file_refs[file_id] = path
elif envelope_node.name == "DiskSection":
for disk_node in envelope_node.children:
if disk_node.name != "Disk":
continue
fmt = disk_node.prop("format")
if not fmt:
fmt = diskcfg.DISK_FORMAT_VMDK
elif fmt.lower().count("vmdk"):
fmt = diskcfg.DISK_FORMAT_VMDK
else:
fmt = diskcfg.DISK_FORMAT_VMDK
disk_id = disk_node.prop("diskId")
file_ref = disk_node.prop("fileRef")
capacity = disk_node.prop("capacity")
alloc_str = disk_node.prop("AllocationUnits")
capacity = convert_alloc_val(alloc_str, capacity)
# XXX: Empty fileref means 'create this disk'
disk_section[disk_id] = (file_ref, fmt)
elif envelope_node.name == "NetworkSection":
for net_node in envelope_node.children:
if net_node.name != "Network":
continue
net_name_ignore = net_node.prop("name")
net_section[name] = None
elif not envelope_node.isText():
logging.debug("Unhandled XML section '%s'",
envelope_node.name)
req = bool_val(envelope_node.prop("required"))
if req:
raise StandardError(_("OVF section '%s' is listed as "
"required, but parser doesn't know "
"how to handle it.") %
envelope_node.name)
# Now parse VirtualSystem, since we should have set up all the
# necessary file/disk/whatever refs
for envelope_node in node_list(ens):
if envelope_node.name != "VirtualSystem":
continue
for vs_node in node_list(envelope_node):
if vs_node.name == "Info":
pass
elif vs_node.name == "Name":
name = vs_node.content
elif vs_node.name == "OperatingSystemSection":
os_id_ignore = vs_node.prop("id")
os_ver_ignore = vs_node.prop("version")
# This is the VMWare OS name
os_type_ignore = vs_node.prop("osType")
elif vs_node.name == "VirtualHardwareSection":
_parse_hw_section(vm, node_list(vs_node), file_refs,
disk_section)
elif vs_node.name == "AnnotationSection":
for an_node in node_list(vs_node):
if an_node.name == "Annotation":
desc = an_node.content
vm.name = name
vm.description = desc
vm.validate()
return vm
@staticmethod
def export(vm):
"""
Export a configuration file as a string.
@vm vm configuration instance
Raises ValueError if configuration is not suitable.
"""
raise NotImplementedError
formats.register_parser(ovf_parser)