test/automated-pytest-suite/keywords/nova_helper.py

1311 lines
44 KiB
Python
Executable File

#
# 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
<name>-<auto_count>. 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_id/name>): flavor created successfully
(1, <stderr>): 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,
'--is-public': is_public,
'--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 <aggregate> set successfully with param: <params>)
(1, <std_err>) 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 <aggregate> set successfully with param: <params>)
(1, <std_err>) 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, <std_out>)
(2, "Flavor <flavor_id> 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, <stderr>): 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) <specs> not exist in flavor. Do nothing.')
(0, 'Flavor extra specs unset successfully.'): required extra spec(s)
removed successfully
(1, <stderr>): unset extra spec cli rejected
(2, '<spec_name> 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_id>) # server group created successfully
- (1, <stderr>) # 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: {<server_group1>: [['affinity'], [<vm_id1>, <vm_id2>, ...]],
<server_group2>: ['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, <stderr>) # Deletion rejected for all of the server groups.
Return CLI stderr.
(2, "Some deleted server group(s) still exist on system::
<srv_grp_ids>")
"""
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, <rtn_val>) -- aggregate successfully created
(1, <stderr>) -- 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 <name> is successfully deleted") -- aggregate
successfully deletec
(1, <stderr>) -- cli rejected
(2, "Aggregate <name> 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, <stderr>) cli rejected on at least one host
(2, "Host(s) still exist in aggregate <aggr> after
aggregate-remove-host: <unremoved_hosts>)
"""
__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, <stderr>) 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, <stderr>) cli rejected on at least one host
(2, "Host(s) still exist in aggregate <aggr> after
aggregate-remove-host: <unremoved_hosts>)
"""
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