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
42 from ganeti import netutils
49 class Error(Exception):
50 """Generic exception"""
55 """Configures the logging module.
58 formatter = logging.Formatter("%(asctime)s: %(message)s")
60 stderr_handler = logging.StreamHandler()
61 stderr_handler.setFormatter(formatter)
63 stderr_handler.setLevel(logging.NOTSET)
65 stderr_handler.setLevel(logging.INFO)
67 stderr_handler.setLevel(logging.WARNING)
69 root_logger = logging.getLogger("")
70 root_logger.setLevel(logging.NOTSET)
71 root_logger.addHandler(stderr_handler)
74 def CheckHostname(path):
75 """Ensures hostname matches ssconf value.
77 @param path: Path to ssconf file
80 ssconf_master_node = utils.ReadOneLineFile(path)
81 hostname = netutils.GetHostname().name
83 if ssconf_master_node == hostname:
86 logging.warning("Warning: ssconf says master node is '%s', but this"
87 " machine's name is '%s'; this tool must be run on"
88 " the master node", ssconf_master_node, hostname)
96 global options, args # pylint: disable=W0603
99 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
100 parser.add_option('--dry-run', dest='dry_run',
102 help="Try to do the conversion, but don't write"
104 parser.add_option(cli.FORCE_OPT)
105 parser.add_option(cli.DEBUG_OPT)
106 parser.add_option(cli.VERBOSE_OPT)
107 parser.add_option("--ignore-hostname", dest="ignore_hostname",
108 action="store_true", default=False,
109 help="Don't abort if hostname doesn't match")
110 parser.add_option('--path', help="Convert configuration in this"
111 " directory instead of '%s'" % constants.DATA_DIR,
112 default=constants.DATA_DIR, dest="data_dir")
113 parser.add_option("--no-verify",
114 help="Do not verify configuration after upgrade",
115 action="store_true", dest="no_verify", default=False)
116 (options, args) = parser.parse_args()
118 # We need to keep filenames locally because they might be renamed between
120 options.data_dir = os.path.abspath(options.data_dir)
121 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
122 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
123 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
124 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
125 options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
126 options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
127 options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
128 options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
129 options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
130 options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
136 raise Error("No arguments expected")
139 if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
140 logging.error("Aborting due to hostname mismatch")
141 sys.exit(constants.EXIT_FAILURE)
143 if not options.force:
144 usertext = ("Please make sure you have read the upgrade notes for"
145 " Ganeti %s (available in the UPGRADE file and included"
146 " in other documentation formats). Continue with upgrading"
147 " configuration?" % constants.RELEASE_VERSION)
148 if not cli.AskUser(usertext):
149 sys.exit(constants.EXIT_FAILURE)
151 # Check whether it's a Ganeti configuration directory
152 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
153 os.path.isfile(options.SERVER_PEM_PATH) and
154 os.path.isfile(options.KNOWN_HOSTS_PATH)):
155 raise Error(("%s does not seem to be a Ganeti configuration"
156 " directory") % options.data_dir)
158 config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
161 config_version = config_data["version"]
163 raise Error("Unable to determine configuration version")
165 (config_major, config_minor, config_revision) = \
166 constants.SplitVersion(config_version)
168 logging.info("Found configuration version %s (%d.%d.%d)",
169 config_version, config_major, config_minor, config_revision)
171 if "config_version" in config_data["cluster"]:
172 raise Error("Inconsistent configuration: found config_version in"
173 " configuration file")
175 # Upgrade from 2.0/2.1/2.2/2.3 to 2.4
176 if config_major == 2 and config_minor in (0, 1, 2, 3, 4):
177 if config_revision != 0:
178 logging.warning("Config revision is %s, not 0", config_revision)
180 config_data["version"] = constants.BuildVersion(2, 5, 0)
182 elif config_major == 2 and config_minor == 5:
183 logging.info("No changes necessary")
186 raise Error("Configuration version %d.%d.%d not supported by this tool" %
187 (config_major, config_minor, config_revision))
189 if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
190 not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
191 if os.path.exists(options.RAPI_USERS_FILE):
192 raise Error("Found pre-2.4 RAPI users file at %s, but another file"
193 " already exists at %s" %
194 (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
195 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
196 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
197 if not options.dry_run:
198 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
199 mkdir=True, mkdir_mode=0750)
201 # Create a symlink for RAPI users file
202 if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
203 os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
204 os.path.isfile(options.RAPI_USERS_FILE)):
205 logging.info("Creating symlink from %s to %s",
206 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
207 if not options.dry_run:
208 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
210 # Remove old watcher state file if it exists
211 if os.path.exists(options.WATCHER_STATEFILE):
212 logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
213 if not options.dry_run:
214 utils.RemoveFile(options.WATCHER_STATEFILE)
217 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
218 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
219 data=serializer.DumpJson(config_data),
221 dry_run=options.dry_run,
224 if not options.dry_run:
225 bootstrap.GenerateClusterCrypto(False, False, False, False,
226 nodecert_file=options.SERVER_PEM_PATH,
227 rapicert_file=options.RAPI_CERT_FILE,
228 hmackey_file=options.CONFD_HMAC_KEY,
229 cds_file=options.CDS_FILE)
232 logging.critical("Writing configuration failed. It is probably in an"
233 " inconsistent state and needs manual intervention.")
236 # test loading the config file
237 if not (options.dry_run or options.no_verify):
238 logging.info("Testing the new config file...")
239 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
240 accept_foreign=options.ignore_hostname,
242 # if we reached this, it's all fine
243 vrfy = cfg.VerifyConfig()
245 logging.error("Errors after conversion:")
247 logging.error(" - %s", item)
249 logging.info("File loaded successfully")
251 cli.ToStderr("Configuration successfully upgraded for version %s.",
252 constants.RELEASE_VERSION)
255 if __name__ == "__main__":