# # Copyright (c) 2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # from utils import cli, exceptions from utils import table_parser from utils.tis_log import LOG from consts.proj_vars import ProjVar from consts.auth import Tenant from consts.stx import FlavorSpec, GuestImages from keywords import common from testfixtures.fixture_resources import ResourceCleanup def create_flavor(name=None, flavor_id=None, vcpus=1, ram=1024, root_disk=None, ephemeral=None, swap=None, is_public=None, rxtx_factor=None, project=None, project_domain=None, description=None, guest_os=None, fail_ok=False, auth_info=Tenant.get('admin'), con_ssh=None, storage_backing=None, rtn_id=True, cleanup=None, add_default_specs=True, properties=None): """ Create a flavor with given criteria. Args: name (str): substring of flavor name. Whole name will be -. e,g., 'myflavor-1'. If None, name will be set to 'flavor'. flavor_id (str): auto generated by default unless specified. vcpus (int): ram (int): root_disk (int): ephemeral (int): swap (int|None): is_public (bool): rxtx_factor (str): project project_domain description guest_os (str|None): guest name such as 'tis-centos-guest' or None - default tis guest assumed fail_ok (bool): whether it's okay to fail to create a flavor. Default to False. auth_info (dict): This is set to Admin by default. Can be set to other tenant for negative test. con_ssh (SSHClient): storage_backing (str): storage backing in extra flavor. Auto set storage backing based on system config if None. Valid values: 'local_image', 'remote' rtn_id (bool): return id or name cleanup (str|None): cleanup scope. function, class, module, or session add_default_specs (False): Whether to automatically add extra specs that are needed to launch vm properties (str|list|dict) Returns (tuple): (rtn_code (int), flavor_id/err_msg (str)) (0, ): flavor created successfully (1, ): create flavor cli rejected """ table_ = table_parser.table( cli.openstack('flavor list', ssh_client=con_ssh, auth_info=auth_info)[ 1]) existing_names = table_parser.get_column(table_, 'Name') if name is None: name = 'flavor' flavor_name = common.get_unique_name(name_str=name, existing_names=existing_names, resource_type='flavor') if root_disk is None: if not guest_os: guest_os = GuestImages.DEFAULT['guest'] root_disk = GuestImages.IMAGE_FILES[guest_os][1] args_dict = { '--ephemeral': ephemeral, '--swap': swap, '--rxtx-factor': rxtx_factor, '--disk': root_disk, '--ram': ram, '--vcpus': vcpus, '--id': flavor_id, '--project': project, '--project-domain': project_domain, '--description': description, '--public': True if is_public else None, '--private': True if is_public is False else None, '--property': properties, } args = '{} {}'.format(common.parse_args(args_dict, repeat_arg=True), flavor_name) LOG.info("Creating flavor {}...".format(flavor_name)) LOG.info("openstack flavor create option: {}".format(args)) exit_code, output = cli.openstack('flavor create', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exit_code > 1: return 1, output table_ = table_parser.table(output) flavor_id = table_parser.get_value_two_col_table(table_, 'id') LOG.info("Flavor {} created successfully.".format(flavor_name)) if cleanup: ResourceCleanup.add('flavor', flavor_id, scope=cleanup) if add_default_specs: extra_specs = {FlavorSpec.MEM_PAGE_SIZE: '2048'} # extra_specs = {FlavorSpec.MEM_PAGE_SIZE: 'small'} default_flavor_backing = ProjVar.get_var('DEFAULT_INSTANCE_BACKING') sys_inst_backing = ProjVar.get_var('INSTANCE_BACKING') if not default_flavor_backing: from keywords import host_helper sys_inst_backing = host_helper.get_hosts_per_storage_backing( up_only=False, auth_info=auth_info, con_ssh=con_ssh, refresh=True) configured_backings = [backing for backing in sys_inst_backing if sys_inst_backing.get(backing)] LOG.debug( "configured backing:{} sys inst backing: {}, required storage " "backing: {}". format(configured_backings, sys_inst_backing, storage_backing)) if storage_backing and storage_backing not in configured_backings: raise ValueError( 'Required local_storage {} is not configured on any nova ' 'hypervisor'. format(storage_backing)) if len(configured_backings) > 1: extra_specs[ FlavorSpec.STORAGE_BACKING] = storage_backing if \ storage_backing else \ ProjVar.get_var('DEFAULT_INSTANCE_BACKING') if extra_specs: LOG.info("Setting flavor specs: {}".format(extra_specs)) set_flavor(flavor_id, con_ssh=con_ssh, auth_info=auth_info, **extra_specs) flavor = flavor_id if rtn_id else flavor_name return 0, flavor, storage_backing def set_aggregate(aggregate, properties=None, no_property=None, zone=None, name=None, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Set aggregate with given params Args: aggregate (str): aggregate to set properties (dict|None): no_property (bool|None): zone (str|None): name (str|None): fail_ok (bool): con_ssh: auth_info: Returns (tuple): (0, "Aggregate set successfully with param: ) (1, ) returns only if fail_ok=True """ args_dict = { '--zone': zone, '--name': name, '--property': properties, '--no-property': no_property, } args = '{} {}'.format(common.parse_args(args_dict, repeat_arg=True), aggregate) code, output = cli.openstack('aggregate set', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output msg = "Aggregate {} set successfully with param: {}".format(aggregate, args) LOG.info(msg) return 0, msg def unset_aggregate(aggregate, properties, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Unset given properties for aggregate Args: aggregate (str): aggregate to unset properties (list|tuple|str|None): fail_ok (bool): con_ssh: auth_info: Returns (tuple): (0, "Aggregate set successfully with param: ) (1, ) returns only if fail_ok=True """ if isinstance(properties, str): properties = (properties,) args = ' '.join(['--property {}'.format(key) for key in properties]) args = '{} {}'.format(args, aggregate) code, output = cli.openstack('aggregate unset', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output msg = "Aggregate {} properties unset successfully: {}".format(aggregate, properties) LOG.info(msg) return 0, msg def get_aggregate_values(aggregate, fields, con_ssh=None, auth_info=Tenant.get('admin'), fail_ok=False): """ Get values of a nova aggregate for given fields Args: aggregate (str): fields (str|list|tuple): con_ssh: auth_info (dict): fail_ok (bool) Returns (list): """ code, out = cli.openstack('aggregate show', aggregate, ssh_client=con_ssh, auth_info=auth_info, fail_ok=fail_ok) if code > 0: return [] table_ = table_parser.table(out) return table_parser.get_multi_values_two_col_table( table_, fields, evaluate=True, dict_fields=('properties',)) def delete_flavors(flavors, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Delete given flavor(s) Args: flavors (list|str): id(s) of flavor(s) to delete check_first (bool) fail_ok (bool): whether to raise exception if any flavor fails to delete con_ssh (SSHClient): auth_info (dict): Returns (tuple): (-1, 'None of the flavor(s) exists. Do nothing.') (0, 'Flavor is successfully deleted') (1, ) (2, "Flavor still exists on system after deleted.") """ if isinstance(flavors, str): flavors = [flavors] if check_first: existing_favors = get_flavors(con_ssh=con_ssh, auth_info=auth_info) flavors = list(set(flavors) & set(existing_favors)) if not flavors: msg = "None of the given flavors exist. Do nothing." LOG.info(msg) return -1, msg LOG.info("Flavor(s) to delete: {}".format(flavors)) code, output = cli.openstack('flavor delete', ' '.join(flavors), ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, output existing_favors = get_flavors(con_ssh=con_ssh, auth_info=auth_info) flavors_still_exist = list(set(flavors) & set(existing_favors)) if flavors_still_exist: err_msg = "Flavor(s) still exist after deletion: {}".format( flavors_still_exist) LOG.warning(err_msg) if fail_ok: return 2, err_msg else: raise exceptions.FlavorError(err_msg) success_msg = "Flavor(s) deleted successfully." LOG.info(success_msg) return 0, success_msg def get_flavors(name=None, memory=None, disk=None, ephemeral=None, swap=None, vcpu=None, rxtx=None, is_public=None, flv_id=None, long=False, con_ssh=None, auth_info=None, strict=True, field='id'): """ Get a flavor id with given criteria. If no criteria given, a random flavor will be returned. Args: name (str): name of a flavor memory (int): memory size in MB disk (int): size of the disk in GB ephemeral (int): size of ephemeral disk in GB swap (int): size of swap disk in GB vcpu (int): number of vcpus rxtx (str): is_public (bool): flv_id (str) long (bool) con_ssh (SSHClient): auth_info (dict): strict (bool): whether or not to perform strict search on provided values field (str|list|tuple) Returns (list): """ args = '--long' if long else '' table_ = table_parser.table( cli.openstack('flavor list', args, ssh_client=con_ssh, auth_info=auth_info)[1]) req_dict = {'Name': name, 'RAM': memory, 'Disk': disk, 'Ephemeral': ephemeral, 'Swap': '' if str(swap) == '0' else swap, 'VCPUs': vcpu, 'RXTX Factor': rxtx, 'Is Public': is_public, 'ID': flv_id, } final_dict = {k: str(v) for k, v in req_dict.items() if v is not None} return table_parser.get_multi_values(table_, field, strict=strict, **final_dict) def get_basic_flavor(auth_info=None, con_ssh=None, guest_os='', rtn_id=True): """ Get a basic flavor with the default arg values and without adding extra specs. Args: auth_info (dict): con_ssh (SSHClient): guest_os rtn_id (bool): return flavor id or name Returns (str): id of the basic flavor """ if not guest_os: guest_os = GuestImages.DEFAULT['guest'] size = GuestImages.IMAGE_FILES[guest_os][1] default_flavor_name = 'flavor-default-size{}'.format(size) rtn_val = 'id' if rtn_id else 'name' flavors = get_flavors(name=default_flavor_name, con_ssh=con_ssh, auth_info=auth_info, strict=False, field=rtn_val) flavor = flavors[0] if flavors else \ create_flavor(name=default_flavor_name, root_disk=size, con_ssh=con_ssh, cleanup='session', rtn_id=rtn_id)[1] return flavor def set_flavor(flavor, project=None, project_domain=None, description=None, no_property=None, con_ssh=None, auth_info=Tenant.get('admin'), fail_ok=False, **properties): """ Set flavor with given parameters Args: flavor (str): id of a flavor project (str) project_domain (str) description (str) no_property (bool) con_ssh (SSHClient): auth_info (dict): fail_ok (bool): **properties: extra specs to set. e.g., **{"hw:mem_page_size": "2048"} Returns (tuple): (rtn_code (int), message (str)) (0, 'Flavor extra specs set successfully.'): required extra spec(s) added successfully (1, ): add extra spec cli rejected """ args_dict = { '--description': description, '--project': project, '--project-domain': project_domain, '--no-property': no_property and not properties, '--property': properties } args = common.parse_args(args_dict, repeat_arg=True) if not args.strip(): raise ValueError("Nothing is provided to set") LOG.info("Setting flavor {} with args: {}".format(flavor, args)) args = '{} {}'.format(args, flavor) exit_code, output = cli.openstack('flavor set', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exit_code == 1: return 1, output msg = "Flavor {} set successfully".format(flavor) LOG.info(msg) return 0, flavor def unset_flavor(flavor, properties=None, project=None, project_domain=None, check_first=True, fail_ok=False, auth_info=Tenant.get('admin'), con_ssh=None): """ Unset specific extra spec(s) from given flavor. Args: flavor (str): id of the flavor properties (str|list|tuple): extra spec(s) to be removed. At least one should be provided. project_domain project check_first (bool): Whether to check if extra spec exists in flavor before attempt to unset con_ssh (SSHClient): auth_info (dict): fail_ok (bool): con_ssh Returns (tuple): (rtn_code (int), message (str)) (-1, 'Extra spec(s) not exist in flavor. Do nothing.') (0, 'Flavor extra specs unset successfully.'): required extra spec(s) removed successfully (1, ): unset extra spec cli rejected (2, ' is still in the extra specs list'): post action check failed """ if isinstance(properties, str): properties = [properties] if properties and check_first: existing_specs = get_flavor_values(flavor, fields='properties', con_ssh=con_ssh, auth_info=auth_info)[0] properties = list(set(properties) & set(existing_specs.keys())) args_dict = { '--property': properties, '--project': project, '--project_domain': project_domain, } args = common.parse_args(args_dict, repeat_arg=True) if not args: msg = "Nothing to unset for flavor {}. Do nothing.".format(flavor) LOG.info(msg) return -1, msg LOG.info("Unsetting flavor {} with args: {}".format(flavor, args)) exit_code, output = cli.openstack('flavor unset', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exit_code > 0: return 1, output success_msg = "Flavor {} unset successfully".format(flavor) LOG.info(success_msg) return 0, success_msg def get_flavor_properties(flavor, con_ssh=None, auth_info=Tenant.get('admin')): """ Get extra specs of a flavor as dictionary Args: flavor (str): id of a flavor con_ssh (SSHClient): auth_info (dict): Returns (dict): e.g., {"aggregate_instance_extra_specs:storage": "local_image", "hw:mem_page_size": "2048"} """ return get_flavor_values(flavor, fields='properties', con_ssh=con_ssh, auth_info=auth_info)[0] def create_server_group(name=None, policy='affinity', rule=None, fail_ok=False, auth_info=None, con_ssh=None, rtn_exist=False, field='id'): """ Create a server group with given criteria Args: name (str): name of the server group policy (str): affinity or anti_infinity rule (str|None): max_server_per_host can be specified when policy=anti-affinity fail_ok (bool): auth_info (dict): con_ssh (SSHClient): rtn_exist (bool): Whether to return existing server group that matches the given name field (str): id or name Returns (tuple): (rtn_code (int), err_msg_or_srv_grp_id (str)) - (0, ) # server group created successfully - (1, ) # create server group cli rejected """ # process server group metadata if name and rtn_exist: existing_grp = get_server_groups(name=name, strict=False, con_ssh=con_ssh, auth_info=auth_info, field=field) if existing_grp: LOG.debug( "Returning existing server group {}".format(existing_grp[0])) return -1, existing_grp[0] # process server group name and policy if not name: name = 'grp_{}'.format(policy.replace('-', '_')) name = common.get_unique_name(name_str=name) args = '{}{} {}'.format('--rule {} '.format(rule) if rule else '', name, policy.replace('_', '-')) LOG.info("Creating server group with args: {}...".format(args)) exit_code, output = cli.nova('server-group-create', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if exit_code > 0: return 1, output table_ = table_parser.table(output) srv_grp_id = table_parser.get_values(table_, field)[0] LOG.info("Server group {} created successfully.".format(name)) return 0, srv_grp_id def get_server_groups(field='ID', all_projects=True, long=False, strict=True, regex=False, auth_info=Tenant.get('admin'), con_ssh=None, **kwargs): """ Get server groups ids based on the given criteria Args: auth_info (dict): con_ssh (SSHClient): strict (bool): whether to do strict search for given name regex (bool): whether or not to use regex when for given name all_projects(bool): whether to list for all projects long field (str|list|tuple): **kwargs: filters Returns (list): list of server groups """ args_dict = { '--all-projects': all_projects, '--long': long } args = common.parse_args(args_dict) table_ = table_parser.table( cli.openstack('server group list', args, ssh_client=con_ssh, auth_info=auth_info)[1]) def _parse_list(value_str): return [val.strip() for val in value_str.split(',')] parsers = {_parse_list: ('Policies', 'Members')} return table_parser.get_multi_values(table_, field, strict=strict, regex=regex, parsers=parsers, **kwargs) def get_server_groups_info(headers=('Policies', 'Members'), auth_info=None, con_ssh=None, strict=False, **kwargs): """ Get a server group(s) info as a list Args: headers (str|list|tuple): header string for info. such as 'Member', 'Metadata', 'Policies' auth_info (dict): con_ssh (SSHClient): strict kwargs Returns (dict): server group(s) info in dict. server group id as key, and values of specified headers as value. Examples: {: [['affinity'], [, , ...]], : ['anti-affinity', []]} """ if isinstance(headers, str): headers = [headers] headers = ['ID'] + list(headers) values = get_server_groups(field=headers, all_projects=True, long=True, con_ssh=con_ssh, auth_info=auth_info, strict=strict, **kwargs) group_ids = values.pop(0) values = list(zip(*values)) srv_groups_info = {group_ids[i]: values[i] for i in range(len(group_ids))} return srv_groups_info def get_server_group_info(group_id=None, group_name=None, headers=('Policies', 'Members'), strict=False, auth_info=None, con_ssh=None): """ Get server group info for specified server group Args: group_id: group_name: headers (str|list|tuple): auth_info: strict con_ssh: Returns (list): """ filters = {'ID': group_id} if group_name: filters['Name'] = group_name group_info = get_server_groups_info(headers=headers, auth_info=auth_info, strict=strict, con_ssh=con_ssh, **filters) assert len(group_info) == 1, "More than 1 server group filtered" values = list(group_info.values())[0] return values def server_group_exists(srv_grp_id, auth_info=Tenant.get('admin'), con_ssh=None): """ Return True if given server group exists else False Args: srv_grp_id (str): auth_info (dict): con_ssh (SSHClient): Returns (bool): True or False """ existing_server_groups = get_server_groups(all_projects=True, auth_info=auth_info, con_ssh=con_ssh) return srv_grp_id in existing_server_groups def delete_server_groups(srv_grp_ids=None, check_first=True, fail_ok=False, auth_info=Tenant.get('admin'), con_ssh=None): """ Delete server group(s) Args: srv_grp_ids (list|str): id(s) for server group(s) to delete. check_first (bool): whether to check existence of given server groups before attempt to delete. Default: True. fail_ok (bool): auth_info (dict|None): con_ssh (SSHClient): Returns (tuple): (rtn_code(int), msg(str)) # rtn_code 1,2 only returns when fail_ok=True (-1, 'No server group(s) to delete.') # "Empty vm list/string provided and no vm exist on system. (-1, 'None of the given server group(s) exists on system.') (0, "Server group(s) deleted successfully.") (1, ) # Deletion rejected for all of the server groups. Return CLI stderr. (2, "Some deleted server group(s) still exist on system:: ") """ existing_sgs = None if not srv_grp_ids: existing_sgs = srv_grp_ids = get_server_groups(con_ssh=con_ssh, auth_info=auth_info) elif isinstance(srv_grp_ids, str): srv_grp_ids = [srv_grp_ids] srv_grp_ids = [sg for sg in srv_grp_ids if sg] if not srv_grp_ids: LOG.info("No server group(s) to delete. Do Nothing") return -1, 'No server group(s) to delete.' if check_first: if existing_sgs is None: existing_sgs = get_server_groups(con_ssh=con_ssh, auth_info=auth_info) srv_grp_ids = list(set(srv_grp_ids) & set(existing_sgs)) if not srv_grp_ids: msg = "None of the given server group(s) exists on system. Do " \ "nothing" LOG.info(msg) return -1, msg LOG.info("Deleting server group(s): {}".format(srv_grp_ids)) code, output = cli.openstack('server group delete', ' '.join(srv_grp_ids), ssh_client=con_ssh, fail_ok=True, auth_info=auth_info, timeout=60) if code == 1: return 1, output existing_sgs = get_server_groups(con_ssh=con_ssh, auth_info=auth_info) grps_undeleted = list(set(srv_grp_ids) & set(existing_sgs)) if grps_undeleted: msg = "Some server group(s) still exist on system after deletion: " \ "{}".format(grps_undeleted) LOG.warning(msg) if fail_ok: return 2, msg raise exceptions.NovaError(msg) msg = "Server group(s) deleted successfully." LOG.info(msg) return 0, "Server group(s) deleted successfully." def get_keypairs(name=None, field='Name', con_ssh=None, auth_info=None): """ Args: name (str): Name of the key pair to filter for a given user field (str|list|tuple) con_ssh (SSHClient): auth_info (dict): Tenant to be used to execute the cli if none Primary tenant will be used Returns (list):return keypair names """ table_ = table_parser.table( cli.openstack('keypair list', ssh_client=con_ssh, auth_info=auth_info)[ 1]) return table_parser.get_multi_values(table_, field, Name=name) def get_flavor_values(flavor, fields, strict=True, con_ssh=None, auth_info=Tenant.get('admin')): """ Get flavor values for given fields via openstack flavor show Args: flavor (str): fields (str|list|tuple): strict (bool): strict search for field name or not con_ssh: auth_info: Returns (list): """ table_ = table_parser.table( cli.openstack('flavor show', flavor, ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_multi_values_two_col_table( table_, fields, merge_lines=True, evaluate=True, strict=strict, dict_fields=('properties',)) def copy_flavor(origin_flavor, new_name=None, con_ssh=None): """ Extract the info from an existing flavor and create a new flavor that is has identical info Args: origin_flavor (str): id of an existing flavor to extract the info from new_name: con_ssh: Returns (str): flavor_id """ table_ = table_parser.table( cli.openstack('flavor show', origin_flavor, ssh_client=con_ssh, auth_info=Tenant.get('admin'))[1]) extra_specs = table_parser.get_value_two_col_table(table_, 'properties') extra_specs = table_parser.convert_value_to_dict(value=extra_specs) ephemeral = table_parser.get_value_two_col_table(table_, 'ephemeral', strict=False) disk = table_parser.get_value_two_col_table(table_, 'disk') is_public = table_parser.get_value_two_col_table(table_, 'is_public', strict=False) ram = table_parser.get_value_two_col_table(table_, 'ram') rxtx_factor = table_parser.get_value_two_col_table(table_, 'rxtx_factor') swap = table_parser.get_value_two_col_table(table_, 'swap') vcpus = table_parser.get_value_two_col_table(table_, 'vcpus') old_name = table_parser.get_value_two_col_table(table_, 'name') if not new_name: new_name = "{}-{}".format(old_name, new_name) swap = swap if swap else 0 new_flavor_id = \ create_flavor(name=new_name, vcpus=vcpus, ram=ram, swap=swap, root_disk=disk, ephemeral=ephemeral, is_public=is_public, rxtx_factor=rxtx_factor, con_ssh=con_ssh)[1] set_flavor(new_flavor_id, con_ssh=con_ssh, **extra_specs) return new_flavor_id # TODO: nova providernet-show no longer exists for pci pfs/vfs info. Update # required. def get_provider_net_info(providernet_id, field='pci_pfs_configured', strict=True, auth_info=Tenant.get('admin'), con_ssh=None, rtn_int=True): """ Get provider net info from "nova providernet-show" Args: providernet_id (str): id of a providernet field (str): Field name such as pci_vfs_configured, pci_pfs_used, etc strict (bool): whether to perform a strict search on field name auth_info (dict): con_ssh (SSHClient): rtn_int (bool): whether to return integer or string Returns (int|str): value of specified field. Convert to integer by default unless rnt_int=False. """ if not providernet_id: raise ValueError("Providernet id is not provided.") table_ = table_parser.table( cli.nova('providernet-show', providernet_id, ssh_client=con_ssh, auth_info=auth_info)[1]) info_str = table_parser.get_value_two_col_table(table_, field, strict=strict) return int(info_str) if rtn_int else info_str def get_pci_interface_stats_for_providernet( providernet_id, fields=('pci_pfs_configured', 'pci_pfs_used', 'pci_vfs_configured', 'pci_vfs_used'), auth_info=Tenant.get('admin'), con_ssh=None): """ get pci interface usage Args: providernet_id (str): id of a providernet fields: fields such as ('pci_vfs_configured', 'pci_pfs_used') auth_info (dict): con_ssh (SSHClient): Returns (tuple): tuple of integers """ if not providernet_id: raise ValueError("Providernet id is not provided.") table_ = table_parser.table( cli.nova('providernet-show', providernet_id, ssh_client=con_ssh, auth_info=auth_info)[1]) rtn_vals = [] for field in fields: pci_stat = int( table_parser.get_value_two_col_table(table_, field, strict=True)) rtn_vals.append(pci_stat) return tuple(rtn_vals) def create_aggregate(field='name', name=None, avail_zone=None, properties=None, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Add a aggregate with given name and availability zone. Args: field (str): name or id name (str): name for aggregate to create avail_zone (str|None): properties (dict|None) check_first (bool) fail_ok (bool): con_ssh (SSHClient): auth_info (dict): Returns (tuple): (0, ) -- aggregate successfully created (1, ) -- cli rejected (2, "Created aggregate is not as specified") -- name and/or availability zone mismatch """ if not name: existing_names = get_aggregates(field='name') name = common.get_unique_name(name_str='stxauto', existing_names=existing_names) args_dict = { '--zone': avail_zone, '--property': properties, } args = '{} {}'.format(common.parse_args(args_dict, repeat_arg=True), name) if check_first: aggregates_ = get_aggregates(field=field, name=name, avail_zone=avail_zone) if aggregates_: LOG.warning("Aggregate {} already exists. Do nothing.".format(name)) return -1, aggregates_[0] LOG.info("Adding aggregate {}".format(name)) res, out = cli.openstack('aggregate create', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if res == 1: return res, out out_tab = table_parser.table(out) succ_msg = "Aggregate {} is successfully created".format(name) LOG.info(succ_msg) return 0, table_parser.get_value_two_col_table(out_tab, field) def get_aggregates(field='name', name=None, avail_zone=None, con_ssh=None, auth_info=Tenant.get('admin')): """ Get a list of aggregates Args: field (str|list|tuple): id or name name (str|list): filter out the aggregates with given name if specified avail_zone (str): filter out the aggregates with given availability zone if specified con_ssh (SSHClient): auth_info (dict): Returns (list): """ kwargs = {} if avail_zone: kwargs['Availability Zone'] = avail_zone if name: kwargs['Name'] = name aggregates_tab = table_parser.table( cli.openstack('aggregate list', ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_multi_values(aggregates_tab, field, **kwargs) def delete_aggregates(names, check_first=True, remove_hosts=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Add a aggregate with given name and availability zone. Args: names (str|list): name for aggregate to delete check_first (bool) remove_hosts (bool) fail_ok (bool): con_ssh (SSHClient): auth_info (dict): Returns (tuple): (0, "Aggregate is successfully deleted") -- aggregate successfully deletec (1, ) -- cli rejected (2, "Aggregate still exists in aggregate-list after deletion") -- failed although cli accepted """ if check_first: names = get_aggregates(name=names, con_ssh=con_ssh, auth_info=auth_info) if not names: msg = 'Aggregate {} does not exists. Do nothing.'.format(names) LOG.warning(msg) return -1, msg elif isinstance(names, str): names = [names] if remove_hosts: for name in names: remove_hosts_from_aggregate(aggregate=name, check_first=True) LOG.info("Deleting aggregate {}".format(names)) res, out = cli.openstack('aggregate delete', ' '.join(names), ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if res == 1: return res, out post_aggregates = get_aggregates(name=names, con_ssh=con_ssh, auth_info=auth_info) if post_aggregates: err_msg = "Aggregate {} still exists in openstack aggregate list " \ "after deletion.".format(post_aggregates) LOG.warning(err_msg) if fail_ok: return 2, err_msg else: raise exceptions.NovaError(err_msg) succ_msg = "Aggregate(s) successfully deleted: {}".format(names) LOG.info(succ_msg) return 0, succ_msg def get_compute_services(field, con_ssh=None, auth_info=Tenant.get('admin'), **kwargs): """ Get values from compute services list System: Regular, Small footprint Args: field (str) con_ssh (SSHClient): auth_info (dict): kwargs: Valid keys: Id, Binary, Host, Zone, Status, State, Updated At Returns (list): a list of hypervisors in given zone """ table_ = table_parser.table( cli.openstack('compute service list', ssh_client=con_ssh, auth_info=auth_info)[1]) return table_parser.get_values(table_, field, **kwargs) def remove_hosts_from_aggregate(aggregate, hosts=None, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Remove hosts from specified aggregate Args: aggregate (str): name of the aggregate to remove hosts. stxauto aggregate can be added via add_stxauto_zone session fixture hosts (list|str): host(s) to remove from aggregate check_first (bool): fail_ok (bool): con_ssh (SSHClient): auth_info (dict): Returns (tuple): (0, "Hosts successfully removed from aggregate") (1, ) cli rejected on at least one host (2, "Host(s) still exist in aggregate after aggregate-remove-host: ) """ __remove_or_add_hosts_in_aggregate(remove=True, aggregate=aggregate, hosts=hosts, check_first=check_first, fail_ok=fail_ok, con_ssh=con_ssh, auth_info=auth_info) def add_hosts_to_aggregate(aggregate, hosts, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Add host(s) to specified aggregate Args: aggregate (str): name of the aggregate to add hosts. stxauto aggregate can be added via add_stxauto_zone session fixture hosts (list|str): host(s) to add to aggregate check_first (bool): fail_ok (bool): con_ssh (SSHClient): auth_info (dict): Returns (tuple): (0, "Hosts successfully added from aggregate") (1, ) cli rejected on at least one host (2, "aggregate-add-host accepted, but some host(s) are not added in aggregate") """ __remove_or_add_hosts_in_aggregate(remove=False, aggregate=aggregate, hosts=hosts, check_first=check_first, fail_ok=fail_ok, con_ssh=con_ssh, auth_info=auth_info) def __remove_or_add_hosts_in_aggregate(aggregate, hosts=None, remove=False, check_first=True, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Remove/Add hosts from/to given aggregate Args: aggregate (str): name of the aggregate to add/remove hosts. stxauto aggregate can be added via add_stxauto_zone session fixture hosts (list|str): remove (bool): True if remove hosts from given aggregate, otherwise add hosts to aggregate check_first (bool): fail_ok (bool): con_ssh (SSHClient): auth_info (dict): Returns (tuple): (0, "Hosts successfully removed from aggregate") (1, ) cli rejected on at least one host (2, "Host(s) still exist in aggregate after aggregate-remove-host: ) """ hosts_in_aggregate = get_hosts_in_aggregate(aggregate, con_ssh=con_ssh) if hosts is None: if remove: hosts = hosts_in_aggregate else: from keywords import host_helper hosts = host_helper.get_hypervisors() if isinstance(hosts, str): hosts = [hosts] msg_str = 'Remov' if remove else 'Add' LOG.info("{}ing hosts {} in aggregate {}".format(msg_str, hosts, aggregate)) if check_first: if remove: hosts_to_rm_or_add = list(set(hosts) & set(hosts_in_aggregate)) else: hosts_to_rm_or_add = list(set(hosts) - set(hosts_in_aggregate)) else: hosts_to_rm_or_add = list(hosts) if not hosts_to_rm_or_add: warn_str = 'No' if remove else 'All' msg = "{} given host(s) in aggregate {}. Do nothing. Given hosts: " \ "{}; hosts in aggregate: {}". \ format(warn_str, aggregate, hosts, hosts_in_aggregate) LOG.warning(msg) return -1, msg failed_res = {} cmd = 'aggregate remove host' if remove else 'aggregate add host' for host in hosts_to_rm_or_add: args = '{} {}'.format(aggregate, host) code, output = cli.openstack(cmd, args, ssh_client=con_ssh, fail_ok=True, auth_info=auth_info) if code > 0: failed_res[host] = output if failed_res: err_msg = "'{}' is rejected for following host(s) in aggregate " \ "{}: {}".format(cmd, aggregate, failed_res) if fail_ok: LOG.warning(err_msg) return 1, err_msg else: raise exceptions.NovaError(err_msg) post_hosts_in_aggregate = get_hosts_in_aggregate(aggregate, con_ssh=con_ssh) if remove: failed_hosts = list(set(hosts) & set(post_hosts_in_aggregate)) else: failed_hosts = list(set(hosts) - set(post_hosts_in_aggregate)) if failed_hosts: err_msg = "{} accepted, but some host(s) are not {}ed in aggregate " \ "{}: {}".format(cmd, msg_str, aggregate, failed_hosts) if fail_ok: LOG.warning(err_msg) return 2, err_msg else: raise exceptions.NovaError(err_msg) succ_msg = "Hosts successfully {}ed in aggregate {}: {}".format( msg_str.lower(), aggregate, hosts) LOG.info(succ_msg) return 0, succ_msg def get_migration_list_table(con_ssh=None, auth_info=Tenant.get('admin')): """ nova migration-list to collect migration history of each vm Args: con_ssh (SSHClient): auth_info (dict): """ LOG.info("Listing migration history...") return table_parser.table( cli.nova('migration-list', ssh_client=con_ssh, auth_info=auth_info)[1]) def create_keypair(name, public_key=None, private_key=None, fail_ok=False, con_ssh=None, auth_info=Tenant.get('admin')): """ Create a new keypair Args: name (str): keypair name to create public_key (str|None): existing public key file path to use private_key (str|None): file path to save private key fail_ok (bool) con_ssh (SSHClient): auth_info (dict): Returns (tuple): """ args_dict = {'--public-key': public_key, '--private-key': private_key} args = '{} "{}"'.format(common.parse_args(args_dict), name) LOG.info("Creating keypair with args: {}".format(args)) code, out = cli.openstack('keypair create', args, ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return 1, out LOG.info("Keypair {} created successfully".format(name)) return 0, name def delete_keypairs(keypairs, check_first=True, fail_ok=False, con_ssh=None, auth_info=None): """ Delete keypair(s) Args: keypairs (list/str): keypair(s) to delete check_first (bool) fail_ok (bool) con_ssh (SSHClient): auth_info (dict): Returns (tuple): """ if isinstance(keypairs, str): keypairs = (keypairs,) if check_first: existing_keypairs = get_keypairs(con_ssh=con_ssh, auth_info=auth_info) keypairs = list(set(keypairs) & set(existing_keypairs)) if not keypairs: msg = 'Give keypair(s) not exist. Do nothing.' LOG.info(msg) return -1, msg LOG.info('Deleting keypairs: {}'.format(keypairs)) code, out = cli.openstack('keypair delete', ' '.join(keypairs), ssh_client=con_ssh, fail_ok=fail_ok, auth_info=auth_info) if code > 0: return code, out post_keypairs = get_keypairs(con_ssh=con_ssh, auth_info=auth_info) undeleted_kp_names = list(set(keypairs) & set(post_keypairs)) if undeleted_kp_names: raise exceptions.NovaError( "keypair(s) still exist after deletion: {}".format( undeleted_kp_names)) msg = 'keypairs deleted successfully: {}'.format(keypairs) LOG.info(msg) return 0, msg def get_hosts_in_aggregate(aggregate, con_ssh=None, auth_info=Tenant.get('admin'), fail_ok=False): """ Get list of hosts in given nova aggregate Args: aggregate (str): con_ssh: auth_info: fail_ok (bool) Returns (list): """ if 'image' in aggregate: aggregate = 'local_storage_image_hosts' elif 'remote' in aggregate: aggregate = 'remote_storage_hosts' hosts = get_aggregate_values(aggregate, 'hosts', con_ssh=con_ssh, auth_info=auth_info, fail_ok=fail_ok) if hosts: hosts = hosts[0] LOG.info("Hosts in {} aggregate: {}".format(aggregate, hosts)) return hosts