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