""" Copyright (c) 2015-2017 Wind River Systems, Inc. SPDX-License-Identifier: Apache-2.0 """ from __future__ import print_function from six.moves import configparser import os import readline import sys import textwrap from controllerconfig.common import constants from controllerconfig.common import log from controllerconfig.common.exceptions import BackupFail from controllerconfig.common.exceptions import RestoreFail from controllerconfig.common.exceptions import UserQuit from controllerconfig.common.exceptions import CloneFail from configutilities import lag_mode_to_str from configutilities import Network from configutilities import validate from configutilities import ConfigFail from configutilities import DEFAULT_CONFIG from configutilities import REGION_CONFIG from configutilities import SUBCLOUD_CONFIG from configutilities import MGMT_TYPE from configutilities import HP_NAMES from configutilities import DEFAULT_NAMES from controllerconfig.configassistant import ConfigAssistant from controllerconfig import backup_restore from controllerconfig import utils from controllerconfig import clone # Temporary file for building cgcs_config TEMP_CGCS_CONFIG_FILE = "/tmp/cgcs_config" LOG = log.get_logger(__name__) def parse_system_config(config_file): """Parse system config file""" system_config = configparser.RawConfigParser() try: system_config.read(config_file) except Exception as e: LOG.exception(e) raise ConfigFail("Error parsing system config file") # Dump configuration for debugging # for section in config.sections(): # print "Section: %s" % section # for (name, value) in config.items(section): # print "name: %s, value: %s" % (name, value) return system_config def configure_management_interface(region_config, config_type=REGION_CONFIG): """Bring up management interface """ mgmt_network = Network() if region_config.has_section('CLM_NETWORK'): naming_type = HP_NAMES else: naming_type = DEFAULT_NAMES if config_type == SUBCLOUD_CONFIG: min_addresses = 5 else: min_addresses = 8 try: mgmt_network.parse_config(region_config, config_type, MGMT_TYPE, min_addresses=min_addresses, naming_type=naming_type) except ConfigFail: raise except Exception as e: LOG.exception("Error parsing configuration file") raise ConfigFail("Error parsing configuration file: %s" % e) try: # Remove interface config files currently installed utils.remove_interface_config_files() # Create the management interface configuration files. # Code based on ConfigAssistant._write_interface_config_management parameters = utils.get_interface_config_static( mgmt_network.start_address, mgmt_network.cidr, mgmt_network.gateway_address) if mgmt_network.logical_interface.lag_interface: management_interface = 'bond0' else: management_interface = mgmt_network.logical_interface.ports[0] if mgmt_network.vlan: management_interface_name = "%s.%s" % (management_interface, mgmt_network.vlan) utils.write_interface_config_vlan( management_interface_name, mgmt_network.logical_interface.mtu, parameters) # underlying interface has no additional parameters parameters = None else: management_interface_name = management_interface if mgmt_network.logical_interface.lag_interface: utils.write_interface_config_bond( management_interface, mgmt_network.logical_interface.mtu, lag_mode_to_str(mgmt_network.logical_interface.lag_mode), None, constants.LAG_MIIMON_FREQUENCY, mgmt_network.logical_interface.ports[0], mgmt_network.logical_interface.ports[1], parameters) else: utils.write_interface_config_ethernet( management_interface, mgmt_network.logical_interface.mtu, parameters) # Restart networking with the new management interface configuration utils.restart_networking() # Send a GARP for floating address. Doing this to help in # cases where we are re-installing in a lab and another node # previously held the floating address. if mgmt_network.cidr.version == 4: utils.send_interface_garp(management_interface_name, mgmt_network.start_address) except Exception: LOG.exception("Failed to configure management interface") raise ConfigFail("Failed to configure management interface") def create_cgcs_config_file(output_file, system_config, services, endpoints, domains, config_type=REGION_CONFIG, validate_only=False): """ Create cgcs_config file or just perform validation of the system_config if validate_only=True. :param output_file: filename of output cgcs_config file :param system_config: system configuration :param services: keystone services (not used if validate_only) :param endpoints: keystone endpoints (not used if validate_only) :param domains: keystone domains (not used if validate_only) :param config_type: specify region, subcloud or standard config :param validate_only: used to validate the input system_config :return: """ cgcs_config = None if not validate_only: cgcs_config = configparser.RawConfigParser() cgcs_config.optionxform = str # general error checking, if not validate_only cgcs config data is returned validate(system_config, config_type, cgcs_config) # Region configuration: services, endpoints and domain if config_type in [REGION_CONFIG, SUBCLOUD_CONFIG] and not validate_only: # The services and endpoints are not available in the validation phase region_1_name = system_config.get('SHARED_SERVICES', 'REGION_NAME') keystone_service_name = system_config.get('SHARED_SERVICES', 'KEYSTONE_SERVICE_NAME') keystone_service_type = system_config.get('SHARED_SERVICES', 'KEYSTONE_SERVICE_TYPE') keystone_service_id = services.get_service_id(keystone_service_name, keystone_service_type) keystone_admin_url = endpoints.get_service_url(region_1_name, keystone_service_id, "admin") keystone_internal_url = endpoints.get_service_url(region_1_name, keystone_service_id, "internal") keystone_public_url = endpoints.get_service_url(region_1_name, keystone_service_id, "public") cgcs_config.set('cREGION', 'KEYSTONE_AUTH_URI', keystone_internal_url) cgcs_config.set('cREGION', 'KEYSTONE_IDENTITY_URI', keystone_admin_url) cgcs_config.set('cREGION', 'KEYSTONE_ADMIN_URI', keystone_admin_url) cgcs_config.set('cREGION', 'KEYSTONE_INTERNAL_URI', keystone_internal_url) cgcs_config.set('cREGION', 'KEYSTONE_PUBLIC_URI', keystone_public_url) is_glance_cached = False if system_config.has_option('SHARED_SERVICES', 'GLANCE_CACHED'): if (system_config.get('SHARED_SERVICES', 'GLANCE_CACHED').upper() == 'TRUE'): is_glance_cached = True cgcs_config.set('cREGION', 'GLANCE_CACHED', is_glance_cached) if (system_config.has_option('SHARED_SERVICES', 'GLANCE_SERVICE_NAME') and not is_glance_cached): glance_service_name = system_config.get('SHARED_SERVICES', 'GLANCE_SERVICE_NAME') glance_service_type = system_config.get('SHARED_SERVICES', 'GLANCE_SERVICE_TYPE') glance_region_name = region_1_name glance_service_id = services.get_service_id(glance_service_name, glance_service_type) glance_internal_url = endpoints.get_service_url(glance_region_name, glance_service_id, "internal") glance_public_url = endpoints.get_service_url(glance_region_name, glance_service_id, "public") cgcs_config.set('cREGION', 'GLANCE_ADMIN_URI', glance_internal_url) cgcs_config.set('cREGION', 'GLANCE_PUBLIC_URI', glance_public_url) cgcs_config.set('cREGION', 'GLANCE_INTERNAL_URI', glance_internal_url) # if ldap is a shared service if (system_config.has_option('SHARED_SERVICES', 'LDAP_SERVICE_URL')): ldap_service_url = system_config.get('SHARED_SERVICES', 'LDAP_SERVICE_URL') cgcs_config.set('cREGION', 'LDAP_SERVICE_URI', ldap_service_url) cgcs_config.set('cREGION', 'LDAP_SERVICE_NAME', 'open-ldap') cgcs_config.set('cREGION', 'LDAP_REGION_NAME', region_1_name) # The domains are not available in the validation phase heat_admin_domain = system_config.get('REGION_2_SERVICES', 'HEAT_ADMIN_DOMAIN') cgcs_config.set('cREGION', 'HEAT_ADMIN_DOMAIN_NAME', heat_admin_domain) # If primary region is non-TiC and keystone entries already created, # the flag will tell puppet not to create them. if (system_config.has_option('REGION_2_SERVICES', 'CREATE') and system_config.get('REGION_2_SERVICES', 'CREATE') == 'Y'): cgcs_config.set('cREGION', 'REGION_SERVICES_CREATE', 'True') # System Timezone configuration if system_config.has_option('SYSTEM', 'TIMEZONE'): timezone = system_config.get('SYSTEM', 'TIMEZONE') if not os.path.isfile("/usr/share/zoneinfo/%s" % timezone): raise ConfigFail( "Timezone file %s does not exist" % timezone) # Dump results for debugging # for section in cgcs_config.sections(): # print "[%s]" % section # for (name, value) in cgcs_config.items(section): # print "%s=%s" % (name, value) if not validate_only: # Write config file with open(output_file, 'w') as config_file: cgcs_config.write(config_file) def configure_system(config_file): """Configure the system""" # Parse the system config file print("Parsing system configuration file... ", end=' ') system_config = parse_system_config(config_file) print("DONE") # Validate the system config file print("Validating system configuration file... ", end=' ') try: create_cgcs_config_file(None, system_config, None, None, None, DEFAULT_CONFIG, validate_only=True) except configparser.Error as e: raise ConfigFail("Error parsing configuration file %s: %s" % (config_file, e)) print("DONE") # Create cgcs_config file print("Creating config apply file... ", end=' ') try: create_cgcs_config_file(TEMP_CGCS_CONFIG_FILE, system_config, None, None, None, DEFAULT_CONFIG) except configparser.Error as e: raise ConfigFail("Error parsing configuration file %s: %s" % (config_file, e)) print("DONE") def show_help(): print("Usage: %s\n" "Perform system configuration\n" "\nThe default action is to perform the initial configuration for " "the system.\nThe following options are also available:\n" "--config-file Perform configuration using INI file\n" "--backup Backup configuration using the given " "name\n" "--clone-iso Clone and create an image with " "the given file name\n" "--clone-status Status of the last installation of " "cloned image\n" "--restore-system " " " "\n" " Restore system configuration from backup " "file with\n" " the given name, full path required\n" "--restore-images Restore images from backup file with the " "given name,\n" " full path required\n" "--restore-complete Complete restore of controller-0\n" "--allow-ssh Allow configuration to be executed in " "ssh\n" % sys.argv[0]) def show_help_lab_only(): print("Usage: %s\n" "Perform initial configuration\n" "\nThe following options are for lab use only:\n" "--answerfile Apply the configuration from the specified " "file without\n" " any validation or user interaction\n" "--default Apply default configuration with no NTP or " "DNS server\n" " configuration (suitable for testing in a " "virtual\n" " environment)\n" "--archive-dir Directory to store the archive in\n" "--provision Provision initial system data only\n" % sys.argv[0]) def no_complete(text, state): return def main(): options = {} answerfile = None backup_name = None archive_dir = constants.BACKUPS_PATH do_default_config = False do_backup = False do_system_restore = False include_storage_reinstall = False do_images_restore = False do_complete_restore = False do_clone = False do_non_interactive = False do_provision = False system_config_file = "/home/wrsroot/system_config" allow_ssh = False # Disable completion as the default completer shows python commands readline.set_completer(no_complete) # remove any previous config fail flag file if os.path.exists(constants.CONFIG_FAIL_FILE) is True: os.remove(constants.CONFIG_FAIL_FILE) if os.environ.get('CGCS_LABMODE'): options['labmode'] = True arg = 1 while arg < len(sys.argv): if sys.argv[arg] == "--answerfile": arg += 1 if arg < len(sys.argv): answerfile = sys.argv[arg] else: print("--answerfile option requires a file to be specified") exit(1) elif sys.argv[arg] == "--backup": arg += 1 if arg < len(sys.argv): backup_name = sys.argv[arg] else: print("--backup requires the name of the backup") exit(1) do_backup = True elif sys.argv[arg] == "--restore-system": arg += 1 if arg < len(sys.argv): if sys.argv[arg] in ["include-storage-reinstall", "exclude-storage-reinstall"]: if sys.argv[arg] == "include-storage-reinstall": include_storage_reinstall = True arg += 1 if arg < len(sys.argv): backup_name = sys.argv[arg] else: print(textwrap.fill( "--restore-system requires the filename " " of the backup", 80)) exit(1) else: backup_name = sys.argv[arg] else: print(textwrap.fill( "--restore-system requires the filename " "of the backup", 80)) exit(1) do_system_restore = True elif sys.argv[arg] == "--restore-images": arg += 1 if arg < len(sys.argv): backup_name = sys.argv[arg] else: print("--restore-images requires the filename of the backup") exit(1) do_images_restore = True elif sys.argv[arg] == "--restore-complete": do_complete_restore = True elif sys.argv[arg] == "--archive-dir": arg += 1 if arg < len(sys.argv): archive_dir = sys.argv[arg] else: print("--archive-dir requires a directory") exit(1) elif sys.argv[arg] == "--clone-iso": arg += 1 if arg < len(sys.argv): backup_name = sys.argv[arg] else: print("--clone-iso requires the name of the image") exit(1) do_clone = True elif sys.argv[arg] == "--clone-status": clone.clone_status() exit(0) elif sys.argv[arg] == "--default": do_default_config = True elif sys.argv[arg] == "--config-file": arg += 1 if arg < len(sys.argv): system_config_file = sys.argv[arg] else: print("--config-file requires the filename of the config file") exit(1) do_non_interactive = True elif sys.argv[arg] in ["--help", "-h", "-?"]: show_help() exit(1) elif sys.argv[arg] == "--labhelp": show_help_lab_only() exit(1) elif sys.argv[arg] == "--provision": do_provision = True elif sys.argv[arg] == "--allow-ssh": allow_ssh = True elif sys.argv[arg] == "--kubernetes": # This is a temporary flag for use during development. Once things # are stable, we will remove it and make kubernetes the default. options['kubernetes'] = True else: print("Invalid option. Use --help for more information.") exit(1) arg += 1 if [do_backup, do_system_restore, do_images_restore, do_complete_restore, do_clone, do_default_config, do_non_interactive].count(True) > 1: print("Invalid combination of options selected") exit(1) if answerfile and [do_backup, do_system_restore, do_images_restore, do_complete_restore, do_clone, do_default_config, do_non_interactive].count(True) > 0: print("The --answerfile option cannot be used with the selected " "option") exit(1) log.configure() if not do_backup and not do_clone: # Check if that the command is being run from the console if utils.is_ssh_parent(): if allow_ssh: print(textwrap.fill(constants.SSH_WARNING_MESSAGE, 80)) print('') else: print(textwrap.fill(constants.SSH_ERROR_MESSAGE, 80)) exit(1) # Reduce the printk console log level to avoid noise during configuration printk_levels = '' with open('/proc/sys/kernel/printk', 'r') as f: printk_levels = f.readline() temp_printk_levels = '3' + printk_levels[1:] with open('/proc/sys/kernel/printk', 'w') as f: f.write(temp_printk_levels) try: if do_backup: backup_restore.backup(backup_name, archive_dir) print("\nBackup complete") elif do_system_restore: backup_restore.restore_system(backup_name, include_storage_reinstall) print("\nSystem restore complete") elif do_images_restore: backup_restore.restore_images(backup_name) print("\nImages restore complete") elif do_complete_restore: backup_restore.restore_complete() elif do_clone: clone.clone(backup_name, archive_dir) print("\nCloning complete") elif do_provision: assistant = ConfigAssistant(**options) assistant.provision(answerfile) else: if do_non_interactive: if not os.path.isfile(system_config_file): raise ConfigFail("Config file %s does not exist." % system_config_file) if (os.path.exists(constants.CGCS_CONFIG_FILE) or os.path.exists(constants.CONFIG_PERMDIR) or os.path.exists( constants.INITIAL_CONFIG_COMPLETE_FILE)): raise ConfigFail("Configuration has already been done " "and cannot be repeated.") configure_system(system_config_file) answerfile = TEMP_CGCS_CONFIG_FILE assistant = ConfigAssistant(**options) assistant.configure(answerfile, do_default_config) print("\nConfiguration was applied\n") print(textwrap.fill( "Please complete any out of service commissioning steps " "with system commands and unlock controller to proceed.", 80)) assistant.check_required_interfaces_status() except KeyboardInterrupt: print("\nAborting configuration") except BackupFail as e: print("\nBackup failed: {}".format(e)) except RestoreFail as e: print("\nRestore failed: {}".format(e)) except ConfigFail as e: print("\nConfiguration failed: {}".format(e)) except CloneFail as e: print("\nCloning failed: {}".format(e)) except UserQuit: print("\nAborted configuration") finally: if os.path.isfile(TEMP_CGCS_CONFIG_FILE): os.remove(TEMP_CGCS_CONFIG_FILE) # Restore the printk console log level with open('/proc/sys/kernel/printk', 'w') as f: f.write(printk_levels)