cfgupgrade: Fix bug when checking configuration directory
[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   (options, args) = parser.parse_args()
94
95   # We need to keep filenames locally because they might be renamed between
96   # versions.
97   options.data_dir = os.path.abspath(options.data_dir)
98   options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
99   options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
100   options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
101   options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
102   options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
103   options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
104
105   SetupLogging()
106
107   # Option checking
108   if args:
109     raise Error("No arguments expected")
110
111   if not options.force:
112     usertext = ("%s MUST be run on the master node. Is this the master"
113                 " node and are ALL instances down?" % program)
114     if not cli.AskUser(usertext):
115       sys.exit(constants.EXIT_FAILURE)
116
117   # Check whether it's a Ganeti configuration directory
118   if not (os.path.isfile(options.CONFIG_DATA_PATH) and
119           os.path.isfile(options.SERVER_PEM_PATH) and
120           os.path.isfile(options.KNOWN_HOSTS_PATH)):
121     raise Error(("%s does not seem to be a Ganeti configuration"
122                  " directory") % options.data_dir)
123
124   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
125
126   try:
127     config_version = config_data["version"]
128   except KeyError:
129     raise Error("Unable to determine configuration version")
130
131   (config_major, config_minor, config_revision) = \
132     constants.SplitVersion(config_version)
133
134   logging.info("Found configuration version %s (%d.%d.%d)",
135                config_version, config_major, config_minor, config_revision)
136
137   if "config_version" in config_data["cluster"]:
138     raise Error("Inconsistent configuration: found config_version in"
139                 " configuration file")
140
141   # Upgrade from 2.0/2.1 to 2.2
142   if config_major == 2 and config_minor in (0, 1):
143     if config_revision != 0:
144       logging.warning("Config revision is %s, not 0", config_revision)
145
146     config_data["version"] = constants.BuildVersion(2, 2, 0)
147
148   elif config_major == 2 and config_minor == 2:
149     logging.info("No changes necessary")
150
151   else:
152     raise Error("Configuration version %d.%d.%d not supported by this tool" %
153                 (config_major, config_minor, config_revision))
154
155   try:
156     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
157     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
158                     data=serializer.DumpJson(config_data),
159                     mode=0600,
160                     dry_run=options.dry_run,
161                     backup=True)
162
163     if not options.dry_run:
164       bootstrap.GenerateClusterCrypto(False, False, False, False,
165                                       nodecert_file=options.SERVER_PEM_PATH,
166                                       rapicert_file=options.RAPI_CERT_FILE,
167                                       hmackey_file=options.CONFD_HMAC_KEY,
168                                       cds_file=options.CDS_FILE)
169
170   except Exception:
171     logging.critical("Writing configuration failed. It is probably in an"
172                      " inconsistent state and needs manual intervention.")
173     raise
174
175   # test loading the config file
176   if not options.dry_run:
177     logging.info("Testing the new config file...")
178     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
179                               offline=True)
180     # if we reached this, it's all fine
181     vrfy = cfg.VerifyConfig()
182     if vrfy:
183       logging.error("Errors after conversion:")
184       for item in vrfy:
185         logging.error(" - %s", item)
186     del cfg
187     logging.info("File loaded successfully")
188
189
190 if __name__ == "__main__":
191   main()
192
193 # vim: set foldmethod=marker :