mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-03-09 08:58:27 +03:00
Init a shared log instance in virtinst/logger.py, and use that throughout the code base, so we aren't calling directly into 'logging'. This helps protect our logging output from being cluttered with other library output, as happens with some 'requests' usage
299 lines
8.6 KiB
Python
299 lines
8.6 KiB
Python
# Copyright (C) 2013 Red Hat, Inc.
|
|
#
|
|
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
|
|
# Use is subject to license terms.
|
|
#
|
|
# This work is licensed under the GNU GPLv2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
#
|
|
|
|
import collections
|
|
import os
|
|
import re
|
|
import shlex
|
|
|
|
import virtinst
|
|
from virtinst import log
|
|
from virtinst import xmlutil
|
|
|
|
from .formats import parser_class
|
|
|
|
|
|
class _VMXLine(object):
|
|
"""
|
|
Class tracking an individual line in a VMX/VMDK file
|
|
"""
|
|
def __init__(self, content):
|
|
self.content = content
|
|
|
|
self.pair = None
|
|
self.is_blank = False
|
|
self.is_comment = False
|
|
self.is_disk = False
|
|
self._parse()
|
|
|
|
def _parse(self):
|
|
line = self.content.strip()
|
|
if not line:
|
|
self.is_blank = True
|
|
elif line.startswith("#"):
|
|
self.is_comment = True
|
|
elif line.startswith("RW ") or line.startswith("RDONLY "):
|
|
self.is_disk = True
|
|
else:
|
|
# Expected that this will raise an error for unknown format
|
|
before_eq, after_eq = line.split("=", 1)
|
|
key = before_eq.strip().lower()
|
|
value = after_eq.strip().strip('"')
|
|
self.pair = (key, value)
|
|
|
|
def parse_disk_path(self):
|
|
# format:
|
|
# RW 16777216 VMFS "test-flat.vmdk"
|
|
# RDONLY 156296322 V2I "virtual-pc-diskformat.v2i"
|
|
content = self.content.split(" ", 3)[3]
|
|
if not content.startswith("\""):
|
|
raise ValueError("Path was not fourth entry in VMDK storage line")
|
|
return shlex.split(content, " ", 1)[0]
|
|
|
|
|
|
class _VMXFile(object):
|
|
"""
|
|
Class tracking a parsed VMX/VMDK format file
|
|
"""
|
|
def __init__(self, content):
|
|
self.content = content
|
|
self.lines = []
|
|
|
|
self._parse()
|
|
|
|
def _parse(self):
|
|
for line in self.content:
|
|
try:
|
|
lineobj = _VMXLine(line)
|
|
self.lines.append(lineobj)
|
|
except Exception as e:
|
|
raise Exception(_("Syntax error at line %d: %s\n%s") %
|
|
(len(self.lines) + 1, line.strip(), e))
|
|
|
|
def pairs(self):
|
|
ret = collections.OrderedDict()
|
|
for line in self.lines:
|
|
if line.pair:
|
|
ret[line.pair[0]] = line.pair[1]
|
|
return ret
|
|
|
|
|
|
def parse_vmdk(filename):
|
|
"""
|
|
Parse a VMDK descriptor file
|
|
Reference: http://sanbarrow.com/vmdk-basics.html
|
|
"""
|
|
# Detect if passed file is a descriptor file
|
|
# Assume descriptor isn't larger than 10K
|
|
if not os.path.exists(filename):
|
|
log.debug("VMDK file '%s' doesn't exist", filename)
|
|
return
|
|
if os.path.getsize(filename) > (10 * 1024):
|
|
log.debug("VMDK file '%s' too big to be a descriptor", filename)
|
|
return
|
|
|
|
f = open(filename, "r")
|
|
content = f.readlines()
|
|
f.close()
|
|
|
|
try:
|
|
vmdkfile = _VMXFile(content)
|
|
except Exception:
|
|
log.exception("%s looked like a vmdk file, but parsing failed",
|
|
filename)
|
|
return
|
|
|
|
disklines = [l for l in vmdkfile.lines if l.is_disk]
|
|
if len(disklines) == 0:
|
|
raise RuntimeError(_("Didn't detect a storage line in the VMDK "
|
|
"descriptor file"))
|
|
if len(disklines) > 1:
|
|
raise RuntimeError(_("Don't know how to handle multistorage VMDK "
|
|
"descriptors"))
|
|
|
|
return disklines[0].parse_disk_path()
|
|
|
|
|
|
def parse_netdev_entry(conn, ifaces, fullkey, value):
|
|
"""
|
|
Parse a particular key/value for a network. Throws ValueError.
|
|
"""
|
|
ignore, ignore, inst, key = re.split("^(ethernet)([0-9]+).", fullkey)
|
|
lvalue = value.lower()
|
|
|
|
if key == "present" and lvalue == "false":
|
|
return
|
|
|
|
net = None
|
|
for checkiface in ifaces:
|
|
if getattr(checkiface, "vmx_inst") == inst:
|
|
net = checkiface
|
|
break
|
|
if not net:
|
|
net = virtinst.DeviceInterface(conn)
|
|
setattr(net, "vmx_inst", inst)
|
|
net.set_default_source()
|
|
ifaces.append(net)
|
|
|
|
if key == "virtualdev":
|
|
# "vlance", "vmxnet", "e1000"
|
|
if lvalue in ["e1000"]:
|
|
net.model = lvalue
|
|
if key == "addresstype" and lvalue == "generated":
|
|
# Autogenerate a MAC address, the default
|
|
pass
|
|
if key == "address":
|
|
# we ignore .generatedAddress for auto mode
|
|
net.macaddr = lvalue
|
|
return net, inst
|
|
|
|
|
|
def parse_disk_entry(conn, disks, fullkey, value, topdir):
|
|
"""
|
|
Parse a particular key/value for a disk.
|
|
"""
|
|
# skip bus values, e.g. 'scsi0.present = "TRUE"'
|
|
if re.match(r"^(scsi|ide)[0-9]+[^:]", fullkey):
|
|
return
|
|
|
|
ignore, bus, bus_nr, inst, key = re.split(
|
|
r"^(scsi|ide)([0-9]+):([0-9]+)\.", fullkey)
|
|
|
|
lvalue = value.lower()
|
|
|
|
if key == "present" and lvalue == "false":
|
|
return
|
|
|
|
# Does anyone else think it's scary that we're still doing things
|
|
# like this?
|
|
if bus == "ide":
|
|
inst = int(bus_nr) * 2 + (int(inst) % 2)
|
|
elif bus == "scsi":
|
|
inst = int(bus_nr) * 16 + (int(inst) % 16)
|
|
|
|
disk = None
|
|
for checkdisk in disks:
|
|
if checkdisk.bus == bus and getattr(checkdisk, "vmx_inst") == inst:
|
|
disk = checkdisk
|
|
break
|
|
if not disk:
|
|
disk = virtinst.DeviceDisk(conn)
|
|
disk.bus = bus
|
|
setattr(disk, "vmx_inst", inst)
|
|
disks.append(disk)
|
|
|
|
if key == "devicetype":
|
|
if (lvalue == "atapi-cdrom" or
|
|
lvalue == "cdrom-raw" or
|
|
lvalue == "cdrom-image"):
|
|
disk.device = "cdrom"
|
|
|
|
if key == "filename":
|
|
disk.path = value
|
|
fmt = "raw"
|
|
if lvalue.endswith(".vmdk"):
|
|
fmt = "vmdk"
|
|
# See if the filename is actually a VMDK descriptor file
|
|
newpath = parse_vmdk(os.path.join(topdir, disk.path))
|
|
if newpath:
|
|
log.debug("VMDK file parsed path %s->%s",
|
|
disk.path, newpath)
|
|
disk.path = newpath
|
|
|
|
disk.driver_type = fmt
|
|
|
|
|
|
class vmx_parser(parser_class):
|
|
"""
|
|
Support for VMWare .vmx files. Note that documentation is
|
|
particularly sparse on this format, with pretty much the best
|
|
resource being http://sanbarrow.com/vmx.html
|
|
"""
|
|
name = "vmx"
|
|
suffix = ".vmx"
|
|
|
|
@staticmethod
|
|
def identify_file(input_file):
|
|
"""
|
|
Return True if the given file is of this format.
|
|
"""
|
|
if os.path.getsize(input_file) > (1024 * 1024 * 2):
|
|
return
|
|
|
|
infile = open(input_file, "r")
|
|
content = infile.readlines()
|
|
infile.close()
|
|
|
|
for line in content:
|
|
# some .vmx files don't bother with the header
|
|
if (re.match(r'^config.version\s+=', line) or
|
|
re.match(r'^#!\s*/usr/bin/vm(ware|player)', line)):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def export_libvirt(conn, input_file):
|
|
topdir = os.path.dirname(os.path.abspath(input_file))
|
|
infile = open(input_file, "r")
|
|
contents = infile.readlines()
|
|
infile.close()
|
|
log.debug("Importing VMX file:\n%s", "".join(contents))
|
|
|
|
vmxfile = _VMXFile(contents)
|
|
config = vmxfile.pairs()
|
|
|
|
if not config.get("displayname"):
|
|
raise ValueError(_("No displayName defined in '%s'") %
|
|
input_file)
|
|
|
|
name = config.get("displayname")
|
|
mem = config.get("memsize")
|
|
desc = config.get("annotation")
|
|
vcpus = config.get("numvcpus")
|
|
|
|
def _find_keys(prefixes):
|
|
ret = []
|
|
for key, value in config.items():
|
|
for p in xmlutil.listify(prefixes):
|
|
if key.startswith(p):
|
|
ret.append((key, value))
|
|
break
|
|
return ret
|
|
|
|
disks = []
|
|
for key, value in _find_keys(["scsi", "ide"]):
|
|
parse_disk_entry(conn, disks, key, value, topdir)
|
|
|
|
ifaces = []
|
|
for key, value in _find_keys("ethernet"):
|
|
parse_netdev_entry(conn, ifaces, key, value)
|
|
|
|
for disk in disks:
|
|
if disk.device == "disk":
|
|
continue
|
|
|
|
# vmx files often have dross left in path for CD entries
|
|
if (disk.path is None or
|
|
disk.path.lower() == "auto detect" or
|
|
not os.path.exists(disk.path)):
|
|
disk.path = None
|
|
|
|
guest = virtinst.Guest(conn)
|
|
guest.name = name.replace(" ", "_")
|
|
guest.description = desc or None
|
|
if vcpus:
|
|
guest.vcpus = int(vcpus)
|
|
if mem:
|
|
guest.currentMemory = int(mem) * 1024
|
|
|
|
for dev in ifaces + disks:
|
|
guest.add_device(dev)
|
|
|
|
return guest
|