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.SPICE_CERT_FILE = options.data_dir + "/spice.pem"
126 options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem"
127 options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
128 options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
129 options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
130 options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
131 options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
132 options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
138 raise Error("No arguments expected")
141 if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
142 logging.error("Aborting due to hostname mismatch")
143 sys.exit(constants.EXIT_FAILURE)
145 if not options.force:
146 usertext = ("Please make sure you have read the upgrade notes for"
147 " Ganeti %s (available in the UPGRADE file and included"
148 " in other documentation formats). Continue with upgrading"
149 " configuration?" % constants.RELEASE_VERSION)
150 if not cli.AskUser(usertext):
151 sys.exit(constants.EXIT_FAILURE)
153 # Check whether it's a Ganeti configuration directory
154 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
155 os.path.isfile(options.SERVER_PEM_PATH) and
156 os.path.isfile(options.KNOWN_HOSTS_PATH)):
157 raise Error(("%s does not seem to be a Ganeti configuration"
158 " directory") % options.data_dir)
160 config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
163 config_version = config_data["version"]
165 raise Error("Unable to determine configuration version")
167 (config_major, config_minor, config_revision) = \
168 constants.SplitVersion(config_version)
170 logging.info("Found configuration version %s (%d.%d.%d)",
171 config_version, config_major, config_minor, config_revision)
173 if "config_version" in config_data["cluster"]:
174 raise Error("Inconsistent configuration: found config_version in"
175 " configuration file")
177 # Upgrade from 2.0/2.1/2.2/2.3 to 2.4
178 if config_major == 2 and config_minor in (0, 1, 2, 3, 4):
179 if config_revision != 0:
180 logging.warning("Config revision is %s, not 0", config_revision)
182 config_data["version"] = constants.BuildVersion(2, 5, 0)
184 elif config_major == 2 and config_minor == 5:
185 logging.info("No changes necessary")
188 raise Error("Configuration version %d.%d.%d not supported by this tool" %
189 (config_major, config_minor, config_revision))
191 if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
192 not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
193 if os.path.exists(options.RAPI_USERS_FILE):
194 raise Error("Found pre-2.4 RAPI users file at %s, but another file"
195 " already exists at %s" %
196 (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
197 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
198 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
199 if not options.dry_run:
200 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
201 mkdir=True, mkdir_mode=0750)
203 # Create a symlink for RAPI users file
204 if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
205 os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
206 os.path.isfile(options.RAPI_USERS_FILE)):
207 logging.info("Creating symlink from %s to %s",
208 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
209 if not options.dry_run:
210 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
212 # Remove old watcher state file if it exists
213 if os.path.exists(options.WATCHER_STATEFILE):
214 logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
215 if not options.dry_run:
216 utils.RemoveFile(options.WATCHER_STATEFILE)
219 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
220 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
221 data=serializer.DumpJson(config_data),
223 dry_run=options.dry_run,
226 if not options.dry_run:
227 bootstrap.GenerateClusterCrypto(False, False, False, False, False,
228 nodecert_file=options.SERVER_PEM_PATH,
229 rapicert_file=options.RAPI_CERT_FILE,
230 spicecert_file=options.SPICE_CERT_FILE,
231 spicecacert_file=options.SPICE_CACERT_FILE,
232 hmackey_file=options.CONFD_HMAC_KEY,
233 cds_file=options.CDS_FILE)
236 logging.critical("Writing configuration failed. It is probably in an"
237 " inconsistent state and needs manual intervention.")
240 # test loading the config file
241 if not (options.dry_run or options.no_verify):
242 logging.info("Testing the new config file...")
243 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
244 accept_foreign=options.ignore_hostname,
246 # if we reached this, it's all fine
247 vrfy = cfg.VerifyConfig()
249 logging.error("Errors after conversion:")
251 logging.error(" - %s", item)
253 logging.info("File loaded successfully")
255 cli.ToStderr("Configuration successfully upgraded for version %s.",
256 constants.RELEASE_VERSION)
259 if __name__ == "__main__":