[et-mgmt-tools] [PATCH] modular virt-convert

John Levon levon at movementarian.org
Wed Jul 2 16:04:08 UTC 2008


This is merged with current tip and now installs properly.

thanks
john


Create config parser plugin system

Start a plugin system for config formats parsing, and implement enough
of it to get to where we were with virt-convert.

Signed-off-by: John Levon <john.levon at sun.com>

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




More information about the et-mgmt-tools mailing list