def should_register_cse(client, ext_install): """Decides if CSE installation should register CSE to vCD. Returns False if CSE is already registered, or if the user declines registration. :param pyvcloud.vcd.client.Client client: :param str ext_install: 'skip' skips registration, 'config' allows registration without prompting user, 'prompt' asks user before registration. :return: boolean that signals whether we should register CSE to vCD. :rtype: bool """ if ext_install == 'skip': return False ext = APIExtension(client) try: cse_info = ext.get_extension_info(CSE_NAME, namespace=CSE_NAMESPACE) msg = f"Found 'cse' extension on vCD, enabled={cse_info['enabled']}" click.secho(msg, fg='green') LOGGER.info(msg) return False except MissingRecordException: prompt_msg = "Register 'cse' as an API extension in vCD?" if ext_install == 'prompt' and not click.confirm(prompt_msg): msg = f"Skipping CSE registration." click.secho(msg, fg='yellow') LOGGER.warning(msg) return False return True
def install_cse(config_file_name, skip_template_creation, force_update, ssh_key, retain_temp_vapp, pks_config_file_name=None, skip_config_decryption=False, decryption_password=None, msg_update_callback=None): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, extension registration, catalog setup, and template creation. :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param str pks_config_file_name: pks config file name. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. :raises AmqpError: if AMQP exchange could not be created. """ configure_install_logger() config = get_validated_config( config_file_name, pks_config_file_name=pks_config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) client = None try: log_filename = None log_wire = str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right(client, right_name=CSE_NATIVE_DEPLOY_RIGHT_NAME, description=CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, category=CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) _register_right(client, right_name=CSE_PKS_DEPLOY_RIGHT_NAME, description=CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, category=CSE_PKS_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) # set up cse catalog org = get_org(client, org_name=config['broker']['org']) create_and_share_catalog(org, config['broker']['catalog'], catalog_desc='CSE templates', msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." if msg_update_callback: msg_update_callback.info(msg) LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_update, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config.get('pks_config')['nsxt_servers'] for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify'), logger_instance=LOGGER, log_requests=True, log_headers=True, log_body=True) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 except Exception: if msg_update_callback: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") LOGGER.error("CSE Installation Error", exc_info=True) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def install_cse(config_file_name, skip_template_creation, force_update, ssh_key, retain_temp_vapp, pks_config_file_name=None, skip_config_decryption=False, decryption_password=None, msg_update_callback=utils.NullPrinter()): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, defined entity schema registration for vCD api version >= 35, extension registration, catalog setup and template creation. Also records telemetry data on installation details. :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param str pks_config_file_name: pks config file name. :param bool skip_config_decryption: do not decrypt the config file. :param str decryption_password: password to decrypt the config file. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object. :raises cse_exception.AmqpError: if AMQP exchange could not be created. """ config = get_validated_config( config_file_name, pks_config_file_name=pks_config_file_name, skip_config_decryption=skip_config_decryption, decryption_password=decryption_password, log_wire_file=INSTALL_WIRELOG_FILEPATH, logger_debug=INSTALL_LOGGER, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" msg_update_callback.info(msg) INSTALL_LOGGER.info(msg) client = None try: # Telemetry - Construct telemetry data telemetry_data = { PayloadKey.WAS_DECRYPTION_SKIPPED: bool(skip_config_decryption), # noqa: E501 PayloadKey.WAS_PKS_CONFIG_FILE_PROVIDED: bool(pks_config_file_name), # noqa: E501 PayloadKey.WERE_TEMPLATES_SKIPPED: bool(skip_template_creation), # noqa: E501 PayloadKey.WERE_TEMPLATES_FORCE_UPDATED: bool(force_update), # noqa: E501 PayloadKey.WAS_TEMP_VAPP_RETAINED: bool(retain_temp_vapp), # noqa: E501 PayloadKey.WAS_SSH_KEY_SPECIFIED: bool(ssh_key) # noqa: E501 } # Telemetry - Record detailed telemetry data on install record_user_action_details( CseOperation.SERVICE_INSTALL, telemetry_data, telemetry_settings=config['service']['telemetry']) # noqa: E501 log_filename = None log_wire = utils.str_to_bool(config['service'].get('log_wire')) if log_wire: log_filename = INSTALL_WIRELOG_FILEPATH client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=log_filename, log_requests=log_wire, log_headers=log_wire, log_bodies=log_wire) credentials = BasicLoginCredentials(config['vcd']['username'], server_constants.SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register cse def schema on VCD # schema should be located at # ~/.cse-schema/api-v<API VERSION>/schema.json _register_def_schema(client, msg_update_callback=msg_update_callback, log_wire=log_wire) # Since we use CSE extension id as our telemetry instance_id, the # validated config won't have the instance_id yet. Now that CSE has # been registered as an extension, we should update the telemetry # config with the correct instance_id if config['service']['telemetry']['enable']: store_telemetry_settings(config) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right( client, right_name=server_constants. CSE_NATIVE_DEPLOY_RIGHT_NAME, # noqa: E501 description=server_constants. CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, # noqa: E501 category=server_constants. CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, # noqa: E501 bundle_key=server_constants. CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, # noqa: E501 msg_update_callback=msg_update_callback) _register_right( client, right_name=server_constants. CSE_PKS_DEPLOY_RIGHT_NAME, # noqa: E501 description=server_constants. CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, # noqa: E501 category=server_constants. CSE_PKS_DEPLOY_RIGHT_CATEGORY, # noqa: E501 bundle_key=server_constants. CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, # noqa: E501 msg_update_callback=msg_update_callback) # set up placement policies for all types of clusters _setup_placement_policies( client, policy_list=server_constants. CLUSTER_PLACEMENT_POLICIES, # noqa: E501 msg_update_callback=msg_update_callback, log_wire=log_wire) # set up cse catalog org = vcd_utils.get_org(client, org_name=config['broker']['org']) vcd_utils.create_and_share_catalog( org, config['broker']['catalog'], catalog_desc='CSE templates', logger=INSTALL_LOGGER, msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." msg_update_callback.info(msg) INSTALL_LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=INSTALL_LOGGER, msg_update_callback=msg_update_callback) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: # TODO tag created templates with placement policies _install_template( client=client, remote_template_manager=rtm, template=template, org_name=config['broker']['org'], vdc_name=config['broker']['vdc'], catalog_name=config['broker']['catalog'], network_name=config['broker']['network'], ip_allocation_mode=config['broker']['ip_allocation_mode'], storage_profile=config['broker']['storage_profile'], force_update=force_update, retain_temp_vapp=retain_temp_vapp, ssh_key=ssh_key, msg_update_callback=msg_update_callback) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config['pks_config']['nsxt_servers'] wire_logger = NULL_LOGGER if log_wire: wire_logger = SERVER_NSXT_WIRE_LOGGER for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." msg_update_callback.general(msg) INSTALL_LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), logger_debug=INSTALL_LOGGER, logger_wire=wire_logger, http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify')) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 # Telemetry - Record successful install action record_user_action(CseOperation.SERVICE_INSTALL, telemetry_settings=config['service']['telemetry']) except Exception: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") INSTALL_LOGGER.error("CSE Installation Error", exc_info=True) # Telemetry - Record failed install action record_user_action(CseOperation.SERVICE_INSTALL, status=OperationStatus.FAILED, telemetry_settings=config['service']['telemetry']) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def install_cse(ctx, config_file_name='config.yaml', skip_template_creation=True, force_update=False, ssh_key=None, retain_temp_vapp=False, msg_update_callback=None): """Handle logistics for CSE installation. Handles decision making for configuring AMQP exchange/settings, extension registration, catalog setup, and template creation. :param click.core.Context ctx: :param str config_file_name: config file name. :param bool skip_template_creation: If True, skip creating the templates. :param bool force_update: if True and templates already exist in vCD, overwrites existing templates. :param str ssh_key: public ssh key to place into template vApp(s). :param bool retain_temp_vapp: if True, temporary vApp will not destroyed, so the user can ssh into and debug the vm. :param utils.ConsoleMessagePrinter msg_update_callback: Callback object that writes messages onto console. :raises AmqpError: if AMQP exchange could not be created. """ configure_install_logger() config = get_validated_config(config_file_name, msg_update_callback=msg_update_callback) populate_vsphere_list(config['vcs']) msg = f"Installing CSE on vCloud Director using config file " \ f"'{config_file_name}'" if msg_update_callback: msg_update_callback.info(msg) LOGGER.info(msg) client = None try: client = Client(config['vcd']['host'], api_version=config['vcd']['api_version'], verify_ssl_certs=config['vcd']['verify'], log_file=INSTALL_WIRELOG_FILEPATH, log_requests=True, log_headers=True, log_bodies=True) credentials = BasicLoginCredentials(config['vcd']['username'], SYSTEM_ORG_NAME, config['vcd']['password']) client.set_credentials(credentials) msg = f"Connected to vCD as system administrator: " \ f"{config['vcd']['host']}:{config['vcd']['port']}" if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) # create amqp exchange if it doesn't exist amqp = config['amqp'] _create_amqp_exchange(amqp['exchange'], amqp['host'], amqp['port'], amqp['vhost'], amqp['ssl'], amqp['username'], amqp['password'], msg_update_callback=msg_update_callback) # register or update cse on vCD _register_cse(client, amqp['routing_key'], amqp['exchange'], msg_update_callback=msg_update_callback) # register rights to vCD # TODO() should also remove rights when unregistering CSE _register_right(client, right_name=CSE_NATIVE_DEPLOY_RIGHT_NAME, description=CSE_NATIVE_DEPLOY_RIGHT_DESCRIPTION, category=CSE_NATIVE_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_NATIVE_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) _register_right(client, right_name=CSE_PKS_DEPLOY_RIGHT_NAME, description=CSE_PKS_DEPLOY_RIGHT_DESCRIPTION, category=CSE_PKS_DEPLOY_RIGHT_CATEGORY, bundle_key=CSE_PKS_DEPLOY_RIGHT_BUNDLE_KEY, msg_update_callback=msg_update_callback) org_name = config['broker']['org'] catalog_name = config['broker']['catalog'] # set up cse catalog org = get_org(client, org_name=org_name) create_and_share_catalog(org, catalog_name, catalog_desc='CSE templates', msg_update_callback=msg_update_callback) if skip_template_creation: msg = "Skipping creation of templates." if msg_update_callback: msg_update_callback.info(msg) LOGGER.warning(msg) else: # read remote template cookbook, download all scripts rtm = RemoteTemplateManager( remote_template_cookbook_url=config['broker'] ['remote_template_cookbook_url'], # noqa: E501 logger=LOGGER, msg_update_callback=ConsoleMessagePrinter()) remote_template_cookbook = rtm.get_remote_template_cookbook() # create all templates defined in cookbook for template in remote_template_cookbook['templates']: rtm.download_template_scripts( template_name=template[RemoteTemplateKey.NAME], revision=template[RemoteTemplateKey.REVISION], force_overwrite=force_update) catalog_item_name = get_revisioned_template_name( template[RemoteTemplateKey.NAME], template[RemoteTemplateKey.REVISION]) build_params = { 'template_name': template[RemoteTemplateKey.NAME], 'template_revision': template[RemoteTemplateKey.REVISION], 'source_ova_name': template[RemoteTemplateKey.SOURCE_OVA_NAME], # noqa: E501 'source_ova_href': template[RemoteTemplateKey.SOURCE_OVA_HREF], # noqa: E501 'source_ova_sha256': template[ RemoteTemplateKey.SOURCE_OVA_SHA256], # noqa: E501 'org_name': org_name, 'vdc_name': config['broker']['vdc'], 'catalog_name': catalog_name, 'catalog_item_name': catalog_item_name, 'catalog_item_description': template[RemoteTemplateKey.DESCRIPTION], # noqa: E501 'temp_vapp_name': template[RemoteTemplateKey.NAME] + '_temp', # noqa: E501 'cpu': template[RemoteTemplateKey.CPU], 'memory': template[RemoteTemplateKey.MEMORY], 'network_name': config['broker']['network'], 'ip_allocation_mode': config['broker']['ip_allocation_mode'], # noqa: E501 'storage_profile': config['broker']['storage_profile'] } builder = TemplateBuilder( client, client, build_params, ssh_key=ssh_key, logger=LOGGER, msg_update_callback=ConsoleMessagePrinter()) builder.build(force_recreate=force_update, retain_temp_vapp=retain_temp_vapp) # remote definition is a super set of local definition, barring # the key 'catalog_item_name' template_definition = dict(template) template_definition['catalog_item_name'] = catalog_item_name save_k8s_local_template_definition_as_metadata( client=client, catalog_name=catalog_name, catalog_item_name=catalog_item_name, template_definition=template_definition, org_name=org_name) # if it's a PKS setup, setup NSX-T constructs if config.get('pks_config'): nsxt_servers = config.get('pks_config')['nsxt_servers'] for nsxt_server in nsxt_servers: msg = f"Configuring NSX-T server ({nsxt_server.get('name')})" \ " for CSE. Please check install logs for details." if msg_update_callback: msg_update_callback.general(msg) LOGGER.info(msg) nsxt_client = NSXTClient(host=nsxt_server.get('host'), username=nsxt_server.get('username'), password=nsxt_server.get('password'), http_proxy=nsxt_server.get('proxy'), https_proxy=nsxt_server.get('proxy'), verify_ssl=nsxt_server.get('verify'), logger_instance=LOGGER, log_requests=True, log_headers=True, log_body=True) setup_nsxt_constructs( nsxt_client=nsxt_client, nodes_ip_block_id=nsxt_server.get('nodes_ip_block_ids'), pods_ip_block_id=nsxt_server.get('pods_ip_block_ids'), ncp_boundary_firewall_section_anchor_id=nsxt_server.get( 'distributed_firewall_section_anchor_id') ) # noqa: E501 except Exception: if msg_update_callback: msg_update_callback.error( "CSE Installation Error. Check CSE install logs") LOGGER.error("CSE Installation Error", exc_info=True) raise # TODO() need installation relevant exceptions for rollback finally: if client is not None: client.logout()
def should_configure_amqp(client, amqp_config, amqp_install): """Decides if CSE installation should configure vCD AMQP settings. Returns False if config file AMQP settings are the same as vCD AMQP settings, or if the user declines configuration. :param pyvcloud.vcd.client.Client client: :param dict amqp_config: 'amqp' section of the config file :param str amqp_install: 'skip' skips vCD AMQP configuration, 'config' configures vCD AMQP settings without prompting user, 'prompt' asks user before configuring vCD AMQP settings. :return: boolean that signals whether we should configure AMQP settings. :rtype: bool """ if amqp_install == 'skip': msg = f"Skipping AMQP configuration. vCD and config file may have " \ f"different AMQP settings" click.secho(msg, fg='yellow') LOGGER.warning(msg) return False current_settings = to_dict(AmqpService(client).get_settings()) amqp = { 'AmqpExchange': amqp_config['exchange'], 'AmqpHost': amqp_config['host'], 'AmqpPort': str(amqp_config['port']), 'AmqpPrefix': amqp_config['prefix'], 'AmqpSslAcceptAll': str(amqp_config['ssl_accept_all']).lower(), 'AmqpUseSSL': str(amqp_config['ssl']).lower(), 'AmqpUsername': amqp_config['username'], 'AmqpVHost': amqp_config['vhost'] } diff_settings = [k for k, v in current_settings.items() if amqp[k] != v] if diff_settings: msg = 'current vCD AMQP setting(s):' click.secho(msg, fg='blue') LOGGER.info(msg) for setting in diff_settings: msg = f"{setting}: {current_settings[setting]}" click.echo(msg) LOGGER.info(msg) msg = '\nconfig file AMQP setting(s):' click.secho(msg, fg='blue') LOGGER.info(msg) for setting in diff_settings: msg = f"{setting}: {amqp[setting]}" click.echo(msg) LOGGER.info(msg) msg = '\nConfigure AMQP with the config file settings?' if amqp_install == 'prompt' and not click.confirm(msg): msg = f"Skipping AMQP configuration. vCD and config file may " \ f"have different AMQP settings" click.secho(msg, fg='yellow') LOGGER.warning(msg) return False return True msg = "vCD and config file AMQP settings are the same, " \ "skipping AMQP configuration" click.secho(msg, fg='green') LOGGER.info(msg) return False