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