一、前言
《一文说清Rally自动化测试框架》介绍了Rally自动化测试框架的安装部署,使用方法及架构组成。本篇文章将从使用方法,代码实现方面详细介绍Rally的配置组件(Config)
二、脚本参数
在编写执行脚本时,我们经常会遇到诸如 CONF.openstack.nova_server_boot_timeout 的参数,这些参数定义在rally-openstack/rally_openstack/cfg/ 目录下,比如:
OPTS = {"openstack": [
# prepoll delay, timeout, poll interval
# "start": (0, 300, 1)
cfg.FloatOpt("nova_server_start_prepoll_delay",
default=0.0,
deprecated_group="benchmark",
help="Time to sleep after start before polling for status"),
cfg.FloatOpt("nova_server_start_timeout",
default=300.0,
deprecated_group="benchmark",
help="Server start timeout"),
cfg.FloatOpt("nova_server_start_poll_interval",
deprecated_group="benchmark",
default=1.0,
help="Server start poll interval"),
# "stop": (0, 300, 2)
cfg.FloatOpt("nova_server_stop_prepoll_delay",
default=0.0,
help="Time to sleep after stop before polling for status"),
cfg.FloatOpt("nova_server_stop_timeout",
default=300.0,
deprecated_group="benchmark",
help="Server stop timeout"),
cfg.FloatOpt("nova_server_stop_poll_interval",
default=2.0,
deprecated_group="benchmark",
help="Server stop poll interval"),
# "boot": (1, 300, 1)
cfg.FloatOpt("nova_server_boot_prepoll_delay",
default=1.0,
deprecated_group="benchmark",
help="Time to sleep after boot before polling for status"),
cfg.FloatOpt("nova_server_boot_timeout",
default=300.0,
deprecated_group="benchmark",
help="Server boot timeout"),
cfg.FloatOpt("nova_server_boot_poll_interval",
default=2.0,
deprecated_group="benchmark",
help="Server boot poll interval"),
...
]}
这些参数控制着测试运行的方方面面,一般情况下,我们无须改动。
但在一些情况下,我们需要调整一下参数,来进行调试或适应场景,比如,调整ping的超时时间 CONF.openstack.vm_ping_timeout
参数设置方法
当然,我们可以直接修改代码里的默认值,但这样并不是好的方法,rally提供了几种方法来设置参数
rally.conf
可以参考 rally-openstack/etc/rally/rally.conf.sample 文件
[openstack]
#
# From rally
#
# Time to sleep after creating a resource before polling for it status
# (floating point value)
#cinder_volume_create_prepoll_delay = 2.0
# Time to wait for cinder volume to be created. (floating point value)
#cinder_volume_create_timeout = 600.0
# Interval between checks when waiting for volume creation. (floating
# point value)
#cinder_volume_create_poll_interval = 2.0
# Time to wait for cinder volume to be deleted. (floating point value)
#cinder_volume_delete_timeout = 600.0
# Interval between checks when waiting for volume deletion. (floating
# point value)
#cinder_volume_delete_poll_interval = 2.0
# Time to wait for cinder backup to be restored. (floating point
# value)
#cinder_backup_restore_timeout = 600.0
...
把相应的参数项去掉注释,修改参数值,然后把文件拷贝到相应的位置即可,位置有:
- rally.conf文件拷贝到~/.rally/目录下
执行测试时,rally会自动读取这些配置
- rally支持通过--config-file 来指定配置文件的位置
下面的命令获取 ~/rally.conf 文件的参数,用于测试的执行
rally --config-file ~/rally.conf task start ...
代码说明(rally/rally/api.py):
class API(object):
CONFIG_SEARCH_PATHS = [sys.prefix + "/etc/rally", "~/.rally", "/etc/rally"]
CONFIG_FILE_NAME = "rally.conf"
def __init__(self, config_file=None, config_args=None,
plugin_paths=None, skip_db_check=False):
"""Initialize Rally API instance
:param config_file: Path to rally configuration file. If None, default
path will be selected
:type config_file: str
:param config_args: Arguments for initialization current configuration
:type config_args: list
:param plugin_paths: Additional custom plugin locations
:type plugin_paths: list
:param skip_db_check: Allows to skip db revision check
:type skip_db_check: bool
"""
try:
config_files = ([config_file] if config_file else
self._default_config_file())
CONF(config_args or [],
project="rally",
version=rally_version.version_string(),
default_config_files=config_files)
代码第3行设置了查找rally.conf的路径,优先级依次为:
sys.prefix + "/etc/rally" > "~/.rally" > "/etc/rally"
代码第24行完成参数注册
PS:sys.prefix的解释
sys.prefix
A string giving the site-specific directory prefix where the platform independent Python files are installed; by default, this is the string '/usr/local'. This can be set at build time with the --prefix argument to the configure script. The main collection of Python library modules is installed in the directory prefix/lib/pythonX.Y while the platform independent header files (all except pyconfig.h) are stored in prefix/include/pythonX.Y, where X.Y is the version number of Python, for example 2.7.
环境变量
rally支持通过环境变量设置参数,参数形式为 OS_OPENSTACK__OPTION, 比如 OS_OPENSTACK__NOVA_SERVER_BOOT_TIMEOUT
代码: oslo_config/sources/_environment.py
class EnvironmentConfigurationSource(sources.ConfigurationSource):
"""A configuration source for options in the environment."""
@staticmethod
def _make_name(group_name, option_name):
group_name = group_name or 'DEFAULT'
return 'OS_{}__{}'.format(group_name.upper(), option_name.upper())
def get(self, group_name, option_name, opt):
env_name = self._make_name(group_name, option_name)
try:
value = os.environ[env_name]
loc = oslo_config.cfg.LocationInfo(
oslo_config.cfg.Locations.environment, env_name)
return (value, loc)
except KeyError:
return (sources._NoValue, None)
参数优先级
参数配置的优先级
A. 环境变量设置的参数
B. 通过命令行--config-file指定的配置文件 或 默认位置查找到的配置文件
C. 代码定义的参数
代码:oslo_config/cfg.py
def _do_get(self, name, group=None, namespace=None):
"""Look up an option value.
:param name: the opt name (or 'dest', more precisely)
:param group: an OptGroup
:param namespace: the namespace object to get the option value from
:returns: 2-tuple of the option value or a GroupAttr object
and LocationInfo or None
:raises: NoSuchOptError, NoSuchGroupError, ConfigFileValueError,
TemplateSubstitutionError
"""
if group is None and name in self._groups:
return (self.GroupAttr(self, self._get_group(name)), None)
info = self._get_opt_info(name, group)
opt = info['opt']
if 'location' in info:
loc = info['location']
else:
loc = opt._set_location
if isinstance(opt, SubCommandOpt):
return (self.SubCommandAttr(self, group, opt.dest), None)
if 'override' in info:
return (self._substitute(info['override']), loc)
def convert(value):
return self._convert_value(
self._substitute(value, group, namespace), opt)
group_name = group.name if group else None
key = (group_name, name)
# If use_env is true, get a value from the environment but don't use
# it yet. We will look at the command line first, below.
env_val = (sources._NoValue, None)
if self._use_env:
env_val = self._env_driver.get(group_name, name, opt)
if opt.mutable and namespace is None:
namespace = self._mutable_ns
if namespace is None:
namespace = self._namespace
if namespace is not None:
try:
try:
val, alt_loc = opt._get_from_namespace(namespace,
group_name)
# Try command line first
if (val != sources._NoValue
and alt_loc.location == Locations.command_line):
return (convert(val), alt_loc)
# Environment source second
if env_val[0] != sources._NoValue:
return (convert(env_val[0]), env_val[1])
# Default file source third
if val != sources._NoValue:
return (convert(val), alt_loc)
except KeyError: # nosec: Valid control flow instruction
# If there was a KeyError looking at config files or
# command line, retry the env_val.
if env_val[0] != sources._NoValue:
return (convert(env_val[0]), env_val[1])
except ValueError as ve:
message = "Value for option %s from %s is not valid: %s" % (
opt.name, alt_loc, str(ve))
# Preserve backwards compatibility for file-based value
# errors.
if alt_loc.location == Locations.user:
raise ConfigFileValueError(message)
raise ConfigSourceValueError(message)
try:
return self.__drivers_cache[key]
except KeyError: # nosec: Valid control flow instruction
pass
for source in self._sources:
val = source.get(group_name, name, opt)
if val[0] != sources._NoValue:
result = (convert(val[0]), val[1])
self.__drivers_cache[key] = result
return result
if 'default' in info:
return (self._substitute(info['default']), loc)
if self._validate_default_values:
if opt.default is not None:
try:
convert(opt.default)
except ValueError as e:
raise ConfigFileValueError(
"Default value for option %s is not valid: %s"
% (opt.name, str(e)))
if opt.default is not None:
return (convert(opt.default), loc)
return (None, None)
代码:oslo_config/cfg.py : 命令行--config-file指定的配置文件 优先于 默认位置查找到的配置文件,而且只有一个配置文件生效
def _parse_config_files(self):
"""Parse configure files options.
:raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError,
ConfigFilesPermissionDeniedError,
RequiredOptError, DuplicateOptError
"""
namespace = _Namespace(self)
# handle --config-file args or the default_config_files
for arg in self._args:
if arg == '--config-file' or arg.startswith('--config-file='):
break
else:
for config_file in self.default_config_files:
ConfigParser._parse_file(config_file, namespace)
# handle --config-dir args or the default_config_dirs
for arg in self._args:
if arg == '--config-dir' or arg.startswith('--config-dir='):
break
else:
for config_dir in self.default_config_dirs:
# for the default config-dir directories we just continue
# if the directories do not exist. This is different to the
# case where --config-dir is given on the command line.
if not os.path.exists(config_dir):
continue
config_dir_glob = os.path.join(config_dir, '*.conf')
for config_file in sorted(glob.glob(config_dir_glob)):
ConfigParser._parse_file(config_file, namespace)
self._oparser.parse_args(self._args, namespace)
self._validate_cli_options(namespace)
return namespace
三、总结
本文介绍了Rally自动化测试框架有关脚本参数的使用方法,包括配置文件,自定义文件,环境变量等,便于我们在多种场景下进行自动化测试