Merge branch 'devel-2.1'
[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 # Dictionary with instance old keys, and new hypervisor keys
48 INST_HV_CHG = {
49   'hvm_pae': constants.HV_PAE,
50   'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS,
51   'initrd_path': constants.HV_INITRD_PATH,
52   'hvm_nic_type': constants.HV_NIC_TYPE,
53   'kernel_path': constants.HV_KERNEL_PATH,
54   'hvm_acpi': constants.HV_ACPI,
55   'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH,
56   'hvm_boot_order': constants.HV_BOOT_ORDER,
57   'hvm_disk_type': constants.HV_DISK_TYPE,
58   }
59
60 # Instance beparams changes
61 INST_BE_CHG = {
62   'vcpus': constants.BE_VCPUS,
63   'memory': constants.BE_MEMORY,
64   'auto_balance': constants.BE_AUTO_BALANCE,
65   }
66
67 # Field names
68 F_SERIAL = 'serial_no'
69
70
71 class Error(Exception):
72   """Generic exception"""
73   pass
74
75
76 def SetupLogging():
77   """Configures the logging module.
78
79   """
80   formatter = logging.Formatter("%(asctime)s: %(message)s")
81
82   stderr_handler = logging.StreamHandler()
83   stderr_handler.setFormatter(formatter)
84   if options.debug:
85     stderr_handler.setLevel(logging.NOTSET)
86   elif options.verbose:
87     stderr_handler.setLevel(logging.INFO)
88   else:
89     stderr_handler.setLevel(logging.CRITICAL)
90
91   root_logger = logging.getLogger("")
92   root_logger.setLevel(logging.NOTSET)
93   root_logger.addHandler(stderr_handler)
94
95
96 def main():
97   """Main program.
98
99   """
100   global options, args # pylint: disable-msg=W0603
101
102   program = os.path.basename(sys.argv[0])
103
104   # Option parsing
105   parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
106   parser.add_option('--dry-run', dest='dry_run',
107                     action="store_true",
108                     help="Try to do the conversion, but don't write"
109                          " output file")
110   parser.add_option(cli.FORCE_OPT)
111   parser.add_option(cli.DEBUG_OPT)
112   parser.add_option(cli.VERBOSE_OPT)
113   parser.add_option('--path', help="Convert configuration in this"
114                     " directory instead of '%s'" % constants.DATA_DIR,
115                     default=constants.DATA_DIR, dest="data_dir")
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.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
126
127   SetupLogging()
128
129   # Option checking
130   if args:
131     raise Error("No arguments expected")
132
133   if not options.force:
134     usertext = ("%s MUST be run on the master node. Is this the master"
135                 " node and are ALL instances down?" % program)
136     if not cli.AskUser(usertext):
137       sys.exit(1)
138
139   # Check whether it's a Ganeti configuration directory
140   if not (os.path.isfile(options.CONFIG_DATA_PATH) and
141           os.path.isfile(options.SERVER_PEM_PATH) or
142           os.path.isfile(options.KNOWN_HOSTS_PATH)):
143     raise Error(("%s does not seem to be a known Ganeti configuration"
144                  " directory") % options.data_dir)
145
146   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
147
148   try:
149     config_version = config_data["version"]
150   except KeyError:
151     raise Error("Unable to determine configuration version")
152
153   (config_major, config_minor, config_revision) = \
154     constants.SplitVersion(config_version)
155
156   logging.info("Found configuration version %s (%d.%d.%d)",
157                config_version, config_major, config_minor, config_revision)
158
159   if "config_version" in config_data["cluster"]:
160     raise Error("Inconsistent configuration: found config_version in"
161                 " configuration file")
162
163   if config_major == 2 and config_minor == 0:
164     if config_revision != 0:
165       logging.warning("Config revision is not 0")
166
167     config_data["version"] = constants.BuildVersion(2, 1, 0)
168
169   try:
170     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
171     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
172                     data=serializer.DumpJson(config_data),
173                     mode=0600,
174                     dry_run=options.dry_run,
175                     backup=True)
176
177     if not options.dry_run:
178       bootstrap.GenerateClusterCrypto(False, False, False, False,
179                                       nodecert_file=options.SERVER_PEM_PATH,
180                                       rapicert_file=options.RAPI_CERT_FILE,
181                                       hmackey_file=options.CONFD_HMAC_KEY)
182
183   except:
184     logging.critical("Writing configuration failed. It is probably in an"
185                      " inconsistent state and needs manual intervention.")
186     raise
187
188   # test loading the config file
189   if not options.dry_run:
190     logging.info("Testing the new config file...")
191     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
192                               offline=True)
193     # if we reached this, it's all fine
194     vrfy = cfg.VerifyConfig()
195     if vrfy:
196       logging.error("Errors after conversion:")
197       for item in vrfy:
198         logging.error(" - %s", item)
199     del cfg
200     logging.info("File loaded successfully")
201
202
203 if __name__ == "__main__":
204   main()
205
206 # vim: set foldmethod=marker :