Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 9d199a65

History | View | Annotate | Download (7.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
from ganeti import netutils
43

    
44

    
45
options = None
46
args = None
47

    
48

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

    
53

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

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

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

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

    
73

    
74
def CheckHostname(path):
75
  """Ensures hostname matches ssconf value.
76

    
77
  @param path: Path to ssconf file
78

    
79
  """
80
  ssconf_master_node = utils.ReadOneLineFile(path)
81
  hostname = netutils.GetHostname().name
82

    
83
  if ssconf_master_node == hostname:
84
    return True
85

    
86
  logging.warning("Warning: ssconf says master node is '%s', but this"
87
                  " machine's name is '%s'; this tool must be run on"
88
                  " the master node", ssconf_master_node, hostname)
89
  return False
90

    
91

    
92
def main():
93
  """Main program.
94

    
95
  """
96
  global options, args # pylint: disable-msg=W0603
97

    
98
  # Option parsing
99
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
100
  parser.add_option('--dry-run', dest='dry_run',
101
                    action="store_true",
102
                    help="Try to do the conversion, but don't write"
103
                         " output file")
104
  parser.add_option(cli.FORCE_OPT)
105
  parser.add_option(cli.DEBUG_OPT)
106
  parser.add_option(cli.VERBOSE_OPT)
107
  parser.add_option("--ignore-hostname", dest="ignore_hostname",
108
                    action="store_true", default=False,
109
                    help="Don't abort if hostname doesn't match")
110
  parser.add_option('--path', help="Convert configuration in this"
111
                    " directory instead of '%s'" % constants.DATA_DIR,
112
                    default=constants.DATA_DIR, dest="data_dir")
113
  parser.add_option("--no-verify",
114
                    help="Do not verify configuration after upgrade",
115
                    action="store_true", dest="no_verify", default=False)
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
  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
128

    
129
  SetupLogging()
130

    
131
  # Option checking
132
  if args:
133
    raise Error("No arguments expected")
134

    
135
  # Check master name
136
  if not (CheckHostname(options.SSCONF_MASTER_NODE) or options.ignore_hostname):
137
    logging.error("Aborting due to hostname mismatch")
138
    sys.exit(constants.EXIT_FAILURE)
139

    
140
  if not options.force:
141
    usertext = ("Please make sure you have read the upgrade notes for"
142
                " Ganeti %s (available in the UPGRADE file and included"
143
                " in other documentation formats). Continue with upgrading"
144
                " configuration?" % constants.RELEASE_VERSION)
145
    if not cli.AskUser(usertext):
146
      sys.exit(constants.EXIT_FAILURE)
147

    
148
  # Check whether it's a Ganeti configuration directory
149
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
150
          os.path.isfile(options.SERVER_PEM_PATH) and
151
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
152
    raise Error(("%s does not seem to be a Ganeti configuration"
153
                 " directory") % options.data_dir)
154

    
155
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
156

    
157
  try:
158
    config_version = config_data["version"]
159
  except KeyError:
160
    raise Error("Unable to determine configuration version")
161

    
162
  (config_major, config_minor, config_revision) = \
163
    constants.SplitVersion(config_version)
164

    
165
  logging.info("Found configuration version %s (%d.%d.%d)",
166
               config_version, config_major, config_minor, config_revision)
167

    
168
  if "config_version" in config_data["cluster"]:
169
    raise Error("Inconsistent configuration: found config_version in"
170
                " configuration file")
171

    
172
  # Upgrade from 2.0/2.1/2.2 to 2.3
173
  if config_major == 2 and config_minor in (0, 1, 2):
174
    if config_revision != 0:
175
      logging.warning("Config revision is %s, not 0", config_revision)
176

    
177
    config_data["version"] = constants.BuildVersion(2, 3, 0)
178

    
179
  elif config_major == 2 and config_minor == 3:
180
    logging.info("No changes necessary")
181

    
182
  else:
183
    raise Error("Configuration version %d.%d.%d not supported by this tool" %
184
                (config_major, config_minor, config_revision))
185

    
186
  try:
187
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
188
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
189
                    data=serializer.DumpJson(config_data),
190
                    mode=0600,
191
                    dry_run=options.dry_run,
192
                    backup=True)
193

    
194
    if not options.dry_run:
195
      bootstrap.GenerateClusterCrypto(False, False, False, False,
196
                                      nodecert_file=options.SERVER_PEM_PATH,
197
                                      rapicert_file=options.RAPI_CERT_FILE,
198
                                      hmackey_file=options.CONFD_HMAC_KEY,
199
                                      cds_file=options.CDS_FILE)
200

    
201
  except Exception:
202
    logging.critical("Writing configuration failed. It is probably in an"
203
                     " inconsistent state and needs manual intervention.")
204
    raise
205

    
206
  # test loading the config file
207
  if not (options.dry_run or options.no_verify):
208
    logging.info("Testing the new config file...")
209
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
210
                              offline=True)
211
    # if we reached this, it's all fine
212
    vrfy = cfg.VerifyConfig()
213
    if vrfy:
214
      logging.error("Errors after conversion:")
215
      for item in vrfy:
216
        logging.error(" - %s", item)
217
    del cfg
218
    logging.info("File loaded successfully")
219

    
220

    
221
if __name__ == "__main__":
222
  main()
223

    
224
# vim: set foldmethod=marker :