Revision ac4d25b6

b/tools/cfgupgrade
40 40
from ganeti import utils
41 41
from ganeti import cli
42 42
from ganeti import bootstrap
43
from ganeti import config
43 44

  
44 45

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

  
53 46
options = None
54 47
args = None
55 48

  
56 49
# Unique object to identify calls without default value
57 50
NoDefault = object()
58 51

  
52
# Dictionary with instance old keys, and new hypervisor keys
53
INST_HV_CHG = {
54
  'hvm_pae': constants.HV_PAE,
55
  'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS,
56
  'initrd_path': constants.HV_INITRD_PATH,
57
  'hvm_nic_type': constants.HV_NIC_TYPE,
58
  'kernel_path': constants.HV_KERNEL_PATH,
59
  'hvm_acpi': constants.HV_ACPI,
60
  'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH,
61
  'hvm_boot_order': constants.HV_BOOT_ORDER,
62
  'hvm_disk_type': constants.HV_DISK_TYPE,
63
  }
64

  
65
# Instance beparams changes
66
INST_BE_CHG = {
67
  'vcpus': constants.BE_VCPUS,
68
  'memory': constants.BE_MEMORY,
69
  'auto_balance': constants.BE_AUTO_BALANCE,
70
  }
59 71

  
60 72
class Error(Exception):
61 73
  """Generic exception"""
......
66 78
  """Returns the file name of an (old) ssconf key.
67 79

  
68 80
  """
69
  return "%s/ssconf_%s" % (constants.DATA_DIR, key)
81
  return "%s/ssconf_%s" % (options.data_dir, key)
70 82

  
71 83

  
72 84
def ReadFile(file_name, default=NoDefault):
......
96 108
                  dry_run=options.dry_run, backup=True)
97 109

  
98 110

  
111
def GenerateSecret(all_secrets):
112
  """Generate an unique DRBD secret.
113

  
114
  This is a copy from ConfigWriter.
115

  
116
  """
117
  retries = 64
118
  while retries > 0:
119
    secret = utils.GenerateSecret()
120
    if secret not in all_secrets:
121
      break
122
    retries -= 1
123
  else:
124
    raise Error("Can't generate unique DRBD secret")
125
  return secret
126

  
127

  
99 128
def SetupLogging():
100 129
  """Configures the logging module.
101 130

  
......
158 187
  if 'file_storage_dir' not in cluster:
159 188
    cluster['file_storage_dir'] = constants.DEFAULT_FILE_STORAGE_DIR
160 189

  
190
  # candidate pool size
191
  if 'candidate_pool_size' not in cluster:
192
    cluster['candidate_pool_size'] = constants.MASTER_POOL_SIZE_DEFAULT
193

  
161 194

  
162 195
def Node12To20(node):
163 196
  """Upgrades a node from 1.2 to 2.0.
......
173 206
      node[key] = False
174 207

  
175 208

  
209
def Instance12To20(drbd_minors, secrets, hypervisor, instance):
210
  """Upgrades an instance from 1.2 to 2.0.
211

  
212
  """
213
  if 'hypervisor' not in instance:
214
    instance['hypervisor'] = hypervisor
215

  
216
  # hvparams changes
217
  if 'hvparams' not in instance:
218
    instance['hvparams'] = hvp = {}
219
  for old, new in INST_HV_CHG.items():
220
    if old in instance:
221
      if (instance[old] is not None and
222
          instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0
223
          new in constants.HVC_DEFAULTS[hypervisor]):
224
        hvp[new] = instance[old]
225
      del instance[old]
226

  
227
  # beparams changes
228
  if 'beparams' not in instance:
229
    instance['beparams'] = bep = {}
230
  for old, new in INST_BE_CHG.items():
231
    if old in instance:
232
      if instance[old] is not None:
233
        bep[new] = instance[old]
234
      del instance[old]
235

  
236
  # disk changes
237
  for disk in instance['disks']:
238
    Disk12To20(drbd_minors, secrets, disk)
239

  
240
  # other instance changes
241
  if 'status' in instance:
242
    instance['admin_up'] = instance['status'] == 'up'
243
    del instance['status']
244

  
245

  
246
def Disk12To20(drbd_minors, secrets, disk):
247
  """Upgrades a disk from 1.2 to 2.0.
248

  
249
  """
250
  if 'mode' not in disk:
251
    disk['mode'] = constants.DISK_RDWR
252
  if disk['dev_type'] == constants.LD_DRBD8:
253
    old_lid = disk['logical_id']
254
    for node in old_lid[:2]:
255
      if node not in drbd_minors:
256
        raise Error("Can't find node '%s' while upgrading disk" % node)
257
      drbd_minors[node] += 1
258
      minor = drbd_minors[node]
259
      old_lid.append(minor)
260
    old_lid.append(GenerateSecret(secrets))
261
    del disk['physical_id']
262
  if disk['children']:
263
    for child in disk['children']:
