diff --git a/Makefile.am b/Makefile.am index 23aae310b1933627021453a1426041d220d4b0cd..9e6dcdc5553ae8f8b76b71bb90c6d58851cabe95 100644 --- a/Makefile.am +++ b/Makefile.am @@ -193,6 +193,7 @@ pkgpython_PYTHON = \ lib/netutils.py \ lib/objects.py \ lib/opcodes.py \ + lib/ovf.py \ lib/qlang.py \ lib/query.py \ lib/rpc.py \ @@ -513,6 +514,7 @@ dist_tools_PYTHON = \ tools/cluster-merge \ tools/lvmstrap \ tools/move-instance \ + tools/ovfconverter \ tools/setup-ssh \ tools/sanitize-config diff --git a/lib/ovf.py b/lib/ovf.py new file mode 100644 index 0000000000000000000000000000000000000000..21816e8a68d7d056e32f68082770889775daab5e --- /dev/null +++ b/lib/ovf.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# + +# Copyright (C) 2011 Google Inc. +# +# 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. + + +"""Converter tools between ovf and ganeti config file + +""" + +import os.path +import shutil + +from ganeti import errors +from ganeti import utils + + +class Converter(object): + """Converter class for OVF packages. + + Converter is a class above both ImporterOVF and ExporterOVF. It's purpose is + to provide a common interface for the two. + + @type options: optparse.Values + @ivar options: options parsed from the command line + @type output_dir: string + @ivar output_dir: directory to which the results of conversion shall be + written + @type temp_file_manager: L{utils.TemporaryFileManager} + @ivar temp_file_manager: container for temporary files created during + conversion + @type temp_dir: string + @ivar temp_dir: temporary directory created then we deal with OVA + + """ + def __init__(self, input_path, options): + """Initialize the converter. + + @type input_path: string + @param input_path: path to the Converter input file + @type options: optparse.Values + @param options: command line options + + @raise errors.OpPrereqError: if file does not exist + + """ + input_path = os.path.abspath(input_path) + if not os.path.isfile(input_path): + raise errors.OpPrereqError("File does not exist: %s" % input_path) + self.options = options + self.temp_file_manager = utils.TemporaryFileManager() + self.temp_dir = None + self.output_dir = None + self._ReadInputData(input_path) + + def _ReadInputData(self, input_path): + """Reads the data on which the conversion will take place. + + @type input_path: string + @param input_path: absolute path to the Converter input file + + """ + raise NotImplementedError() + + def Parse(self): + """Parses the data and creates a structure containing all required info. + + """ + raise NotImplementedError() + + def Save(self): + """Saves the gathered configuration in an apropriate format. + + """ + raise NotImplementedError() + + def Cleanup(self): + """Cleans the temporary directory, if one was created. + + """ + self.temp_file_manager.Cleanup() + if self.temp_dir: + shutil.rmtree(self.temp_dir) + self.temp_dir = None + + +class OVFImporter(Converter): + def _ReadInputData(self, input_path): + pass + + def Parse(self): + pass + + def Save(self): + pass + + +class OVFExporter(Converter): + def _ReadInputData(self, input_path): + pass + + def Parse(self): + pass + + def Save(self): + pass diff --git a/tools/ovfconverter b/tools/ovfconverter new file mode 100755 index 0000000000000000000000000000000000000000..17024bf202bd9ed20fa07f4f4a833b3ed456cdf1 --- /dev/null +++ b/tools/ovfconverter @@ -0,0 +1,211 @@ +#!/usr/bin/python +# + +# Copyright (C) 2011 Google Inc. +# +# 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. + + +"""Tool to translate between ovf and ganeti backup format. + +""" + +import logging +import optparse +import os + +from ganeti import cli +from ganeti import constants +from ganeti import errors +from ganeti import ovf + + +IMPORT_MODE = "import" +EXPORT_MODE = "export" + + +def CheckOptions(parser, options_dict, required, forbidden, excluding, mode): + """Performes check on the command line options. + + Checks whether the required arguments are present and if none of the arguments + not supported for the current mode are given. + + @type options_dict: list + @param options_dict: dictionary containing all the options from the command + line + @type required: list + @param required: list of pairs (option, argument) where 'option' is required + in mode 'mode' + @type forbidden: list + @param forbidden: list of pairs (option, argument) which are not allowed in + mode 'mode' + @type excluding: list + @param excluding: list of pairs (argument1, argument2); each pair contains + mutually exclusive arguments + @type mode: string + @param mode: current mode of the converter + + """ + for (option, argument) in required: + if not options_dict[option]: + parser.error("Argument %s is required for %s" % (argument, mode)) + for (option, argument) in forbidden: + if options_dict[option]: + parser.error("Argument %s is not allowed in %s mode" % (argument, mode)) + for (arg1, arg2) in excluding: + if options_dict[arg1] and options_dict[arg2]: + parser.error("Arguments %s and %s exclude each other" % (arg1, arg2)) + + +def ParseOptions(): + """Parses the command line options and arguments. + + In case of mismatching parameters, it will show the correct usage and exit. + + @rtype: tuple + @return: (mode, sourcefile to read from, additional options) + + """ + usage = ("%%prog {%s|%s} <source-cfg-file> [options...]" % + (IMPORT_MODE, EXPORT_MODE)) + parser = optparse.OptionParser(usage=usage) + + #global options + parser.add_option(cli.DEBUG_OPT) + parser.add_option(cli.VERBOSE_OPT) + parser.add_option("-n", "--name", dest="name", action="store", + help="Name of the instance") + parser.add_option("--output-dir", dest="output_dir", + help="Path to the output directory") + + #import options + import_group = optparse.OptionGroup(parser, "Import options") + import_group.add_option(cli.BACKEND_OPT) + import_group.add_option(cli.DISK_OPT) + import_group.add_option(cli.DISK_TEMPLATE_OPT) + import_group.add_option(cli.HYPERVISOR_OPT) + import_group.add_option(cli.NET_OPT) + import_group.add_option(cli.NONICS_OPT) + import_group.add_option(cli.OS_OPT) + import_group.add_option(cli.OSPARAMS_OPT) + import_group.add_option(cli.TAG_ADD_OPT) + parser.add_option_group(import_group) + + #export options + export_group = optparse.OptionGroup(parser, "Export options") + export_group.add_option("--compress", dest="compression", + action="store_true", default=False, + help="The exported disk will be compressed to tar.gz") + export_group.add_option("--external", dest="ext_usage", + action="store_true", default=False, + help="The package will be used externally (ommits the" + " Ganeti-specific parts of configuration)") + export_group.add_option("-f", "--format", dest="disk_format", + action="store", + choices=("raw", "cow", "vmdk"), + help="Disk format for export (one of raw/cow/vmdk)") + export_group.add_option("--ova", dest="ova_package", + action="store_true", default=False, + help="Export everything into OVA package") + parser.add_option_group(export_group) + + options, args = parser.parse_args() + if len(args) != 2: + parser.error("Wrong number of arguments") + mode = args.pop(0) + input_path = os.path.abspath(args.pop(0)) + + if mode == IMPORT_MODE: + required = [] + forbidden = [ + ("compression", "--compress"), + ("disk_format", "--format"), + ("ext_usage", "--external"), + ("ova_package", "--ova"), + ] + excluding = [("nics", "no_nics")] + elif mode == EXPORT_MODE: + required = [("disk_format", "--format")] + forbidden = [ + ("beparams", "--backend-parameters"), + ("disk_template", "--disk-template"), + ("disks", "--disk"), + ("hypervisor", "--hypervisor-parameters"), + ("nics", "--net"), + ("no_nics", "--no-nics"), + ("os", "--os-type"), + ("osparams", "--os-parameters"), + ("tags", "--tags"), + ] + excluding = [] + else: + parser.error("First argument should be either '%s' or '%s'" % + (IMPORT_MODE, EXPORT_MODE)) + + options_dict = vars(options) + CheckOptions(parser, options_dict, required, forbidden, excluding, mode) + + return (mode, input_path, options) + + +def SetupLogging(options): + """Setting up logging infrastructure. + + @type options: optparse.Values + @param options: parsed command line options + + """ + formatter = logging.Formatter("%(asctime)s: %(levelname)s %(message)s") + + stderr_handler = logging.StreamHandler() + stderr_handler.setFormatter(formatter) + if options.debug: + stderr_handler.setLevel(logging.NOTSET) + elif options.verbose: + stderr_handler.setLevel(logging.INFO) + else: + stderr_handler.setLevel(logging.WARNING) + + root_logger = logging.getLogger("") + root_logger.setLevel(logging.NOTSET) + root_logger.addHandler(stderr_handler) + + +def main(): + """Main routine. + + """ + (mode, input_path, options) = ParseOptions() + SetupLogging(options) + logging.info("Chosen %s mode, reading the %s file", mode, input_path) + assert mode in (IMPORT_MODE, EXPORT_MODE) + converter = None + try: + if mode == IMPORT_MODE: + converter = ovf.OVFImporter(input_path, options) + elif mode == EXPORT_MODE: + converter = ovf.OVFExporter(input_path, options) + converter.Parse() + converter.Save() + except errors.OpPrereqError, err: + if converter: + converter.Cleanup() + logging.exception(err) + return constants.EXIT_FAILURE + + +if __name__ == "__main__": + main()