xref: /aosp_15_r20/external/autotest/client/common_lib/global_config.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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