264
      Disk12To20(drbd_minors, secrets, child)
265

  
266

  
176 267
def main():
177 268
  """Main program.
178 269

  
......
192 283
  parser.add_option('-v', '--verbose', dest='verbose',
193 284
                    action="store_true",
194 285
                    help="Verbose output")
286
  parser.add_option('--path', help="Convert configuration in this"
287
                    " directory instead of '%s'" % constants.DATA_DIR,
288
                    default=constants.DATA_DIR, dest="data_dir")
195 289
  (options, args) = parser.parse_args()
196 290

  
291
  # We need to keep filenames locally because they might be renamed between
292
  # versions.
293
  options.CONFIG_DATA_PATH = options.data_dir + "/config.data"
294
  options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
295
  options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
296
  options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
297

  
197 298
  SetupLogging()
198 299

  
199 300
  # Option checking
......
207 308
      sys.exit(1)
208 309

  
209 310
  # Check whether it's a Ganeti configuration directory
210
  if not (os.path.isfile(CONFIG_DATA_PATH) and
211
          os.path.isfile(SERVER_PEM_PATH) or
212
          os.path.isfile(KNOWN_HOSTS_PATH)):
311
  if not (os.path.isfile(options.CONFIG_DATA_PATH) and
312
          os.path.isfile(options.SERVER_PEM_PATH) or
313
          os.path.isfile(options.KNOWN_HOSTS_PATH)):
213 314
    raise Error(("%s does not seem to be a known Ganeti configuration"
214
                 " directory") % constants.DATA_DIR)
315
                 " directory") % options.data_dir)
215 316

  
216
  config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip()
317
  config_version = ReadFile(SsconfName('config_version'), "1.2").strip()
217 318
  logging.info("Found configuration version %s", config_version)
218 319

  
219
  config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH))
320
  config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH))
220 321

  
221 322
  # Ganeti 1.2?
222 323
  if config_version == "1.2":
......
236 337

  
237 338
    # Make sure no instance uses remote_raid1 anymore
238 339
    remote_raid1_instances = []
239
    for instance in config_data["instances"]:
340
    for instance in config_data["instances"].values():
240 341
      if instance["disk_template"] == "remote_raid1":
241 342
        remote_raid1_instances.append(instance["name"])
242 343
    if remote_raid1_instances:
......
246 347
                  " instances using remote_raid1 disk template")
247 348

  
248 349
    # Build content of new known_hosts file
249
    cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip()
350
    cluster_name = ReadFile(SsconfName('cluster_name')).rstrip()
250 351
    cluster_key = cluster['rsahostkeypub']
251 352
    known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key)
252 353

  
......
258 359
    for node_name in utils.NiceSort(config_data['nodes'].keys()):
259 360
      Node12To20(config_data['nodes'][node_name])
260 361

  
261
    # instance changes
262
    # TODO: add instance upgrade
263
    for instance in config_data['instances'].values():
264
      pass
362
    # Instance changes
363
    logging.info("Upgrading instances")
364
    drbd_minors = dict.fromkeys(config_data['nodes'], 0)
365
    secrets = set()
366
    # stable-sort the names to have repeatable runs
367
    for instance_name in utils.NiceSort(config_data['instances'].keys()):
368
      Instance12To20(drbd_minors, secrets, cluster['default_hypervisor'],
369
                     config_data['instances'][instance_name])
265 370

  
266 371
  else:
267 372
    logging.info("Found a Ganeti 2.0 configuration")
......
274 379

  
275 380
  try:
276 381
    logging.info("Writing configuration file")
277
    WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data))
382
    WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data))
278 383

  
279 384
    if known_hosts is not None:
280 385
      logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip())
281
      WriteFile(KNOWN_HOSTS_PATH, known_hosts)
386
      WriteFile(options.KNOWN_HOSTS_PATH, known_hosts)
282 387

  
283 388
    if not options.dry_run:
284
      if not os.path.exists(constants.RAPI_CERT_FILE):
285
        bootstrap._GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
389
      if not os.path.exists(options.RAPI_CERT_FILE):
390
        bootstrap._GenerateSelfSignedSslCert(options.RAPI_CERT_FILE)
286 391

  
287 392
  except:
288 393
    logging.critical("Writing configuration failed. It is proably in an"
289 394
                     " inconsistent state and needs manual intervention.")
290 395
    raise
291 396

  
397
  # test loading the config file
398
  if not options.dry_run:
399
    logging.info("Testing the new config file...")
400
    cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH,
401
                              offline=True)
402
    # if we reached this, it's all fine
403
    vrfy = cfg.VerifyConfig()
404
    if vrfy:
405
      logging.error("Errors after conversion:")
406
      for item in vrfy:
407
        logging.error(" - %s" % item)
408
    del cfg
409
    logging.info("File loaded successfully")
410

  
292 411

  
293 412
if __name__ == "__main__":
294 413
  main()

Also available in: Unified diff