Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 7eda951b

History | View | Annotate | Download (9.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=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.SPICE_CERT_FILE = options.data_dir + "/spice.pem"
126
  options.SPICE_CACERT_FILE = options.data_dir + "/spice-ca.pem"
127
  options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
128
  options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
129
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
130
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
131
  options.SSCONF_MASTER_NODE = options.data_dir + "/ssconf_master_node"
132
  options.WATCHER_STATEFILE = options.data_dir + "/watcher.data"
133

    
134
  SetupLogging()
135

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

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

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

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

    
160
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
161

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

    
167
  (config_major, config_minor, config_revision) = \
168
    constants.SplitVersion(config_version)
169

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

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

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

    
182
    config_data["version"] = constants.BuildVersion(2, 5, 0)
183

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

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

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

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

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

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

    
226
    if not options.dry_run:
227
      bootstrap.GenerateClusterCrypto(False, False, False, False, False,
228
                                     nodecert_file=options.SERVER_PEM_PATH,
229
                                     rapicert_file=options.RAPI_CERT_FILE,
230
                                     spicecert_file=options.SPICE_CERT_FILE,
231
                                     spicecacert_file=options.SPICE_CACERT_FILE,
232
                                     hmackey_file=options.CONFD_HMAC_KEY,
233
                                     cds_file=options.CDS_FILE)
234

    
235
  except Exception:
236
    logging.critical("Writing configuration failed. It is probably in an"
237
                     " inconsistent state and needs manual intervention.")
238
    raise
239

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

    
255
  cli.ToStderr("Configuration successfully upgraded for version %s.",
256
               constants.RELEASE_VERSION)
257

    
258

    
259
if __name__ == "__main__":
260
  main()