Home | History | Annotate | Download | only in common_lib
      1 """A singleton class for accessing global config values
      2 
      3 provides access to global configuration file
      4 """
      5 
      6 # The config values can be stored in 3 config files:
      7 #     global_config.ini
      8 #     moblab_config.ini
      9 #     shadow_config.ini
     10 # When the code is running in Moblab, config values in moblab config override
     11 # values in global config, and config values in shadow config override values
     12 # in both moblab and global config.
     13 # When the code is running in a non-Moblab host, moblab_config.ini is ignored.
     14 # Config values in shadow config will override values in global config.
     15 
     16 __author__ = 'raphtee (at] google.com (Travis Miller)'
     17 
     18 import collections
     19 import ConfigParser
     20 import os
     21 import re
     22 import sys
     23 
     24 from autotest_lib.client.common_lib import error
     25 from autotest_lib.client.common_lib import lsbrelease_utils
     26 
     27 class ConfigError(error.AutotestError):
     28     """Configuration error."""
     29     pass
     30 
     31 
     32 class ConfigValueError(ConfigError):
     33     """Configuration value error, raised when value failed to be converted to
     34     expected type."""
     35     pass
     36 
     37 
     38 
     39 common_lib_dir = os.path.dirname(sys.modules[__name__].__file__)
     40 client_dir = os.path.dirname(common_lib_dir)
     41 root_dir = os.path.dirname(client_dir)
     42 
     43 # Check if the config files are at autotest's root dir
     44 # This will happen if client is executing inside a full autotest tree, or if
     45 # other entry points are being executed
     46 global_config_path_root = os.path.join(root_dir, 'global_config.ini')
     47 moblab_config_path_root = os.path.join(root_dir, 'moblab_config.ini')
     48 shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini')
     49 config_in_root = os.path.exists(global_config_path_root)
     50 
     51 # Check if the config files are at autotest's client dir
     52 # This will happen if a client stand alone execution is happening
     53 global_config_path_client = os.path.join(client_dir, 'global_config.ini')
     54 config_in_client = os.path.exists(global_config_path_client)
     55 
     56 if config_in_root:
     57     DEFAULT_CONFIG_FILE = global_config_path_root
     58     if os.path.exists(moblab_config_path_root):
     59         DEFAULT_MOBLAB_FILE = moblab_config_path_root
     60     else:
     61         DEFAULT_MOBLAB_FILE = None
     62     if os.path.exists(shadow_config_path_root):
     63         DEFAULT_SHADOW_FILE = shadow_config_path_root
     64     else:
     65         DEFAULT_SHADOW_FILE = None
     66     RUNNING_STAND_ALONE_CLIENT = False
     67 elif config_in_client:
     68     DEFAULT_CONFIG_FILE = global_config_path_client
     69     DEFAULT_MOBLAB_FILE = None
     70     DEFAULT_SHADOW_FILE = None
     71     RUNNING_STAND_ALONE_CLIENT = True
     72 else:
     73     DEFAULT_CONFIG_FILE = None
     74     DEFAULT_MOBLAB_FILE = None
     75     DEFAULT_SHADOW_FILE = None
     76     RUNNING_STAND_ALONE_CLIENT = True
     77 
     78 
     79 class global_config_class(object):
     80     """Object to access config values."""
     81     _NO_DEFAULT_SPECIFIED = object()
     82 
     83     _config = None
     84     config_file = DEFAULT_CONFIG_FILE
     85     moblab_file=DEFAULT_MOBLAB_FILE
     86     shadow_file = DEFAULT_SHADOW_FILE
     87     running_stand_alone_client = RUNNING_STAND_ALONE_CLIENT
     88 
     89 
     90     @property
     91     def config(self):
     92         """ConfigParser instance.
     93 
     94         If the instance dict doesn't have a config key, this descriptor
     95         will be called to ensure the config file is parsed (setting the
     96         config key in the instance dict as a side effect).  Once the
     97         instance dict has a config key, that value will be used in
     98         preference.
     99         """
    100         if self._config is None:
    101             self.parse_config_file()
    102         return self._config
    103 
    104 
    105     @config.setter
    106     def config(self, value):
    107         """Set config attribute.
    108 
    109         @param value: value to set
    110         """
    111         self._config = value
    112 
    113 
    114     def check_stand_alone_client_run(self):
    115         """Check if this is a stand alone client that does not need config."""
    116         return self.running_stand_alone_client
    117 
    118 
    119     def set_config_files(self, config_file=DEFAULT_CONFIG_FILE,
    120                          shadow_file=DEFAULT_SHADOW_FILE,
    121                          moblab_file=DEFAULT_MOBLAB_FILE):
    122         self.config_file = config_file
    123         self.moblab_file = moblab_file
    124         self.shadow_file = shadow_file
    125         self._config = None
    126 
    127 
    128     def _handle_no_value(self, section, key, default):
    129         if default is self._NO_DEFAULT_SPECIFIED:
    130             msg = ("Value '%s' not found in section '%s'" %
    131                    (key, section))
    132             raise ConfigError(msg)
    133         else:
    134             return default
    135 
    136 
    137     def get_section_as_dict(self, section):
    138         """Return a dict mapping section options to values.
    139 
    140         This is useful if a config section is being used like a
    141         dictionary.  If the section is missing, return an empty dict.
    142 
    143         This returns an OrderedDict, preserving the order of the options
    144         in the section.
    145 
    146         @param section: Section to get.
    147         @return: OrderedDict
    148         """
    149         if self.config.has_section(section):
    150             return collections.OrderedDict(self.config.items(section))
    151         else:
    152             return collections.OrderedDict()
    153 
    154 
    155     def get_section_values(self, section):
    156         """
    157         Return a config parser object containing a single section of the
    158         global configuration, that can be later written to a file object.
    159 
    160         @param section: Section we want to turn into a config parser object.
    161         @return: ConfigParser() object containing all the contents of section.
    162         """
    163         cfgparser = ConfigParser.ConfigParser()
    164         cfgparser.add_section(section)
    165         for option, value in self.config.items(section):
    166             cfgparser.set(section, option, value)
    167         return cfgparser
    168 
    169 
    170     def get_config_value(self, section, key, type=str,
    171                          default=_NO_DEFAULT_SPECIFIED, allow_blank=False):
    172         """Get a configuration value
    173 
    174         @param section: Section the key is in.
    175         @param key: The key to look up.
    176         @param type: The expected type of the returned value.
    177         @param default: A value to return in case the key couldn't be found.
    178         @param allow_blank: If False, an empty string as a value is treated like
    179                             there was no value at all. If True, empty strings
    180                             will be returned like they were normal values.
    181 
    182         @raises ConfigError: If the key could not be found and no default was
    183                              specified.
    184 
    185         @return: The obtained value or default.
    186         """
    187         try:
    188             val = self.config.get(section, key)
    189         except ConfigParser.Error:
    190             return self._handle_no_value(section, key, default)
    191 
    192         if not val.strip() and not allow_blank:
    193             return self._handle_no_value(section, key, default)
    194 
    195         return self._convert_value(key, section, val, type)
    196 
    197 
    198     def get_config_value_regex(self, section, key_regex, type=str):
    199         """Get a dict of configs in given section with key matched to key-regex.
    200 
    201         @param section: Section the key is in.
    202         @param key_regex: The regex that key should match.
    203         @param type: data type the value should have.
    204 
    205         @return: A dictionary of key:value with key matching `key_regex`. Return
    206                  an empty dictionary if no matching key is found.
    207         """
    208         configs = {}
    209         for option, value in self.config.items(section):
    210             if re.match(key_regex, option):
    211                 configs[option] = self._convert_value(option, section, value,
    212                                                       type)
    213         return configs
    214 
    215 
    216     # This order of parameters ensures this can be called similar to the normal
    217     # get_config_value which is mostly called with (section, key, type).
    218     def get_config_value_with_fallback(self, section, key, fallback_key,
    219                                        type=str, fallback_section=None,
    220                                        default=_NO_DEFAULT_SPECIFIED, **kwargs):
    221         """Get a configuration value if it exists, otherwise use fallback.
    222 
    223         Tries to obtain a configuration value for a given key. If this value
    224         does not exist, the value looked up under a different key will be
    225         returned.
    226 
    227         @param section: Section the key is in.
    228         @param key: The key to look up.
    229         @param fallback_key: The key to use in case the original key wasn't
    230                              found.
    231         @param type: data type the value should have.
    232         @param fallback_section: The section the fallback key resides in. In
    233                                  case none is specified, the the same section as
    234                                  for the primary key is used.
    235         @param default: Value to return if values could neither be obtained for
    236                         the key nor the fallback key.
    237         @param **kwargs: Additional arguments that should be passed to
    238                          get_config_value.
    239 
    240         @raises ConfigError: If the fallback key doesn't exist and no default
    241                              was provided.
    242 
    243         @return: The value that was looked up for the key. If that didn't
    244                  exist, the value looked up for the fallback key will be
    245                  returned. If that also didn't exist, default will be returned.
    246         """
    247         if fallback_section is None:
    248             fallback_section = section
    249 
    250         try:
    251             return self.get_config_value(section, key, type, **kwargs)
    252         except ConfigError:
    253             return self.get_config_value(fallback_section, fallback_key,
    254                                          type, default=default, **kwargs)
    255 
    256 
    257     def override_config_value(self, section, key, new_value):
    258         """Override a value from the config file with a new value.
    259 
    260         @param section: Name of the section.
    261         @param key: Name of the key.
    262         @param new_value: new value.
    263         """
    264         self.config.set(section, key, new_value)
    265 
    266 
    267     def reset_config_values(self):
    268         """
    269         Reset all values to those found in the config files (undoes all
    270         overrides).
    271         """
    272         self.parse_config_file()
    273 
    274 
    275     def merge_configs(self, override_config):
    276         """Merge existing config values with the ones in given override_config.
    277 
    278         @param override_config: Configs to override existing config values.
    279         """
    280         # overwrite whats in config with whats in override_config
    281         sections = override_config.sections()
    282         for section in sections:
    283             # add the section if need be
    284             if not self.config.has_section(section):
    285                 self.config.add_section(section)
    286             # now run through all options and set them
    287             options = override_config.options(section)
    288             for option in options:
    289                 val = override_config.get(section, option)
    290                 self.config.set(section, option, val)
    291 
    292 
    293     def parse_config_file(self):
    294         """Parse config files."""
    295         self.config = ConfigParser.ConfigParser()
    296         if self.config_file and os.path.exists(self.config_file):
    297             self.config.read(self.config_file)
    298         else:
    299             raise ConfigError('%s not found' % (self.config_file))
    300 
    301         # If it's running in Moblab, read moblab config file if exists,
    302         # overwrite the value in global config.
    303         if (lsbrelease_utils.is_moblab() and self.moblab_file and
    304             os.path.exists(self.moblab_file)):
    305             moblab_config = ConfigParser.ConfigParser()
    306             moblab_config.read(self.moblab_file)
    307             # now we merge moblab into global
    308             self.merge_configs(moblab_config)
    309 
    310         # now also read the shadow file if there is one
    311         # this will overwrite anything that is found in the
    312         # other config
    313         if self.shadow_file and os.path.exists(self.shadow_file):
    314             shadow_config = ConfigParser.ConfigParser()
    315             shadow_config.read(self.shadow_file)
    316             # now we merge shadow into global
    317             self.merge_configs(shadow_config)
    318 
    319 
    320     # the values that are pulled from ini
    321     # are strings.  But we should attempt to
    322     # convert them to other types if needed.
    323     def _convert_value(self, key, section, value, value_type):
    324         # strip off leading and trailing white space
    325         sval = value.strip()
    326 
    327         # if length of string is zero then return None
    328         if len(sval) == 0:
    329             if value_type == str:
    330                 return ""
    331             elif value_type == bool:
    332                 return False
    333             elif value_type == int:
    334                 return 0
    335             elif value_type == float:
    336                 return 0.0
    337             elif value_type == list:
    338                 return []
    339             else:
    340                 return None
    341 
    342         if value_type == bool:
    343             if sval.lower() == "false":
    344                 return False
    345             else:
    346                 return True
    347 
    348         if value_type == list:
    349             # Split the string using ',' and return a list
    350             return [val.strip() for val in sval.split(',')]
    351 
    352         try:
    353             conv_val = value_type(sval)
    354             return conv_val
    355         except:
    356             msg = ("Could not convert %s value %r in section %s to type %s" %
    357                     (key, sval, section, value_type))
    358             raise ConfigValueError(msg)
    359 
    360 
    361     def get_sections(self):
    362         """Return a list of sections available."""
    363         return self.config.sections()
    364 
    365 
    366 # insure the class is a singleton.  Now the symbol global_config
    367 # will point to the one and only one instace of the class
    368 global_config = global_config_class()
    369 
    370 
    371 class FakeGlobalConfig(object):
    372     """Fake replacement for global_config singleton object.
    373 
    374     Unittest will want to fake the global_config so that developers'
    375     shadow_config doesn't leak into unittests. Provide a fake object for that
    376     purpose.
    377 
    378     """
    379     # pylint: disable=missing-docstring
    380 
    381     def __init__(self):
    382         self._config_info = {}
    383 
    384 
    385     def set_config_value(self, section, key, value):
    386         self._config_info[(section, key)] = value
    387 
    388 
    389     def get_config_value(self, section, key, type=str,
    390                          default=None, allow_blank=False):
    391         identifier = (section, key)
    392         if identifier not in self._config_info:
    393             return default
    394         return self._config_info[identifier]
    395 
    396 
    397     def parse_config_file(self):
    398         pass
    399