[et-mgmt-tools] [PATCH 11 of 11] virt-convert: Add "virt-instance" formatter

john.levon at sun.com john.levon at sun.com
Thu Jul 10 13:48:41 UTC 2008


# HG changeset patch
# User john.levon at sun.com
# Date 1215697572 25200
# Node ID 112cd8c4aa97e9da9d7532fee7d826d35e1003e8
# Parent  92de696f634f80d47b9413b9db5101de0edf9b7f
virt-convert: Add "virt-instance" formatter.

Allow output in libvirt.rng format

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

diff --git a/man/en/virt-convert.1 b/man/en/virt-convert.1
--- a/man/en/virt-convert.1
+++ b/man/en/virt-convert.1
@@ -174,7 +174,8 @@
 Input format. Currently, \f(CW\*(C`vmx\*(C'\fR  is the only supported input format.
 .IP "\-o format" 4
 .IX Item "-o format"
-Output format. Currently, \f(CW\*(C`virt\-image\*(C'\fR  is the only supported output format.
+Output format. Currently, the only supported outputs are \f(CW\*(C`virt\-image\*(C'\fR
+and \f(CW\*(C`virt\-instance\*(C'\fR.
 .IP "\-D format" 4
 .IX Item "-D format"
 Output disk format, or \f(CW\*(C`none\*(C'\fR if no conversion should be performed. See
diff --git a/man/en/virt-convert.pod b/man/en/virt-convert.pod
--- a/man/en/virt-convert.pod
+++ b/man/en/virt-convert.pod
@@ -56,7 +56,8 @@
 
 =item  -o format
 
-Output format. Currently, C<virt-image>  is the only supported output format.
+Output format. Currently, the only supported outputs are C<virt-image>
+and C<virt-instance>.
 
 =item  -D format
 
diff --git a/virt-convert b/virt-convert
--- a/virt-convert
+++ b/virt-convert
@@ -156,10 +156,10 @@
     inp = formats.parser_by_name(options.input_format)
     outp = formats.parser_by_name(options.output_format)
 
-    vmdef = None
+    vmdef = vmcfg.vm(options.arch, options.paravirt)
 
     try:
-        vmdef = inp.import_file(options.input_file)
+        inp.import_file(options.input_file, vmdef)
     except IOError, e:
         logging.error("Couldn't import file \"%s\": %s" %
             (options.input_file, e.strerror))
@@ -169,12 +169,6 @@
             (options.input_file, e.message))
         sys.exit(1)
 
-    if options.paravirt:
-        vmdef.type = vmcfg.VM_TYPE_PV
-    else:
-        vmdef.type = vmcfg.VM_TYPE_HVM
-
-    vmdef.arch = options.arch
     vmdef.os_type = options.os_type
     vmdef.os_variant = options.os_variant
     vmdef.noapic = options.noapic
diff --git a/virtconv/formats.py b/virtconv/formats.py
--- a/virtconv/formats.py
+++ b/virtconv/formats.py
@@ -39,7 +39,7 @@
         raise NotImplementedError
 
     @staticmethod
-    def import_file(input_file):
+    def import_file(input_file, vm):
         """
         Import a configuration file.  Raises if the file couldn't be
         opened, or parsing otherwise failed.
diff --git a/virtconv/parsers/virtimage.py b/virtconv/parsers/virtimage.py
--- a/virtconv/parsers/virtimage.py
+++ b/virtconv/parsers/virtimage.py
@@ -195,7 +195,7 @@
         raise NotImplementedError
 
     @staticmethod
-    def import_file(input_file):
+    def import_file(input_file, vm):
         """
         Import a configuration file.  Raises if the file couldn't be
         opened, or parsing otherwise failed.
diff --git a/virtconv/parsers/virtinstance.py b/virtconv/parsers/virtinstance.py
new file mode 100644
--- /dev/null
+++ b/virtconv/parsers/virtinstance.py
@@ -0,0 +1,370 @@
+#
+# 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.vmcfg as vmcfg
+import virtconv.diskcfg as diskcfg
+import virtconv.netdevcfg as netdevcfg
+import virtconv.formats as formats
+import virtinst.FullVirtGuest as fv
+
+import os
+import stat
+import re
+
+bootdevs = {
+    vmcfg.VM_TYPE_PV: "",
+    vmcfg.VM_TYPE_HVM: "<boot dev='hd' />",
+}
+
+consoles = {
+    vmcfg.VM_TYPE_PV: "",
+    vmcfg.VM_TYPE_HVM: "<console type='pty' />",
+}
+
+disk_template = """
+<disk %(typeattr)s device='%(device)s'>
+ %(hostdev)s
+ <target dev='%(prefix)s%(dev)s' />
+ %(readonly)s
+</disk>
+"""
+
+netdev_template = """
+<interface type='%(type)s'>
+ %(source)s
+ %(mac)s
+ %(model)s
+</interface>
+"""
+
+instance_template = """
+<domain type='xen'>
+  <name>%(name)s</name>
+  %(bootloader)s
+  <os>
+    <type>%(type)s</type>
+    %(loader)s
+    %(bootdev)s
+  </os>
+  <memory>%(memory)s</memory>
+  <vcpu>%(nr_vcpus)s</vcpu>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <clock offset='%(clock)s'/>
+  <features>
+    %(acpi)s
+    %(apic)s
+    <pae />
+  </features>
+  <devices>
+    %(emulator)s
+    %(disks)s
+    %(netdevs)s
+    <graphics type='vnc' port='-1' />
+    <input type='mouse' bus='ps2' />
+    %(usbtablet)s
+    %(console)s
+  </devices>
+</domain>
+"""
+
+def export_netdevs(vm):
+    """
+    Export code for the network devices.
+    """
+
+    netdevs = []
+
+    for netdev in vm.netdevs.values():
+        mac = ""
+        if netdev.mac != "auto":
+            mac = "<mac address='%s' />" % netdev.mac
+
+        nettype = {
+            netdevcfg.NETDEV_TYPE_DEV : "ethernet",
+            netdevcfg.NETDEV_TYPE_BRIDGE : "bridge",
+            netdevcfg.NETDEV_TYPE_UNKNOWN : "bridge",
+            netdevcfg.NETDEV_TYPE_NETWORK : "network",
+        }.get(netdev.type)
+
+        source = ""
+        if netdev.source:
+            srcattr = nettype
+            if netdev.type == netdevcfg.NETDEV_TYPE_DEV:
+                srcattr = "dev"
+            source = "<source %s='%s' />" % (srcattr, netdev.source)
+
+        # FIXME: should warn here
+        if not nettype:
+            continue
+
+        model = ""
+        if netdev.driver:
+            model = "<model type='%s' />" % netdev.driver
+
+        netdevs.append(netdev_template % {
+            "type" : nettype,
+            "source" : source,
+            "mac" : mac,
+            "model": model,
+        })
+
+    return netdevs
+
+def export_disks(vm):
+    """
+    Export code for the disks.  Slightly tricky for two reasons.
+    
+    We can't handle duplicate disks: some vmx files define SCSI/IDE devices
+    that point to the same storage, and Xen isn't happy about that. We
+    just ignore any entries that have duplicate paths.
+
+    Since there is no meaningful SCSI support in rombios/qemu, we
+    forcibly switch the disks to IDE, and expect the guest OS to cope
+    (which at least Linux does admirably).
+    """
+
+    out = []
+    paths = []
+
+    disks = {}
+
+    for (bus, instance), disk in sorted(vm.disks.iteritems()):
+
+        if disk.path and disk.path in paths:
+            continue
+
+        if bus == "scsi":
+            instance = 0
+            while disks.get(("ide", instance)):
+                instance += 1
+
+        disks[("ide", instance)] = disk
+
+        if disk.path:
+            paths += [ disk.path ]
+
+    for (bus, instance), disk in sorted(disks.iteritems()):
+
+        if not disk.path:
+            typeattr = ""
+            hostdev = ""
+
+            # It's quite common for .vmx files to reference a
+            # non-existent ISO (which was cleaned up in vmx_parser).
+            # Just skip them.
+            if disk.type == diskcfg.DISK_TYPE_ISO:
+                continue
+        else:
+            # Of course, this file path might be relative, so we won't be
+            # able to stat() it.  In such a case, it's almost certainly a
+            # file anyway, so the fallback is fine.
+            typeattr = "type='file'"
+            hostdev = ("<driver name='file' />\n"
+                "<source file='%s' />\n" % disk.path)
+
+            try:
+                if stat.S_ISBLK(os.stat(disk.path)[0]):
+                    typeattr = "type='block'"
+                    hostdev = ("<driver name='phy' />\n"
+                        "<source dev='%s' />\n" % disk.path)
+            except:
+                pass
+
+            if disk.format == diskcfg.DISK_FORMAT_VDISK:
+                hostdev = ("<driver name='tap' type='vdisk' />\n"
+                    "<source file='%s' />\n" % disk.path)
+
+        device = "disk"
+        readonly = ""
+
+        if (disk.type == diskcfg.DISK_TYPE_CDROM or
+            disk.type == diskcfg.DISK_TYPE_ISO):
+            device = "cdrom"
+            readonly = "<readonly />"
+
+        bus = "ide"
+
+        disk_prefix = "xvd"
+        if vm.type == vmcfg.VM_TYPE_HVM:
+            if bus == "ide":
+                disk_prefix = "hd"
+            else:
+                disk_prefix = "sd"
+
+        # FIXME: needs updating for later Xen enhancements; need to
+        # implement capabilities checking for max disks etc.
+        drive_nr = ascii_letters[int(instance) % 26]
+
+        instance += 1
+
+        out.append(disk_template % {
+            "typeattr" : typeattr,
+            "device" : device,
+            "hostdev" : hostdev,
+            "prefix" : disk_prefix,
+            "dev" :  drive_nr,
+            "readonly" : readonly,
+        })
+ 
+    return out
+
+def export_os_params(vm):
+    """
+    Export OS-specific parameters.
+    """
+    ostype = None
+    osvariant = None
+
+    ostype = fv.OS_TYPES.get(vm.os_type)
+    if ostype:
+        osvariant = ostype.variants.get(vm.os_variant)
+
+    def get_os_val(key, default):
+        val = None
+        if osvariant:
+            val = osvariant.get(key)
+        if not val and ostype:
+            val = ostype.get(key)
+        if not val:
+            val = default
+        return val
+
+    acpi = ""
+    if vm.noacpi is False and get_os_val("acpi", True):
+        acpi = "<acpi />"
+
+    apic = ""
+    if vm.noapic is False and get_os_val("apic", False):
+        apic = "<apic />"
+
+    clock = get_os_val("clock", "utc")
+
+    usbtablet = ""
+    if get_os_val("input", [ "tablet", "usb" ])[0] == "tablet":
+        usbtablet = "<input type='tablet' bus='usb' />"
+
+    return acpi, apic, clock, usbtablet
+
+
+class virtinstance_parser(formats.parser):
+    """
+    Support for libvirt's domain instance format as defined (mostly) by
+    libvirt.rng.  Currently, this only produces domains for Xen.
+
+    This is a somewhat challenging format as it encodes significant
+    amounts of information specific to a particular platform (for
+    example, the pygrub path.  For now, we'll assume it's for the current
+    platform.  Later, we'd take an optional hypervisor connection.
+
+    Known limitations:
+
+    - Only handles bridge, ethernet, and network type netdevs
+    - doesn't handle netdev script or IP address
+    """
+
+    name = "virt-instance"
+    suffix = ".virt-instance.xml"
+    can_import = False
+    can_export = True
+    can_identify = False
+
+    @staticmethod
+    def identify_file(input_file):
+        """
+        Return True if the given file is of this format.
+        """
+        raise NotImplementedError
+
+    @staticmethod
+    def import_file(input_file, vm):
+        """
+        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")
+
+        # xend wants the name to match r'^[A-Za-z0-9_\-\.\:\/\+]+$'
+        vmname = re.sub(r'[^A-Za-z0-9_.:/+-]+',  '_', vm.name)
+
+        vmtype = "xen"
+        if vm.type == vmcfg.VM_TYPE_HVM:
+            vmtype = "hvm"
+
+        emulator = vm.emulator()
+        bootloader = ""
+        loader = vm.loader()
+
+        if vm.type == vmcfg.VM_TYPE_PV:
+            bootloader = loader
+            loader = ""
+
+        if bootloader:
+            bootloader = "<bootloader>%s</bootloader>" % bootloader
+        if emulator:
+            emulator = "<emulator>%s</emulator>" % emulator
+        if loader:
+            loader = "<loader>%s</loader>" % loader
+
+        netdevs = export_netdevs(vm)
+        disks = export_disks(vm)
+  
+        acpi, apic, clock, usbtablet = export_os_params(vm)
+
+        out = instance_template % {
+            "name" : vmname,
+            "bootloader" : bootloader,
+            "type" : vmtype,
+            "loader"  : loader,
+            "bootdev" : bootdevs[vm.type],
+            # Mb to Kb
+            "memory" : int(vm.memory) * 1024,
+            "nr_vcpus" : vm.nr_vcpus,
+            "clock" : clock,
+            "acpi" : acpi,
+            "apic" : apic,
+            "emulator" : emulator,
+            "disks" : "".join(disks),
+            "netdevs" : "".join(netdevs),
+            "usbtablet" : usbtablet,
+            "console" : consoles[vm.type],
+        }
+
+        outfile = open(output_file, "w")
+        outfile.writelines(out)
+        outfile.close()
+
+formats.register_parser(virtinstance_parser)
diff --git a/virtconv/parsers/vmx.py b/virtconv/parsers/vmx.py
--- a/virtconv/parsers/vmx.py
+++ b/virtconv/parsers/vmx.py
@@ -118,13 +118,11 @@
         return re.match(r'^#!\s*/usr/bin/vm(ware|player)', line) is not None
 
     @staticmethod
-    def import_file(input_file):
+    def import_file(input_file, vm):
         """
         Import a configuration file.  Raises if the file couldn't be
         opened, or parsing otherwise failed.
         """
-
-        vm = vmcfg.vm()
 
         infile = open(input_file, "r")
         contents = infile.readlines()
@@ -175,7 +173,6 @@
         vm.nr_vcpus = config.get("numvcpus")
      
         vm.validate()
-        return vm
 
     @staticmethod
     def export_file(vm, output_file):
diff --git a/virtconv/vmcfg.py b/virtconv/vmcfg.py
--- a/virtconv/vmcfg.py
+++ b/virtconv/vmcfg.py
@@ -21,6 +21,7 @@
 import platform
 from virtconv import diskcfg
 from virtinst import CapabilitiesParser
+from virtinst import util
 
 VM_TYPE_UNKNOWN = 0
 VM_TYPE_PV = 1
@@ -46,19 +47,21 @@
     name = None
     suffix = None
 
-    def __init__(self):
+    def __init__(self, arch, is_pv):
         self.name = None
         self.description = None
         self.memory = None
         self.nr_vcpus = None
         self.disks = {}
         self.netdevs = {}
-        self.type = VM_TYPE_HVM
-        self.arch = "i686"
+        self.arch = arch
         self.noacpi = None
         self.noapic = None
         self.os_type = None
         self.os_variant = None
+        self.type = VM_TYPE_HVM
+        if is_pv:
+            self.type = VM_TYPE_PV
 
     def validate(self):
         """
@@ -82,6 +85,41 @@
                 raise ValueError("Disk %s:%s storage does not exist"
                     % (bus, inst))
 
+    def emulator(self, conn=None):
+        """
+        Return the emulator path for this VM.
+        """
+        typename = "xen"
+        if self.type == VM_TYPE_HVM:
+            typename = "hvm"
+
+        if conn:
+            caps = CapabilitiesParser.parse(conn.getCapabilities())
+            guest = caps.guestForOSType(typename, self.arch)
+            if guest and len(guest.domains):
+                return guest.domains[0].emulator
+
+        if self.type == VM_TYPE_PV:
+            return ""
+        return util.default_emulator()
+
+    def loader(self, conn=None):
+        """
+        Return the loader path for this VM.
+        """
+
+        # loader is not set for PV in capabilities
+        if self.type == VM_TYPE_PV:
+            return util.pygrub_path(conn)
+
+        if conn:
+            caps = CapabilitiesParser.parse(conn.getCapabilities())
+            guest = caps.guestForOSType("hvm", self.arch)
+            if guest and len(guest.domains):
+                return guest.domains[0].loader
+
+        return util.default_loader()
+
 def host(conn=None):
     """
     Return the host, as seen in platform.system(), but possibly from a
diff --git a/virtinst/FullVirtGuest.py b/virtinst/FullVirtGuest.py
--- a/virtinst/FullVirtGuest.py
+++ b/virtinst/FullVirtGuest.py
@@ -124,13 +124,10 @@
         self.arch = arch
         if emulator is None:
             if self.type == "xen":
-                if os.uname()[4] in ("x86_64"):
-                    emulator = "/usr/lib64/xen/bin/qemu-dm"
-                else:
-                    emulator = "/usr/lib/xen/bin/qemu-dm"
+                emulator = util.default_emulator()
         self.emulator = emulator
         if self.type == "xen":
-            self.loader = "/usr/lib/xen/boot/hvmloader"
+            self.loader = util.default_loader()
         else:
             self.loader = None
         self._os_type = None
diff --git a/virtinst/util.py b/virtinst/util.py
--- a/virtinst/util.py
+++ b/virtinst/util.py
@@ -295,3 +295,13 @@
     if platform.system() == "SunOS":
         return "/usr/lib/xen/bin/pygrub"
     return "/usr/bin/pygrub"
+
+def default_emulator():
+    """Return a default emulator for a domain."""
+    if os.uname()[4] in ("x86_64"):
+        return "/usr/lib64/xen/bin/qemu-dm"
+    return "/usr/lib/xen/bin/qemu-dm"
+
+def default_loader():
+    """Return a default loader for a domain."""
+    return "/usr/lib/xen/boot/hvmloader"




More information about the et-mgmt-tools mailing list