diff options
Diffstat (limited to '')
-rwxr-xr-x | ctdb/utils/etcd/ctdb_etcd_lock | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/ctdb/utils/etcd/ctdb_etcd_lock b/ctdb/utils/etcd/ctdb_etcd_lock new file mode 100755 index 0000000..dac2436 --- /dev/null +++ b/ctdb/utils/etcd/ctdb_etcd_lock @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Copyright (C) 2016 Jose A. Rivera <jarrpa@samba.org> +# Copyright (C) 2016 Ira Cooper <ira@samba.org> +"""CTDB mutex helper using etcd. + +This script is intended to be run as a mutex helper for CTDB. It will try to +connect to an existing etcd cluster and grab an etcd.Lock() to function as +CTDB's cluster lock. Please see ctdb/doc/cluster_mutex_helper.txt for +details on what we're SUPPOSED to be doing. :) To use this, include +the following line in the ctdb.conf: + + cluster lock = !/path/to/script + +You can also pass "-v", "-vv", or "-vvv" to include verbose output in the +CTDB log. Additional "v"s indicate increases in verbosity. + +This mutex helper expects the system Python interpreter to have access to the +etcd Python module. It also expects an etcd cluster to be configured and +running. To integrate with this, there is an optional config file of the +following format: + +key = value + +The following configuration variables (and their defaults) are defined for +use by this script: + +port = 2379 # connecting port for the etcd cluster +lock_ttl = 9 # seconds for TTL +refresh = 2 # seconds between attempts to maintain lock +locks_dir = _ctdb # where to store CTDB locks in etcd + # The final etcd directory for any given lock looks like: + # /_locks/{locks_dir}/{netbios name}/ + +In addition, any keyword parameter that can be used to configure an etcd +client may be specified and modified here. For more documentation on these +parameters, see here: https://github.com/jplana/python-etcd/ + +""" +import signal +import time +import sys +import os +import argparse +import logging +import subprocess + +import etcd + +# Helper Functions ------------------------------------------------------------ +# + + +def process_args(): + '''Process command-line arguments and return them. + ''' + parser = argparse.ArgumentParser( + description=__doc__, + epilog='', + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-v', '--verbose', + action='count', + help='Display verbose output to stderr. ' + 'Default is no output.', + default=0, + ) + parser.add_argument('-c', '--config', + action='store', + help='Configuration file to use. The default behavior ' + 'is to look is the base CTDB configuration ' + 'directory, which can be overwritten by setting ' + 'the CTDB_BASE environment variable, for a file ' + 'called \'etcd\'. Default value is %(default)s.', + default=os.path.join(os.getenv('CTDB_BASE', + '/usr/local/etc/ctdb'), + 'etcd'), + ) + args = parser.parse_args() + + return args + + +def setup_logging(verbose): + '''Setup logging based on specified verbosity. + ''' + + log_levels = [logging.ERROR, logging.WARNING, logging.DEBUG] + logging.basicConfig(level=log_levels[min(verbose, len(log_levels)-1)]) + + +def sigterm_handler(signum, frame): + """Handler for SIGTERM signals. + """ + sys.exit() + + +def print_nonl(out): + """Dumb shortcut for printing to stdout with no newline. + """ + sys.stdout.write(str(out)) + sys.stdout.flush() + + +def int_or_not(s): + """Try to convert input to an integer. + """ + try: + return int(s) + except ValueError: + return s + +# Mainline -------------------------------------------------------------------- +# + + +def main(): + args = process_args() + + setup_logging(args.verbose) + + # etcd config defaults + etcd_config = { + 'port': 2379, + 'locks_dir': '_ctdb', + 'lock_ttl': 9, + 'lock_refresh': 2, + } + # Find and read etcd config file + etcd_client_params = ( + 'host', + 'port', + 'srv_domain', + 'version_prefix', + 'read_timeout', + 'allow_redirect', + 'protocol', + 'cert', + 'ca_cert', + 'username', + 'password', + 'allow_reconnect', + 'use_proxies', + 'expected_cluster_id', + 'per_host_pool_size', + ) + if os.path.isfile(args.config): + f = open(args.config, 'r') + for line in f: + (key, value) = line.split("=", 1) + etcd_config[key.strip()] = int_or_not(value.strip()) + + # Minor hack: call out to shell to retrieve CTDB netbios name and PNN. + tmp = subprocess.Popen("testparm -s --parameter-name 'netbios name'; \ + ctdb pnn", + shell=True, + universal_newlines=True, + stdout=subprocess.PIPE + ).stdout.read().strip() + nb_name, pnn = tmp.split() + + # Try to get and hold the lock + try: + client = etcd.Client( + **{k: etcd_config[k] for k in + set(etcd_client_params).intersection(etcd_config)}) + lock = etcd.Lock(client, etcd_config['locks_dir'] + "/" + nb_name) + lock._uuid = lock._uuid + "_" + pnn + logging.debug("Updated lock UUID: %s", lock.uuid) + ppid = os.getppid() + while True: + lock.acquire(blocking=False, lock_ttl=etcd_config['lock_ttl']) + if lock.is_acquired: + print_nonl(0) + else: + locks = "No locks found." + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + keys = client.read(lock.path, recursive=True) + if keys is not None: + locks = "Existing locks:\n " + locks += '\n '.join( + (child.key + ": " + child.value for child in + keys.children)) + logging.debug("Lock contention. %s", locks) + print_nonl(1) + break + os.kill(ppid, 0) + time.sleep(etcd_config['lock_refresh']) + except (OSError, SystemExit): + if lock is not None and lock.is_acquired: + lock.release() + except Exception: + print_nonl(3) + if logging.getLogger().getEffectiveLevel() == logging.DEBUG: + raise + + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, sigterm_handler) + + main() |