Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 24f6a6e6

History | View | Annotate | Download (6.3 kB)

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

    
48
class Error(Exception):
49
  """Generic exception"""
50
  pass
51

    
52

    
53
def SetupLogging():
54
  """Configures the logging module.
55

    
56
  """
57
  formatter = logging.Formatter("%(asctime)s: %(message)s")
58

    
59
  stderr_handler = logging.StreamHandler()
60
  stderr_handler.setFormatter(formatter)
61
  if options.debug:
62
    stderr_handler.setLevel(logging.NOTSET)
63
  elif options.verbose:
64
    stderr_handler.setLevel(logging.INFO)
65
  else:
66
    stderr_handler.setLevel(logging.CRITICAL)
67

    
68
  root_logger = logging.getLogger("")
69
  root_logger.setLevel(logging.NOTSET)
70
  root_logger.addHandler(stderr_handler)
71

    
72

    
73
def main():
74
  """Main program.
75

    
76
  """
77
  global options, args # pylint: disable-msg=W0603
78

    
79
  program = os.path.basename(sys.argv[0])
80

    
81
  # Option parsing
82
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
83
  parser.add_option('--dry-run', dest='dry_run',
84
                    action="store_true",
85
                    help="Try to do the conversion, but don't write"
86
                         " output file")
87
  parser.add_option(cli.FORCE_OPT)
88
  parser.add_option(cli.DEBUG_OPT)
89
  parser.add_option(cli.VERBOSE_OPT)
90
  parser.add_option('--path', help="Convert configuration in this"
91
                    " directory instead of '%s'" % constants.DATA_DIR,
92
                    default=constants.DATA_DIR, dest="data_dir")
93
  parser.add_option("--no-verify",
94
                    help="Do not verify configuration after upgrade",
95
                    action="store_true", dest="no_verify", default=False)
96
  (options, args) = parser.parse_args()
97

    
98
  # We need to keep filenames locally because they might be renamed between
99
  # versions.
100
  options.data_dir = os.path.abspath(options.data_dir)
101
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
102
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
103
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
104
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
105
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
106
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
107

    
108
  SetupLogging()
109

    
110
  # Option checking
111
  if args:
112
    raise Error("No arguments expected")
113

    
114
  if not options.force:
115
    usertext = ("%s MUST be run on the master node. Is this the master"
116
                " node and are ALL instances down?" % program)
117
    if not cli.AskUser(usertext):
118
      sys.exit(constants.EXIT_FAILURE)
119

    
120
  # Check whether it's a Ganeti configuration directory
121
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
122
          os.path.isfile(options.SERVER_PEM_PATH) and
123
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
124
    raise Error(("%s does not seem to be a Ganeti configuration"
125
                 " directory") % options.data_dir)
126

    
127
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
128

    
129
  try:
130
    config_version = config_data["version"]
131
  except KeyError:
132
    raise Error("Unable to determine configuration version")
133

    
134
  (config_major, config_minor, config_revision) = \
135
    constants.SplitVersion(config_version)
136

    
137
  logging.info("Found configuration version %s (%d.%d.%d)",
138
               config_version, config_major, config_minor, config_revision)
139

    
140
  if "config_version" in config_data["cluster"]:
141
    raise Error("Inconsistent configuration: found config_version in"
142
                " configuration file")
143

    
144
  # Upgrade from 2.0/2.1 to 2.2
145
  if config_major == 2 and config_minor in (0, 1):
146
    if config_revision != 0:
147
      logging.warning("Config revision is %s, not 0", config_revision)
148

    
149
    config_data["version"] = constants.BuildVersion(2, 2, 0)
150

    
151
  elif config_major == 2 and config_minor == 2:
152
    logging.info("No changes necessary")
153

    
154
  else:
155
    raise Error("Configuration version %d.%d.%d not supported by this tool" %
156
                (config_major, config_minor, config_revision))
157

    
158
  try:
159
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
160
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
161
                    data=serializer.DumpJson(config_data),
162
                    mode=0600,
163
                    dry_run=options.dry_run,
164
                    backup=True)
165

    
166
    if not options.dry_run:
167
      bootstrap.GenerateClusterCrypto(False, False, False, False,
168
                                      nodecert_file=options.SERVER_PEM_PATH,
169
                                      rapicert_file=options.RAPI_CERT_FILE,
170
                                      hmackey_file=options.CONFD_HMAC_KEY,
171
                                      cds_file=options.CDS_FILE)
172

    
173
  except Exception:
174
    logging.critical("Writing configuration failed. It is probably in an"
175
                     " inconsistent state and needs manual intervention.")
176
    raise
177

    
178
  # test loading the config file
179
  if not (options.dry_run or options.no_verify):
180
    logging.info("Testing the new config file...")
181
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
182
                              offline=True)
183
    # if we reached this, it's all fine
184
    vrfy = cfg.VerifyConfig()
185
    if vrfy:
186
      logging.error("Errors after conversion:")
187
      for item in vrfy:
188
        logging.error(" - %s", item)
189
    del cfg
190
    logging.info("File loaded successfully")
191

    
192

    
193
if __name__ == "__main__":
194
  main()
195

    
196
# vim: set foldmethod=marker :