# # Copyright (c) 2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import re from consts.auth import Tenant, HostLinuxUser from consts.proj_vars import ProjVar from utils import cli, exceptions, table_parser from utils.clients.ssh import ControllerClient from utils.tis_log import LOG from keywords import common def get_roles(field='ID', con_ssh=None, auth_info=Tenant.get('admin'), **kwargs): table_ = table_parser.table(cli.openstack('role list', ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_multi_values(table_, field, **kwargs) def get_users(field='ID', con_ssh=None, auth_info=Tenant.get('admin'), **kwargs): """ Return a list of user id(s) with given user name. Args: field (str|list|tuple): con_ssh (SSHClient): auth_info Returns (list): list of user id(s) """ table_ = table_parser.table(cli.openstack('user list', ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_multi_values(table_, field, **kwargs) def add_or_remove_role(add_=True, role='admin', project=None, user=None, domain=None, group=None, group_domain=None, project_domain=None, user_domain=None, inherited=None, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Add or remove given role for specified user and tenant. e.g., add admin role to tenant2 user on tenant2 project Args: add_(bool): whether to add or remove role (str): an existing role from openstack role list project (str): tenant name. When unset, the primary tenant name will be used user (str): an existing user that belongs to given tenant domain (str): Include (name or ID) group (str): Include (name or ID) group_domain (str): Domain the group belongs to (name or ID). This can be used in case collisions between group names exist. project_domain (str): Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. user_domain (str): Domain the user belongs to (name or ID). This can be used in case collisions between user names exist. inherited (bool): Specifies if the role grant is inheritable to the sub projects check_first (bool): whether to check if role already exists for given user and tenant fail_ok (bool): whether to throw exception on failure con_ssh (SSHClient): active controller ssh session auth_info (dict): auth info to use to executing the add role cli Returns (tuple): """ tenant_dict = {} if project is None: if auth_info and auth_info.get('platform'): project = auth_info['tenant'] else: tenant_dict = Tenant.get_primary() project = tenant_dict['tenant'] if user is None: user = tenant_dict.get('user', project) if check_first: existing_roles = get_role_assignments(role=role, project=project, user=user, user_domain=user_domain, group=group, group_domain=group_domain, domain=domain, project_domain=project_domain, inherited=inherited, effective_only=False, con_ssh=con_ssh, auth_info=auth_info) if existing_roles: if add_: msg = "Role already exists with given criteria: {}".format( existing_roles) LOG.info(msg) return -1, msg else: if not add_: msg = "Role with given criteria does not exist. Do nothing." LOG.info(msg) return -1, msg msg_str = 'Add' if add_ else 'Remov' LOG.info( "{}ing {} role to {} user under {} project".format(msg_str, role, user, project)) sub_cmd = "--user {} --project {}".format(user, project) if inherited is True: sub_cmd += ' --inherited' optional_args = { 'domain': domain, 'group': group, 'group-domain': group_domain, 'project-domain': project_domain, 'user-domain': user_domain, } for key, val in optional_args.items(): if val is not None: sub_cmd += ' --{} {}'.format(key, val) sub_cmd += ' {}'.format(role) cmd = 'role add' if add_ else 'role remove' res, out = cli.openstack(cmd, sub_cmd, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if res == 1: return 1, out LOG.info("{} cli accepted. Check role is {}ed " "successfully".format(cmd, msg_str)) post_roles = get_role_assignments(role=role, project=project, user=user, user_domain=user_domain, group=group, group_domain=group_domain, domain=domain, project_domain=project_domain, inherited=inherited, effective_only=True, con_ssh=con_ssh, auth_info=auth_info) err_msg = '' if add_ and not post_roles: err_msg = "No role is added with given criteria" elif post_roles and not add_: err_msg = "Role is not removed" if err_msg: if fail_ok: LOG.warning(err_msg) return 2, err_msg else: raise exceptions.KeystoneError(err_msg) succ_msg = "Role is successfully {}ed".format(msg_str) LOG.info(succ_msg) return 0, succ_msg def get_role_assignments(field='Role', names=True, role=None, user=None, project=None, user_domain=None, group=None, group_domain=None, domain=None, project_domain=None, inherited=None, effective_only=None, con_ssh=None, auth_info=Tenant.get('admin')): """ Get values from 'openstack role assignment list' table Args: field (str|list|tuple): role assignment table header to determine which values to return names (bool): whether to display role assignment with name (default is ID) role (str): an existing role from openstack role list project (str): tenant name. When unset, the primary tenant name will be used user (str): an existing user that belongs to given tenant domain (str): Include (name or ID) group (str): Include (name or ID) group_domain (str): Domain the group belongs to (name or ID). This can be used in case collisions between group names exist. project_domain (str): Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. user_domain (str): Domain the user belongs to (name or ID). This can be used in case collisions between user names exist. inherited (bool): Specifies if the role grant is inheritable to the sub projects effective_only (bool): Whether to show effective roles only con_ssh (SSHClient): active controller ssh session auth_info (dict): auth info to use to executing the add role cli Returns (list): list of values """ optional_args = { 'role': role, 'user': user, 'project': project, 'domain': domain, 'group': group, 'group-domain': group_domain, 'project-domain': project_domain, 'user-domain': user_domain, 'names': True if names else None, 'effective': True if effective_only else None, 'inherited': True if inherited else None } args = common.parse_args(optional_args) role_assignment_tab = table_parser.table( cli.openstack('role assignment list', args, ssh_client=con_ssh, auth_info=auth_info)[1]) if not role_assignment_tab['headers']: LOG.info("No role assignment is found with criteria: {}".format(args)) return [] return table_parser.get_multi_values(role_assignment_tab, field) def set_current_user_password(original_password, new_password, fail_ok=False, auth_info=None, con_ssh=None): """ Set password for current user Args: original_password: new_password: fail_ok: auth_info: con_ssh: Returns (tuple): """ args = "--password '{}' --original-password '{}'".format(new_password, original_password) code, output = cli.openstack('user password set', args, ssh_client=con_ssh, auth_info=auth_info, fail_ok=fail_ok) if code > 0: return 1, output if not auth_info: auth_info = Tenant.get_primary() user = auth_info['user'] tenant_dictname = user if auth_info.get('platform'): tenant_dictname += '_platform' Tenant.update(tenant_dictname, password=new_password) if user == 'admin': from consts.proj_vars import ProjVar if ProjVar.get_var('REGION') != 'RegionOne': LOG.info( "Run openstack_update_admin_password on secondary region " "after admin password change") if not con_ssh: con_ssh = ControllerClient.get_active_controller() with con_ssh.login_as_root(timeout=30) as con_ssh: con_ssh.exec_cmd( "echo 'y' | openstack_update_admin_password '{}'".format(new_password)) msg = 'User {} password successfully updated from {} to {}'.format(user, original_password, new_password) LOG.info(msg) return 0, output def set_user(user, name=None, project=None, password=None, project_doamin=None, email=None, description=None, enable=None, fail_ok=False, auth_info=Tenant.get('admin'), con_ssh=None): LOG.info("Updating {}...".format(user)) arg = '' optional_args = { 'name': name, 'project': project, 'password': password, 'project-domain': project_doamin, 'email': email, 'description': description, } for key, val in optional_args.items(): if val is not None: arg += "--{} '{}' ".format(key, val) if enable is not None: arg += '--{} '.format('enable' if enable else 'disable') if not arg.strip(): raise ValueError( "Please specify the param(s) and value(s) to change to") arg += user code, output = cli.openstack('user set', arg, ssh_client=con_ssh, timeout=120, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output if name or project or password: tenant_dictname = user if auth_info and auth_info.get('platform'): tenant_dictname += '_platform' Tenant.update(tenant_dictname, username=name, password=password, tenant=project) if password and user == 'admin': from consts.proj_vars import ProjVar if ProjVar.get_var('REGION') != 'RegionOne': LOG.info( "Run openstack_update_admin_password on secondary region " "after admin password change") if not con_ssh: con_ssh = ControllerClient.get_active_controller() with con_ssh.login_as_root(timeout=30) as con_ssh: con_ssh.exec_cmd( "echo 'y' | openstack_update_admin_password '{}'".format( password)) msg = 'User {} updated successfully'.format(user) LOG.info(msg) return 0, output def get_endpoints(field='ID', endpoint_id=None, service_name=None, service_type=None, enabled=None, interface="admin", region=None, url=None, strict=False, auth_info=Tenant.get('admin'), con_ssh=None, cli_filter=True): """ Get a list of endpoints with given arguments Args: field (str|list|tuple): valid header of openstack endpoints list table. 'ID' endpoint_id (str): id of the endpoint service_name (str): Service name of endpoint like novaav3, neutron, keystone. vim, heat, swift, etc service_type(str): Service type enabled (str): True/False interface (str): Interface of endpoints. valid entries: admin, internal, public region (str): RegionOne or RegionTwo url (str): url of endpoint strict(bool): auth_info (dict): con_ssh (SSHClient): cli_filter (bool): whether to filter out using cli. e.g., openstack endpoint list --service xxx Returns (list): """ pre_args_str = '' if cli_filter: pre_args_dict = { '--service': service_name, '--interface': interface, '--region': region, } pre_args = [] for key, val in pre_args_dict.items(): if val: pre_args.append('{}={}'.format(key, val)) pre_args_str = ' '.join(pre_args) output = cli.openstack('endpoint list', positional_args=pre_args_str, ssh_client=con_ssh, auth_info=auth_info)[1] if not output.strip(): LOG.warning("No endpoints returned with param: {}".format(pre_args_str)) return [] table_ = table_parser.table(output) kwargs = { 'ID': endpoint_id, 'Service Name': service_name, 'Service Type': service_type, 'Enabled': enabled, 'Interface': interface, 'URL': url, 'Region': region, } kwargs = {k: v for k, v in kwargs.items() if v} return table_parser.get_multi_values(table_, field, strict=strict, regex=True, merge_lines=True, **kwargs) def get_endpoints_values(endpoint_id, fields, con_ssh=None, auth_info=Tenant.get('admin')): """ Gets the endpoint target field value for given endpoint Id Args: endpoint_id: the endpoint id to get the value of fields: the target field name to retrieve value of con_ssh: auth_info Returns (list): list of endpoint values """ table_ = table_parser.table( cli.openstack('endpoint show', endpoint_id, ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_multi_values_two_col_table(table_, fields) def is_https_enabled(con_ssh=None, source_openrc=True, auth_info=Tenant.get('admin_platform')): if not con_ssh: con_name = auth_info.get('region') if ( auth_info and ProjVar.get_var('IS_DC')) else None con_ssh = ControllerClient.get_active_controller(name=con_name) table_ = table_parser.table( cli.openstack('endpoint list', ssh_client=con_ssh, auth_info=auth_info, source_openrc=source_openrc)[1]) con_ssh.exec_cmd('unset OS_REGION_NAME') # Workaround filters = {'Service Name': 'keystone', 'Service Type': 'identity', 'Interface': 'public'} keystone_pub = table_parser.get_values(table_=table_, target_header='URL', **filters)[0] return 'https' in keystone_pub def delete_users(user, fail_ok=False, auth_info=Tenant.get('admin'), con_ssh=None): """ Delete the given openstack user Args: user: user name to delete fail_ok: if the deletion expected to fail auth_info con_ssh Returns: tuple, (code, msg) """ LOG.info('Deleting {} keystone user: {}'.format('platform' if auth_info and auth_info.get( 'platform') else 'containerized', user)) return cli.openstack('user delete', user, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) def get_projects(field='ID', auth_info=Tenant.get('admin'), con_ssh=None, strict=False, **filters): """ Get list of Project names or IDs Args: field (str|list|tuple): auth_info: con_ssh: strict (bool): used for filters filters Returns (list): """ table_ = table_parser.table( cli.openstack('project list', ssh_client=con_ssh, auth_info=auth_info)[ 1]) return table_parser.get_multi_values(table_, field, strict=strict, **filters) def create_project(name=None, field='ID', domain=None, parent=None, description=None, enable=None, con_ssh=None, rtn_exist=None, fail_ok=False, auth_info=Tenant.get('admin'), **properties): """ Create a openstack project Args: name (str|None): field (str): ID or Name. Whether to return project id or name if created successfully domain (str|None): parent (str|None): description (str|None): enable (bool|None): con_ssh: rtn_exist fail_ok: auth_info: **properties: Returns (tuple): (0, ) (1, ) """ if not name: existing_names = get_projects(field='Name', auth_info=Tenant.get('admin'), con_ssh=con_ssh) max_count = 0 end_str = '' for name in existing_names: match = re.match(r'tenant(\d+)(.*)', name) if match: count, end_str = match.groups() max_count = max(int(count), max_count) name = 'tenant{}{}'.format(max_count + 1, end_str) LOG.info("Create/Show openstack project {}".format(name)) arg_dict = { 'domain': domain, 'parent': parent, 'description': description, 'enable': True if enable is True else None, 'disable': True if enable is False else None, 'or-show': rtn_exist, 'property': properties, } arg_str = common.parse_args(args_dict=arg_dict, repeat_arg=True) arg_str += ' {}'.format(name) code, output = cli.openstack('project create', arg_str, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output project_ = table_parser.get_value_two_col_table(table_parser.table(output), field=field) LOG.info("Project {} successfully created/showed.".format(project_)) return 0, project_ def create_user(name=None, field='name', domain=None, project=None, project_domain=None, rtn_exist=None, password=None, email=None, description=None, enable=None, auth_info=Tenant.get('admin'), fail_ok=False, con_ssh=None): """ Create an openstack user Args: name (str|None): field: name or id domain: project (str|None): default project project_domain: rtn_exist (bool) password: email: description: enable: auth_info: fail_ok: con_ssh: Returns (tuple): (0, ) (1, ) """ if not password: password = HostLinuxUser.get_password() if not name: name = 'user' common.get_unique_name(name_str=name) LOG.info("Create/Show openstack user {}".format(name)) arg_dict = { 'domain': domain, 'project': project, 'project-domain': project_domain, 'password': password, 'email': email, 'description': description, 'enable': True if enable is True else None, 'disable': True if enable is False else None, 'or-show': rtn_exist, } arg_str = '{} {}'.format(common.parse_args(args_dict=arg_dict), name) code, output = cli.openstack('user create', arg_str, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output table_ = table_parser.table(output) username = table_parser.get_value_two_col_table(table_, field='name') user = username if field == 'name' else table_parser.get_value_two_col_table(table_, field=field) is_platform = auth_info and auth_info.get('platform') keystone = 'platform' if is_platform else 'containerized' dictname = user + '_platform' if is_platform else user existing_auth = Tenant.get(dictname) if existing_auth: if existing_auth['user'] != username: raise ValueError('Tenant.{} already exists for a different user {}'.format( dictname, existing_auth['user'])) Tenant.update(dictname, username=username, password=password, tenant=project, platform=is_platform) else: Tenant.add(username=username, tenantname=project, dictname=dictname, password=password, platform=is_platform) LOG.info('Tenant.{} for {} keystone user {} is added'.format(dictname, keystone, user)) LOG.info("{} keystone user {} successfully created/showed".format(keystone, user)) return 0, user