Make bootstrap._GenerateSelfSignedSslCert public
[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
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   try:
163     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
164     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
165                     data=serializer.DumpJson(config_data),
166                     mode=0600,
167                     dry_run=options.dry_run,
168                     backup=True)
169
170     if not options.dry_run:
171       if not os.path.exists(options.RAPI_CERT_FILE):
172         logging.debug("Writing RAPI certificate to %s", options.RAPI_CERT_FILE)
173         bootstrap.GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
174
175   except:
176     logging.critical("Writing configuration failed. It is probably in an"
177                      " inconsistent state and needs manual intervention.")
178     raise
179
180   # test loading the config file
181   if not options.dry_run:
182     logging.info("Testing the new config file...")
183     cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
184                               offline=True)
185     # if we reached this, it's all fine
186     vrfy = cfg.VerifyConfig()
187     if vrfy:
188       logging.error("Errors after conversion:")
189       for item in vrfy:
190         logging.error(" - %s" % item)
191     del cfg
192     logging.info("File loaded successfully")
193
194
195 if __name__ == "__main__":
196   main()
197
198 # vim: set foldmethod=marker :