Statistics
| Branch: | Tag: | Revision:

root / tools / cfgupgrade @ 95e4a814

History | View | Annotate | Download (6.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2007, 2008 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 example, "set"
25
is a "list".
26

    
27
"""
28

    
29

    
30
import os
31
import os.path
32
import sys
33
import optparse
34
import tempfile
35
import logging
36
import errno
37

    
38
from ganeti import constants
39
from ganeti import serializer
40
from ganeti import utils
41
from ganeti import cli
42

    
43

    
44
# We need to keep filenames locally because they might be renamed between
45
# versions.
46
CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data"
47
SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem"
48
KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts"
49
SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name"
50
SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version"
51

    
52
options = None
53
args = None
54

    
55
# Unique object to identify calls without default value
56
NoDefault = object()
57

    
58

    
59
class Error(Exception):
60
  """Generic exception"""
61
  pass
62

    
63

    
64
def ReadFile(file_name, default=NoDefault):
65
  """Reads a file.
66

    
67
  """
68
  logging.debug("Reading %s", file_name)
69
  try:
70
    fh = open(file_name, 'r')
71
  except IOError, err:
72
    if default is not NoDefault and err.errno == errno.ENOENT:
73
      return default
74
    raise
75

    
76
  try:
77
    return fh.read()
78
  finally:
79
    fh.close()
80

    
81

    
82
def WriteFile(file_name, data):
83
  """Writes a configuration file.
84

    
85
  """
86
  logging.debug("Writing %s", file_name)
87
  utils.WriteFile(file_name=file_name, data=data, mode=0600,
88
                  dry_run=options.dry_run, backup=True)
89

    
90

    
91
def SetupLogging():
92
  """Configures the logging module.
93

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

    
97
  stderr_handler = logging.StreamHandler()
98
  stderr_handler.setFormatter(formatter)
99
  if options.debug:
100
    stderr_handler.setLevel(logging.NOTSET)
101
  elif options.verbose:
102
    stderr_handler.setLevel(logging.INFO)
103
  else:
104
    stderr_handler.setLevel(logging.CRITICAL)
105

    
106
  root_logger = logging.getLogger("")
107
  root_logger.setLevel(logging.NOTSET)
108
  root_logger.addHandler(stderr_handler)
109

    
110

    
111
def main():
112
  """Main program.
113

    
114
  """
115
  global options, args
116

    
117
  program = os.path.basename(sys.argv[0])
118

    
119
  # Option parsing
120
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
121
  parser.add_option('--dry-run', dest='dry_run',
122
                    action="store_true",
123
                    help="Try to do the conversion, but don't write"
124
                         " output file")
125
  parser.add_option(cli.FORCE_OPT)
126
  parser.add_option(cli.DEBUG_OPT)
127
  parser.add_option('--verbose', dest='verbose',
128
                    action="store_true",
129
                    help="Verbose output")
130
  (options, args) = parser.parse_args()
131

    
132
  SetupLogging()
133

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

    
138
  if not options.force:
139
    usertext = ("%s MUST run on the master node. Is this the master"
140
                " node?" % program)
141
    if not cli.AskUser(usertext):
142
      sys.exit(1)
143

    
144
  # Check whether it's a Ganeti configuration directory
145
  if not (os.path.isfile(CONFIG_DATA_PATH) and
146
          os.path.isfile(SERVER_PEM_PATH) or
147
          os.path.isfile(KNOWN_HOSTS_PATH)):
148
    raise Error(("%s does not seem to be a known Ganeti configuration"
149
                 " directory") % constants.DATA_DIR)
150

    
151
  config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip()
152
  logging.info("Found configuration version %s", config_version)
153

    
154
  config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH))
155

    
156
  # Ganeti 1.2?
157
  if config_version == "1.2":
158
    logging.info("Found a Ganeti 1.2 configuration")
159

    
160
    old_config_version = config_data["cluster"].get("config_version", None)
161
    logging.info("Found old configuration version %s", old_config_version)
162
    if old_config_version not in (3, ):
163
      raise Error("Unsupported configuration version: %s" %
164
                  old_config_version)
165

    
166
    # Make sure no instance uses remote_raid1 anymore
167
    remote_raid1_instances = []
168
    for instance in config_data["instances"]:
169
      if instance["disk_template"] == "remote_raid1":
170
        remote_raid1_instances.append(instance["name"])
171
    if remote_raid1_instances:
172
      for name in remote_raid1_instances:
173
        logging.error("Instance %s still using remote_raid1 disk template")
174
      raise Error("Unable to convert configuration as long as there are"
175
                  " instances using remote_raid1 disk template")
176

    
177
    # The configuration version will be stored in a ssconf file
178
    if 'config_version' in config_data['cluster']:
179
      del config_data['cluster']['config_version']
180

    
181
    # Build content of new known_hosts file
182
    cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip()
183
    cluster_key = config_data['cluster']['rsahostkeypub']
184
    known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
185

    
186
  else:
187
    logging.info("Found a Ganeti 2.0 configuration")
188

    
189
    if "config_version" in config_data["cluster"]:
190
      raise Error("Inconsistent configuration: found config_data in"
191
                  " configuration file")
192

    
193
    known_hosts = None
194

    
195
  config_version_str = "%s\n" % constants.BuildVersion(2, 0, 0)
196
  try:
197
    logging.info("Writing configuration file")
198
    WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data))
199

    
200
    logging.info("Writing configuration version %s",
201
                 config_version_str.strip())
202
    WriteFile(SSCONF_CONFIG_VERSION_PATH, config_version_str)
203

    
204
    if known_hosts is not None:
205
      logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
206
      WriteFile(KNOWN_HOSTS_PATH, known_hosts)
207
  except:
208
    logging.critical("Writing configuration failed. It is proably in an"
209
                     " inconsistent state and needs manual intervention.")
210
    raise
211

    
212

    
213
if __name__ == "__main__":
214
  main()
215

    
216
# vim: set foldmethod=marker :