cfgupgrade 8.99 KB
Newer Older
1
2
3
#!/usr/bin/python
#

4
# Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Tool to upgrade the configuration file.

24
25
This code handles only the types supported by simplejson. As an
example, 'set' is a 'list'.
26
27
28
29
30
31
32
33

"""


import os
import os.path
import sys
import optparse
34
import logging
35

36
37
from ganeti import constants
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
46
options = None
args = None
47

Iustin Pop's avatar
Iustin Pop committed
48

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


54
55
56
57
58
59
60
61
62
63
64
65
66
def SetupLogging():
  """Configures the logging module.

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

  stderr_handler = logging.StreamHandler()
  stderr_handler.setFormatter(formatter)
  if options.debug:
    stderr_handler.setLevel(logging.NOTSET)
  elif options.verbose:
    stderr_handler.setLevel(logging.INFO)
  else:
67
    stderr_handler.setLevel(logging.WARNING)
68
69
70
71
72
73

  root_logger = logging.getLogger("")
  root_logger.setLevel(logging.NOTSET)
  root_logger.addHandler(stderr_handler)


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def CheckHostname(path):
  """Ensures hostname matches ssconf value.

  @param path: Path to ssconf file

  """
  ssconf_master_node = utils.ReadOneLineFile(path)
  hostname = netutils.GetHostname().name

  if ssconf_master_node == hostname:
    return True

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


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

  """
Iustin Pop's avatar
Iustin Pop committed
96
  global options, args # pylint: disable-msg=W0603
97

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

118
119
  # We need to keep filenames locally because they might be renamed between
  # versions.
120
  options.data_dir = os.path.abspath(options.data_dir)
121
122
123
124
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
125
126
  options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
  options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
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
  options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
131

132
133
  SetupLogging()

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

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

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

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

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

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

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

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

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

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

180
    config_data["version"] = constants.BuildVersion(2, 5, 0)
181

182
  elif config_major == 2 and config_minor == 5:
183
184
185
186
187
    logging.info("No changes necessary")

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

189
190
191
192
193
194
  if (os.path.isfile(options.RAPI_USERS_FILE_PRE24) and
      not os.path.islink(options.RAPI_USERS_FILE_PRE24)):
    if os.path.exists(options.RAPI_USERS_FILE):
      raise Error("Found pre-2.4 RAPI users file at %s, but another file"
                  " already exists at %s" %
                  (options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE))
195
196
    logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
197
198
199
    if not options.dry_run:
      utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
                       mkdir=True, mkdir_mode=0750)
200
201

  # Create a symlink for RAPI users file
202
203
204
  if (not (os.path.islink(options.RAPI_USERS_FILE_PRE24) or
           os.path.isfile(options.RAPI_USERS_FILE_PRE24)) and
      os.path.isfile(options.RAPI_USERS_FILE)):
205
206
    logging.info("Creating symlink from %s to %s",
                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
207
208
    if not options.dry_run:
      os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
209

210
211
212
213
214
215
  # Remove old watcher state file if it exists
  if os.path.exists(options.WATCHER_STATEFILE):
    logging.info("Removing watcher state file %s", options.WATCHER_STATEFILE)
    if not options.dry_run:
      utils.RemoveFile(options.WATCHER_STATEFILE)

216
217
218
219
220
221
222
  try:
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
                    data=serializer.DumpJson(config_data),
                    mode=0600,
                    dry_run=options.dry_run,
                    backup=True)
223
224

    if not options.dry_run:
Iustin Pop's avatar
Iustin Pop committed
225
      bootstrap.GenerateClusterCrypto(False, False, False, False,
226
227
                                      nodecert_file=options.SERVER_PEM_PATH,
                                      rapicert_file=options.RAPI_CERT_FILE,
228
229
                                      hmackey_file=options.CONFD_HMAC_KEY,
                                      cds_file=options.CDS_FILE)
230

231
  except Exception:
232
    logging.critical("Writing configuration failed. It is probably in an"
233
234
                     " inconsistent state and needs manual intervention.")
    raise
235

236
  # test loading the config file
237
  if not (options.dry_run or options.no_verify):
238
239
    logging.info("Testing the new config file...")
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
240
                              accept_foreign=options.ignore_hostname,
241
242
243
244
245
246
                              offline=True)
    # if we reached this, it's all fine
    vrfy = cfg.VerifyConfig()
    if vrfy:
      logging.error("Errors after conversion:")
      for item in vrfy:
Iustin Pop's avatar
Iustin Pop committed
247
        logging.error(" - %s", item)
248
249
250
    del cfg
    logging.info("File loaded successfully")

251
252
253
  cli.ToStderr("Configuration successfully upgraded for version %s.",
               constants.RELEASE_VERSION)

254
255
256

if __name__ == "__main__":
  main()