cfgupgrade: Add option to override config verification
[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
43
44 options = None
45 args = None
46
47
48 class Error(Exception):
49   """Generic exception"""
50   pass
51
52
53 def SetupLogging():
54   """Configures the logging module.
55
56   """
57   formatter = logging.Formatter("%(asctime)s: %(message)s")
58
59   stderr_handler = logging.StreamHandler()
60   stderr_handler.setFormatter(formatter)
61   if options.debug:
62     stderr_handler.setLevel(logging.NOTSET)
63   elif options.verbose:
64     stderr_handler.setLevel(logging.INFO)
65   else:
66     stderr_handler.setLevel(logging.CRITICAL)
67
68   root_logger = logging.getLogger("")
69   root_logger.setLevel(logging.NOTSET)
70   root_logger.addHandler(stderr_handler)
71
72
73 def main():
74   """Main program.
75
76   """
77   global options, args # pylint: disable-msg=W0603
78
79   program = os.path.basename(sys.argv[0])
80
81   # Option parsing
82   parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
83   parser.add_option('--dry-run', dest='dry_run',
84                     action="store_true",
85                     help="Try to do the conversion, but don't write"
86                          " output file")
87   parser.add_option(cli.FORCE_OPT)
88   parser.add_option(cli.DEBUG_OPT)
89   parser.add_option(cli.VERBOSE_OPT)
90   parser.add_option('--path', help="Convert configuration in this"
91                     " directory instead of '%s'" % constants.DATA_DIR,
92                     default=constants.DATA_DIR, dest="data_dir")
93   parser.add_option("--no-verify",
94                     help="Do not verify configuration after upgrade",
95                     action="store_true", dest="no_verify", default=False)
96   (options, args) = parser.parse_args()
97
98   # We need to keep filenames locally because they might be renamed between
99   # versions.
100   options.data_dir = os.path.abspath(options.data_dir)
101   options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
102   options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
103   options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
104   options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
105   options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
106   options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
107
108   SetupLogging()
109
110   # Option checking
111   if args:
112     raise Error("No arguments expected")
113
114   if not options.force:
115     usertext = ("%s MUST be run on the master node. Is this the master"
116                 " node and are ALL instances down?" % program)
117     if not cli.AskUser(usertext):
118       sys.exit(constants.EXIT_FAILURE)
119
120   # Check whether it's a Ganeti configuration directory
121   if not (os.path.isfile(options.CONFIG_DATA_PATH) and
122           os.path.isfile(options.SERVER_PEM_PATH) and
123           os.path.isfile(options.KNOWN_HOSTS_PATH)):
124     raise Error(("%s does not seem to be a Ganeti configuration"
125                  " directory") % options.data_dir)
126
127   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
128
129   try:
130     config_version = config_data["version"]
131   except KeyError:
132     raise Error("Unable to determine configuration version")
133
134   (config_major, config_minor, config_revision) = \
135     constants.SplitVersion(config_version)
136
137   logging.info("Found configuration version %s (%d.%d.%d)",
138                config_version, config_major, config_minor, config_revision)
139
140   if "config_version" in config_data["cluster"]:
141     raise Error("Inconsistent configuration: found config_version in"
142                 " configuration file")
143
144   # Upgrade from 2.0/2.1 to 2.2
145   if config_major == 2 and config_minor in (0, 1):
146     if config_revision != 0:
147       logging.warning("Config revision is %s, not 0", config_revision)
148
149     config_data["version"] = constants.BuildVersion(2, 2, 0)
150
151   elif config_major == 2 and config_minor == 2:
152     logging.info("No changes necessary")
153
154   else:
155     raise Error("Configuration version %d.%d.%d not supported by this tool" %
156                 (config_major, config_minor, config_revision))
157
158   try:
159     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
160     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
161                     data=serializer.DumpJson(config_data),
162                     mode=0600,
163                     dry_run=options.dry_run,
164                     backup=True)
165
166     if not options.dry_run:
167       bootstrap.GenerateClusterCrypto(False, False, False, False,
168                                       nodecert_file=options.SERVER_PEM_PATH,
169                                       rapicert_file=options.RAPI_CERT_FILE,
170                                       hmackey_file=options.CONFD_HMAC_KEY,
171                                       cds_file=options.CDS_FILE)
172
173   except Exception:
174     logging.critical("Writing configuration failed. It is probably in an"
175                      " inconsistent state and needs manual intervention.")
176     raise
177
178   # test loading the config file
179   if not (options.dry_run or options.no_verify):
180     logging.info("Testing the new config file...")
181     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
182                               offline=True)
183     # if we reached this, it's all fine
184     vrfy = cfg.VerifyConfig()
185     if vrfy:
186       logging.error("Errors after conversion:")
187       for item in vrfy:
188         logging.error(" - %s", item)
189     del cfg
190     logging.info("File loaded successfully")
191
192
193 if __name__ == "__main__":
194   main()
195
196 # vim: set foldmethod=marker :