global_config.py revision 773a86ebaa2e4e4cce213b8553aeb4b30c1f3a9d
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 os, sys, ConfigParser
19from autotest_lib.client.common_lib import error
20from autotest_lib.client.common_lib import lsbrelease_utils
21
22class ConfigError(error.AutotestError):
23    """Configuration error."""
24    pass
25
26
27class ConfigValueError(ConfigError):
28    """Configuration value error, raised when value failed to be converted to
29    expected type."""
30    pass
31
32
33
34common_lib_dir = os.path.dirname(sys.modules[__name__].__file__)
35client_dir = os.path.dirname(common_lib_dir)
36root_dir = os.path.dirname(client_dir)
37
38# Check if the config files are at autotest's root dir
39# This will happen if client is executing inside a full autotest tree, or if
40# other entry points are being executed
41global_config_path_root = os.path.join(root_dir, 'global_config.ini')
42moblab_config_path_root = os.path.join(root_dir, 'moblab_config.ini')
43shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini')
44config_in_root = os.path.exists(global_config_path_root)
45
46# Check if the config files are at autotest's client dir
47# This will happen if a client stand alone execution is happening
48global_config_path_client = os.path.join(client_dir, 'global_config.ini')
49config_in_client = os.path.exists(global_config_path_client)
50
51if config_in_root:
52    DEFAULT_CONFIG_FILE = global_config_path_root
53    if os.path.exists(moblab_config_path_root):
54        DEFAULT_MOBLAB_FILE = moblab_config_path_root
55    else:
56        DEFAULT_MOBLAB_FILE = None
57    if os.path.exists(shadow_config_path_root):
58        DEFAULT_SHADOW_FILE = shadow_config_path_root
59    else:
60        DEFAULT_SHADOW_FILE = None
61    RUNNING_STAND_ALONE_CLIENT = False
62elif config_in_client:
63    DEFAULT_CONFIG_FILE = global_config_path_client
64    DEFAULT_MOBLAB_FILE = None
65    DEFAULT_SHADOW_FILE = None
66    RUNNING_STAND_ALONE_CLIENT = True
67else:
68    DEFAULT_CONFIG_FILE = None
69    DEFAULT_MOBLAB_FILE = None
70    DEFAULT_SHADOW_FILE = None
71    RUNNING_STAND_ALONE_CLIENT = True
72
73class global_config_class(object):
74    """Object to access config values."""
75    _NO_DEFAULT_SPECIFIED = object()
76
77    config = None
78    config_file = DEFAULT_CONFIG_FILE
79    moblab_file=DEFAULT_MOBLAB_FILE
80    shadow_file = DEFAULT_SHADOW_FILE
81    running_stand_alone_client = RUNNING_STAND_ALONE_CLIENT
82
83
84    def check_stand_alone_client_run(self):
85        """Check if this is a stand alone client that does not need config."""
86        return self.running_stand_alone_client
87
88
89    def set_config_files(self, config_file=DEFAULT_CONFIG_FILE,
90                         shadow_file=DEFAULT_SHADOW_FILE,
91                         moblab_file=DEFAULT_MOBLAB_FILE):
92        self.config_file = config_file
93        self.moblab_file = moblab_file
94        self.shadow_file = shadow_file
95        self.config = None
96
97
98    def _handle_no_value(self, section, key, default):
99        if default is self._NO_DEFAULT_SPECIFIED:
100            msg = ("Value '%s' not found in section '%s'" %
101                   (key, section))
102            raise ConfigError(msg)
103        else:
104            return default
105
106
107    def get_section_values(self, section):
108        """
109        Return a config parser object containing a single section of the
110        global configuration, that can be later written to a file object.
111
112        @param section: Section we want to turn into a config parser object.
113        @return: ConfigParser() object containing all the contents of section.
114        """
115        cfgparser = ConfigParser.ConfigParser()
116        cfgparser.add_section(section)
117        for option, value in self.config.items(section):
118            cfgparser.set(section, option, value)
119        return cfgparser
120
121
122    def get_config_value(self, section, key, type=str,
123                         default=_NO_DEFAULT_SPECIFIED, allow_blank=False):
124        """Get a configuration value
125
126        @param section: Section the key is in.
127        @param key: The key to look up.
128        @param type: The expected type of the returned value.
129        @param default: A value to return in case the key couldn't be found.
130        @param allow_blank: If False, an empty string as a value is treated like
131                            there was no value at all. If True, empty strings
132                            will be returned like they were normal values.
133
134        @raises ConfigError: If the key could not be found and no default was
135                             specified.
136
137        @return: The obtained value or default.
138        """
139        self._ensure_config_parsed()
140
141        try:
142            val = self.config.get(section, key)
143        except ConfigParser.Error:
144            return self._handle_no_value(section, key, default)
145
146        if not val.strip() and not allow_blank:
147            return self._handle_no_value(section, key, default)
148
149        return self._convert_value(key, section, val, type)
150
151
152    # This order of parameters ensures this can be called similar to the normal
153    # get_config_value which is mostly called with (section, key, type).
154    def get_config_value_with_fallback(self, section, key, fallback_key,
155                                       type=str, fallback_section=None,
156                                       default=_NO_DEFAULT_SPECIFIED, **kwargs):
157        """Get a configuration value if it exists, otherwise use fallback.
158
159        Tries to obtain a configuration value for a given key. If this value
160        does not exist, the value looked up under a different key will be
161        returned.
162
163        @param section: Section the key is in.
164        @param key: The key to look up.
165        @param fallback_key: The key to use in case the original key wasn't
166                             found.
167        @param type: data type the value should have.
168        @param fallback_section: The section the fallback key resides in. In
169                                 case none is specified, the the same section as
170                                 for the primary key is used.
171        @param default: Value to return if values could neither be obtained for
172                        the key nor the fallback key.
173        @param **kwargs: Additional arguments that should be passed to
174                         get_config_value.
175
176        @raises ConfigError: If the fallback key doesn't exist and no default
177                             was provided.
178
179        @return: The value that was looked up for the key. If that didn't
180                 exist, the value looked up for the fallback key will be
181                 returned. If that also didn't exist, default will be returned.
182        """
183        if fallback_section is None:
184            fallback_section = section
185
186        try:
187            return self.get_config_value(section, key, type, **kwargs)
188        except ConfigError:
189            return self.get_config_value(fallback_section, fallback_key,
190                                         type, default=default, **kwargs)
191
192
193    def override_config_value(self, section, key, new_value):
194        """Override a value from the config file with a new value.
195
196        @param section: Name of the section.
197        @param key: Name of the key.
198        @param new_value: new value.
199        """
200        self._ensure_config_parsed()
201        self.config.set(section, key, new_value)
202
203
204    def reset_config_values(self):
205        """
206        Reset all values to those found in the config files (undoes all
207        overrides).
208        """
209        self.parse_config_file()
210
211
212    def _ensure_config_parsed(self):
213        """Make sure config files are parsed.
214        """
215        if self.config is None:
216            self.parse_config_file()
217
218
219    def merge_configs(self, override_config):
220        """Merge existing config values with the ones in given override_config.
221
222        @param override_config: Configs to override existing config values.
223        """
224        # overwrite whats in config with whats in override_config
225        sections = override_config.sections()
226        for section in sections:
227            # add the section if need be
228            if not self.config.has_section(section):
229                self.config.add_section(section)
230            # now run through all options and set them
231            options = override_config.options(section)
232            for option in options:
233                val = override_config.get(section, option)
234                self.config.set(section, option, val)
235
236
237    def parse_config_file(self):
238        """Parse config files."""
239        self.config = ConfigParser.ConfigParser()
240        if self.config_file and os.path.exists(self.config_file):
241            self.config.read(self.config_file)
242        else:
243            raise ConfigError('%s not found' % (self.config_file))
244
245        # If it's running in Moblab, read moblab config file if exists,
246        # overwrite the value in global config.
247        if (lsbrelease_utils.is_moblab() and self.moblab_file and
248            os.path.exists(self.moblab_file)):
249            moblab_config = ConfigParser.ConfigParser()
250            moblab_config.read(self.moblab_file)
251            # now we merge moblab into global
252            self.merge_configs(moblab_config)
253
254        # now also read the shadow file if there is one
255        # this will overwrite anything that is found in the
256        # other config
257        if self.shadow_file and os.path.exists(self.shadow_file):
258            shadow_config = ConfigParser.ConfigParser()
259            shadow_config.read(self.shadow_file)
260            # now we merge shadow into global
261            self.merge_configs(shadow_config)
262
263
264    # the values that are pulled from ini
265    # are strings.  But we should attempt to
266    # convert them to other types if needed.
267    def _convert_value(self, key, section, value, value_type):
268        # strip off leading and trailing white space
269        sval = value.strip()
270
271        # if length of string is zero then return None
272        if len(sval) == 0:
273            if value_type == str:
274                return ""
275            elif value_type == bool:
276                return False
277            elif value_type == int:
278                return 0
279            elif value_type == float:
280                return 0.0
281            elif value_type == list:
282                return []
283            else:
284                return None
285
286        if value_type == bool:
287            if sval.lower() == "false":
288                return False
289            else:
290                return True
291
292        if value_type == list:
293            # Split the string using ',' and return a list
294            return [val.strip() for val in sval.split(',')]
295
296        try:
297            conv_val = value_type(sval)
298            return conv_val
299        except:
300            msg = ("Could not convert %s value %r in section %s to type %s" %
301                    (key, sval, section, value_type))
302            raise ConfigValueError(msg)
303
304
305    def get_sections(self):
306        """Return a list of sections available."""
307        self._ensure_config_parsed()
308        return self.config.sections()
309
310
311# insure the class is a singleton.  Now the symbol global_config
312# will point to the one and only one instace of the class
313global_config = global_config_class()
314