#!/usr/bin/python # # Copyright (C) 2007, 2008 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 upgrade the configuration file. This code handles only the types supported by simplejson. As an example, "set" is a "list". """ import os import os.path import sys import optparse import tempfile import logging import errno from ganeti import constants from ganeti import serializer from ganeti import utils from ganeti import cli # We need to keep filenames locally because they might be renamed between # versions. CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data" SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem" KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts" SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name" SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version" options = None args = None # Unique object to identify calls without default value NoDefault = object() class Error(Exception): """Generic exception""" pass def ReadFile(file_name, default=NoDefault): """Reads a file. """ logging.debug("Reading %s", file_name) try: fh = open(file_name, 'r') except IOError, err: if default is not NoDefault and err.errno == errno.ENOENT: return default raise try: return fh.read() finally: fh.close() def WriteFile(file_name, data): """Writes a configuration file. """ logging.debug("Writing %s", file_name) utils.WriteFile(file_name=file_name, data=data, mode=0600, dry_run=options.dry_run, backup=True) def SetupLogging(): """Configures the logging module. """ formatter = logging.Formatter("%(asctime)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.CRITICAL) root_logger = logging.getLogger("") root_logger.setLevel(logging.NOTSET) root_logger.addHandler(stderr_handler) def main(): """Main program. """ global options, args program = os.path.basename(sys.argv[0]) # Option parsing parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]") parser.add_option('--dry-run', dest='dry_run', action="store_true", help="Try to do the conversion, but don't write" " output file") parser.add_option(cli.FORCE_OPT) parser.add_option(cli.DEBUG_OPT) parser.add_option('--verbose', dest='verbose', action="store_true", help="Verbose output") (options, args) = parser.parse_args() SetupLogging() # Option checking if args: raise Error("No arguments expected") if not options.force: usertext = ("%s MUST run on the master node. Is this the master" " node?" % program) if not cli.AskUser(usertext): sys.exit(1) # Check whether it's a Ganeti configuration directory if not (os.path.isfile(CONFIG_DATA_PATH) and os.path.isfile(SERVER_PEM_PATH) or os.path.isfile(KNOWN_HOSTS_PATH)): raise Error(("%s does not seem to be a known Ganeti configuration" " directory") % constants.DATA_DIR) config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip() logging.info("Found configuration version %s", config_version) config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH)) # Ganeti 1.2? if config_version == "1.2": logging.info("Found a Ganeti 1.2 configuration") old_config_version = config_data["cluster"].get("config_version", None) logging.info("Found old configuration version %s", old_config_version) if old_config_version not in (3, ): raise Error("Unsupported configuration version: %s" % old_config_version) # Make sure no instance uses remote_raid1 anymore remote_raid1_instances = [] for instance in config_data["instances"]: if instance["disk_template"] == "remote_raid1": remote_raid1_instances.append(instance["name"]) if remote_raid1_instances: for name in remote_raid1_instances: logging.error("Instance %s still using remote_raid1 disk template") raise Error("Unable to convert configuration as long as there are" " instances using remote_raid1 disk template") # The configuration version will be stored in a ssconf file if 'config_version' in config_data['cluster']: del config_data['cluster']['config_version'] # Build content of new known_hosts file cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip() cluster_key = config_data['cluster']['rsahostkeypub'] known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key) else: logging.info("Found a Ganeti 2.0 configuration") if "config_version" in config_data["cluster"]: raise Error("Inconsistent configuration: found config_data in" " configuration file") known_hosts = None config_version_str = "%s\n" % constants.BuildVersion(2, 0, 0) try: logging.info("Writing configuration file") WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data)) logging.info("Writing configuration version %s", config_version_str.strip()) WriteFile(SSCONF_CONFIG_VERSION_PATH, config_version_str) if known_hosts is not None: logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip()) WriteFile(KNOWN_HOSTS_PATH, known_hosts) except: logging.critical("Writing configuration failed. It is proably in an" " inconsistent state and needs manual intervention.") raise if __name__ == "__main__": main() # vim: set foldmethod=marker :