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-msg=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"
135 raise Error("No arguments expected")
138 if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
139 logging.error("Aborting due to hostname mismatch")
140 sys.exit(constants.EXIT_FAILURE)
142 if not options.force:
143 usertext = ("Please make sure you have read the upgrade notes for"
144 " Ganeti %s (available in the UPGRADE file and included"
145 " in other documentation formats). Continue with upgrading"
146 " configuration?" % constants.RELEASE_VERSION)
147 if not cli.AskUser(usertext):
148 sys.exit(constants.EXIT_FAILURE)
150 # Check whether it's a Ganeti configuration directory
151 if not (os.path.isfile(options.CONFIG_DATA_PATH) and
152 os.path.isfile(options.SERVER_PEM_PATH) and
153 os.path.isfile(options.KNOWN_HOSTS_PATH)):
154 raise Error(("%s does not seem to be a Ganeti configuration"
155 " directory") % options.data_dir)
157 config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
160 config_version = config_data["version"]
162 raise Error("Unable to determine configuration version")
164 (config_major, config_minor, config_revision) = \
165 constants.SplitVersion(config_version)
167 logging.info("Found configuration version %s (%d.%d.%d)",
168 config_version, config_major, config_minor, config_revision)
170 if "config_version" in config_data["cluster"]:
171 raise Error("Inconsistent configuration: found config_version in"
172 " configuration file")
174 # Upgrade from 2.0/2.1/2.2/2.3 to 2.4
175 if config_major == 2 and config_minor in (0, 1, 2, 3):
176 if config_revision != 0:
177 logging.warning("Config revision is %s, not 0", config_revision)
179 config_data["version"] = constants.BuildVersion(2, 4, 0)
181 elif config_major == 2 and config_minor == 4:
182 logging.info("No changes necessary")
185 raise Error("Configuration version %d.%d.%d not supported by this tool" %
186 (config_major, config_minor, config_revision))
188 if os.path.isfile(options.RAPI_USERS_FILE_PRE24):
189 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
190 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
191 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
192 mkdir=True, mkdir_mode=0750)
194 # Create a symlink for RAPI users file
195 if not os.path.islink(options.RAPI_USERS_FILE_PRE24):
196 logging.info("Creating symlink from %s to %s",
197 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
198 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
201 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
202 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
203 data=serializer.DumpJson(config_data),
205 dry_run=options.dry_run,
208 if not options.dry_run:
209 bootstrap.GenerateClusterCrypto(False, False, False, False,
210 nodecert_file=options.SERVER_PEM_PATH,
211 rapicert_file=options.RAPI_CERT_FILE,
212 hmackey_file=options.CONFD_HMAC_KEY,
213 cds_file=options.CDS_FILE)
216 logging.critical("Writing configuration failed. It is probably in an"
217 " inconsistent state and needs manual intervention.")
220 # test loading the config file
221 if not (options.dry_run or options.no_verify):
222 logging.info("Testing the new config file...")
223 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
224 accept_foreign=options.ignore_hostname,
226 # if we reached this, it's all fine
227 vrfy = cfg.VerifyConfig()
229 logging.error("Errors after conversion:")
231 logging.error(" - %s", item)
233 logging.info("File loaded successfully")
236 if __name__ == "__main__":