Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 2c9cf6bb

History | View | Annotate | Download (6.6 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
import socket
36

    
37
from ganeti import constants
38
from ganeti import serializer
39
from ganeti import utils
40
from ganeti import cli
41
from ganeti import bootstrap
42
from ganeti import config
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.CRITICAL)
68

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

    
73

    
74
def Cluster22To23(cluster):
75
  """Upgrades the cluster object from 2.2 to 2.3.
76

    
77
  """
78
  logging.info("Upgrading the cluster object")
79
  if "primary_ip_family" not in cluster:
80
    # Add primary ip family to config
81
    cluster["primary_ip_family"] = socket.AF_INET
82

    
83

    
84
def main():
85
  """Main program.
86

    
87
  """
88
  global options, args # pylint: disable-msg=W0603
89

    
90
  program = os.path.basename(sys.argv[0])
91

    
92
  # Option parsing
93
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
94
  parser.add_option('--dry-run', dest='dry_run',
95
                    action="store_true",
96
                    help="Try to do the conversion, but don't write"
97
                         " output file")
98
  parser.add_option(cli.FORCE_OPT)
99
  parser.add_option(cli.DEBUG_OPT)
100
  parser.add_option(cli.VERBOSE_OPT)
101
  parser.add_option('--path', help="Convert configuration in this"
102
                    " directory instead of '%s'" % constants.DATA_DIR,
103
                    default=constants.DATA_DIR, dest="data_dir")
104
  parser.add_option("--no-verify",
105
                    help="Do not verify configuration after upgrade",
106
                    action="store_true", dest="no_verify", default=False)
107
  (options, args) = parser.parse_args()
108

    
109
  # We need to keep filenames locally because they might be renamed between
110
  # versions.
111
  options.data_dir = os.path.abspath(options.data_dir)
112
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
113
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
114
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
115
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
116
  options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
117
  options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
118

    
119
  SetupLogging()
120

    
121
  # Option checking
122
  if args:
123
    raise Error("No arguments expected")
124

    
125
  if not options.force:
126
    usertext = ("%s MUST be run on the master node. Is this the master"
127
                " node and are ALL instances down?" % program)
128
    if not cli.AskUser(usertext):
129
      sys.exit(constants.EXIT_FAILURE)
130

    
131
  # Check whether it's a Ganeti configuration directory
132
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
133
          os.path.isfile(options.SERVER_PEM_PATH) and
134
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
135
    raise Error(("%s does not seem to be a Ganeti configuration"
136
                 " directory") % options.data_dir)
137

    
138
  config_data = serializer.LoadJson(utils.ReadFile(options.CONFIG_DATA_PATH))
139

    
140
  try:
141
    config_version = config_data["version"]
142
  except KeyError:
143
    raise Error("Unable to determine configuration version")
144

    
145
  (config_major, config_minor, config_revision) = \
146
    constants.SplitVersion(config_version)
147

    
148
  logging.info("Found configuration version %s (%d.%d.%d)",
149
               config_version, config_major, config_minor, config_revision)
150

    
151
  if "config_version" in config_data["cluster"]:
152
    raise Error("Inconsistent configuration: found config_version in"
153
                " configuration file")
154

    
155
  # Upgrade from 2.0/2.1 to 2.2
156
  if config_major == 2 and config_minor in (0, 1):
157
    if config_revision != 0:
158
      logging.warning("Config revision is %s, not 0", config_revision)
159

    
160
    config_data["version"] = constants.BuildVersion(2, 2, 0)
161

    
162
  elif config_major == 2 and config_minor == 2:
163
    logging.info("No changes necessary")
164
    # TODO: For Ganeti 2.3 uncomment the following line
165
    # Cluster22To23(config_data["cluster"])
166

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

    
171
  try:
172
    logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
173
    utils.WriteFile(file_name=options.CONFIG_DATA_PATH,
174
                    data=serializer.DumpJson(config_data),
175
                    mode=0600,
176
                    dry_run=options.dry_run,
177
                    backup=True)
178

    
179
    if not options.dry_run:
180
      bootstrap.GenerateClusterCrypto(False, False, False, False,
181
                                      nodecert_file=options.SERVER_PEM_PATH,
182
                                      rapicert_file=options.RAPI_CERT_FILE,
183
                                      hmackey_file=options.CONFD_HMAC_KEY,
184
                                      cds_file=options.CDS_FILE)
185

    
186
  except Exception:
187
    logging.critical("Writing configuration failed. It is probably in an"
188
                     " inconsistent state and needs manual intervention.")
189
    raise
190

    
191
  # test loading the config file
192
  if not (options.dry_run or options.no_verify):
193
    logging.info("Testing the new config file...")
194
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
195
                              offline=True)
196
    # if we reached this, it's all fine
197
    vrfy = cfg.VerifyConfig()
198
    if vrfy:
199
      logging.error("Errors after conversion:")
200
      for item in vrfy:
201
        logging.error(" - %s", item)
202
    del cfg
203
    logging.info("File loaded successfully")
204

    
205

    
206
if __name__ == "__main__":
207
  main()
208

    
209
# vim: set foldmethod=marker :