4 # Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Tool to upgrade the configuration file.
24 This code handles only the types supported by simplejson. As an
25 example, 'set' is a 'list'.
36 from ganeti import constants
37 from ganeti import serializer
38 from ganeti import utils
39 from ganeti import cli
40 from ganeti import bootstrap
41 from ganeti import config
48 class Error(Exception):
49 """Generic exception"""
54 """Configures the logging module.
57 formatter = logging.Formatter("%(asctime)s: %(message)s")
59 stderr_handler = logging.StreamHandler()
60 stderr_handler.setFormatter(formatter)
62 stderr_handler.setLevel(logging.NOTSET)
64 stderr_handler.setLevel(logging.INFO)
66 stderr_handler.setLevel(logging.CRITICAL)
68 root_logger = logging.getLogger("")
69 root_logger.setLevel(logging.NOTSET)
70 root_logger.addHandler(stderr_handler)
77 global options, args # pylint: disable-msg=W0603
79 program = os.path.basename(sys.argv[0])
82 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
83 parser.add_option('--dry-run', dest='dry_run',
85 help="Try to do the conversion, but don't write"
87 parser.add_option(cli.FORCE_OPT)
88 parser.add_option(cli.DEBUG_OPT)
89 parser.add_option(cli.VERBOSE_OPT)
90 parser.add_option('--path', help="Convert configuration in this"
91 " directory instead of '%s'" % constants.DATA_DIR,
92 default=constants.DATA_DIR, dest="data_dir")
93 parser.add_option("--no-verify",
94 help="Do not verify configuration after upgrade",
95 action="store_true", dest="no_verify", default=False)
96 (options, args) = parser.parse_args()
98 # We need to keep filenames locally because they might be renamed between
100 options.data_dir = os.path.abspath(options.data_dir)
101 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
102 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
103 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
104 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
105 options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
106 options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
107 options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
108 options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
114 raise Error("No arguments expected")
116 if not options.force:
117 usertext = ("%s MUST be run on the master node. Is this the master"
118 " node and are ALL instances down?" % program)
119 if not cli.AskUser(usertext):
120 sys.exit(constants.EXIT_FAILURE)
122 # Check whether it's a Ganeti configuration directory
123 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
124 os.path.isfile(options.SERVER_PEM_PATH) and
125 os.path.isfile(options.KNOWN_HOSTS_PATH)):
126 raise Error(("%s does not seem to be a Ganeti configuration"
127 " directory") % options.data_dir)
129 config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
132 config_version = config_data["version"]
134 raise Error("Unable to determine configuration version")
136 (config_major, config_minor, config_revision) = \
137 constants.SplitVersion(config_version)
139 logging.info("Found configuration version %s (%d.%d.%d)",
140 config_version, config_major, config_minor, config_revision)
142 if "config_version" in config_data["cluster"]:
143 raise Error("Inconsistent configuration: found config_version in"
144 " configuration file")
146 # Upgrade from 2.0/2.1/2.2 to 2.3
147 if config_major == 2 and config_minor in (0, 1, 2):
148 if config_revision != 0:
149 logging.warning("Config revision is %s, not 0", config_revision)
151 config_data["version"] = constants.BuildVersion(2, 3, 0)
153 elif config_major == 2 and config_minor == 3:
154 logging.info("No changes necessary")
157 raise Error("Configuration version %d.%d.%d not supported by this tool" %
158 (config_major, config_minor, config_revision))
160 if os.path.isfile(options.RAPI_USERS_FILE_PRE24):
161 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
162 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
163 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
164 mkdir=True, mkdir_mode=0750)
166 # Create a symlink for RAPI users file
167 if not os.path.islink(options.RAPI_USERS_FILE_PRE24):
168 logging.info("Creating symlink from %s to %s",
169 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
170 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
173 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
174 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
175 data=serializer.DumpJson(config_data),
177 dry_run=options.dry_run,
180 if not options.dry_run:
181 bootstrap.GenerateClusterCrypto(False, False, False, False,
182 nodecert_file=options.SERVER_PEM_PATH,
183 rapicert_file=options.RAPI_CERT_FILE,
184 hmackey_file=options.CONFD_HMAC_KEY,
185 cds_file=options.CDS_FILE)
188 logging.critical("Writing configuration failed. It is probably in an"
189 " inconsistent state and needs manual intervention.")
192 # test loading the config file
193 if not (options.dry_run or options.no_verify):
194 logging.info("Testing the new config file...")
195 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
197 # if we reached this, it's all fine
198 vrfy = cfg.VerifyConfig()
200 logging.error("Errors after conversion:")
202 logging.error(" - %s", item)
204 logging.info("File loaded successfully")
207 if __name__ == "__main__":