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) and
189 not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
190 if os.path.exists(options.RAPI_USERS_FILE):
191 raise Error("Found pre-2.4 RAPI users file at %s, but another file"
192 " already exists at %s" %
193 (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
194 logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
195 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
196 if not options.dry_run:
197 utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
198 mkdir=True, mkdir_mode=0750)
200 # Create a symlink for RAPI users file
201 if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
202 os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
203 os.path.isfile(options.RAPI_USERS_FILE)):
204 logging.info("Creating symlink from %s to %s",
205 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
206 if not options.dry_run:
207 os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
210 logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
211 utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
212 data=serializer.DumpJson(config_data),
214 dry_run=options.dry_run,
217 if not options.dry_run:
218 bootstrap.GenerateClusterCrypto(False, False, False, False,
219 nodecert_file=options.SERVER_PEM_PATH,
220 rapicert_file=options.RAPI_CERT_FILE,
221 hmackey_file=options.CONFD_HMAC_KEY,
222 cds_file=options.CDS_FILE)
225 logging.critical("Writing configuration failed. It is probably in an"
226 " inconsistent state and needs manual intervention.")
229 # test loading the config file
230 if not (options.dry_run or options.no_verify):
231 logging.info("Testing the new config file...")
232 cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
234 # if we reached this, it's all fine
235 vrfy = cfg.VerifyConfig()
237 logging.error("Errors after conversion:")
239 logging.error(" - %s", item)
241 logging.info("File loaded successfully")
244 if __name__ == "__main__":