mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-11 05:17:59 +03:00
0df75c7603
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>
440 lines
14 KiB
Python
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)
|