[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