400 lines
14 KiB
Python
400 lines
14 KiB
Python
# Lint as: python2, python3
|
|
"""A singleton class for accessing global config values
|
|
|
|
provides access to global configuration file
|
|
"""
|
|
|
|
# The config values can be stored in 3 config files:
|
|
# global_config.ini
|
|
# moblab_config.ini
|
|
# shadow_config.ini
|
|
# When the code is running in Moblab, config values in moblab config override
|
|
# values in global config, and config values in shadow config override values
|
|
# in both moblab and global config.
|
|
# When the code is running in a non-Moblab host, moblab_config.ini is ignored.
|
|
# Config values in shadow config will override values in global config.
|
|
|
|
import collections
|
|
import os
|
|
import re
|
|
import six.moves.configparser as ConfigParser
|
|
import sys
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib import lsbrelease_utils
|
|
from autotest_lib.client.common_lib import seven
|
|
|
|
|
|
class ConfigError(error.AutotestError):
|
|
"""Configuration error."""
|
|
pass
|
|
|
|
|
|
class ConfigValueError(ConfigError):
|
|
"""Configuration value error, raised when value failed to be converted to
|
|
expected type."""
|
|
pass
|
|
|
|
|
|
|
|
common_lib_dir = os.path.dirname(sys.modules[__name__].__file__)
|
|
client_dir = os.path.dirname(common_lib_dir)
|
|
root_dir = os.path.dirname(client_dir)
|
|
|
|
# Check if the config files are at autotest's root dir
|
|
# This will happen if client is executing inside a full autotest tree, or if
|
|
# other entry points are being executed
|
|
global_config_path_root = os.path.join(root_dir, 'global_config.ini')
|
|
moblab_config_path_root = os.path.join(root_dir, 'moblab_config.ini')
|
|
shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini')
|
|
config_in_root = os.path.exists(global_config_path_root)
|
|
|
|
# Check if the config files are at autotest's client dir
|
|
# This will happen if a client stand alone execution is happening
|
|
global_config_path_client = os.path.join(client_dir, 'global_config.ini')
|
|
config_in_client = os.path.exists(global_config_path_client)
|
|
|
|
if config_in_root:
|
|
DEFAULT_CONFIG_FILE = global_config_path_root
|
|
if os.path.exists(moblab_config_path_root):
|
|
DEFAULT_MOBLAB_FILE = moblab_config_path_root
|
|
else:
|
|
DEFAULT_MOBLAB_FILE = None
|
|
if os.path.exists(shadow_config_path_root):
|
|
DEFAULT_SHADOW_FILE = shadow_config_path_root
|
|
else:
|
|
DEFAULT_SHADOW_FILE = None
|
|
RUNNING_STAND_ALONE_CLIENT = False
|
|
elif config_in_client:
|
|
DEFAULT_CONFIG_FILE = global_config_path_client
|
|
DEFAULT_MOBLAB_FILE = None
|
|
DEFAULT_SHADOW_FILE = None
|
|
RUNNING_STAND_ALONE_CLIENT = True
|
|
else:
|
|
DEFAULT_CONFIG_FILE = None
|
|
DEFAULT_MOBLAB_FILE = None
|
|
DEFAULT_SHADOW_FILE = None
|
|
RUNNING_STAND_ALONE_CLIENT = True
|
|
|
|
|
|
class global_config_class(object):
|
|
"""Object to access config values."""
|
|
_NO_DEFAULT_SPECIFIED = object()
|
|
|
|
_config = None
|
|
config_file = DEFAULT_CONFIG_FILE
|
|
moblab_file=DEFAULT_MOBLAB_FILE
|
|
shadow_file = DEFAULT_SHADOW_FILE
|
|
running_stand_alone_client = RUNNING_STAND_ALONE_CLIENT
|
|
|
|
|
|
@property
|
|
def config(self):
|
|
"""ConfigParser instance.
|
|
|
|
If the instance dict doesn't have a config key, this descriptor
|
|
will be called to ensure the config file is parsed (setting the
|
|
config key in the instance dict as a side effect). Once the
|
|
instance dict has a config key, that value will be used in
|
|
preference.
|
|
"""
|
|
if self._config is None:
|
|
self.parse_config_file()
|
|
return self._config
|
|
|
|
|
|
@config.setter
|
|
def config(self, value):
|
|
"""Set config attribute.
|
|
|
|
@param value: value to set
|
|
"""
|
|
self._config = value
|
|
|
|
|
|
def check_stand_alone_client_run(self):
|
|
"""Check if this is a stand alone client that does not need config."""
|
|
return self.running_stand_alone_client
|
|
|
|
|
|
def set_config_files(self, config_file=DEFAULT_CONFIG_FILE,
|
|
shadow_file=DEFAULT_SHADOW_FILE,
|
|
moblab_file=DEFAULT_MOBLAB_FILE):
|
|
self.config_file = config_file
|
|
self.moblab_file = moblab_file
|
|
self.shadow_file = shadow_file
|
|
self._config = None
|
|
|
|
|
|
def _handle_no_value(self, section, key, default):
|
|
if default is self._NO_DEFAULT_SPECIFIED:
|
|
msg = ("Value '%s' not found in section '%s'" %
|
|
(key, section))
|
|
raise ConfigError(msg)
|
|
else:
|
|
return default
|
|
|
|
|
|
def get_section_as_dict(self, section):
|
|
"""Return a dict mapping section options to values.
|
|
|
|
This is useful if a config section is being used like a
|
|
dictionary. If the section is missing, return an empty dict.
|
|
|
|
This returns an OrderedDict, preserving the order of the options
|
|
in the section.
|
|
|
|
@param section: Section to get.
|
|
@return: OrderedDict
|
|
"""
|
|
if self.config.has_section(section):
|
|
return collections.OrderedDict(self.config.items(section))
|
|
else:
|
|
return collections.OrderedDict()
|
|
|
|
|
|
def get_section_values(self, section):
|
|
"""
|
|
Return a config parser object containing a single section of the
|
|
global configuration, that can be later written to a file object.
|
|
|
|
@param section: Section we want to turn into a config parser object.
|
|
@return: ConfigParser() object containing all the contents of section.
|
|
"""
|
|
cfgparser = seven.config_parser()
|
|
cfgparser.add_section(section)
|
|
for option, value in self.config.items(section):
|
|
cfgparser.set(section, option, value)
|
|
return cfgparser
|
|
|
|
|
|
def get_config_value(self, section, key, type=str,
|
|
default=_NO_DEFAULT_SPECIFIED, allow_blank=False):
|
|
"""Get a configuration value
|
|
|
|
@param section: Section the key is in.
|
|
@param key: The key to look up.
|
|
@param type: The expected type of the returned value.
|
|
@param default: A value to return in case the key couldn't be found.
|
|
@param allow_blank: If False, an empty string as a value is treated like
|
|
there was no value at all. If True, empty strings
|
|
will be returned like they were normal values.
|
|
|
|
@raises ConfigError: If the key could not be found and no default was
|
|
specified.
|
|
|
|
@return: The obtained value or default.
|
|
"""
|
|
try:
|
|
val = self.config.get(section, key)
|
|
except ConfigParser.Error:
|
|
return self._handle_no_value(section, key, default)
|
|
|
|
if not val.strip() and not allow_blank:
|
|
return self._handle_no_value(section, key, default)
|
|
|
|
return self._convert_value(key, section, val, type)
|
|
|
|
|
|
def get_config_value_regex(self, section, key_regex, type=str):
|
|
"""Get a dict of configs in given section with key matched to key-regex.
|
|
|
|
@param section: Section the key is in.
|
|
@param key_regex: The regex that key should match.
|
|
@param type: data type the value should have.
|
|
|
|
@return: A dictionary of key:value with key matching `key_regex`. Return
|
|
an empty dictionary if no matching key is found.
|
|
"""
|
|
configs = {}
|
|
for option, value in self.config.items(section):
|
|
if re.match(key_regex, option):
|
|
configs[option] = self._convert_value(option, section, value,
|
|
type)
|
|
return configs
|
|
|
|
|
|
# This order of parameters ensures this can be called similar to the normal
|
|
# get_config_value which is mostly called with (section, key, type).
|
|
def get_config_value_with_fallback(self, section, key, fallback_key,
|
|
type=str, fallback_section=None,
|
|
default=_NO_DEFAULT_SPECIFIED, **kwargs):
|
|
"""Get a configuration value if it exists, otherwise use fallback.
|
|
|
|
Tries to obtain a configuration value for a given key. If this value
|
|
does not exist, the value looked up under a different key will be
|
|
returned.
|
|
|
|
@param section: Section the key is in.
|
|
@param key: The key to look up.
|
|
@param fallback_key: The key to use in case the original key wasn't
|
|
found.
|
|
@param type: data type the value should have.
|
|
@param fallback_section: The section the fallback key resides in. In
|
|
case none is specified, the the same section as
|
|
for the primary key is used.
|
|
@param default: Value to return if values could neither be obtained for
|
|
the key nor the fallback key.
|
|
@param **kwargs: Additional arguments that should be passed to
|
|
get_config_value.
|
|
|
|
@raises ConfigError: If the fallback key doesn't exist and no default
|
|
was provided.
|
|
|
|
@return: The value that was looked up for the key. If that didn't
|
|
exist, the value looked up for the fallback key will be
|
|
returned. If that also didn't exist, default will be returned.
|
|
"""
|
|
if fallback_section is None:
|
|
fallback_section = section
|
|
|
|
try:
|
|
return self.get_config_value(section, key, type, **kwargs)
|
|
except ConfigError:
|
|
return self.get_config_value(fallback_section, fallback_key,
|
|
type, default=default, **kwargs)
|
|
|
|
|
|
def override_config_value(self, section, key, new_value):
|
|
"""Override a value from the config file with a new value.
|
|
|
|
@param section: Name of the section.
|
|
@param key: Name of the key.
|
|
@param new_value: new value.
|
|
"""
|
|
self.config.set(section, key, new_value)
|
|
|
|
|
|
def reset_config_values(self):
|
|
"""
|
|
Reset all values to those found in the config files (undoes all
|
|
overrides).
|
|
"""
|
|
self.parse_config_file()
|
|
|
|
|
|
def merge_configs(self, override_config):
|
|
"""Merge existing config values with the ones in given override_config.
|
|
|
|
@param override_config: Configs to override existing config values.
|
|
"""
|
|
# overwrite whats in config with whats in override_config
|
|
sections = override_config.sections()
|
|
for section in sections:
|
|
# add the section if need be
|
|
if not self.config.has_section(section):
|
|
self.config.add_section(section)
|
|
# now run through all options and set them
|
|
options = override_config.options(section)
|
|
for option in options:
|
|
val = override_config.get(section, option)
|
|
self.config.set(section, option, val)
|
|
|
|
|
|
def parse_config_file(self):
|
|
"""Parse config files."""
|
|
self.config = seven.config_parser()
|
|
if self.config_file and os.path.exists(self.config_file):
|
|
self.config.read(self.config_file)
|
|
else:
|
|
raise ConfigError('%s not found' % (self.config_file))
|
|
|
|
# If it's running in Moblab, read moblab config file if exists,
|
|
# overwrite the value in global config.
|
|
if (lsbrelease_utils.is_moblab() and self.moblab_file and
|
|
os.path.exists(self.moblab_file)):
|
|
moblab_config = seven.config_parser()
|
|
moblab_config.read(self.moblab_file)
|
|
# now we merge moblab into global
|
|
self.merge_configs(moblab_config)
|
|
|
|
# now also read the shadow file if there is one
|
|
# this will overwrite anything that is found in the
|
|
# other config
|
|
if self.shadow_file and os.path.exists(self.shadow_file):
|
|
shadow_config = seven.config_parser()
|
|
shadow_config.read(self.shadow_file)
|
|
# now we merge shadow into global
|
|
self.merge_configs(shadow_config)
|
|
|
|
|
|
# the values that are pulled from ini
|
|
# are strings. But we should attempt to
|
|
# convert them to other types if needed.
|
|
def _convert_value(self, key, section, value, value_type):
|
|
# strip off leading and trailing white space
|
|
sval = value.strip()
|
|
|
|
# if length of string is zero then return None
|
|
if len(sval) == 0:
|
|
if value_type == str:
|
|
return ""
|
|
elif value_type == bool:
|
|
return False
|
|
elif value_type == int:
|
|
return 0
|
|
elif value_type == float:
|
|
return 0.0
|
|
elif value_type == list:
|
|
return []
|
|
else:
|
|
return None
|
|
|
|
if value_type == bool:
|
|
if sval.lower() == "false":
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
if value_type == list:
|
|
# Split the string using ',' and return a list
|
|
return [val.strip() for val in sval.split(',')]
|
|
|
|
try:
|
|
conv_val = value_type(sval)
|
|
return conv_val
|
|
except:
|
|
msg = ("Could not convert %s value %r in section %s to type %s" %
|
|
(key, sval, section, value_type))
|
|
raise ConfigValueError(msg)
|
|
|
|
|
|
def get_sections(self):
|
|
"""Return a list of sections available."""
|
|
return self.config.sections()
|
|
|
|
|
|
# insure the class is a singleton. Now the symbol global_config
|
|
# will point to the one and only one instace of the class
|
|
global_config = global_config_class()
|
|
|
|
|
|
class FakeGlobalConfig(object):
|
|
"""Fake replacement for global_config singleton object.
|
|
|
|
Unittest will want to fake the global_config so that developers'
|
|
shadow_config doesn't leak into unittests. Provide a fake object for that
|
|
purpose.
|
|
|
|
"""
|
|
# pylint: disable=missing-docstring
|
|
|
|
def __init__(self):
|
|
self._config_info = {}
|
|
|
|
|
|
def set_config_value(self, section, key, value):
|
|
self._config_info[(section, key)] = value
|
|
|
|
|
|
def get_config_value(self, section, key, type=str,
|
|
default=None, allow_blank=False):
|
|
identifier = (section, key)
|
|
if identifier not in self._config_info:
|
|
return default
|
|
return self._config_info[identifier]
|
|
|
|
|
|
def parse_config_file(self):
|
|
pass
|