# HG changeset patch # User john.levon@xxxxxxx # Date 1215627983 25200 # Node ID a36c666ef5d381e3961545d0359949e82d6d064b # Parent c96c084d9b8d39a6df238cd9798011a8c4a34dd0 Cleanup on failure If we can't convert the disks or export the file, perform some cleanup. Signed-off-by: John Levon <john.levon@xxxxxxx> diff --git a/virt-convert b/virt-convert --- a/virt-convert +++ b/virt-convert @@ -29,9 +29,12 @@ import virtinst.cli as cli import virtinst.util as util import virtconv -import virtconv.vmconfig as vmconfig +import virtconv.formats as formats +import virtconv.vmcfg as vmcfg +import virtconv.diskcfg as diskcfg def parse_args(): + """Parse and verify command line.""" opts = OptionParser() opts.set_usage("%prog [options] inputdir|input.vmx " "[outputdir|output.xml]") @@ -98,19 +101,41 @@ if not options.quiet: print msg +def cleanup(msg, options, vmdef, paths): + """ + After failure, clean up anything we created. + """ + logging.error(msg) + + try: + paths.reverse() + for path in paths: + if os.path.isdir(path): + os.rmdir(path) + elif os.path.isfile(path): + os.remove(path) + + for disk in vmdef.disks: + disk.cleanup() + except OSError, e: + logging.error("Couldn't clean up output directory \"%s\": %s" % + (options.output_dir, e.strerror)) + + sys.exit(1) + def main(): options = parse_args() cli.setupLogging("virt-convert", options.debug) try: - inp = virtconv.vmconfig.find_parser_by_name(options.input_format) + inp = formats.find_parser_by_name(options.input_format) except: logging.error("No parser of format \"%s\" was found." % options.input_format) sys.exit(1) try: - outp = virtconv.vmconfig.find_parser_by_name(options.output_format) + outp = formats.find_parser_by_name(options.output_format) except: logging.error("No parser of format \"%s\" was found." % options.output_format) @@ -130,17 +155,21 @@ sys.exit(1) if options.paravirt: - vmdef.type = vmconfig.VM_TYPE_PV + vmdef.type = vmcfg.VM_TYPE_PV else: - vmdef.type = vmconfig.VM_TYPE_HVM + vmdef.type = vmcfg.VM_TYPE_HVM vmdef.arch = options.arch + clean = [] + unixname = vmdef.name.replace(" ", "-") + if not options.output_dir: options.output_dir = unixname try: logging.debug("Creating directory %s" % options.output_dir) + clean += [ options.output_dir ] os.mkdir(options.output_dir) except OSError, e: if (e.errno != errno.EEXIST): @@ -163,18 +192,20 @@ try: for d in vmdef.disks: verbose(options, "Converting disk \"%s\" to type %s..." % - (d.path, vmconfig.qemu_formats[vmconfig.DISK_TYPE_RAW])) - d.convert(options.input_dir, options.output_dir, - vmconfig.DISK_TYPE_RAW) - except Exception, e: - logging.error(e) - sys.exit(1) - + (d.path, "raw")) + d.convert(options.input_dir, options.output_dir, "raw") + except OSError, e: + cleanup("Couldn't convert disks: %s" % e.strerror, + options, vmdef, clean) + except RuntimeError, e: + cleanup("Couldn't convert disks: %s" % e.message, options, vmdef, clean) + try: + clean += [ options.output_file ] outp.export_file(vmdef, options.output_file) - except Exception, e: - logging.error(e) - sys.exit(1) + except ValueError, e: + cleanup("Couldn't export to file \"%s\": %s" % + (options.output_file, e.message), options, vmdef, clean) verbose(options, "Done.") diff --git a/virtconv/diskcfg.py b/virtconv/diskcfg.py new file mode 100644 --- /dev/null +++ b/virtconv/diskcfg.py @@ -0,0 +1,181 @@ +# +# 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 shutil +import errno +import os + +DISK_FORMAT_NONE = 0 +DISK_FORMAT_RAW = 1 +DISK_FORMAT_VMDK = 2 +DISK_FORMAT_VDISK = 3 + +DISK_TYPE_DISK = 0 +DISK_TYPE_CDROM = 1 +DISK_TYPE_ISO = 2 + +disk_suffixes = { + DISK_FORMAT_RAW: ".img", + DISK_FORMAT_VMDK: ".vmdk", + DISK_FORMAT_VDISK: ".vdisk.xml", +} + +qemu_formats = { + DISK_FORMAT_RAW: "raw", + DISK_FORMAT_VMDK: "vmdk", + DISK_FORMAT_VDISK: "vdisk", +} + +disk_format_names = { + "none": DISK_FORMAT_NONE, + "raw": DISK_FORMAT_RAW, + "vmdk": DISK_FORMAT_VMDK, + "vdisk": DISK_FORMAT_VDISK, +} + +def ensuredirs(path): + """ + Make sure that all the containing directories of the given file + path exist. + """ + try: + os.makedirs(os.path.dirname(path)) + except OSError, e: + if e.errno != errno.EEXIST: + raise + +class disk(object): + """Definition of an individual disk instance.""" + + def __init__(self, path = None, number = 0, format = None, bus = "ide", + type = DISK_TYPE_DISK): + self.path = path + self.format = format + self.number = number + self.bus = bus + self.type = type + self.clean = [] + + def cleanup(self): + """ + Remove any generated output. + """ + + for path in self.clean: + if os.path.isfile(path): + os.remove(path) + + self.clean = [] + + def copy_file(self, infile, outfile): + """Copy an individual file.""" + self.clean += [ outfile ] + ensuredirs(outfile) + shutil.copy(infile, outfile) + + def copy(self, indir, outdir, out_format): + """ + Copy the underlying disk files to a destination, if necessary. + Return True if we need a further conversion step. + """ + + if os.path.isabs(self.path): + return False + + need_copy = False + need_convert = False + + if self.format == out_format: + need_convert = False + need_copy = (indir != outdir) + else: + if out_format == DISK_FORMAT_NONE: + need_copy = (indir != outdir) + need_convert = False + else: + need_copy = (indir != outdir and out_format == DISK_FORMAT_VDISK) + need_convert = True + + if need_copy: + if out_format == DISK_FORMAT_VDISK: + stdin, stdout = os.popen2(["/usr/bin/vdiskadm", "import", "-n", + "-f", "-t", qemu_formats[self.format], + "\"%s\"" % os.path.join(indir, self.path)]) + paths = stdout.readlines() + stdin.close() + stdout.close() + for path in paths: + self.copy_file(os.path.join(indir, path), + os.path.join(outdir, path)) + return need_convert + + # this is not correct for all VMDK files, but it will have + # to do for now + self.copy_file(os.path.join(indir, self.path), + os.path.join(outdir, self.path)) + + return need_convert + + def convert(self, indir, outdir, output_format): + """ + Convert a disk into the requested format if possible, in the + given output directory. Raises RuntimeError or other + failures. + """ + + out_format = disk_format_names[output_format] + indir = os.path.normpath(os.path.abspath(indir)) + outdir = os.path.normpath(os.path.abspath(outdir)) + + need_convert = self.copy(indir, outdir, out_format) + if not need_convert: + return + + relin = self.path + relout = self.path.replace(disk_suffixes[self.format], + disk_suffixes[out_format]) + absin = os.path.join(indir, relin) + absout = os.path.join(outdir, relout) + + ensuredirs(absout) + + if out_format == DISK_FORMAT_VDISK: + convert_cmd = ("""/usr/bin/vdiskadm import -t %s "%s" "%s" """ % + (qemu_formats[out_format], absin, absout)) + elif out_format == DISK_FORMAT_RAW: + convert_cmd = ("""qemu-img convert "%s" -O %s "%s" """ % + (absin, qemu_formats[out_format], absout)) + else: + raise NotImplementedError("Cannot convert to disk format %s" % + output_format) + + ret = os.system(convert_cmd) + if ret != 0: + raise RuntimeError("Disk conversion failed with exit status %d" + % ret) + + self.path = relout + self.format = out_format + +def disk_formats(): + """ + Return a list of supported disk formats. + """ + return disk_format_names.keys() diff --git a/virtconv/formats.py b/virtconv/formats.py new file mode 100644 --- /dev/null +++ b/virtconv/formats.py @@ -0,0 +1,82 @@ +# +# 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. +# + +_parsers = [ ] + +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 diff --git a/virtconv/parsers/virtimage.py b/virtconv/parsers/virtimage.py --- a/virtconv/parsers/virtimage.py +++ b/virtconv/parsers/virtimage.py @@ -19,7 +19,9 @@ # from string import ascii_letters -import virtconv.vmconfig as vmconfig +import virtconv.formats as formats +import virtconv.vmcfg as vmcfg +import virtconv.diskcfg as diskcfg pv_boot_template = """ <boot type="xen"> @@ -70,7 +72,7 @@ </image> """ -class virtimage_parser(vmconfig.parser): +class virtimage_parser(formats.parser): """ Support for virt-install's image format (see virt-image man page). """ @@ -122,9 +124,10 @@ hvm_disks.append("""<drive disk="%s" target="hd%s" />\n""" % (path, ascii_letters[number % 26])) storage_disks.append( - """<disk file="%s" use="system" format="raw"/>\n""" % path) + """<disk file="%s" use="system" format="%s"/>\n""" + % (path, diskcfg.qemu_formats[disk.format])) - if vm.type == vmconfig.VM_TYPE_PV: + if vm.type == vmcfg.VM_TYPE_PV: boot_template = pv_boot_template else: boot_template = hvm_boot_template @@ -149,4 +152,4 @@ outfile.writelines(out) outfile.close() -vmconfig.register_parser(virtimage_parser) +formats.register_parser(virtimage_parser) diff --git a/virtconv/parsers/vmx.py b/virtconv/parsers/vmx.py --- a/virtconv/parsers/vmx.py +++ b/virtconv/parsers/vmx.py @@ -18,9 +18,11 @@ # MA 02110-1301 USA. # -import virtconv.vmconfig as vmconfig +import virtconv.formats as formats +import virtconv.vmcfg as vmcfg +import virtconv.diskcfg as diskcfg -class vmx_parser(vmconfig.parser): +class vmx_parser(formats.parser): """ Support for VMWare .vmx files. Note that documentation is particularly sparse on this format, with pretty much the best @@ -44,7 +46,7 @@ opened, or parsing otherwise failed. """ - vm = vmconfig.vm() + vm = vmcfg.vm() infile = open(input_file, "r") contents = infile.readlines() @@ -86,7 +88,7 @@ vm.nr_vcpus = config.get("numvcpus") for (number, path) in enumerate(disks): - vm.disks += [ vmconfig.disk(path, number, vmconfig.DISK_TYPE_VMDK) ] + vm.disks += [ diskcfg.disk(path, number, diskcfg.DISK_FORMAT_VMDK) ] vm.validate() return vm @@ -104,4 +106,4 @@ raise NotImplementedError -vmconfig.register_parser(vmx_parser) +formats.register_parser(vmx_parser) diff --git a/virtconv/vmconfig.py b/virtconv/vmcfg.py rename from virtconv/vmconfig.py rename to virtconv/vmcfg.py --- a/virtconv/vmconfig.py +++ b/virtconv/vmcfg.py @@ -18,71 +18,8 @@ # 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): """ @@ -111,7 +48,7 @@ self.nr_vcpus = None self.disks = [ ] self.type = VM_TYPE_HVM - self.arch = "i686" # FIXME? + self.arch = "i686" def validate(self): """ @@ -129,65 +66,3 @@ 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 _______________________________________________ et-mgmt-tools mailing list et-mgmt-tools@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/et-mgmt-tools