• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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