# 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 logging import os import re import shlex import virtinst from virtinst import util 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): logging.debug("VMDK file '%s' doesn't exist", filename) return if os.path.getsize(filename) > (10 * 1024): logging.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: logging.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: logging.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() logging.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 util.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.memory = int(mem) * 1024 for dev in ifaces + disks: guest.add_device(dev) return guest