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 ConfigParser 19import os 20import re 21import sys 22 23from autotest_lib.client.common_lib import error 24from autotest_lib.client.common_lib import lsbrelease_utils 25 26class ConfigError(error.AutotestError): 27 """Configuration error.""" 28 pass 29 30 31class ConfigValueError(ConfigError): 32 """Configuration value error, raised when value failed to be converted to 33 expected type.""" 34 pass 35 36 37 38common_lib_dir = os.path.dirname(sys.modules[__name__].__file__) 39client_dir = os.path.dirname(common_lib_dir) 40root_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 45global_config_path_root = os.path.join(root_dir, 'global_config.ini') 46moblab_config_path_root = os.path.join(root_dir, 'moblab_config.ini') 47shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini') 48config_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 52global_config_path_client = os.path.join(client_dir, 'global_config.ini') 53config_in_client = os.path.exists(global_config_path_client) 54 55if 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 66elif 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 71else: 72 DEFAULT_CONFIG_FILE = None 73 DEFAULT_MOBLAB_FILE = None 74 DEFAULT_SHADOW_FILE = None 75 RUNNING_STAND_ALONE_CLIENT = True 76 77class 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 336global_config = global_config_class() 337