# HG changeset patch # User john.levon@xxxxxxx # 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@xxxxxxx> 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" _______________________________________________ et-mgmt-tools mailing list et-mgmt-tools@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/et-mgmt-tools