# # Copyright (c) 2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import time from utils import table_parser, cli, exceptions from utils.tis_log import LOG from utils.clients.ssh import get_cli_client from consts.stx import GuestImages, HeatStackStatus, HEAT_CUSTOM_TEMPLATES from consts.filepaths import TestServerPath from keywords import network_helper, common from testfixtures.fixture_resources import ResourceCleanup def _wait_for_heat_stack_deleted(stack_name=None, timeout=120, check_interval=3, con_ssh=None, auth_info=None): """ This will wait for the heat stack to be deleted Args: stack_name(str): Heat stack name to check for state con_ssh (SSHClient): If None, active controller ssh will be used. auth_info (dict): Tenant dict. If None, primary tenant will be used. Returns: """ LOG.info("Waiting for {} to be deleted...".format(stack_name)) end_time = time.time() + timeout while time.time() < end_time: stack_status = get_stack_status(stack=stack_name, auth_info=auth_info, con_ssh=con_ssh, fail_ok=True) if not stack_status: return True elif stack_status[0] == HeatStackStatus.DELETE_FAILED: LOG.warning('Heat stack in DELETE_FAILED state') return False time.sleep(check_interval) msg = "Heat stack {} did not get deleted within timeout".format(stack_name) LOG.warning(msg) return False def wait_for_heat_status(stack_name=None, status=HeatStackStatus.CREATE_COMPLETE, timeout=300, check_interval=5, fail_ok=False, con_ssh=None, auth_info=None): """ This will wait for the desired state of the heat stack or timeout Args: stack_name(str): Heat stack name to check for state status(str): Status to check for timeout (int) check_interval (int) fail_ok (bool con_ssh (SSHClient): If None, active controller ssh will be used. auth_info (dict): Tenant dict. If None, primary tenant will be used. Returns (tuple): , """ LOG.info("Waiting for {} to be shown in {} ...".format(stack_name, status)) end_time = time.time() + timeout fail_status = current_status = None if status == HeatStackStatus.CREATE_COMPLETE: fail_status = HeatStackStatus.CREATE_FAILED elif status == HeatStackStatus.UPDATE_COMPLETE: fail_status = HeatStackStatus.UPDATE_FAILED while time.time() < end_time: current_status = get_stack_status(stack=stack_name, auth_info=auth_info, con_ssh=con_ssh) if status == current_status: return True, 'Heat stack {} has reached {} status'.format( stack_name, status) elif fail_status == current_status: stack_id = get_stack_values(stack=stack_name, fields='id', auth_info=auth_info, con_ssh=con_ssh)[0] get_stack_resources(stack=stack_id, auth_info=auth_info, con_ssh=con_ssh) err = "Heat stack {} failed to reach {}, actual status: {}".format( stack_name, status, fail_status) if fail_ok: LOG.warning(err) return False, err raise exceptions.HeatError(err) time.sleep(check_interval) stack_id = get_stack_values(stack=stack_name, fields='id', auth_info=auth_info, con_ssh=con_ssh)[0] get_stack_resources(stack=stack_id, auth_info=auth_info, con_ssh=con_ssh) err_msg = "Heat stack {} did not reach {} within {}s. Actual " \ "status: {}".format(stack_name, status, timeout, current_status) if fail_ok: LOG.warning(err_msg) return False, err_msg raise exceptions.HeatError(err_msg) def get_stack_values(stack, fields='stack_status_reason', con_ssh=None, auth_info=None, fail_ok=False): code, out = cli.openstack('stack show', stack, ssh_client=con_ssh, auth_info=auth_info, fail_ok=fail_ok) if code > 0: return None table_ = table_parser.table(out) return table_parser.get_multi_values_two_col_table(table_=table_, fields=fields) def get_stacks(name=None, field='id', con_ssh=None, auth_info=None, all_=True): """ Get the stacks list based on name if given for a given tenant. Args: con_ssh (SSHClient): If None, active controller ssh will be used. auth_info (dict): Tenant dict. If None, primary tenant will be used. all_ (bool): whether to display all stacks for admin user name (str): Given name for the heat stack field (str|list|tuple) Returns (list): list of heat stacks. """ args = '' if auth_info is not None: if auth_info['user'] == 'admin' and all_: args = '--a' table_ = table_parser.table( cli.openstack('stack list', positional_args=args, ssh_client=con_ssh, auth_info=auth_info)[1]) kwargs = {'Stack Name': name} if name else {} return table_parser.get_multi_values(table_, field, **kwargs) def get_stack_status(stack, con_ssh=None, auth_info=None, fail_ok=False): """ Get the stacks status based on name if given for a given tenant. Args: con_ssh (SSHClient): If None, active controller ssh will be used. auth_info (dict): Tenant dict. If None, primary tenant will be used. stack (str): Given name for the heat stack fail_ok (bool): Returns (str): Heat stack status of a specific tenant. """ status = get_stack_values(stack, fields='stack_status', con_ssh=con_ssh, auth_info=auth_info, fail_ok=fail_ok) status = status[0] if status else None return status def get_stack_resources(stack, field='resource_name', auth_info=None, con_ssh=None, **kwargs): """ Args: stack (str): id (or name) for the heat stack. ID is required if admin user is used to display tenant resource. field: values to return auth_info: con_ssh: kwargs: key/value pair to filer out the values to return Returns (list): """ table_ = table_parser.table( cli.openstack('stack resource list --long', stack, ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_values(table_, target_header=field, **kwargs) def delete_stack(stack, fail_ok=False, check_first=False, con_ssh=None, auth_info=None): """ Delete the given heat stack for a given tenant. Args: con_ssh (SSHClient): If None, active controller ssh will be used. fail_ok (bool): check_first (bool): whether or not to check the stack existence before attempt to delete auth_info (dict): Tenant dict. If None, primary tenant will be used. stack (str): Given name for the heat stack Returns (tuple): Status and msg of the heat deletion. """ if not stack: raise ValueError("stack_name is not provided.") if check_first: if not get_stack_status(stack, con_ssh=con_ssh, auth_info=auth_info, fail_ok=True): msg = "Heat stack {} doesn't exist on the system. Do " \ "nothing.".format(stack) LOG.info(msg) return -1, msg LOG.info("Deleting Heat Stack %s", stack) exitcode, output = cli.openstack('stack delete -y', stack, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exitcode > 1: LOG.warning("Delete heat stack request rejected.") return 1, output if not _wait_for_heat_stack_deleted(stack_name=stack, auth_info=auth_info): stack_id = get_stack_values(stack=stack, fields='id', auth_info=auth_info, con_ssh=con_ssh)[0] get_stack_resources(stack=stack_id, auth_info=auth_info, con_ssh=con_ssh) msg = "heat stack {} is not removed after stack-delete.".format(stack) if fail_ok: LOG.warning(msg) return 2, msg raise exceptions.HeatError(msg) succ_msg = "Heat stack {} is successfully deleted.".format(stack) LOG.info(succ_msg) return 0, succ_msg def get_heat_params(param_name=None): """ Generate parameters for heat based on keywords Args: param_name (str): template to be used to create heat stack. Returns (str): return None if failure or the val for the given param """ if param_name == 'NETWORK': net_id = network_helper.get_mgmt_net_id() return network_helper.get_net_name_from_id(net_id=net_id) elif param_name == 'FLAVOR': return 'small_ded' elif param_name == 'IMAGE': return GuestImages.DEFAULT['guest'] else: return None def create_stack(stack_name, template, pre_creates=None, environments=None, stack_timeout=None, parameters=None, param_files=None, enable_rollback=None, dry_run=None, wait=None, tags=None, fail_ok=False, con_ssh=None, auth_info=None, cleanup='function', timeout=300): """ Create the given heat stack for a given tenant. Args: stack_name (str): Given name for the heat stack template (str): path of heat template pre_creates (str|list|None) environments (str|list|None) stack_timeout (int|str|None): stack creating timeout in minutes parameters (str|dict|None) param_files (str|dict|None) enable_rollback (bool|None) dry_run (bool|None) wait (bool|None) tags (str|list|None) auth_info (dict): Tenant dict. If None, primary tenant will be used. con_ssh (SSHClient): If None, active controller ssh will be used. timeout (int): automation timeout in seconds fail_ok (bool): cleanup (str|None) Returns (tuple): Status and msg of the heat deletion. """ args_dict = { '--template': template, '--environment': environments, '--timeout': stack_timeout, '--pre-create': pre_creates, '--enable-rollback': enable_rollback, '--parameter': parameters, '--parameter-file': param_files, '--wait': wait, '--tags': ','.join(tags) if isinstance(tags, (list, tuple)) else tags, '--dry-run': dry_run, } args = common.parse_args(args_dict, repeat_arg=True) LOG.info("Create Heat Stack {} with args: {}".format(stack_name, args)) exitcode, output = cli.openstack('stack create', '{} {}'. format(args, stack_name), ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info, timeout=timeout) if exitcode > 0: return 1, output if cleanup: ResourceCleanup.add('heat_stack', resource_id=stack_name, scope=cleanup) LOG.info("Wait for Heat Stack Status to reach CREATE_COMPLETE for " "stack %s", stack_name) res, msg = wait_for_heat_status(stack_name=stack_name, status=HeatStackStatus.CREATE_COMPLETE, auth_info=auth_info, fail_ok=fail_ok) if not res: return 2, msg LOG.info("Stack {} created successfully".format(stack_name)) return 0, stack_name def update_stack(stack_name, params_string, fail_ok=False, con_ssh=None, auth_info=None, timeout=300): """ Update the given heat stack for a given tenant. Args: con_ssh (SSHClient): If None, active controller ssh will be used. fail_ok (bool): params_string: Parameters to pass to the heat create cmd. ex: -f -P IMAGE=tis auth_info (dict): Tenant dict. If None, primary tenant will be used. stack_name (str): Given name for the heat stack timeout (int) Returns (tuple): Status and msg of the heat deletion. """ if not params_string: raise ValueError("Parameters not provided.") LOG.info("Create Heat Stack %s", params_string) exitcode, output = cli.heat('stack-update', params_string, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exitcode == 1: LOG.warning("Create heat stack request rejected.") return 1, output LOG.info("Wait for Heat Stack Status to reach UPDATE_COMPLETE for stack %s", stack_name) res, msg = wait_for_heat_status(stack_name=stack_name, status=HeatStackStatus.UPDATE_COMPLETE, auth_info=auth_info, fail_ok=fail_ok, timeout=timeout) if not res: return 2, msg LOG.info("Stack {} updated successfully".format(stack_name)) return 0, stack_name def get_custom_heat_files(file_name, file_dir=HEAT_CUSTOM_TEMPLATES, cli_client=None): """ Args: file_name: file_dir: cli_client: Returns: """ file_path = '{}/{}'.format(file_dir, file_name) if cli_client is None: cli_client = get_cli_client() if not cli_client.file_exists(file_path=file_path): LOG.debug('Create userdata directory if not already exists') cmd = 'mkdir -p {}'.format(file_dir) cli_client.exec_cmd(cmd, fail_ok=False) source_file = TestServerPath.CUSTOM_HEAT_TEMPLATES + file_name dest_path = common.scp_from_test_server_to_user_file_dir( source_path=source_file, dest_dir=file_dir, dest_name=file_name, timeout=300, con_ssh=cli_client) if dest_path is None: raise exceptions.CommonError( "Heat template file {} does not exist after download".format( file_path)) return file_path