Revision 95e4a814 tools/cfgupgrade

b/tools/cfgupgrade
32 32
import sys
33 33
import optparse
34 34
import tempfile
35
import simplejson
36 35
import logging
36
import errno
37 37

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

  
41 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

  
42 52
options = None
43 53
args = None
44 54

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

  
45 58

  
46 59
class Error(Exception):
47 60
  """Generic exception"""
48 61
  pass
49 62

  
50 63

  
51
def ReadConfig(path):
52
  """Reads configuration file.
64
def ReadFile(file_name, default=NoDefault):
65
  """Reads a file.
53 66

  
54 67
  """
55
  f = open(path, 'r')
68
  logging.debug("Reading %s", file_name)
56 69
  try:
57
    return simplejson.load(f)
58
  finally:
59
    f.close()
60

  
61

  
62
def WriteConfig(path, data):
63
  """Writes the configuration file.
64

  
65
  """
66
  if not options.dry_run:
67
    utils.CreateBackup(path)
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
68 75

  
69
  (fd, name) = tempfile.mkstemp(dir=os.path.dirname(path))
70
  f = os.fdopen(fd, 'w')
71 76
  try:
72
    try:
73
      simplejson.dump(data, f)
74
      f.flush()
75
      if options.dry_run:
76
        os.unlink(name)
77
      else:
78
        os.rename(name, path)
79
    except:
80
      os.unlink(name)
81
      raise
77
    return fh.read()
82 78
  finally:
83
    f.close()
79
    fh.close()
84 80

  
85 81

  
86
def UpdateFromVersion2To3(cfg):
87
  """Updates the configuration from version 2 to 3.
82
def WriteFile(file_name, data):
83
  """Writes a configuration file.
88 84

  
89 85
  """
90
  if cfg['cluster']['config_version'] != 2:
91
    return
92

  
93
  # Add port pool
94
  if 'tcpudp_port_pool' not in cfg['cluster']:
95
    cfg['cluster']['tcpudp_port_pool'] = []
96

  
97
  # Add bridge settings
98
  if 'default_bridge' not in cfg['cluster']:
99
    cfg['cluster']['default_bridge'] = 'xen-br0'
100
  for inst in cfg['instances'].values():
101
    for nic in inst['nics']:
102
      if 'bridge' not in nic:
103
        nic['bridge'] = None
104

  
105
  cfg['cluster']['config_version'] = 3
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)
106 89

  
107 90

  
108 91
def SetupLogging():
......
134 117
  program = os.path.basename(sys.argv[0])
135 118

  
136 119
  # Option parsing
137
  parser = optparse.OptionParser(usage="%prog [options] <config-file>")
120
  parser = optparse.OptionParser(usage="%prog [--debug|--verbose] [--force]")
138 121
  parser.add_option('--dry-run', dest='dry_run',
139 122
                    action="store_true",
140 123
                    help="Try to do the conversion, but don't write"
......
150 133

  
151 134
  # Option checking
152 135
  if args:
153
    cfg_file = args[0]
154
  else:
155
    raise Error("Configuration file not specified")
136
    raise Error("No arguments expected")
156 137

  
157 138
  if not options.force:
158 139
    usertext = ("%s MUST run on the master node. Is this the master"
......
160 141
    if not cli.AskUser(usertext):
161 142
      sys.exit(1)
162 143

  
163
  config = ReadConfig(cfg_file)
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)
164 185

  
165
  if options.verbose:
166
    import pprint
167
    print "Before upgrade:"
168
    pprint.pprint(config)
169
    print
170

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

  
173
  if options.verbose:
174
    print "After upgrade:"
175
    pprint.pprint(config)
176
    print
189
    if "config_version" in config_data["cluster"]:
190
      raise Error("Inconsistent configuration: found config_data in"
191
                  " configuration file")
177 192

  
178
  WriteConfig(cfg_file, config)
193
    known_hosts = None
179 194

  
180
  print "The configuration file has been updated successfully. Please run"
181
  print "  gnt-cluster copyfile %s" % cfg_file
182
  print "now."
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
183 211

  
184 212

  
185 213
if __name__ == "__main__":

Also available in: Unified diff