Handle ESRCH when sending signals
[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   options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
127
128   SetupLogging()
129
130   # Option checking
131   if args:
132     raise Error("No arguments expected")
133
134   if not options.force:
135     usertext = ("%s MUST be run on the master node. Is this the master"
136                 " node and are ALL instances down?" % program)
137     if not cli.AskUser(usertext):
138       sys.exit(1)
139
140   # Check whether it's a Ganeti configuration directory
141   if not (os.path.isfile(options.CONFIG_DATA_PATH) and
142           os.path.isfile(options.SERVER_PEM_PATH) or
143           os.path.isfile(options.KNOWN_HOSTS_PATH)):
144     raise Error(("%s does not seem to be a known Ganeti configuration"
145                  " directory") % options.data_dir)
146
147   config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
148
149   try:
150     config_version = config_data["version"]
151   except KeyError:
152     raise Error("Unable to determine configuration version")
153
154   (config_major, config_minor, config_revision) = \
155     constants.SplitVersion(config_version)
156
157   logging.info("Found configuration version %s (%d.%d.%d)",
158                config_version, config_major, config_minor, config_revision)
159
160   if "config_version" in config_data["cluster"]:
161     raise Error("Inconsistent configuration: found config_version in"
162                 " configuration file")
163
164   if config_major == 2 and config_minor == 0:
165     if config_revision != 0:
166       logging.warning("Config revision is not 0")
167
168     config_data["version"] = constants.BuildVersion(2, 1, 0)
169
170   try:
171     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
172     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
173                     data=serializer.DumpJson(config_data),
174                     mode=0600,
175                     dry_run=options.dry_run,
176                     backup=True)
177
178     if not options.dry_run:
179       bootstrap.GenerateClusterCrypto(False, False, False, False,
180                                       nodecert_file=options.SERVER_PEM_PATH,
181                                       rapicert_file=options.RAPI_CERT_FILE,
182                                       hmackey_file=options.CONFD_HMAC_KEY,
183                                       cds_file=options.CDS_FILE)
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 :