searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Rally自动化测试框架详解之Config

2023-06-20 01:24:57
45
0

一、前言

一文说清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自动化测试框架有关脚本参数的使用方法,包括配置文件,自定义文件,环境变量等,便于我们在多种场景下进行自动化测试 

 

 

0条评论
0 / 1000
刘****磊
5文章数
0粉丝数
刘****磊
5 文章 | 0 粉丝
原创

Rally自动化测试框架详解之Config

2023-06-20 01:24:57
45
0

一、前言

一文说清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自动化测试框架有关脚本参数的使用方法,包括配置文件,自定义文件,环境变量等,便于我们在多种场景下进行自动化测试 

 

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0