diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,8 @@ import os, sys
import os, sys
import tests.coverage as coverage
-pkgs = ['virtinst']
+pkgs = ['virtinst', 'virtconv', 'virtconv.parsers' ]
+
datafiles = [('share/man/man1', ['man/en/virt-install.1',
'man/en/virt-clone.1',
'man/en/virt-image.1',
diff --git a/virt-convert b/virt-convert
--- a/virt-convert
+++ b/virt-convert
@@ -21,56 +21,58 @@
# MA 02110-1301 USA.
import sys
-from string import ascii_letters
-import virtinst.cli as cli
import os
import logging
import errno
from optparse import OptionParser
+import virtinst.cli as cli
+import virtconv
+import virtconv.vmconfig as vmconfig
+
def parse_args():
- parser = OptionParser()
- parser.set_usage("%prog [options] inputdir|input.vmx "
+ opts = OptionParser()
+ opts.set_usage("%prog [options] inputdir|input.vmx "
"[outputdir|output.xml]")
- parser.add_option("-a", "--arch", type="string", dest="arch",
- help=("Machine Architecture Type (i686/x86_64/ppc)"))
- parser.add_option("-t", "--type", type="string", dest="type",
- help=("Output virtualization type (hvm, paravirt"))
- parser.add_option("-d", "--debug", action="store_true", dest="debug",
- help=("Print debugging information"))
- parser.add_option("-i", "--input-format", action="store",
- dest="inputformat", default="vmx",
- help=("Input format, e.g. 'vmx'"))
- parser.add_option("-o", "--output-format", action="store",
- dest="outputformat", default="virt-image",
- help=("Output format, e.g. 'virt-image'"))
- parser.add_option("-v", "--hvm", action="store_true", dest="fullvirt",
- help=("This guest should be a fully virtualized guest"))
- parser.add_option("-p", "--paravirt", action="store_true", dest="paravirt",
- help=("This guest should be a paravirtualized guest"))
+ opts.add_option("-a", "--arch", type="string", dest="arch",
+ help=("Machine Architecture Type (i686/x86_64/ppc)"))
+ opts.add_option("-t", "--type", type="string", dest="type",
+ help=("Output virtualization type (hvm, paravirt"))
+ opts.add_option("-d", "--debug", action="store_true", dest="debug",
+ help=("Print debugging information"))
+ opts.add_option("-i", "--input-format", action="store",
+ dest="inputformat", default="vmx",
+ help=("Input format, e.g. 'vmx'"))
+ opts.add_option("-o", "--output-format", action="store",
+ dest="outputformat", default="virt-image",
+ help=("Output format, e.g. 'virt-image'"))
+ opts.add_option("-v", "--hvm", action="store_true", dest="fullvirt",
+ help=("This guest should be a fully virtualized guest"))
+ opts.add_option("-p", "--paravirt", action="store_true", dest="paravirt",
+ help=("This guest should be a paravirtualized guest"))
- (options, args) = parser.parse_args()
+ (options, args) = opts.parse_args()
if len(args) < 1:
- parser.error(("You need to provide an input VM definition"))
+ opts.error(("You need to provide an input VM definition"))
if len(args) > 2:
- parser.error(("Too many arguments provided"))
+ opts.error(("Too many arguments provided"))
if (options.arch is None):
- parser.error(("Missing option value \n\nArchitecture: " +
+ opts.error(("Missing option value \n\nArchitecture: " +
str(options.arch)))
# hard-code for now
if options.inputformat != "vmx":
- parser.error(("Unsupported input format \"%s\"" % options.inputformat))
+ opts.error(("Unsupported input format \"%s\"" % options.inputformat))
if options.outputformat != "virt-image":
- parser.error(("Unsupported output format \"%s\""
+ opts.error(("Unsupported output format \"%s\""
% options.outputformat))
if os.path.isdir(args[0]):
vmx_files = [x for x in os.listdir(args[0]) if x.endswith(".vmx") ]
if (len(vmx_files)) == 0:
- parser.error(("No VM definition file was found in %s" % args[0]))
+ opts.error(("No VM definition file was found in %s" % args[0]))
if (len(vmx_files)) > 1:
- parser.error(("Too many .vmx definitions found in %s" % args[0]))
+ opts.error(("Too many .vmx definitions found in %s" % args[0]))
options.input_file = os.path.join(args[0], vmx_files[0])
options.input_dir = args[0]
else:
@@ -91,197 +93,78 @@ def parse_args():
return options
-# Begin creation of xml template from parsed vmx config file
-def vmx_to_image_xml(disks_list, record, options, hvm):
- pv_disk_list = []
- fv_disk_list = []
- storage_disk_list = []
-
- infile = options.input_file
-
- # validate required values for conversion are in the input vmx file
- if record.has_key("displayName"):
- name = record["displayName"]
- else:
- logging.error("displayName key not parsed from %s" % infile)
- sys.exit(1)
-
- if record.has_key("memsize"):
- memory = int(record["memsize"]) * 1024
- else:
- logging.error("memsize key not parsed from %s" % infile)
- sys.exit(1)
-
- if record.has_key("annotation"):
- annotation = record["annotation"]
- else:
- annotation = ""
-
- if record.has_key("numvcpus"):
- vcpus = record["numvcpus"]
- else:
- vcpus = "1"
-
-
-# create disk filename lists for xml template
- for (number, dfile) in enumerate(disks_list):
- dfile = str(dfile.replace(".vmdk","")).strip()
- pv_disk_list.append("""<drive disk="%s.img" target="xvd%s"/>""" % \
- (dfile, ascii_letters[number % 26]))
- fv_disk_list.append("""<drive disk="%s.img" target="hd%s"/>""" % \
- (dfile, ascii_letters[number % 26]))
- storage_disk_list.append("""<disk file="%s.img" use="system" format="raw"/>""" % (dfile))
-
-# determine virtualization type for image.boot section
- if hvm is False:
- virt_boot_template = """<boot type="xen">
- <guest>
- <arch>%(vm_arch)s</arch>
- <features>
- <pae/>
- </features>
- </guest>
- <os>
- <loader>pygrub</loader>
- </os>
- %(vm_pv_disks)s
- </boot>"""
- elif hvm is True:
- virt_boot_template = """<boot type="hvm">
- <guest>
- <arch>%(vm_arch)s</arch>
- </guest>
- <os>
- <loader dev="hd"/>
- </os>
- %(vm_fv_disks)s
- </boot>"""
-
-
-# xml replacements dictionaries
- virt_boot_xml_dict = {
- "vm_pv_disks" : "".join(pv_disk_list),
- "vm_fv_disks" : "".join(fv_disk_list),
- "vm_arch" : options.arch,
- }
- virt_boot_template = virt_boot_template % virt_boot_xml_dict
- virt_image_xml_dict = {
- "virt_boot_template" : virt_boot_template,
- "vm_displayName": name.replace(" ","_"),
- "vm_annotation" : annotation,
- "vm_vcpus" : vcpus,
- "vm_mem" : memory,
- "vm_storage" : "".join(storage_disk_list),
- }
-
- virt_image_xml_template = """<image>
- <name>%(vm_displayName)s</name>
- <label>%(vm_displayName)s</label>
- <description>
- %(vm_annotation)s
- </description>
- <domain>
- %(virt_boot_template)s
- <devices>
- <vcpu>%(vm_vcpus)s</vcpu>
- <memory>%(vm_mem)s</memory>
- <interface/>
- <graphics/>
- </devices>
- </domain>
- <storage>
- %(vm_storage)s
- </storage>
-</image>
-"""
-
- virtimage_xml_template = virt_image_xml_template % virt_image_xml_dict
- return virtimage_xml_template
-
-# parse input vmware configuration
-def parse_vmware_config(options):
- if not os.access(options.input_file, os.R_OK):
- raise ValueError, "Could not read file: %s" % options.input_file
- infile = open(options.input_file, "r")
- contents = infile.readlines()
- infile.close()
- record = {}
- vm_config = []
- disks_list = []
-
- # strip out comment and blank lines for easy splitting of values
- for line in contents:
- if not line.strip() or line.startswith("#"):
- continue
- else:
- vm_config.append(line)
-
- for line in vm_config:
- before_eq, after_eq = line.split("=", 1)
- key = before_eq.replace(" ","")
- value = after_eq.replace('"',"")
- value = value.strip()
- record[key] = value
- logging.debug("Key: %s Value: \"%s\"" % (key, value))
- if value.endswith("vmdk"): # separate disks from config
- disks_list.append(value)
- return record, disks_list
-
-
-def convert_disks(disks_list, options):
- for disk in disks_list:
- infile = disk.strip()
- if not os.path.isabs(infile):
- infile = os.path.join(options.input_dir, infile)
-
- outfile = disk.replace(".vmdk","").strip()
- outfile += ".img"
- if not os.path.isabs(outfile):
- outfile = os.path.join(options.output_dir, outfile)
- convert_cmd = "qemu-img convert %s -O raw %s" % (infile, outfile)
- ret = os.system(convert_cmd)
- print ret
-
-
def main():
options = parse_args()
cli.setupLogging("virt-convert", options.debug)
- vm_config = parse_vmware_config(options)
- record, disks_list = vm_config
+ try:
+ inp = virtconv.vmconfig.find_parser_by_name(options.inputformat)
+ except:
+ logging.error("No parser of format \"%s\" was found." %
+ options.inputformat)
+ sys.exit(1)
+
+ try:
+ outp = virtconv.vmconfig.find_parser_by_name(options.outputformat)
+ except:
+ logging.error("No parser of format \"%s\" was found." %
+ options.outputformat)
+ sys.exit(1)
+
+ vmdef = None
+
+ try:
+ vmdef = inp.import_file(options.input_file)
+ except IOError, e:
+ logging.error("Couldn't import file \"%s\": %s" %
+ (options.input_file, e.strerror))
+ sys.exit(1)
+ except Exception, e:
+ logging.error("Couldn't import file \"%s\": %s" %
+ (options.input_file, e.message))
+ sys.exit(1)
if options.paravirt:
- hvm = False
+ vmdef.type = vmconfig.VM_TYPE_PV
else:
- hvm = True
- out_contents = vmx_to_image_xml(disks_list, record, options, hvm)
+ vmdef.type = vmconfig.VM_TYPE_HVM
- name = record["displayName"].replace(" ","-")
+ vmdef.arch = options.arch
+
+ unixname = vmdef.name.replace(" ", "-")
if not options.output_dir:
- options.output_dir = name
+ options.output_dir = unixname
try:
logging.debug("Creating directory %s" % options.output_dir)
os.mkdir(options.output_dir)
except OSError, e:
if (e.errno != errno.EEXIST):
logging.error("Could not create directory %s: %s" %
- (options.output_dir, str(e)))
+ (options.output_dir, e.strerror))
sys.exit(1)
if not options.output_file:
options.output_file = os.path.join(options.output_dir,
- "%s.virt-image.xml" % name)
+ "%s%s" % (unixname, outp.suffix))
logging.debug("input_file: %s" % options.input_file)
logging.debug("input_dir: %s" % options.input_dir)
logging.debug("output_file: %s" % options.output_file)
logging.debug("output_dir: %s" % options.input_dir)
- # configuration completed, ready to write config file and convert disks
- out = open(options.output_file, "w")
- out.writelines(out_contents)
- out.close()
- convert_disks(disks_list, options)
+ try:
+ for d in vmdef.disks:
+ d.convert(options.input_dir, options.output_dir,
+ vmconfig.DISK_TYPE_RAW)
+ except Exception, e:
+ logging.error(e)
+ sys.exit(1)
+
+ try:
+ outp.export_file(vmdef, options.output_file)
+ except Exception, e:
+ logging.error(e)
+ sys.exit(1)
print "\n\nConversion completed and placed in: %s" % options.output_dir
diff --git a/virtconv/__init__.py b/virtconv/__init__.py
new file mode 100644
--- /dev/null
+++ b/virtconv/__init__.py
@@ -0,0 +1,29 @@
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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 pkgutil
+import imp
+import os
+
+parsers_path = [os.path.join(__path__[0], "parsers/")]
+
+for loader, name, ispkg in pkgutil.iter_modules(parsers_path):
+ filename, pathname, desc = imp.find_module(name, parsers_path)
+ imp.load_module(name, filename, pathname, desc)
diff --git a/virtconv/parsers/virtimage.py b/virtconv/parsers/virtimage.py
new file mode 100644
--- /dev/null
+++ b/virtconv/parsers/virtimage.py
@@ -0,0 +1,152 @@
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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.
+#
+
+from string import ascii_letters
+import virtconv.vmconfig as vmconfig
+
+pv_boot_template = """
+ <boot type="xen">
+ <guest>
+ <arch>%(arch)s</arch>
+ <features>
+ <pae/>
+ </features>
+ </guest>
+ <os>
+ <loader>pygrub</loader>
+ </os>
+ %(pv_disks)s
+ </boot>
+"""
+
+hvm_boot_template = """
+ <boot type="hvm">
+ <guest>
+ <arch>%(arch)s</arch>
+ </guest>
+ <os>
+ <loader dev="hd"/>
+ </os>
+ %(hvm_disks)s
+ </boot>
+"""
+
+image_template = """
+<image>
+ <name>%(name)s</name>
+ <label>%(name)s</label>
+ <description>
+ %(description)s
+ </description>
+ <domain>
+ %(boot_template)s
+ <devices>
+ <vcpu>%(nr_vcpus)s</vcpu>
+ <memory>%(memory)s</memory>
+ <interface/>
+ <graphics/>
+ </devices>
+ </domain>
+ <storage>
+ %(storage)s
+ </storage>
+</image>
+"""
+
+class virtimage_parser(vmconfig.parser):
+ """
+ Support for virt-install's image format (see virt-image man page).
+ """
+ name = "virt-image"
+ suffix = ".virt-image.xml"
+
+ @staticmethod
+ def identify_file(input_file):
+ """
+ Return True if the given file is of this format.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def import_file(input_file):
+ """
+ Import a configuration file. Raises if the file couldn't be
+ opened, or parsing otherwise failed.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def export_file(vm, output_file):
+ """
+ Export a configuration file.
+ @vm vm configuration instance
+ @file Output file
+
+ Raises ValueError if configuration is not suitable, or another
+ exception on failure to write the output file.
+ """
+
+ if not vm.memory:
+ raise ValueError("VM must have a memory setting")
+
+ pv_disks = []
+ hvm_disks = []
+ storage_disks = []
+
+ # create disk filename lists for xml template
+ for disk in vm.disks:
+ number = disk.number
+ path = disk.path
+
+ # FIXME: needs updating for later Xen enhancements; need to
+ # implement capabilities checking for max disks etc.
+ pv_disks.append("""<drive disk="%s" target="xvd%s" />""" %
+ (path, ascii_letters[number % 26]))
+ hvm_disks.append("""<drive disk="%s" target="hd%s" />""" %
+ (path, ascii_letters[number % 26]))
+ storage_disks.append(
+ """<disk file="%s" use="system" format="raw"/>""" % path)
+
+ if vm.type == vmconfig.VM_TYPE_PV:
+ boot_template = pv_boot_template
+ else:
+ boot_template = hvm_boot_template
+
+ boot_xml = boot_template % {
+ "pv_disks" : "".join(pv_disks),
+ "hvm_disks" : "".join(hvm_disks),
+ "arch" : vm.arch,
+ }
+
+ out = image_template % {
+ "boot_template": boot_xml,
+ "name" : vm.name,
+ "description" : vm.description,
+ "nr_vcpus" : vm.nr_vcpus,
+ # Mb to Kb
+ "memory" : int(vm.memory) * 1024,
+ "storage" : "".join(storage_disks),
+ }
+
+ outfile = open(output_file, "w")
+ outfile.writelines(out)
+ outfile.close()
+
+vmconfig.register_parser(virtimage_parser)
diff --git a/virtconv/parsers/vmx.py b/virtconv/parsers/vmx.py
new file mode 100644
--- /dev/null
+++ b/virtconv/parsers/vmx.py
@@ -0,0 +1,107 @@
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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 virtconv.vmconfig as vmconfig
+
+class vmx_parser(vmconfig.parser):
+ """
+ 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.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def import_file(input_file):
+ """
+ Import a configuration file. Raises if the file couldn't be
+ opened, or parsing otherwise failed.
+ """
+
+ vm = vmconfig.vm()
+
+ infile = open(input_file, "r")
+ contents = infile.readlines()
+ infile.close()
+
+ lines = []
+
+ # strip out comment and blank lines for easy splitting of values
+ for line in contents:
+ if not line.strip() or line.startswith("#"):
+ continue
+ else:
+ lines.append(line)
+
+ config = {}
+
+ # split out all remaining entries of key = value form
+ for (line_nr, line) in enumerate(lines):
+ try:
+ before_eq, after_eq = line.split("=", 1)
+ key = before_eq.replace(" ","")
+ value = after_eq.replace('"',"")
+ value = value.strip()
+ config[key] = value
+ except:
+ raise Exception("Syntax error at line %d: %s" %
+ (line_nr + 1, line.strip()))
+
+ if not config.get("displayName"):
+ raise ValueError("No displayName defined in \"%s\"" % input_file)
+ vm.name = config.get("displayName")
+
+ vm.memory = config.get("memsize")
+ vm.description = config.get("annotation")
+ vm.nr_vcpus = config.get("numvcpus")
+
+ # FIXME: this should probably be a lot smarter. I don't think
+ # this even gets disk numbering right.
+ disks = [ d for d in config.values() if d.endswith(".vmdk") ]
+
+ for (number, path) in enumerate(disks):
+ vm.disks += [ vmconfig.disk(path, number, vmconfig.DISK_TYPE_VMDK) ]
+
+ vm.validate()
+ return vm
+
+ @staticmethod
+ def export_file(vm, output_file):
+ """
+ Export a configuration file.
+ @vm vm configuration instance
+ @file Output file
+
+ Raises ValueError if configuration is not suitable, or another
+ exception on failure to write the output file.
+ """
+
+ raise NotImplementedError
+
+vmconfig.register_parser(vmx_parser)
diff --git a/virtconv/vmconfig.py b/virtconv/vmconfig.py
new file mode 100644
--- /dev/null
+++ b/virtconv/vmconfig.py
@@ -0,0 +1,193 @@
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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 os
+
+_parsers = [ ]
+
+VM_TYPE_PV = 0
+VM_TYPE_HVM = 1
+
+DISK_TYPE_RAW = 0
+DISK_TYPE_VMDK = 1
+
+disk_suffixes = {
+ DISK_TYPE_RAW: ".img",
+ DISK_TYPE_VMDK: ".vmdk",
+}
+
+qemu_formats = {
+ DISK_TYPE_RAW: "raw",
+ DISK_TYPE_VMDK: "vmdk",
+}
+
+class disk(object):
+ """Definition of an individual disk instance."""
+
+ def __init__(self, path = None, number = None, type = None):
+ self.path = path
+ self.number = number
+ self.type = type
+
+ def convert(self, input_dir, output_dir, output_type):
+ """
+ Convert a disk into the requested format if possible, in the
+ given output directory. Raises NotImplementedError or other
+ failures.
+ """
+
+ if self.type == output_type:
+ return
+
+ if output_type != DISK_TYPE_RAW:
+ raise NotImplementedError("Cannot convert to disk type %d" %
+ output_type)
+
+ infile = self.path
+
+ if not os.path.isabs(infile):
+ infile = os.path.join(input_dir, infile)
+
+ outfile = self.path
+
+ if os.path.isabs(outfile):
+ outfile = os.path.basename(outfile)
+
+ outfile = outfile.replace(disk_suffixes[self.type],
+ disk_suffixes[output_type]).strip()
+
+ convert_cmd = ("qemu-img convert \"%s\" -O %s \"%s\"" %
+ (infile, qemu_formats[output_type],
+ os.path.join(output_dir, outfile)))
+
+ os.system(convert_cmd)
+
+ # Note: this is the *relative* path still
+ self.path = outfile
+ self.type = output_type
+
+
+class vm(object):
+ """
+ Generic configuration for a particular VM instance.
+
+ At export, a plugin is guaranteed to have the at least the following
+ values set (any others needed should be checked for, raising
+ ValueError on failure):
+
+ vm.name
+ vm.description (defaults to empty string)
+ vm.nr_vcpus (defaults to 1)
+ vm.type
+ vm.arch
+
+ If vm.memory is set, it is in Mb units.
+ """
+
+ name = None
+ suffix = None
+
+ def __init__(self):
+ self.name = None
+ self.description = None
+ self.memory = None
+ self.nr_vcpus = None
+ self.disks = [ ]
+ self.type = VM_TYPE_HVM
+ self.arch = "i686" # FIXME?
+
+ def validate(self):
+ """
+ Validate all parameters, and fix up any unset values to meet the
+ guarantees we make above.
+ """
+
+ if not self.name:
+ raise ValueError("VM name is not set")
+ if not self.description:
+ self.description = ""
+ if not self.nr_vcpus:
+ self.nr_vcpus = 1
+ if not self.type:
+ raise ValueError("VM type is not set")
+ if not self.arch:
+ raise ValueError("VM arch is not set")
+
+
+class parser(object):
+ """
+ Base class for particular config file format definitions of
+ a VM instance.
+
+ Warning: this interface is not (yet) considered stable and may
+ change at will.
+ """
+
+ @staticmethod
+ def identify_file(input_file):
+ """
+ Return True if the given file is of this format.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def import_file(input_file):
+ """
+ Import a configuration file. Raises if the file couldn't be
+ opened, or parsing otherwise failed.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def export_file(vm, output_file):
+ """
+ Export a configuration file.
+ @vm vm configuration instance
+ @output_file Output file
+
+ Raises ValueError if configuration is not suitable, or another
+ exception on failure to write the output file.
+ """
+ raise NotImplementedError
+
+
+def register_parser(parser):
+ """
+ Register a particular config format parser. This should be called by each
+ config plugin on import.
+ """
+
+ global _parsers
+ _parsers += [ parser ]
+
+def find_parser_by_name(name):
+ """
+ Return the parser of the given name
+ """
+ return [p for p in _parsers if p.name == name][0] or None
+
+def find_parser_by_file(input_file):
+ """
+ Return the parser that is capable of comprehending the given file.
+ """
+ for p in _parsers:
+ if p.identify_file(input_file):
+ return p
+ return None