Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / ceph-volume / ceph_volume / configuration.py
1 try:
2     import configparser
3 except ImportError:
4     import ConfigParser as configparser
5 import contextlib
6 import logging
7 import os
8 import re
9 from ceph_volume import terminal
10 from ceph_volume import exceptions
11
12
13 logger = logging.getLogger(__name__)
14
15
16 class _TrimIndentFile(object):
17     """
18     This is used to take a file-like object and removes any
19     leading tabs from each line when it's read. This is important
20     because some ceph configuration files include tabs which break
21     ConfigParser.
22     """
23     def __init__(self, fp):
24         self.fp = fp
25
26     def readline(self):
27         line = self.fp.readline()
28         return line.lstrip(' \t')
29
30     def __iter__(self):
31         return iter(self.readline, '')
32
33
34 def load(abspath=None):
35     if not os.path.exists(abspath):
36         raise exceptions.ConfigurationError(abspath=abspath)
37
38     parser = Conf()
39
40     try:
41         ceph_file = open(abspath)
42         trimmed_conf = _TrimIndentFile(ceph_file)
43         with contextlib.closing(ceph_file):
44             parser.readfp(trimmed_conf)
45             return parser
46     except configparser.ParsingError as error:
47         logger.exception('Unable to parse INI-style file: %s' % abspath)
48         terminal.error(str(error))
49         raise RuntimeError('Unable to read configuration file: %s' % abspath)
50
51
52 class Conf(configparser.SafeConfigParser):
53     """
54     Subclasses from SafeConfigParser to give a few helpers for Ceph
55     configuration.
56     """
57
58     def read_path(self, path):
59         self.path = path
60         return self.read(path)
61
62     def is_valid(self):
63         try:
64             self.get('global', 'fsid')
65         except (configparser.NoSectionError, configparser.NoOptionError):
66             raise exceptions.ConfigurationKeyError('global', 'fsid')
67
68     def get_safe(self, section, key, default=None):
69         """
70         Attempt to get a configuration value from a certain section
71         in a ``cfg`` object but returning None if not found. Avoids the need
72         to be doing try/except {ConfigParser Exceptions} every time.
73         """
74         self.is_valid()
75         try:
76             return self.get(section, key)
77         except (configparser.NoSectionError, configparser.NoOptionError):
78             return default
79
80     def get_list(self, section, key, default=None, split=','):
81         """
82         Assumes that the value for a given key is going to be a list separated
83         by commas. It gets rid of trailing comments.  If just one item is
84         present it returns a list with a single item, if no key is found an
85         empty list is returned.
86
87         Optionally split on other characters besides ',' and return a fallback
88         value if no items are found.
89         """
90         self.is_valid()
91         value = self.get_safe(section, key, [])
92         if value == []:
93             if default is not None:
94                 return default
95             return value
96
97         # strip comments
98         value = re.split(r'\s+#', value)[0]
99
100         # split on commas
101         value = value.split(split)
102
103         # strip spaces
104         return [x.strip() for x in value]
105
106     # XXX Almost all of it lifted from the original ConfigParser._read method,
107     # except for the parsing of '#' in lines. This is only a problem in Python 2.7, and can be removed
108     # once tooling is Python3 only with `Conf(inline_comment_prefixes=('#',';'))`
109     def _read(self, fp, fpname):
110         """Parse a sectioned setup file.
111
112         The sections in setup file contains a title line at the top,
113         indicated by a name in square brackets (`[]'), plus key/value
114         options lines, indicated by `name: value' format lines.
115         Continuations are represented by an embedded newline then
116         leading whitespace.  Blank lines, lines beginning with a '#',
117         and just about everything else are ignored.
118         """
119         cursect = None                        # None, or a dictionary
120         optname = None
121         lineno = 0
122         e = None                              # None, or an exception
123         while True:
124             line = fp.readline()
125             if not line:
126                 break
127             lineno = lineno + 1
128             # comment or blank line?
129             if line.strip() == '' or line[0] in '#;':
130                 continue
131             if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
132                 # no leading whitespace
133                 continue
134             # continuation line?
135             if line[0].isspace() and cursect is not None and optname:
136                 value = line.strip()
137                 if value:
138                     cursect[optname].append(value)
139             # a section header or option header?
140             else:
141                 # is it a section header?
142                 mo = self.SECTCRE.match(line)
143                 if mo:
144                     sectname = mo.group('header')
145                     if sectname in self._sections:
146                         cursect = self._sections[sectname]
147                     elif sectname == 'DEFAULT':
148                         cursect = self._defaults
149                     else:
150                         cursect = self._dict()
151                         cursect['__name__'] = sectname
152                         self._sections[sectname] = cursect
153                     # So sections can't start with a continuation line
154                     optname = None
155                 # no section header in the file?
156                 elif cursect is None:
157                     raise configparser.MissingSectionHeaderError(fpname, lineno, line)
158                 # an option line?
159                 else:
160                     mo = self._optcre.match(line)
161                     if mo:
162                         optname, vi, optval = mo.group('option', 'vi', 'value')
163                         optname = self.optionxform(optname.rstrip())
164                         # This check is fine because the OPTCRE cannot
165                         # match if it would set optval to None
166                         if optval is not None:
167                             # XXX Added support for '#' inline comments
168                             if vi in ('=', ':') and (';' in optval or '#' in optval):
169                                 # strip comments
170                                 optval = re.split(r'\s+(;|#)', optval)[0]
171                                 # if what is left is comment as a value, fallback to an empty string
172                                 # that is: `foo = ;` would mean `foo` is '', which brings parity with
173                                 # what ceph-conf tool does
174                                 if optval in [';','#']:
175                                     optval = ''
176                             optval = optval.strip()
177                             # allow empty values
178                             if optval == '""':
179                                 optval = ''
180                             cursect[optname] = [optval]
181                         else:
182                             # valueless option handling
183                             cursect[optname] = optval
184                     else:
185                         # a non-fatal parsing error occurred.  set up the
186                         # exception but keep going. the exception will be
187                         # raised at the end of the file and will contain a
188                         # list of all bogus lines
189                         if not e:
190                             e = configparser.ParsingError(fpname)
191                         e.append(lineno, repr(line))
192         # if any parsing errors occurred, raise an exception
193         if e:
194             raise e
195
196         # join the multi-line values collected while reading
197         all_sections = [self._defaults]
198         all_sections.extend(self._sections.values())
199         for options in all_sections:
200             for name, val in options.items():
201                 if isinstance(val, list):
202                     options[name] = '\n'.join(val)