Move function generating SSL certs into utils
[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
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.CONFIG_DATA_PATH = options.data_dir + "/config.data"
121   options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
122   options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
123   options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
124   options.HMAC_CLUSTER_KEY = options.data_dir + "/hmac.key"
125
126   SetupLogging()
127
128   # Option checking
129   if args:
130     raise Error("No arguments expected")
131
132   if not options.force:
133     usertext = ("%s MUST be run on the master node. Is this the master"
134                 " node and are ALL instances down?" % program)
135     if not cli.AskUser(usertext):
136       sys.exit(1)
137
138   # Check whether it's a Ganeti configuration directory
139   if not (os.path.isfile(options.CONFIG_DATA_PATH) and
140           os.path.isfile(options.SERVER_PEM_PATH) or
141           os.path.isfile(options.KNOWN_HOSTS_PATH)):
142     raise Error(("%s does not seem to be a known Ganeti configuration"
143                  " directory") % options.data_dir)
144
145   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
146
147   try:
148     config_version = config_data["version"]
149   except KeyError:
150     raise Error("Unable to determine configuration version")
151
152   (config_major, config_minor, config_revision) = \
153     constants.SplitVersion(config_version)
154
155   logging.info("Found configuration version %s (%d.%d.%d)",
156                config_version, config_major, config_minor, config_revision)
157
158   if "config_version" in config_data["cluster"]:
159     raise Error("Inconsistent configuration: found config_version in"
160                 " configuration file")
161
162   if config_major == 2 and config_minor == 0:
163     if config_revision != 0:
164       logging.warning("Config revision is not 0")
165
166     config_data["version"] = constants.BuildVersion(2, 1, 0)
167
168   try:
169     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
170     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
171                     data=serializer.DumpJson(config_data),
172                     mode=0600,
173                     dry_run=options.dry_run,
174                     backup=True)
175
176     if not options.dry_run:
177       if not os.path.exists(options.RAPI_CERT_FILE):
178         logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
179         utils.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
180
181       if not os.path.exists(options.HMAC_CLUSTER_KEY):
182         logging.debug("Writing HMAC key to %s", options.HMAC_CLUSTER_KEY)
183         bootstrap.GenerateHmacKey(options.HMAC_CLUSTER_KEY)
184
185   except:
186     logging.critical("Writing configuration failed. It is probably in an"
187                      " inconsistent state and needs manual intervention.")
188     raise
189
190   # test loading the config file
191   if not options.dry_run:
192     logging.info("Testing the new config file...")
193     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
194                               offline=True)
195     # if we reached this, it's all fine
196     vrfy = cfg.VerifyConfig()
197     if vrfy:
198       logging.error("Errors after conversion:")
199       for item in vrfy:
200         logging.error(" - %s", item)
201     del cfg
202     logging.info("File loaded successfully")
203
204
205 if __name__ == "__main__":
206   main()
207
208 # vim: set foldmethod=marker :