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