• 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 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