From ac4d25b618ea72c44c36451e83e540d36b437588 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Wed, 4 Mar 2009 14:22:52 +0000 Subject: [PATCH] Complete the cfgupgrade script for 2.0 migrations This patch makes the cfgupgrade script to handle: - instance changes - disk changes - further cluster fixes - adds configuration checks at the end, in non-dry-run mode Reviewed-by: ultrotter --- tools/cfgupgrade | 169 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 144 insertions(+), 25 deletions(-) diff --git a/tools/cfgupgrade b/tools/cfgupgrade index c3ab62b62..88b416388 100755 --- a/tools/cfgupgrade +++ b/tools/cfgupgrade @@ -40,22 +40,34 @@ from ganeti import serializer from ganeti import utils from ganeti import cli from ganeti import bootstrap +from ganeti import config -# We need to keep filenames locally because they might be renamed between -# versions. -CONFIG_DATA_PATH = constants.DATA_DIR + "/config.data" -SERVER_PEM_PATH = constants.DATA_DIR + "/server.pem" -KNOWN_HOSTS_PATH = constants.DATA_DIR + "/known_hosts" -SSCONF_CLUSTER_NAME_PATH = constants.DATA_DIR + "/ssconf_cluster_name" -SSCONF_CONFIG_VERSION_PATH = constants.DATA_DIR + "/ssconf_config_version" - options = None args = None # Unique object to identify calls without default value NoDefault = object() +# Dictionary with instance old keys, and new hypervisor keys +INST_HV_CHG = { + 'hvm_pae': constants.HV_PAE, + 'vnc_bind_address': constants.HV_VNC_BIND_ADDRESS, + 'initrd_path': constants.HV_INITRD_PATH, + 'hvm_nic_type': constants.HV_NIC_TYPE, + 'kernel_path': constants.HV_KERNEL_PATH, + 'hvm_acpi': constants.HV_ACPI, + 'hvm_cdrom_image_path': constants.HV_CDROM_IMAGE_PATH, + 'hvm_boot_order': constants.HV_BOOT_ORDER, + 'hvm_disk_type': constants.HV_DISK_TYPE, + } + +# Instance beparams changes +INST_BE_CHG = { + 'vcpus': constants.BE_VCPUS, + 'memory': constants.BE_MEMORY, + 'auto_balance': constants.BE_AUTO_BALANCE, + } class Error(Exception): """Generic exception""" @@ -66,7 +78,7 @@ def SsconfName(key): """Returns the file name of an (old) ssconf key. """ - return "%s/ssconf_%s" % (constants.DATA_DIR, key) + return "%s/ssconf_%s" % (options.data_dir, key) def ReadFile(file_name, default=NoDefault): @@ -96,6 +108,23 @@ def WriteFile(file_name, data): dry_run=options.dry_run, backup=True) +def GenerateSecret(all_secrets): + """Generate an unique DRBD secret. + + This is a copy from ConfigWriter. + + """ + retries = 64 + while retries > 0: + secret = utils.GenerateSecret() + if secret not in all_secrets: + break + retries -= 1 + else: + raise Error("Can't generate unique DRBD secret") + return secret + + def SetupLogging(): """Configures the logging module. @@ -158,6 +187,10 @@ def Cluster12To20(cluster): if 'file_storage_dir' not in cluster: cluster['file_storage_dir'] = constants.DEFAULT_FILE_STORAGE_DIR + # candidate pool size + if 'candidate_pool_size' not in cluster: + cluster['candidate_pool_size'] = constants.MASTER_POOL_SIZE_DEFAULT + def Node12To20(node): """Upgrades a node from 1.2 to 2.0. @@ -173,6 +206,64 @@ def Node12To20(node): node[key] = False +def Instance12To20(drbd_minors, secrets, hypervisor, instance): + """Upgrades an instance from 1.2 to 2.0. + + """ + if 'hypervisor' not in instance: + instance['hypervisor'] = hypervisor + + # hvparams changes + if 'hvparams' not in instance: + instance['hvparams'] = hvp = {} + for old, new in INST_HV_CHG.items(): + if old in instance: + if (instance[old] is not None and + instance[old] != constants.VALUE_DEFAULT and # no longer valid in 2.0 + new in constants.HVC_DEFAULTS[hypervisor]): + hvp[new] = instance[old] + del instance[old] + + # beparams changes + if 'beparams' not in instance: + instance['beparams'] = bep = {} + for old, new in INST_BE_CHG.items(): + if old in instance: + if instance[old] is not None: + bep[new] = instance[old] + del instance[old] + + # disk changes + for disk in instance['disks']: + Disk12To20(drbd_minors, secrets, disk) + + # other instance changes + if 'status' in instance: + instance['admin_up'] = instance['status'] == 'up' + del instance['status'] + + +def Disk12To20(drbd_minors, secrets, disk): + """Upgrades a disk from 1.2 to 2.0. + + """ + if 'mode' not in disk: + disk['mode'] = constants.DISK_RDWR + if disk['dev_type'] == constants.LD_DRBD8: + old_lid = disk['logical_id'] + for node in old_lid[:2]: + if node not in drbd_minors: + raise Error("Can't find node '%s' while upgrading disk" % node) + drbd_minors[node] += 1 + minor = drbd_minors[node] + old_lid.append(minor) + old_lid.append(GenerateSecret(secrets)) + del disk['physical_id'] + if disk['children']: + for child in disk['children']: + Disk12To20(drbd_minors, secrets, child) + + def main(): """Main program. @@ -192,8 +283,18 @@ def main(): parser.add_option('-v', '--verbose', dest='verbose', action="store_true", help="Verbose output") + parser.add_option('--path', help="Convert configuration in this" + " directory instead of '%s'" % constants.DATA_DIR, + default=constants.DATA_DIR, dest="data_dir") (options, args) = parser.parse_args() + # We need to keep filenames locally because they might be renamed between + # versions. + options.CONFIG_DATA_PATH = options.data_dir + "/config.data" + options.SERVER_PEM_PATH = options.data_dir + "/server.pem" + options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts" + options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem" + SetupLogging() # Option checking @@ -207,16 +308,16 @@ def main(): sys.exit(1) # Check whether it's a Ganeti configuration directory - if not (os.path.isfile(CONFIG_DATA_PATH) and - os.path.isfile(SERVER_PEM_PATH) or - os.path.isfile(KNOWN_HOSTS_PATH)): + if not (os.path.isfile(options.CONFIG_DATA_PATH) and + os.path.isfile(options.SERVER_PEM_PATH) or + os.path.isfile(options.KNOWN_HOSTS_PATH)): raise Error(("%s does not seem to be a known Ganeti configuration" - " directory") % constants.DATA_DIR) + " directory") % options.data_dir) - config_version = ReadFile(SSCONF_CONFIG_VERSION_PATH, "1.2").strip() + config_version = ReadFile(SsconfName('config_version'), "1.2").strip() logging.info("Found configuration version %s", config_version) - config_data = serializer.LoadJson(ReadFile(CONFIG_DATA_PATH)) + config_data = serializer.LoadJson(ReadFile(options.CONFIG_DATA_PATH)) # Ganeti 1.2? if config_version == "1.2": @@ -236,7 +337,7 @@ def main(): # Make sure no instance uses remote_raid1 anymore remote_raid1_instances = [] - for instance in config_data["instances"]: + for instance in config_data["instances"].values(): if instance["disk_template"] == "remote_raid1": remote_raid1_instances.append(instance["name"]) if remote_raid1_instances: @@ -246,7 +347,7 @@ def main(): " instances using remote_raid1 disk template") # Build content of new known_hosts file - cluster_name = ReadFile(SSCONF_CLUSTER_NAME_PATH).rstrip() + cluster_name = ReadFile(SsconfName('cluster_name')).rstrip() cluster_key = cluster['rsahostkeypub'] known_hosts = "%s ssh-rsa %s\n" % (cluster_name, cluster_key) @@ -258,10 +359,14 @@ def main(): for node_name in utils.NiceSort(config_data['nodes'].keys()): Node12To20(config_data['nodes'][node_name]) - # instance changes - # TODO: add instance upgrade - for instance in config_data['instances'].values(): - pass + # Instance changes + logging.info("Upgrading instances") + drbd_minors = dict.fromkeys(config_data['nodes'], 0) + secrets = set() + # stable-sort the names to have repeatable runs + for instance_name in utils.NiceSort(config_data['instances'].keys()): + Instance12To20(drbd_minors, secrets, cluster['default_hypervisor'], + config_data['instances'][instance_name]) else: logging.info("Found a Ganeti 2.0 configuration") @@ -274,21 +379,35 @@ def main(): try: logging.info("Writing configuration file") - WriteFile(CONFIG_DATA_PATH, serializer.DumpJson(config_data)) + WriteFile(options.CONFIG_DATA_PATH, serializer.DumpJson(config_data)) if known_hosts is not None: logging.info("Writing SSH known_hosts file (%s)", known_hosts.strip()) - WriteFile(KNOWN_HOSTS_PATH, known_hosts) + WriteFile(options.KNOWN_HOSTS_PATH, known_hosts) if not options.dry_run: - if not os.path.exists(constants.RAPI_CERT_FILE): - bootstrap._GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE) + if not os.path.exists(options.RAPI_CERT_FILE): + bootstrap._GenerateSelfSignedSslCert(options.RAPI_CERT_FILE) except: logging.critical("Writing configuration failed. It is proably in an" " inconsistent state and needs manual intervention.") raise + # test loading the config file + if not options.dry_run: + logging.info("Testing the new config file...") + cfg = config.ConfigWriter(cfg_file=options.CONFIG_DATA_PATH, + offline=True) + # if we reached this, it's all fine + vrfy = cfg.VerifyConfig() + if vrfy: + logging.error("Errors after conversion:") + for item in vrfy: + logging.error(" - %s" % item) + del cfg + logging.info("File loaded successfully") + if __name__ == "__main__": main() -- GitLab