1"""A singleton class for accessing global config values 2 3provides 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@google.com (Travis Miller)' 17 18import collections 19import ConfigParser 20import os 21import re 22import sys 23 24from autotest_lib.client.common_lib import error 25from autotest_lib.client.common_lib import lsbrelease_utils 26 27class ConfigError(error.AutotestError): 28 """Configuration error.""" 29 pass 30 31 32class ConfigValueError(ConfigError): 33 """Configuration value error, raised when value failed to be converted to 34 expected type.""" 35 pass 36 37 38 39common_lib_dir = os.path.dirname(sys.modules[__name__].__file__) 40client_dir = os.path.dirname(common_lib_dir) 41root_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 46global_config_path_root = os.path.join(root_dir, 'global_config.ini') 47moblab_config_path_root = os.path.join(root_dir, 'moblab_config.ini') 48shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini') 49config_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 53global_config_path_client = os.path.join(client_dir, 'global_config.ini') 54config_in_client = os.path.exists(global_config_path_client) 55 56if 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 67elif 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 72else: 73 DEFAULT_CONFIG_FILE = None 74 DEFAULT_MOBLAB_FILE = None 75 DEFAULT_SHADOW_FILE = None 76 RUNNING_STAND_ALONE_CLIENT = True 77 78 79class 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 368global_config = global_config_class() 369 370 371class 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