initial code repo
[stor4nfv.git] / src / ceph / src / ceph-volume / ceph_volume / configuration.py
diff --git a/src/ceph/src/ceph-volume/ceph_volume/configuration.py b/src/ceph/src/ceph-volume/ceph_volume/configuration.py
new file mode 100644 (file)
index 0000000..f515b8c
--- /dev/null
@@ -0,0 +1,202 @@
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+import contextlib
+import logging
+import os
+import re
+from ceph_volume import terminal
+from ceph_volume import exceptions
+
+
+logger = logging.getLogger(__name__)
+
+
+class _TrimIndentFile(object):
+    """
+    This is used to take a file-like object and removes any
+    leading tabs from each line when it's read. This is important
+    because some ceph configuration files include tabs which break
+    ConfigParser.
+    """
+    def __init__(self, fp):
+        self.fp = fp
+
+    def readline(self):
+        line = self.fp.readline()
+        return line.lstrip(' \t')
+
+    def __iter__(self):
+        return iter(self.readline, '')
+
+
+def load(abspath=None):
+    if not os.path.exists(abspath):
+        raise exceptions.ConfigurationError(abspath=abspath)
+
+    parser = Conf()
+
+    try:
+        ceph_file = open(abspath)
+        trimmed_conf = _TrimIndentFile(ceph_file)
+        with contextlib.closing(ceph_file):
+            parser.readfp(trimmed_conf)
+            return parser
+    except configparser.ParsingError as error:
+        logger.exception('Unable to parse INI-style file: %s' % abspath)
+        terminal.error(str(error))
+        raise RuntimeError('Unable to read configuration file: %s' % abspath)
+
+
+class Conf(configparser.SafeConfigParser):
+    """
+    Subclasses from SafeConfigParser to give a few helpers for Ceph
+    configuration.
+    """
+
+    def read_path(self, path):
+        self.path = path
+        return self.read(path)
+
+    def is_valid(self):
+        try:
+            self.get('global', 'fsid')
+        except (configparser.NoSectionError, configparser.NoOptionError):
+            raise exceptions.ConfigurationKeyError('global', 'fsid')
+
+    def get_safe(self, section, key, default=None):
+        """
+        Attempt to get a configuration value from a certain section
+        in a ``cfg`` object but returning None if not found. Avoids the need
+        to be doing try/except {ConfigParser Exceptions} every time.
+        """
+        self.is_valid()
+        try:
+            return self.get(section, key)
+        except (configparser.NoSectionError, configparser.NoOptionError):
+            return default
+
+    def get_list(self, section, key, default=None, split=','):
+        """
+        Assumes that the value for a given key is going to be a list separated
+        by commas. It gets rid of trailing comments.  If just one item is
+        present it returns a list with a single item, if no key is found an
+        empty list is returned.
+
+        Optionally split on other characters besides ',' and return a fallback
+        value if no items are found.
+        """
+        self.is_valid()
+        value = self.get_safe(section, key, [])
+        if value == []:
+            if default is not None:
+                return default
+            return value
+
+        # strip comments
+        value = re.split(r'\s+#', value)[0]
+
+        # split on commas
+        value = value.split(split)
+
+        # strip spaces
+        return [x.strip() for x in value]
+
+    # XXX Almost all of it lifted from the original ConfigParser._read method,
+    # except for the parsing of '#' in lines. This is only a problem in Python 2.7, and can be removed
+    # once tooling is Python3 only with `Conf(inline_comment_prefixes=('#',';'))`
+    def _read(self, fp, fpname):
+        """Parse a sectioned setup file.
+
+        The sections in setup file contains a title line at the top,
+        indicated by a name in square brackets (`[]'), plus key/value
+        options lines, indicated by `name: value' format lines.
+        Continuations are represented by an embedded newline then
+        leading whitespace.  Blank lines, lines beginning with a '#',
+        and just about everything else are ignored.
+        """
+        cursect = None                        # None, or a dictionary
+        optname = None
+        lineno = 0
+        e = None                              # None, or an exception
+        while True:
+            line = fp.readline()
+            if not line:
+                break
+            lineno = lineno + 1
+            # comment or blank line?
+            if line.strip() == '' or line[0] in '#;':
+                continue
+            if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+                # no leading whitespace
+                continue
+            # continuation line?
+            if line[0].isspace() and cursect is not None and optname:
+                value = line.strip()
+                if value:
+                    cursect[optname].append(value)
+            # a section header or option header?
+            else:
+                # is it a section header?
+                mo = self.SECTCRE.match(line)
+                if mo:
+                    sectname = mo.group('header')
+                    if sectname in self._sections:
+                        cursect = self._sections[sectname]
+                    elif sectname == 'DEFAULT':
+                        cursect = self._defaults
+                    else:
+                        cursect = self._dict()
+                        cursect['__name__'] = sectname
+                        self._sections[sectname] = cursect
+                    # So sections can't start with a continuation line
+                    optname = None
+                # no section header in the file?
+                elif cursect is None:
+                    raise configparser.MissingSectionHeaderError(fpname, lineno, line)
+                # an option line?
+                else:
+                    mo = self._optcre.match(line)
+                    if mo:
+                        optname, vi, optval = mo.group('option', 'vi', 'value')
+                        optname = self.optionxform(optname.rstrip())
+                        # This check is fine because the OPTCRE cannot
+                        # match if it would set optval to None
+                        if optval is not None:
+                            # XXX Added support for '#' inline comments
+                            if vi in ('=', ':') and (';' in optval or '#' in optval):
+                                # strip comments
+                                optval = re.split(r'\s+(;|#)', optval)[0]
+                                # if what is left is comment as a value, fallback to an empty string
+                                # that is: `foo = ;` would mean `foo` is '', which brings parity with
+                                # what ceph-conf tool does
+                                if optval in [';','#']:
+                                    optval = ''
+                            optval = optval.strip()
+                            # allow empty values
+                            if optval == '""':
+                                optval = ''
+                            cursect[optname] = [optval]
+                        else:
+                            # valueless option handling
+                            cursect[optname] = optval
+                    else:
+                        # a non-fatal parsing error occurred.  set up the
+                        # exception but keep going. the exception will be
+                        # raised at the end of the file and will contain a
+                        # list of all bogus lines
+                        if not e:
+                            e = configparser.ParsingError(fpname)
+                        e.append(lineno, repr(line))
+        # if any parsing errors occurred, raise an exception
+        if e:
+            raise e
+
+        # join the multi-line values collected while reading
+        all_sections = [self._defaults]
+        all_sections.extend(self._sections.values())
+        for options in all_sections:
+            for name, val in options.items():
+                if isinstance(val, list):
+                    options[name] = '\n'.join(val)