cfgupgrade: Implement upgrade to 2.1.0
[ganeti-local] / tools / cfgupgrade
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2007, 2008, 2009 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 import errno
36
37 from ganeti import constants
38 from ganeti import serializer
39 from ganeti import utils
40 from ganeti import cli
41 from ganeti import bootstrap
42 from ganeti import config
43
44
45 options = None
46 args = None
47
48 # Dictionary with instance old keys, and new hypervisor keys
49 INST_HV_CHG = {
50   'hvm_pae': constants.HV_PAE,
51   'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS,
52   'initrd_path': constants.HV_INITRD_PATH,
53   'hvm_nic_type': constants.HV_NIC_TYPE,
54   'kernel_path': constants.HV_KERNEL_PATH,
55   'hvm_acpi': constants.HV_ACPI,
56   'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH,
57   'hvm_boot_order': constants.HV_BOOT_ORDER,
58   'hvm_disk_type': constants.HV_DISK_TYPE,
59   }
60
61 # Instance beparams changes
62 INST_BE_CHG = {
63   'vcpus': constants.BE_VCPUS,
64   'memory': constants.BE_MEMORY,
65   'auto_balance': constants.BE_AUTO_BALANCE,
66   }
67
68 # Field names
69 F_SERIAL = 'serial_no'
70
71
72 class Error(Exception):
73   """Generic exception"""
74   pass
75
76
77 def SetupLogging():
78   """Configures the logging module.
79
80   """
81   formatter = logging.Formatter("%(asctime)s: %(message)s")
82
83   stderr_handler = logging.StreamHandler()
84   stderr_handler.setFormatter(formatter)
85   if options.debug:
86     stderr_handler.setLevel(logging.NOTSET)
87   elif options.verbose:
88     stderr_handler.setLevel(logging.INFO)
89   else:
90     stderr_handler.setLevel(logging.CRITICAL)
91
92   root_logger = logging.getLogger("")
93   root_logger.setLevel(logging.NOTSET)
94   root_logger.addHandler(stderr_handler)
95
96
97 def main():
98   """Main program.
99
100   """
101   global options, args
102
103   program = os.path.basename(sys.argv[0])
104
105   # Option parsing
106   parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
107   parser.add_option('--dry-run', dest='dry_run',
108                     action="store_true",
109                     help="Try to do the conversion, but don't write"
110                          " output file")
111   parser.add_option(cli.FORCE_OPT)
112   parser.add_option(cli.DEBUG_OPT)
113   parser.add_option(cli.VERBOSE_OPT)
114   parser.add_option('--path', help="Convert configuration in this"
115                     " directory instead of '%s'" % constants.DATA_DIR,
116                     default=constants.DATA_DIR, dest="data_dir")
117   (options, args) = parser.parse_args()
118
119   # We need to keep filenames locally because they might be renamed between
120   # versions.
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.HMAC_CLUSTER_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       if not os.path.exists(options.RAPI_CERT_FILE):
179         logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
180         bootstrap.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
181
182       if not os.path.exists(options.HMAC_CLUSTER_KEY):
183         logging.debug("Writing HMAC key to %s", options.HMAC_CLUSTER_KEY)
184         bootstrap.GenerateHmacKey(options.HMAC_CLUSTER_KEY)
185
186   except:
187     logging.critical("Writing configuration failed. It is probably in an"
188                      " inconsistent state and needs manual intervention.")
189     raise
190
191   # test loading the config file
192   if not options.dry_run:
193     logging.info("Testing the new config file...")
194     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
195                               offline=True)
196     # if we reached this, it's all fine
197     vrfy = cfg.VerifyConfig()
198     if vrfy:
199       logging.error("Errors after conversion:")
200       for item in vrfy:
201         logging.error(" - %s" % item)
202     del cfg
203     logging.info("File loaded successfully")
204
205
206 if __name__ == "__main__":
207   main()
208
209 # vim: set foldmethod=marker :