4 # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 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 #: Target major version we will upgrade to
51 #: Target minor version we will upgrade to
55 class Error(Exception):
56 """Generic exception"""
61 """Configures the logging module.
64 formatter = logging.Formatter("%(asctime)s: %(message)s")
66 stderr_handler = logging.StreamHandler()
67 stderr_handler.setFormatter(formatter)
69 stderr_handler.setLevel(logging.NOTSET)
71 stderr_handler.setLevel(logging.INFO)
73 stderr_handler.setLevel(logging.WARNING)
75 root_logger = logging.getLogger("")
76 root_logger.setLevel(logging.NOTSET)
77 root_logger.addHandler(stderr_handler)
80 def CheckHostname(path):
81 """Ensures hostname matches ssconf value.
83 @param path: Path to ssconf file
86 ssconf_master_node = utils.ReadOneLineFile(path)
87 hostname = netutils.GetHostname().name
89 if ssconf_master_node == hostname:
92 logging.warning("Warning: ssconf says master node is '%s', but this"
93 " machine's name is '%s'; this tool must be run on"
94 " the master node", ssconf_master_node, hostname)
102 global options, args # pylint: disable=W0603
105 parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
106 parser.add_option("--dry-run", dest="dry_run",
108 help="Try to do the conversion, but don't write"
110 parser.add_option(cli.FORCE_OPT)
111 parser.add_option(cli.DEBUG_OPT)
112 parser.add_option(cli.VERBOSE_OPT)
113 parser.add_option("--ignore-hostname", dest="ignore_hostname",
114 action="store_true", default=False,
115 help="Don't abort if hostname doesn't match")
116 parser.add_option("--path", help="Convert configuration in this"
117 " directory instead of '%s'" % constants.DATA_DIR,
118 default=constants.DATA_DIR, dest="data_dir")
119 parser.add_option("--no-verify",
120 help="Do not verify configuration after upgrade",
121 action="store_true", dest="no_verify", default=False)
122 (options, args) = parser.parse_args()
124 # We need to keep filenames locally because they might be renamed between
126 options.data_dir = os.path.abspath(options.data_dir)
127 options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
128 options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
129 options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
130 options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
131 options.SPICE_CERT_FILE = options.data_dir + "/spice.pem"
132 options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem"
133 options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
134 options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
135 options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
136 options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
137 options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
138 options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
144 raise Error("No arguments expected")
147 if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
148 logging.error("Aborting due to hostname mismatch")
149 sys.exit(constants.EXIT_FAILURE)
151 if not options.force:
152 usertext = ("Please make sure you have read the upgrade notes for"
153 " Ganeti %s (available in the UPGRADE file and included"
154 " in other documentation formats). Continue with upgrading"
155 " configuration?" % constants.RELEASE_VERSION)
156 if not cli.AskUser(usertext):
157 sys.exit(constants.EXIT_FAILURE)
159 # Check whether it's a Ganeti configuration directory
160 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
161 os.path.isfile(options.SERVER_PEM_PATH) and
162 os.path.isfile(options.KNOWN_HOSTS_PATH)):
163 raise Error(("%s does not seem to be a Ganeti configuration"
164 " directory") % options.data_dir)
166 config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
169 config_version = config_data["version"]
171 raise Error("Unable to determine configuration version")
173 (config_major, config_minor, config_revision) = \
174 constants.SplitVersion(config_version)
176 logging.info("Found configuration version %s (%d.%d.%d)",
177 config_version, config_major, config_minor, config_revision)
179 if "config_version" in config_data["cluster"]:
180 raise Error("Inconsistent configuration: found config_version in"
181 " configuration file")
183 # Upgrade from 2.0/2.1/2.2/2.3 to 2.4
184 if config_major == 2 and config_minor in (0, 1, 2, 3, 4, 5):
185 if config_revision != 0:
186 logging.warning("Config revision is %s, not 0", config_revision)
188 config_data["version"] = constants.BuildVersion(TARGET_MAJOR,
191 elif config_major == TARGET_MAJOR and config_minor == TARGET_MINOR:
192 logging.info("No changes necessary")
195 raise Error("Configuration version %d.%d.%d not supported by this tool" %
196 (config_major, config_minor, config_revision))
198 if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
199 not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
200 if os.path.exists(options.RAPI_USERS_FILE):
201 raise Error("Found pre-2.4 RAPI users file at %s, but another file"
202 " already exists at %s" %
203 (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
204 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
205 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
206 if not options.dry_run:
207 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
208 mkdir=True, mkdir_mode=0750)
210 # Create a symlink for RAPI users file
211 if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
212 os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
213 os.path.isfile(options.RAPI_USERS_FILE)):
214 logging.info("Creating symlink from %s to %s",
215 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
216 if not options.dry_run:
217 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
219 # Remove old watcher state file if it exists
220 if os.path.exists(options.WATCHER_STATEFILE):
221 logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
222 if not options.dry_run:
223 utils.RemoveFile(options.WATCHER_STATEFILE)
226 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
227 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
228 data=serializer.DumpJson(config_data),
230 dry_run=options.dry_run,
233 if not options.dry_run:
234 bootstrap.GenerateClusterCrypto(False, False, False, False, False,
235 nodecert_file=options.SERVER_PEM_PATH,
236 rapicert_file=options.RAPI_CERT_FILE,
237 spicecert_file=options.SPICE_CERT_FILE,
238 spicecacert_file=options.SPICE_CACERT_FILE,
239 hmackey_file=options.CONFD_HMAC_KEY,
240 cds_file=options.CDS_FILE)
243 logging.critical("Writing configuration failed. It is probably in an"
244 " inconsistent state and needs manual intervention.")
247 # test loading the config file
248 if not (options.dry_run or options.no_verify):
249 logging.info("Testing the new config file...")
250 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
251 accept_foreign=options.ignore_hostname,
253 # if we reached this, it's all fine
254 vrfy = cfg.VerifyConfig()
256 logging.error("Errors after conversion:")
258 logging.error(" - %s", item)
260 logging.info("File loaded successfully")
262 cli.ToStderr("Configuration successfully upgraded for version %s.",
263 constants.RELEASE_VERSION)
266 if __name__ == "__main__":