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