Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 011974df

History | View | Annotate | Download (7.4 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
  program = os.path.basename(sys.argv[0])
99

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

    
120
  # We need to keep filenames locally because they might be renamed between
121
  # versions.
122
  options.data_dir = os.path.abspath(options.data_dir)
123
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
124
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
125
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
126
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
127
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
128
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
129
  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
130

    
131
  SetupLogging()
132

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

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

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

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

    
157
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
158

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

    
164
  (config_major, config_minor, config_revision) = \
165
    constants.SplitVersion(config_version)
166

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

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

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

    
179
    config_data["version"] = constants.BuildVersion(2, 3, 0)
180

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

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

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

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

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

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

    
222

    
223
if __name__ == "__main__":
224
  main()
225

    
226
# vim: set foldmethod=marker :