diff options
Diffstat (limited to 'geo-replication/syncdaemon/configinterface.py.in')
| -rw-r--r-- | geo-replication/syncdaemon/configinterface.py.in | 352 | 
1 files changed, 352 insertions, 0 deletions
diff --git a/geo-replication/syncdaemon/configinterface.py.in b/geo-replication/syncdaemon/configinterface.py.in new file mode 100644 index 00000000000..acb51486a10 --- /dev/null +++ b/geo-replication/syncdaemon/configinterface.py.in @@ -0,0 +1,352 @@ +# +# Copyright (c) 2011-2014 Red Hat, Inc. <http://www.redhat.com> +# This file is part of GlusterFS. + +# This file is licensed to you under your choice of the GNU Lesser +# General Public License, version 3 or any later version (LGPLv3 or +# later), or the GNU General Public License, version 2 (GPLv2), in all +# cases as published by the Free Software Foundation. +# + +try: +    import ConfigParser +except ImportError: +    # py 3 +    import configparser as ConfigParser +import re +from string import Template +import os +import errno +import sys +from stat import ST_DEV, ST_INO, ST_MTIME +import tempfile +import shutil + +from syncdutils import escape, unescape, norm, update_file, GsyncdError + +SECT_ORD = '__section_order__' +SECT_META = '__meta__' +config_version = 2.0 + +re_type = type(re.compile('')) + + +# (SECTION, OPTION, OLD VALUE, NEW VALUE) +CONFIGS = ( +    ("peersrx . .", +     "georep_session_working_dir", +     "", +     "@GLUSTERD_WORKDIR@/geo-replication/${mastervol}_${remotehost}_" +     "${slavevol}/"), +    ("peersrx .", +     "gluster_params", +     "aux-gfid-mount xlator-option=\*-dht.assert-no-child-down=true", +     "aux-gfid-mount"), +    ("peersrx . .", +     "ssh_command_tar", +     "", +     "ssh -oPasswordAuthentication=no -oStrictHostKeyChecking=no " +     "-i @GLUSTERD_WORKDIR@/geo-replication/tar_ssh.pem"), +    ("peersrx . .", +     "changelog_log_file", +     "", +     "${iprefix}/log/glusterfs/geo-replication/${mastervol}" +     "/${eSlave}${local_id}-changes.log"), +    ("peersrx . .", +     "working_dir", +     "@localstatedir@/run/gluster/${mastervol}/${eSlave}", +     "${iprefix}/lib/misc/glusterfsd/${mastervol}/${eSlave}"), +) + + +def upgrade_config_file(path): +    config_change = False +    config = ConfigParser.RawConfigParser() +    config.read(path) + +    for sec, opt, oldval, newval in CONFIGS: +        try: +            val = config.get(sec, opt) +        except ConfigParser.NoOptionError: +            # if new config opt not exists +            config_change = True +            config.set(sec, opt, newval) +            continue +        except ConfigParser.Error: +            """ +            When gsyncd invoked at the time of create, config file +            will not be their. Ignore any ConfigParser errors +            """ +            continue + +        if val == newval: +            # value is same as new val +            continue + +        if val == oldval: +            # config value needs update +            config_change = True +            config.set(sec, opt, newval) + +    if config_change: +        tempConfigFile = tempfile.NamedTemporaryFile(mode="wb", delete=False) +        with open(tempConfigFile.name, 'wb') as configFile: +            config.write(configFile) + +        # If src and dst are two different file system, then os.rename +        # fails, In this case if temp file created in /tmp and if /tmp is +        # seperate fs then os.rename gives following error, so use shutil +        # OSError: [Errno 18] Invalid cross-device link +        # mail.python.org/pipermail/python-list/2005-February/342893.html +        shutil.move(tempConfigFile.name, path) + + +class MultiDict(object): + +    """a virtual dict-like class which functions as the union +    of underlying dicts""" + +    def __init__(self, *dd): +        self.dicts = dd + +    def __getitem__(self, key): +        val = None +        for d in self.dicts: +            if d.get(key) is not None: +                val = d[key] +        if val is None: +            raise KeyError(key) +        return val + + +class GConffile(object): + +    """A high-level interface to ConfigParser which flattens the two-tiered +       config layout by implenting automatic section dispatch based on initial +       parameters. + +    Also ensure section ordering in terms of their time of addition -- a compat +    hack for Python < 2.7. +    """ + +    def _normconfig(self): +        """normalize config keys by s/-/_/g""" +        for n, s in self.config._sections.items(): +            if n.find('__') == 0: +                continue +            s2 = type(s)() +            for k, v in s.items(): +                if k.find('__') != 0: +                    k = norm(k) +                s2[k] = v +            self.config._sections[n] = s2 + +    def __init__(self, path, peers, *dd): +        """ +        - .path: location of config file +        - .config: underlying ConfigParser instance +        - .peers: on behalf of whom we flatten .config +          (master, or master-slave url pair) +        - .auxdicts: template subtituents +        """ +        self.peers = peers +        self.path = path +        self.auxdicts = dd +        self.config = ConfigParser.RawConfigParser() +        self.config.read(path) +        self.dev, self.ino, self.mtime = -1, -1, -1 +        self._normconfig() + +    def _load(self): +        try: +            sres = os.stat(self.path) +            self.dev = sres[ST_DEV] +            self.ino = sres[ST_INO] +            self.mtime = sres[ST_MTIME] +        except (OSError, IOError): +            if sys.exc_info()[1].errno == errno.ENOENT: +                sres = None + +        self.config = ConfigParser.RawConfigParser() +        self.config.read(self.path) +        self._normconfig() + +    def get_realtime(self, opt): +        try: +            sres = os.stat(self.path) +        except (OSError, IOError): +            if sys.exc_info()[1].errno == errno.ENOENT: +                sres = None +            else: +                raise + +        # compare file system stat with that of our stream file handle +        if not sres or sres[ST_DEV] != self.dev or \ +           sres[ST_INO] != self.ino or self.mtime != sres[ST_MTIME]: +            self._load() + +        return self.get(opt, printValue=False) + +    def section(self, rx=False): +        """get the section name of the section representing .peers +        in .config""" +        peers = self.peers +        if not peers: +            peers = ['.', '.'] +            rx = True +        if rx: +            st = 'peersrx' +        else: +            st = 'peers' +        return ' '.join([st] + [escape(u) for u in peers]) + +    @staticmethod +    def parse_section(section): +        """retrieve peers sequence encoded by section name +           (as urls or regexen, depending on section type) +        """ +        sl = section.split() +        st = sl.pop(0) +        sl = [unescape(u) for u in sl] +        if st == 'peersrx': +            sl = [re.compile(u) for u in sl] +        return sl + +    def ord_sections(self): +        """Return an ordered list of sections. + +        Ordering happens based on the auxiliary +        SECT_ORD section storing indices for each +        section added through the config API. + +        To not to go corrupt in case of manually +        written config files, we take care to append +        also those sections which are not registered +        in SECT_ORD. + +        Needed for python 2.{4,5,6} where ConfigParser +        cannot yet order sections/options internally. +        """ +        so = {} +        if self.config.has_section(SECT_ORD): +            so = self.config._sections[SECT_ORD] +        so2 = {} +        for k, v in so.items(): +            if k != '__name__': +                so2[k] = int(v) +        tv = 0 +        if so2: +            tv = max(so2.values()) + 1 +        ss = [s for s in self.config.sections() if s.find('__') != 0] +        for s in ss: +            if s in so.keys(): +                continue +            so2[s] = tv +            tv += 1 + +        def scmp(x, y): +            return cmp(*(so2[s] for s in (x, y))) +        ss.sort(scmp) +        return ss + +    def update_to(self, dct, allow_unresolved=False): +        """update @dct from key/values of ours. + +        key/values are collected from .config by filtering the regexp sections +        according to match, and from .section. The values are treated as +        templates, which are substituted from .auxdicts and (in case of regexp +        sections) match groups. +        """ +        if not self.peers: +            raise GsyncdError('no peers given, cannot select matching options') + +        def update_from_sect(sect, mud): +            for k, v in self.config._sections[sect].items(): +                if k == '__name__': +                    continue +                if allow_unresolved: +                    dct[k] = Template(v).safe_substitute(mud) +                else: +                    dct[k] = Template(v).substitute(mud) +        for sect in self.ord_sections(): +            sp = self.parse_section(sect) +            if isinstance(sp[0], re_type) and len(sp) == len(self.peers): +                match = True +                mad = {} +                for i in range(len(sp)): +                    m = sp[i].search(self.peers[i]) +                    if not m: +                        match = False +                        break +                    for j in range(len(m.groups())): +                        mad['match%d_%d' % (i + 1, j + 1)] = m.groups()[j] +                if match: +                    update_from_sect(sect, MultiDict(dct, mad, *self.auxdicts)) +        if self.config.has_section(self.section()): +            update_from_sect(self.section(), MultiDict(dct, *self.auxdicts)) + +    def get(self, opt=None, printValue=True): +        """print the matching key/value pairs from .config, +           or if @opt given, the value for @opt (according to the +           logic described in .update_to) +        """ +        d = {} +        self.update_to(d, allow_unresolved=True) +        if opt: +            opt = norm(opt) +            v = d.get(opt) +            if v: +                if printValue: +                    print(v) +                else: +                    return v +        else: +            for k, v in d.iteritems(): +                if k == '__name__': +                    continue +                print("%s: %s" % (k, v)) + +    def write(self, trfn, opt, *a, **kw): +        """update on-disk config transactionally + +        @trfn is the transaction function +        """ +        def mergeconf(f): +            self.config = ConfigParser.RawConfigParser() +            self.config.readfp(f) +            self._normconfig() +            if not self.config.has_section(SECT_META): +                self.config.add_section(SECT_META) +            self.config.set(SECT_META, 'version', config_version) +            return trfn(norm(opt), *a, **kw) + +        def updateconf(f): +            self.config.write(f) +        update_file(self.path, updateconf, mergeconf) + +    def _set(self, opt, val, rx=False): +        """set @opt to @val in .section""" +        sect = self.section(rx) +        if not self.config.has_section(sect): +            self.config.add_section(sect) +            # regarding SECT_ORD, cf. ord_sections +            if not self.config.has_section(SECT_ORD): +                self.config.add_section(SECT_ORD) +            self.config.set( +                SECT_ORD, sect, len(self.config._sections[SECT_ORD])) +        self.config.set(sect, opt, val) +        return True + +    def set(self, opt, *a, **kw): +        """perform ._set transactionally""" +        self.write(self._set, opt, *a, **kw) + +    def _delete(self, opt, rx=False): +        """delete @opt from .section""" +        sect = self.section(rx) +        if self.config.has_section(sect): +            return self.config.remove_option(sect, opt) + +    def delete(self, opt, *a, **kw): +        """perform ._delete transactionally""" +        self.write(self._delete, opt, *a, **kw)  | 
