Explicitly pass params to deactivate_master_ip
[ganeti-local] / tools / cfgupgrade
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Tool to upgrade the configuration file.
23
24 This code handles only the types supported by simplejson. As an
25 example, 'set' is a 'list'.
26
27 """
28
29
30 import os
31 import os.path
32 import sys
33 import optparse
34 import logging
35
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
43
44
45 options = None
46 args = None
47
48
49 class Error(Exception):
50   """Generic exception"""
51   pass
52
53
54 def SetupLogging():
55   """Configures the logging module.
56
57   """
58   formatter = logging.Formatter("%(asctime)s: %(message)s")
59
60   stderr_handler = logging.StreamHandler()
61   stderr_handler.setFormatter(formatter)
62   if options.debug:
63     stderr_handler.setLevel(logging.NOTSET)
64   elif options.verbose:
65     stderr_handler.setLevel(logging.INFO)
66   else:
67     stderr_handler.setLevel(logging.WARNING)
68
69   root_logger = logging.getLogger("")
70   root_logger.setLevel(logging.NOTSET)
71   root_logger.addHandler(stderr_handler)
72
73
74 def CheckHostname(path):
75   """Ensures hostname matches ssconf value.
76
77   @param path: Path to ssconf file
78
79   """
80   ssconf_master_node = utils.ReadOneLineFile(path)
81   hostname = netutils.GetHostname().name
82
83   if ssconf_master_node == hostname:
84     return True
85
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)
89   return False
90
91
92 def main():
93   """Main program.
94
95   """
96   global options, args # pylint: disable=W0603
97
98   # Option parsing
99   parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
100   parser.add_option('--dry-run', dest='dry_run',
101                     action="store_true",
102                     help="Try to do the conversion, but don't write"
103                          " output file")
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()
117
118   # We need to keep filenames locally because they might be renamed between
119   # versions.
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"
133
134   SetupLogging()
135
136   # Option checking
137   if args:
138     raise Error("No arguments expected")
139
140   # Check master name
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)
144
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)
152
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)
159
160   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
161
162   try:
163     config_version = config_data["version"]
164   except KeyError:
165     raise Error("Unable to determine configuration version")
166
167   (config_major, config_minor, config_revision) = \
168     constants.SplitVersion(config_version)
169
170   logging.info("Found configuration version %s (%d.%d.%d)",
171                config_version, config_major, config_minor, config_revision)
172
173   if "config_version" in config_data["cluster"]:
174     raise Error("Inconsistent configuration: found config_version in"
175                 " configuration file")
176
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)
181
182     config_data["version"] = constants.BuildVersion(2, 5, 0)
183
184   elif config_major == 2 and config_minor == 5:
185     logging.info("No changes necessary")
186
187   else:
188     raise Error("Configuration version %d.%d.%d not supported by this tool" %
189                 (config_major, config_minor, config_revision))
190
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)
202
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)
211
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)
217
218   try:
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),
222                     mode=0600,
223                     dry_run=options.dry_run,
224                     backup=True)
225
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)
234
235   except Exception:
236     logging.critical("Writing configuration failed. It is probably in an"
237                      " inconsistent state and needs manual intervention.")
238     raise
239
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,
245                               offline=True)
246     # if we reached this, it's all fine
247     vrfy = cfg.VerifyConfig()
248     if vrfy:
249       logging.error("Errors after conversion:")
250       for item in vrfy:
251         logging.error(" - %s", item)
252     del cfg
253     logging.info("File loaded successfully")
254
255   cli.ToStderr("Configuration successfully upgraded for version %s.",
256                constants.RELEASE_VERSION)
257
258
259 if __name__ == "__main__":
260   main()