def test_do_configure_logs_validation_errors(tmpdir, monkeypatch, caplog): """ Configuration validation errors are logged as `error` messages. """ monkeypatch.setenv('BOOTSTRAP_VARIANT', 'test_variant') invalid_config = textwrap.dedent("""--- cluster_name: DC/OS master_discovery: static # Remove `exhibitor_storage_backend` from configuration # exhibitor_storage_backend: static master_list: - 127.0.0.1 bootstrap_url: http://example.com """) create_config(invalid_config, tmpdir) create_fake_build_artifacts(tmpdir) with tmpdir.as_cwd(): assert backend.do_configure(config_path='genconf/config.yaml') == 1 expected_error_message = ( 'exhibitor_storage_backend: Must set exhibitor_storage_backend, ' 'no way to calculate value.') error_logs = [ rec for rec in caplog.records if rec.message == expected_error_message ] assert len(error_logs) == 1 error_log = error_logs[0] assert error_log.levelno == logging.ERROR
def test_do_configure_logs_validation_errors(tmpdir, monkeypatch, caplog): """ Configuration validation errors are logged as `error` messages. """ monkeypatch.setenv('BOOTSTRAP_VARIANT', 'test_variant') invalid_config = textwrap.dedent("""--- cluster_name: DC/OS master_discovery: static # Remove `exhibitor_storage_backend` from configuration # exhibitor_storage_backend: static master_list: - 127.0.0.1 bootstrap_url: http://example.com """) create_config(invalid_config, tmpdir) create_fake_build_artifacts(tmpdir) with tmpdir.as_cwd(): assert backend.do_configure(config_path='genconf/config.yaml') == 1 expected_error_message = ( 'exhibitor_storage_backend: Must set exhibitor_storage_backend, ' 'no way to calculate value.' ) error_logs = [rec for rec in caplog.records if rec.message == expected_error_message] assert len(error_logs) == 1 error_log = error_logs[0] assert error_log.levelno == logging.ERROR
def test_do_configure(tmpdir): genconf_dir = tmpdir.join('genconf') genconf_dir.ensure(dir=True) config_path = genconf_dir.join('config.yaml') config_path.write(simple_full_config) genconf_dir.join('ip-detect').write('#!/bin/bash\necho 127.0.0.1') tmpdir.join('artifacts/bootstrap/12345.bootstrap.tar.xz').write('contents_of_bootstrap', ensure=True) tmpdir.join('artifacts/bootstrap/12345.active.json').write('{"active": "contents"}', ensure=True) with tmpdir.as_cwd(): assert backend.do_configure(config_path=str(config_path)) == 0
def test_do_configure_valid_config_no_duplicate_logging(tmpdir, monkeypatch, caplog): """ Log messages are logged exactly once. """ monkeypatch.setenv('BOOTSTRAP_VARIANT', 'test_variant') create_config(simple_full_config, tmpdir) create_fake_build_artifacts(tmpdir) with tmpdir.as_cwd(): assert backend.do_configure(config_path='genconf/config.yaml') == 0 # The message comes from gen.get_dcosconfig_source_target_and_templates() function expected_message = 'Generating configuration files...' filtered_messages = [rec.message for rec in caplog.records if rec.message == expected_message] assert [expected_message] == filtered_messages
def __init__(self, args=None): """ The web based installer leverages Flask to present end-users of dcos_installer with a clean web interface to configure their site-based installation of DC/OS. """ # If no args are passed to the class, then we're calling this # class from another library or code so we shouldn't execute # parser or anything else if args: options = self.parse_args(args) if len(options.hash_password) > 0: print_header("HASHING PASSWORD TO SHA512") backend.hash_password(options.hash_password) sys.exit(0) make_default_dir() if options.web: print_header("Starting DC/OS installer in web mode") async_server.start(options) if options.validate_config: print_header('VALIDATING CONFIGURATION') log_warn_only() validation_errors = backend.do_validate_gen_config() if validation_errors: print_validation_errors(validation_errors) sys.exit(1) sys.exit(0) if options.genconf: print_header("EXECUTING CONFIGURATION GENERATION") code = backend.do_configure() if code != 0: sys.exit(1) sys.exit(0) validate_ssh_config_or_exit() dispatch_action(options)
def action_action_name(request): """Return /action/<action_name> :param request: a web requeest object. :type request: request | None """ action_name = request.match_info['action_name'] # Update the global action json_state = read_json_state(action_name) app['current_action'] = action_name if request.method == 'GET': log.info('GET {}'.format(action_name)) if json_state: return web.json_response(json_state) return web.json_response({}) elif request.method == 'POST': log.info('POST {}'.format(action_name)) action = action_map.get(action_name) # If the action name is preflight, attempt to run configuration # generation. If genconf fails, present the UI with a usable error # for the end-user if action_name == 'preflight': try: print_header("GENERATING CONFIGURATION") backend.do_configure() except: genconf_failure = { "errors": "Configuration generation failed, please see command line for details" } return web.json_response(genconf_failure, status=400) params = yield from request.post() if json_state: if action_name == 'deploy' and 'retry' in params: if 'hosts' in json_state: failed_hosts = [] for deploy_host, deploy_params in json_state['hosts'].items(): if deploy_params['host_status'] != 'success': failed_hosts.append(Node(deploy_host, tags=deploy_params['tags'])) log.debug('failed hosts: {}'.format(failed_hosts)) if failed_hosts: yield from asyncio.async( action( backend.get_config(), state_json_dir=STATE_DIR, hosts=failed_hosts, try_remove_stale_dcos=True, **params)) return web.json_response({ 'status': 'retried', 'details': sorted(['{}:{}'.format(node.ip, node.port) for node in failed_hosts]) }) if action_name not in remove_on_done: return web.json_response({'status': '{} was already executed, skipping'.format(action_name)}) running = False for host, attributes in json_state['hosts'].items(): if attributes['host_status'].lower() == 'running': running = True log.debug('is action running: {}'.format(running)) if running: return web.json_response({'status': '{} is running, skipping'.format(action_name)}) else: unlink_state_file(action_name) yield from asyncio.async(action(backend.get_config(), state_json_dir=STATE_DIR, options=options, **params)) return web.json_response({'status': '{} started'.format(action_name)})
def test_do_configure(tmpdir, monkeypatch): monkeypatch.setenv('BOOTSTRAP_VARIANT', 'test_variant') create_config(simple_full_config, tmpdir) create_fake_build_artifacts(tmpdir) with tmpdir.as_cwd(): assert backend.do_configure(config_path='genconf/config.yaml') == 0
return 0 def do_uninstall(*args, **kwargs): tall_enough_to_ride() return action_lib.uninstall_dcos(*args, **kwargs) dispatch_dict_simple = { 'version': (do_version, None, 'Print the DC/OS version'), 'web': ( dcos_installer.async_server.start, 'Starting DC/OS installer in web mode', 'Run the web interface'), 'genconf': ( lambda args: backend.do_configure(), 'EXECUTING CONFIGURATION GENERATION', 'Execute the configuration generation (genconf).'), 'validate-config': ( do_validate_config, 'VALIDATING CONFIGURATION', 'Validate the configuration for executing --genconf and deploy arguments in config.yaml'), 'aws-cloudformation': ( lambda args: backend.do_aws_cf_configure(), 'EXECUTING AWS CLOUD FORMATION TEMPLATE GENERATION', 'Generate AWS Advanced AWS CloudFormation templates using the provided config') } dispatch_dict_aio = { 'preflight': ( action_lib.run_preflight,
def action_action_name(request): """Return /action/<action_name> :param request: a web requeest object. :type request: request | None """ global current_action action_name = request.match_info['action_name'] # Update the global action json_state = read_json_state(action_name) current_action = action_name if request.method == 'GET': log.info('GET {}'.format(action_name)) if json_state: return web.json_response(json_state) return web.json_response({}) elif request.method == 'POST': log.info('POST {}'.format(action_name)) action = action_map.get(action_name) # If the action name is preflight, attempt to run configuration # generation. If genconf fails, present the UI with a usable error # for the end-user if action_name == 'preflight': try: log.warning("GENERATING CONFIGURATION") backend.do_configure() except: genconf_failure = { "errors": "Configuration generation failed, please see command line for details" } return web.json_response(genconf_failure, status=400) params = yield from request.post() if json_state: if action_name == 'deploy' and 'retry' in params: if 'hosts' in json_state: failed_hosts = [] for deploy_host, deploy_params in json_state['hosts'].items(): if deploy_params['host_status'] != 'success': failed_hosts.append(Node(deploy_host, tags=deploy_params['tags'])) log.debug('failed hosts: {}'.format(failed_hosts)) if failed_hosts: yield from asyncio.async( action( backend.get_config(), state_json_dir=STATE_DIR, hosts=failed_hosts, try_remove_stale_dcos=True, **params)) return web.json_response({ 'status': 'retried', 'details': sorted(['{}:{}'.format(node.ip, node.port) for node in failed_hosts]) }) if action_name not in remove_on_done: return web.json_response({'status': '{} was already executed, skipping'.format(action_name)}) running = False for host, attributes in json_state['hosts'].items(): if attributes['host_status'].lower() == 'running': running = True log.debug('is action running: {}'.format(running)) if running: return web.json_response({'status': '{} is running, skipping'.format(action_name)}) else: unlink_state_file(action_name) yield from asyncio.async(action(backend.get_config(), state_json_dir=STATE_DIR, options=options, **params)) return web.json_response({'status': '{} started'.format(action_name)})
print_validation_errors(validation_errors) return 1 return 0 def do_uninstall(*args, **kwargs): tall_enough_to_ride() return action_lib.uninstall_dcos(*args, **kwargs) dispatch_dict_simple = { 'version': (do_version, None, 'Print the DC/OS version'), 'web': (dcos_installer.async_server.start, 'Starting DC/OS installer in web mode', 'Run the web interface'), 'genconf': (lambda args: backend.do_configure(), 'EXECUTING CONFIGURATION GENERATION' 'Execute the configuration generation (genconf).'), 'validate-config': (do_validate_config, 'VALIDATING CONFIGURATION', 'Validate the configuration for executing --genconf and deploy arguments in config.yaml' ), 'aws-cloudformation': (lambda args: backend.do_aws_cf_configure(), 'EXECUTING AWS CLOUD FORMATION TEMPLATE GENERATION', 'Generate AWS Advanced AWS CloudFormation templates using the provided config' ) } dispatch_dict_aio = { 'preflight': (action_lib.run_preflight, 'EXECUTING_PREFLIGHT', 'Execute the preflight checks on a series of nodes.'),
def genconf(args): print_header("EXECUTING CONFIGURATION GENERATION") code = backend.do_configure() if code != 0: return 1 return 0
def do_genconf(args): print_header("EXECUTING CONFIGURATION GENERATION") code = backend.do_configure() if code != 0: return 1 return 0
def __init__(self, args=None): """ The web based installer leverages Flask to present end-users of dcos_installer with a clean web interface to configure their site-based installation of DC/OS. """ # If no args are passed to the class, then we're calling this # class from another library or code so we shouldn't execute # parser or anything else make_default_dir() if args: options = self.parse_args(args) if len(options.hash_password) > 0: print_header("HASHING PASSWORD TO SHA512") backend.hash_password(options.hash_password) sys.exit(0) if options.web: print_header("Starting DC/OS installer in web mode") async_server.start(options) if options.genconf: print_header("EXECUTING CONFIGURATION GENERATION") code = backend.do_configure() if code != 0: sys.exit(1) sys.exit(0) if options.validate_config: print_header('VALIDATING CONFIGURATION') log_warn_only() validation_errors = backend.do_validate_gen_config() if validation_errors: print_validation_errors(validation_errors) sys.exit(1) sys.exit(0) validate_ssh_config_or_exit() action = None if options.preflight: print_header("EXECUTING PREFLIGHT") action = action_lib.run_preflight if options.deploy: print_header("EXECUTING DC/OS INSTALLATION") action = action_lib.install_dcos if options.postflight: print_header("EXECUTING POSTFLIGHT") action = action_lib.run_postflight if options.uninstall: print_header("EXECUTING UNINSTALL") tall_enough_to_ride() action = action_lib.uninstall_dcos if options.install_prereqs: print_header("EXECUTING INSTALL PREREQUISITES") action = action_lib.install_prereqs sys.exit(run_loop(action, options))