diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | MAINTAINERS | 8 | ||||
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | cli/src/cli-cmd-peer.c | 12 | ||||
| -rw-r--r-- | cli/src/cli-cmd-volume.c | 24 | ||||
| -rw-r--r-- | configure.ac | 49 | ||||
| -rw-r--r-- | events/Makefile.am | 6 | ||||
| -rw-r--r-- | events/eventskeygen.py | 65 | ||||
| -rw-r--r-- | events/src/Makefile.am | 24 | ||||
| -rw-r--r-- | events/src/__init__.py | 10 | ||||
| -rw-r--r-- | events/src/eventsapiconf.py.in | 22 | ||||
| -rw-r--r-- | events/src/eventsconfig.json | 3 | ||||
| -rw-r--r-- | events/src/eventtypes.py | 9 | ||||
| -rw-r--r-- | events/src/glustereventsd.py | 151 | ||||
| -rw-r--r-- | events/src/handlers.py | 21 | ||||
| -rw-r--r-- | events/src/peer_eventsapi.py | 521 | ||||
| -rw-r--r-- | events/src/utils.py | 150 | ||||
| -rw-r--r-- | events/tools/Makefile.am | 3 | ||||
| -rw-r--r-- | events/tools/eventsdash.py | 74 | ||||
| -rw-r--r-- | extras/systemd/Makefile.am | 9 | ||||
| -rw-r--r-- | extras/systemd/glustereventsd.service.in | 12 | ||||
| -rw-r--r-- | glusterfs.spec.in | 43 | ||||
| -rw-r--r-- | libglusterfs/src/Makefile.am | 6 | ||||
| -rw-r--r-- | libglusterfs/src/events.c | 83 | ||||
| -rw-r--r-- | libglusterfs/src/events.h.in | 23 | ||||
| -rw-r--r-- | libglusterfs/src/eventtypes.h | 22 | ||||
| -rw-r--r-- | libglusterfs/src/glusterfs.h | 4 | 
27 files changed, 1354 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore index 33f6e0905b5..695daea6497 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,7 @@ xlators/experimental/fdl/src/libfdl.c  xlators/experimental/fdl/src/librecon.c  xlators/experimental/jbr-client/src/jbrc-cg.c  xlators/experimental/jbr-server/src/jbr-cg.c +# Eventing +events/src/eventsapiconf.py +libglusterfs/src/events.h +extras/systemd/glustereventsd.service diff --git a/MAINTAINERS b/MAINTAINERS index 486ca46cc74..053747430aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -209,6 +209,14 @@ S: Maintained  F: xlators/mgmt/glusterd/src/glusterd-snap*  F: extras/snap-scheduler.py +Events APIs +M: Aravinda VK <avishwan@redhat.com> +S: Maintained +F: events/ +F: libglusterfs/src/events* +F: libglusterfs/src/eventtypes* +F: extras/systemd/glustereventsd* +  Distribution Specific:  ----------------------  Build: diff --git a/Makefile.am b/Makefile.am index 180f149e328..c90c5f6c60e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ EXTRA_DIST = autogen.sh \  SUBDIRS = $(ARGP_STANDALONE_DIR) libglusterfs rpc api xlators glusterfsd \  	$(FUSERMOUNT_SUBDIR) doc extras cli heal @SYNCDAEMON_SUBDIR@ \ -	@UMOUNTD_SUBDIR@ tools +	@UMOUNTD_SUBDIR@ tools @EVENTS_SUBDIR@  pkgconfigdir = @pkgconfigdir@  pkgconfig_DATA = glusterfs-api.pc libgfchangelog.pc diff --git a/cli/src/cli-cmd-peer.c b/cli/src/cli-cmd-peer.c index d6b4ab147a4..36c328a7c12 100644 --- a/cli/src/cli-cmd-peer.c +++ b/cli/src/cli-cmd-peer.c @@ -90,6 +90,12 @@ out:          CLI_STACK_DESTROY (frame); +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_PEER_ATTACH, "host=%s", (char *)words[2]); +        } +#endif +          return ret;  } @@ -160,6 +166,12 @@ out:          CLI_STACK_DESTROY (frame); +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_PEER_DETACH, "host=%s", (char *)words[2]); +        } +#endif +          return ret;  } diff --git a/cli/src/cli-cmd-volume.c b/cli/src/cli-cmd-volume.c index 2ae4aa2dbf4..08bab2fefb1 100644 --- a/cli/src/cli-cmd-volume.c +++ b/cli/src/cli-cmd-volume.c @@ -243,7 +243,11 @@ out:          }          CLI_STACK_DESTROY (frame); - +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_VOLUME_CREATE, "name=%s", (char *)words[2]); +        } +#endif          return ret;  } @@ -318,6 +322,12 @@ out:          CLI_STACK_DESTROY (frame); +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_VOLUME_DELETE, "name=%s", (char *)words[2]); +        } +#endif +          return ret;  } @@ -392,6 +402,12 @@ out:          CLI_STACK_DESTROY (frame); +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_VOLUME_START, "name=%s", (char *)words[2]); +        } +#endif +          return ret;  } @@ -524,6 +540,12 @@ out:          CLI_STACK_DESTROY (frame); +#if (USE_EVENTS) +        if (ret == 0) { +                gf_event (EVENT_VOLUME_STOP, "name=%s", (char *)words[2]); +        } +#endif +          return ret;  } diff --git a/configure.ac b/configure.ac index 70c53b08a8c..69b426ff500 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,7 @@ AC_CONFIG_HEADERS([config.h])  AC_CONFIG_FILES([Makefile                  libglusterfs/Makefile                  libglusterfs/src/Makefile +                libglusterfs/src/events.h                  libglusterfs/src/gfdb/Makefile                  geo-replication/src/peer_gsec_create                  geo-replication/src/peer_mountbroker @@ -225,6 +226,7 @@ AC_CONFIG_FILES([Makefile                  extras/ganesha/ocf/Makefile                  extras/systemd/Makefile                  extras/systemd/glusterd.service +                extras/systemd/glustereventsd.service                  extras/run-gluster.tmpfiles                  extras/benchmarking/Makefile                  extras/hook-scripts/Makefile @@ -248,6 +250,10 @@ AC_CONFIG_FILES([Makefile                  extras/hook-scripts/reset/post/Makefile                  extras/hook-scripts/reset/pre/Makefile                  extras/snap_scheduler/Makefile +                events/Makefile +                events/src/Makefile +                events/src/eventsapiconf.py +                events/tools/Makefile                  contrib/fuse-util/Makefile                  contrib/umountd/Makefile                  contrib/uuid/uuid_types.h @@ -718,6 +724,43 @@ fi  AC_SUBST(GEOREP_EXTRAS_SUBDIR)  AM_CONDITIONAL(USE_GEOREP, test "x$enable_georeplication" != "xno") +# Events section +AC_ARG_ENABLE([events], +              AC_HELP_STRING([--disable-events], +                             [Do not install Events components])) + +BUILD_EVENTS=no +EVENTS_ENABLED=0 +EVENTS_SUBDIR= +have_python2=no +if test "x$enable_events" != "xno"; then +  EVENTS_SUBDIR=events +  EVENTS_ENABLED=1 + +  BUILD_EVENTS="yes" +  AM_PATH_PYTHON() +  dnl Check if version matches that we require +  if echo $PYTHON_VERSION | grep ^2; then +     have_python2=yes +  fi + +  if test "x$have_python2" = "xno"; then +     if test "x$enable_events" = "xyes"; then +        AC_MSG_ERROR([python 2.x packages required. exiting..]) +     fi +     AC_MSG_WARN([python 2.x not found, disabling events]) +     EVENTS_SUBDIR= +     EVENTS_ENABLED=0 +     BUILD_EVENTS="no" +  else +    AC_DEFINE(USE_EVENTS, 1, [define if events enabled]) +  fi +fi +AC_SUBST(EVENTS_ENABLED) +AC_SUBST(EVENTS_SUBDIR) +AM_CONDITIONAL([BUILD_EVENTS], [test x$BUILD_EVENTS = xyes]) +# end Events section +  # CDC xlator - check if libz is present if so enable HAVE_LIB_Z  BUILD_CDC=yes  PKG_CHECK_MODULES([ZLIB], [zlib >= 1.2.0],, @@ -1097,10 +1140,15 @@ eval sbintemp=\"${sbintemp}\"  eval sbintemp=\"${sbintemp}\"  SBIN_DIR=${sbintemp} +sysconfdirtemp="${sysconfdir}" +eval sysconfdirtemp=\"${sysconfdirtemp}\" +SYSCONF_DIR=${sysconfdirtemp} +  prefix=$prefix_temp  exec_prefix=$exec_prefix_temp  AC_SUBST(SBIN_DIR) +AC_SUBST(SYSCONF_DIR)  # lazy umount emulation  UMOUNTD_SUBDIR="" @@ -1377,4 +1425,5 @@ echo "POSIX ACLs           : $BUILD_POSIX_ACLS"  echo "Data Classification  : $BUILD_GFDB"  echo "firewalld-config     : $BUILD_FIREWALLD"  echo "Experimental xlators : $BUILD_EXPERIMENTAL" +echo "Events               : $BUILD_EVENTS"  echo diff --git a/events/Makefile.am b/events/Makefile.am new file mode 100644 index 00000000000..04a74efc228 --- /dev/null +++ b/events/Makefile.am @@ -0,0 +1,6 @@ +SUBDIRS = src tools + +noinst_PYTHON = eventskeygen.py + +install-data-hook: +	$(INSTALL) -d -m 755 $(DESTDIR)@GLUSTERD_WORKDIR@/events diff --git a/events/eventskeygen.py b/events/eventskeygen.py new file mode 100644 index 00000000000..656a7dce9f1 --- /dev/null +++ b/events/eventskeygen.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +import os + +GLUSTER_SRC_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +eventtypes_h = os.path.join(GLUSTER_SRC_ROOT, "libglusterfs/src/eventtypes.h") +eventtypes_py = os.path.join(GLUSTER_SRC_ROOT, "events/src/eventtypes.py") + +# When adding new keys add it to the END +keys = ( +    "EVENT_PEER_ATTACH", +    "EVENT_PEER_DETACH", + +    "EVENT_VOLUME_CREATE", +    "EVENT_VOLUME_START", +    "EVENT_VOLUME_STOP", +    "EVENT_VOLUME_DELETE", +) + +LAST_EVENT = "EVENT_LAST" + +ERRORS = ( +    "EVENT_SEND_OK", +    "EVENT_ERROR_INVALID_INPUTS", +    "EVENT_ERROR_SOCKET", +    "EVENT_ERROR_CONNECT", +    "EVENT_ERROR_SEND" +) + +# Generate eventtypes.h +with open(eventtypes_h, "w") as f: +    f.write("#ifndef __EVENTTYPES_H__\n") +    f.write("#define __EVENTTYPES_H__\n\n") +    f.write("typedef enum {\n") +    for k in ERRORS: +        f.write("    {0},\n".format(k)) +    f.write("} event_errors_t;\n") + +    f.write("\n") + +    f.write("typedef enum {\n") +    for k in keys: +        f.write("    {0},\n".format(k)) + +    f.write("    {0}\n".format(LAST_EVENT)) +    f.write("} eventtypes_t;\n") +    f.write("\n#endif /* __EVENTTYPES_H__ */\n") + +# Generate eventtypes.py +with open(eventtypes_py, "w") as f: +    f.write("# -*- coding: utf-8 -*-\n") +    f.write("all_events = [\n") +    for ev in keys: +        f.write('    "{0}",\n'.format(ev)) +    f.write("]\n") diff --git a/events/src/Makefile.am b/events/src/Makefile.am new file mode 100644 index 00000000000..528f0208fe2 --- /dev/null +++ b/events/src/Makefile.am @@ -0,0 +1,24 @@ +EXTRA_DIST = glustereventsd.py __init__.py  eventsapiconf.py.in eventtypes.py \ +	handlers.py utils.py peer_eventsapi.py eventsconfig.json + +eventsdir = $(libexecdir)/glusterfs/events +eventspeerscriptdir = $(libexecdir)/glusterfs +eventsconfdir = $(sysconfdir)/glusterfs +eventsconf_DATA = eventsconfig.json + +events_PYTHON = __init__.py eventsapiconf.py eventtypes.py handlers.py utils.py +events_SCRIPTS = glustereventsd.py +eventspeerscript_SCRIPTS = peer_eventsapi.py + +install-exec-hook: +	$(mkdir_p) $(DESTDIR)$(sbindir) +	rm -f $(DESTDIR)$(sbindir)/glustereventsd +	ln -s $(libexecdir)/glusterfs/events/glustereventsd.py \ +		$(DESTDIR)$(sbindir)/glustereventsd +	rm -f $(DESTDIR)$(sbindir)/gluster-eventing +	ln -s $(libexecdir)/glusterfs/peer_eventsapi.py \ +		$(DESTDIR)$(sbindir)/gluster-eventsapi + +uninstall-hook: +	rm -f $(DESTDIR)$(sbindir)/glustereventsd +	rm -f $(DESTDIR)$(sbindir)/gluster-eventsapi diff --git a/events/src/__init__.py b/events/src/__init__.py new file mode 100644 index 00000000000..f27c53a4df4 --- /dev/null +++ b/events/src/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# diff --git a/events/src/eventsapiconf.py.in b/events/src/eventsapiconf.py.in new file mode 100644 index 00000000000..702e1d21820 --- /dev/null +++ b/events/src/eventsapiconf.py.in @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +SERVER_ADDRESS = "@localstatedir@/run/gluster/events.sock" +DEFAULT_CONFIG_FILE = "@SYSCONF_DIR@/glusterfs/eventsconfig.json" +CUSTOM_CONFIG_FILE_TO_SYNC = "/events/config.json" +CUSTOM_CONFIG_FILE = "@GLUSTERD_WORKDIR@" + CUSTOM_CONFIG_FILE_TO_SYNC +WEBHOOKS_FILE_TO_SYNC = "/events/webhooks.json" +WEBHOOKS_FILE = "@GLUSTERD_WORKDIR@" + WEBHOOKS_FILE_TO_SYNC +LOG_FILE = "@localstatedir@/log/glusterfs/events.log" +EVENTSD = "glustereventsd" +CONFIG_KEYS = ["log_level"] +BOOL_CONFIGS = [] +RESTART_CONFIGS = [] diff --git a/events/src/eventsconfig.json b/events/src/eventsconfig.json new file mode 100644 index 00000000000..ce2c775f0bd --- /dev/null +++ b/events/src/eventsconfig.json @@ -0,0 +1,3 @@ +{ +    "log_level": "INFO" +} diff --git a/events/src/eventtypes.py b/events/src/eventtypes.py new file mode 100644 index 00000000000..4812e659de3 --- /dev/null +++ b/events/src/eventtypes.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +all_events = [ +    "EVENT_PEER_ATTACH", +    "EVENT_PEER_DETACH", +    "EVENT_VOLUME_CREATE", +    "EVENT_VOLUME_START", +    "EVENT_VOLUME_STOP", +    "EVENT_VOLUME_DELETE", +] diff --git a/events/src/glustereventsd.py b/events/src/glustereventsd.py new file mode 100644 index 00000000000..3fa57686a8b --- /dev/null +++ b/events/src/glustereventsd.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +from __future__ import print_function +import asyncore +import socket +import os +from multiprocessing import Process, Queue +import sys +import signal + +from eventtypes import all_events +import handlers +import utils +from eventsapiconf import SERVER_ADDRESS +from utils import logger + +# Global Queue, EventsHandler will add items to the queue +# and process_event will gets each item and handles it +events_queue = Queue() +events_server_pid = None + + +def process_event(): +    """ +    Seperate process which handles all the incoming events from Gluster +    processes. +    """ +    while True: +        data = events_queue.get() +        logger.debug("EVENT: {0}".format(repr(data))) +        try: +            # Event Format <TIMESTAMP> <TYPE> <DETAIL> +            ts, key, value = data.split(" ", 2) +        except ValueError: +            logger.warn("Invalid Event Format {0}".format(data)) +            continue + +        data_dict = {} +        try: +            # Format key=value;key=value +            data_dict = dict(x.split('=') for x in value.split(';')) +        except ValueError: +            logger.warn("Unable to parse Event {0}".format(data)) +            continue + +        try: +            # Event Type to Function Map, Recieved event data will be in +            # the form <TIMESTAMP> <TYPE> <DETAIL>, Get Event name for the +            # recieved Type/Key and construct a function name starting with +            # handle_ For example: handle_event_volume_create +            func_name = "handle_" + all_events[int(key)].lower() +        except IndexError: +            # This type of Event is not handled? +            logger.warn("Unhandled Event: {0}".format(key)) +            func_name = None + +        if func_name is not None: +            # Get function from handlers module +            func = getattr(handlers, func_name, None) +            # If func is None, then handler unimplemented for that event. +            if func is not None: +                func(ts, int(key), data_dict) +            else: +                # Generic handler, broadcast whatever received +                handlers.generic_handler(ts, int(key), data_dict) + + +def process_event_wrapper(): +    try: +        process_event() +    except KeyboardInterrupt: +        return + + +class GlusterEventsHandler(asyncore.dispatcher_with_send): + +    def handle_read(self): +        data = self.recv(8192) +        if data: +            events_queue.put(data) +            self.send(data) + + +class GlusterEventsServer(asyncore.dispatcher): + +    def __init__(self): +        global events_server_pid +        asyncore.dispatcher.__init__(self) +        # Start the Events listener process which listens to +        # the global queue +        p = Process(target=process_event_wrapper) +        p.start() +        events_server_pid = p.pid + +        # Create UNIX Domain Socket, bind to path +        self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) +        self.bind(SERVER_ADDRESS) +        self.listen(5) + +    def handle_accept(self): +        pair = self.accept() +        if pair is not None: +            sock, addr = pair +            GlusterEventsHandler(sock) + + +def signal_handler_sigusr2(sig, frame): +    if events_server_pid is not None: +        os.kill(events_server_pid, signal.SIGUSR2) +    utils.load_all() + + +def init_event_server(): +    utils.setup_logger() + +    # Delete Socket file if Exists +    try: +        os.unlink(SERVER_ADDRESS) +    except OSError: +        if os.path.exists(SERVER_ADDRESS): +            print ("Failed to cleanup socket file {0}".format(SERVER_ADDRESS), +                   file=sys.stderr) +            sys.exit(1) + +    utils.load_all() + +    # Start the Eventing Server, UNIX DOMAIN SOCKET Server +    GlusterEventsServer() +    asyncore.loop() + + +def main(): +    try: +        init_event_server() +    except KeyboardInterrupt: +        sys.exit(1) + + +if __name__ == "__main__": +    signal.signal(signal.SIGUSR2, signal_handler_sigusr2) +    main() diff --git a/events/src/handlers.py b/events/src/handlers.py new file mode 100644 index 00000000000..9b756a91d51 --- /dev/null +++ b/events/src/handlers.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +import utils + + +def generic_handler(ts, key, data): +    """ +    Generic handler to broadcast message to all peers, custom handlers +    can be created by func name handler_<event_name> +    Ex: handle_event_volume_create(ts, key, data) +    """ +    utils.publish(ts, key, data) diff --git a/events/src/peer_eventsapi.py b/events/src/peer_eventsapi.py new file mode 100644 index 00000000000..7887d77351c --- /dev/null +++ b/events/src/peer_eventsapi.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +from __future__ import print_function +import os +import json +from errno import EEXIST + +import requests +import fasteners +from prettytable import PrettyTable + +from gluster.cliutils import (Cmd, execute, node_output_ok, node_output_notok, +                              sync_file_to_peers, GlusterCmdException, +                              output_error, execute_in_peers, runcli) + +from events.eventsapiconf import (WEBHOOKS_FILE_TO_SYNC, +                                  WEBHOOKS_FILE, +                                  DEFAULT_CONFIG_FILE, +                                  CUSTOM_CONFIG_FILE, +                                  CUSTOM_CONFIG_FILE_TO_SYNC, +                                  EVENTSD, +                                  CONFIG_KEYS, +                                  BOOL_CONFIGS, +                                  RESTART_CONFIGS) + + +def file_content_overwrite(fname, data): +    with open(fname + ".tmp", "w") as f: +        f.write(json.dumps(data)) + +    os.rename(fname + ".tmp", fname) + + +def create_custom_config_file_if_not_exists(): +    mkdirp(os.path.dirname(CUSTOM_CONFIG_FILE)) +    if not os.path.exists(CUSTOM_CONFIG_FILE): +        with open(CUSTOM_CONFIG_FILE, "w") as f: +            f.write("{}") + + +def create_webhooks_file_if_not_exists(): +    mkdirp(os.path.dirname(WEBHOOKS_FILE)) +    if not os.path.exists(WEBHOOKS_FILE): +        with open(WEBHOOKS_FILE, "w") as f: +            f.write("{}") + + +def boolify(value): +    val = False +    if value.lower() in ["enabled", "true", "on", "yes"]: +        val = True +    return val + + +def mkdirp(path, exit_on_err=False, logger=None): +    """ +    Try creating required directory structure +    ignore EEXIST and raise exception for rest of the errors. +    Print error in stderr and exit +    """ +    try: +        os.makedirs(path) +    except (OSError, IOError) as e: +        if e.errno == EEXIST and os.path.isdir(path): +            pass +        else: +            output_error("Fail to create dir %s: %s" % (path, e)) + + +def is_enabled(service): +    rc, out, err = execute(["systemctl", "is-enabled", service]) +    return rc == 0 + + +def is_active(service): +    rc, out, err = execute(["systemctl", "is-active", service]) +    return rc == 0 + + +def enable_service(service): +    if not is_enabled(service): +        cmd = ["systemctl", "enable", service] +        return execute(cmd) + +    return (0, "", "") + + +def disable_service(service): +    if is_enabled(service): +        cmd = ["systemctl", "disable", service] +        return execute(cmd) + +    return (0, "", "") + + +def start_service(service): +    rc, out, err = enable_service(service) +    if rc != 0: +        return (rc, out, err) + +    cmd = ["systemctl", "start", service] +    return execute(cmd) + + +def stop_service(service): +    rc, out, err = disable_service(service) +    if rc != 0: +        return (rc, out, err) + +    cmd = ["systemctl", "stop", service] +    return execute(cmd) + + +def restart_service(service): +    rc, out, err = stop_service(service) +    if rc != 0: +        return (rc, out, err) + +    return start_service(service) + + +def reload_service(service): +    if is_active(service): +        cmd = ["systemctl", "reload", service] +        return execute(cmd) + +    return (0, "", "") + + +def sync_to_peers(restart=False): +    if os.path.exists(WEBHOOKS_FILE): +        try: +            sync_file_to_peers(WEBHOOKS_FILE_TO_SYNC) +        except GlusterCmdException as e: +            output_error("Failed to sync Webhooks file: [Error: {0}]" +                         "{1}".format(e[0], e[2])) + +    if os.path.exists(CUSTOM_CONFIG_FILE): +        try: +            sync_file_to_peers(CUSTOM_CONFIG_FILE_TO_SYNC) +        except GlusterCmdException as e: +            output_error("Failed to sync Config file: [Error: {0}]" +                         "{1}".format(e[0], e[2])) + +    action = "node-reload" +    if restart: +        action = "node-restart" + +    out = execute_in_peers(action) +    table = PrettyTable(["NODE", "NODE STATUS", "SYNC STATUS"]) +    table.align["NODE STATUS"] = "r" +    table.align["SYNC STATUS"] = "r" + +    for p in out: +        table.add_row([p.hostname, +                       "UP" if p.node_up else "DOWN", +                       "OK" if p.ok else "NOT OK: {0}".format( +                           p.error)]) + +    print (table) + + +def node_output_handle(resp): +    rc, out, err = resp +    if rc == 0: +        node_output_ok(out) +    else: +        node_output_notok(err) + + +def action_handle(action): +    out = execute_in_peers("node-" + action) +    column_name = action.upper() +    if action == "status": +        column_name = EVENTSD.upper() + +    table = PrettyTable(["NODE", "NODE STATUS", column_name + " STATUS"]) +    table.align["NODE STATUS"] = "r" +    table.align[column_name + " STATUS"] = "r" + +    for p in out: +        status_col_val = "OK" if p.ok else "NOT OK: {0}".format( +            p.error) +        if action == "status": +            status_col_val = "DOWN" +            if p.ok: +                status_col_val = p.output + +        table.add_row([p.hostname, +                       "UP" if p.node_up else "DOWN", +                       status_col_val]) + +    print (table) + + +class NodeStart(Cmd): +    name = "node-start" + +    def run(self, args): +        node_output_handle(start_service(EVENTSD)) + + +class StartCmd(Cmd): +    name = "start" + +    def run(self, args): +        action_handle("start") + + +class NodeStop(Cmd): +    name = "node-stop" + +    def run(self, args): +        node_output_handle(stop_service(EVENTSD)) + + +class StopCmd(Cmd): +    name = "stop" + +    def run(self, args): +        action_handle("stop") + + +class NodeRestart(Cmd): +    name = "node-restart" + +    def run(self, args): +        node_output_handle(restart_service(EVENTSD)) + + +class RestartCmd(Cmd): +    name = "restart" + +    def run(self, args): +        action_handle("restart") + + +class NodeReload(Cmd): +    name = "node-reload" + +    def run(self, args): +        node_output_handle(reload_service(EVENTSD)) + + +class ReloadCmd(Cmd): +    name = "reload" + +    def run(self, args): +        action_handle("reload") + + +class NodeStatus(Cmd): +    name = "node-status" + +    def run(self, args): +        node_output_ok("UP" if is_active(EVENTSD) else "DOWN") + + +class StatusCmd(Cmd): +    name = "status" + +    def run(self, args): +        webhooks = {} +        if os.path.exists(WEBHOOKS_FILE): +            webhooks = json.load(open(WEBHOOKS_FILE)) + +        print ("Webhooks: " + ("" if webhooks else "None")) +        for w in webhooks: +            print (w) + +        print () +        action_handle("status") + + +class WebhookAddCmd(Cmd): +    name = "webhook-add" + +    def args(self, parser): +        parser.add_argument("url", help="URL of Webhook") +        parser.add_argument("--bearer_token", "-t", help="Bearer Token", +                            default="") + +    def run(self, args): +        create_webhooks_file_if_not_exists() + +        with fasteners.InterProcessLock(WEBHOOKS_FILE): +            data = json.load(open(WEBHOOKS_FILE)) +            if data.get(args.url, None) is not None: +                output_error("Webhook already exists") + +            data[args.url] = args.bearer_token +            file_content_overwrite(WEBHOOKS_FILE, data) + +        sync_to_peers() + + +class WebhookModCmd(Cmd): +    name = "webhook-mod" + +    def args(self, parser): +        parser.add_argument("url", help="URL of Webhook") +        parser.add_argument("--bearer_token", "-t", help="Bearer Token", +                            default="") + +    def run(self, args): +        create_webhooks_file_if_not_exists() + +        with fasteners.InterProcessLock(WEBHOOKS_FILE): +            data = json.load(open(WEBHOOKS_FILE)) +            if data.get(args.url, None) is None: +                output_error("Webhook does not exists") + +            data[args.url] = args.bearer_token +            file_content_overwrite(WEBHOOKS_FILE, data) + +        sync_to_peers() + + +class WebhookDelCmd(Cmd): +    name = "webhook-del" + +    def args(self, parser): +        parser.add_argument("url", help="URL of Webhook") + +    def run(self, args): +        create_webhooks_file_if_not_exists() + +        with fasteners.InterProcessLock(WEBHOOKS_FILE): +            data = json.load(open(WEBHOOKS_FILE)) +            if data.get(args.url, None) is None: +                output_error("Webhook does not exists") + +            del data[args.url] +            file_content_overwrite(WEBHOOKS_FILE, data) + +        sync_to_peers() + + +class NodeWebhookTestCmd(Cmd): +    name = "node-webhook-test" + +    def args(self, parser): +        parser.add_argument("url") +        parser.add_argument("bearer_token") + +    def run(self, args): +        http_headers = {} +        if args.bearer_token != ".": +            http_headers["Authorization"] = "Bearer " + args.bearer_token + +        try: +            resp = requests.post(args.url, headers=http_headers) +        except requests.ConnectionError as e: +            node_output_notok("{0}".format(e)) + +        if resp.status_code != 200: +            node_output_notok("{0}".format(resp.status_code)) + +        node_output_ok() + + +class WebhookTestCmd(Cmd): +    name = "webhook-test" + +    def args(self, parser): +        parser.add_argument("url", help="URL of Webhook") +        parser.add_argument("--bearer_token", "-t", help="Bearer Token") + +    def run(self, args): +        url = args.url +        bearer_token = args.bearer_token +        if not args.url: +            url = "." +        if not args.bearer_token: +            bearer_token = "." + +        out = execute_in_peers("node-webhook-test", [url, bearer_token]) + +        table = PrettyTable(["NODE", "NODE STATUS", "WEBHOOK STATUS"]) +        table.align["NODE STATUS"] = "r" +        table.align["WEBHOOK STATUS"] = "r" + +        for p in out: +            table.add_row([p.hostname, +                           "UP" if p.node_up else "DOWN", +                           "OK" if p.ok else "NOT OK: {0}".format( +                               p.error)]) + +        print (table) + + +class ConfigGetCmd(Cmd): +    name = "config-get" + +    def args(self, parser): +        parser.add_argument("--name", help="Config Name") + +    def run(self, args): +        data = json.load(open(DEFAULT_CONFIG_FILE)) +        if os.path.exists(CUSTOM_CONFIG_FILE): +            data.update(json.load(open(CUSTOM_CONFIG_FILE))) + +        if args.name is not None and args.name not in CONFIG_KEYS: +            output_error("Invalid Config item") + +        table = PrettyTable(["NAME", "VALUE"]) +        if args.name is None: +            for k, v in data.items(): +                table.add_row([k, v]) +        else: +            table.add_row([args.name, data[args.name]]) + +        print (table) + + +def read_file_content_json(fname): +    content = "{}" +    with open(fname) as f: +        content = f.read() +        if content.strip() == "": +            content = "{}" + +    return json.loads(content) + + +class ConfigSetCmd(Cmd): +    name = "config-set" + +    def args(self, parser): +        parser.add_argument("name", help="Config Name") +        parser.add_argument("value", help="Config Value") + +    def run(self, args): +        if args.name not in CONFIG_KEYS: +            output_error("Invalid Config item") + +        with fasteners.InterProcessLock(CUSTOM_CONFIG_FILE): +            data = json.load(open(DEFAULT_CONFIG_FILE)) +            if os.path.exists(CUSTOM_CONFIG_FILE): +                config_json = read_file_content_json(CUSTOM_CONFIG_FILE) +                data.update(config_json) + +            # Do Nothing if same as previous value +            if data[args.name] == args.value: +                return + +            # TODO: Validate Value +            create_custom_config_file_if_not_exists() +            new_data = read_file_content_json(CUSTOM_CONFIG_FILE) + +            v = args.value +            if args.name in BOOL_CONFIGS: +                v = boolify(args.value) + +            new_data[args.name] = v +            file_content_overwrite(CUSTOM_CONFIG_FILE, new_data) + +            # If any value changed which requires restart of REST server +            restart = False +            if args.name in RESTART_CONFIGS: +                restart = True + +            sync_to_peers(restart=restart) + + +class ConfigResetCmd(Cmd): +    name = "config-reset" + +    def args(self, parser): +        parser.add_argument("name", help="Config Name or all") + +    def run(self, args): +        with fasteners.InterProcessLock(CUSTOM_CONFIG_FILE): +            changed_keys = [] +            data = {} +            if os.path.exists(CUSTOM_CONFIG_FILE): +                data = read_file_content_json(CUSTOM_CONFIG_FILE) + +            if not data: +                return + +            if args.name.lower() == "all": +                for k, v in data.items(): +                    changed_keys.append(k) + +                # Reset all keys +                file_content_overwrite(CUSTOM_CONFIG_FILE, {}) +            else: +                changed_keys.append(args.name) +                del data[args.name] +                file_content_overwrite(CUSTOM_CONFIG_FILE, data) + +            # If any value changed which requires restart of REST server +            restart = False +            for key in changed_keys: +                if key in RESTART_CONFIGS: +                    restart = True +                    break + +            sync_to_peers(restart=restart) + + +class SyncCmd(Cmd): +    name = "sync" + +    def run(self, args): +        sync_to_peers() + + +if __name__ == "__main__": +    runcli() diff --git a/events/src/utils.py b/events/src/utils.py new file mode 100644 index 00000000000..772221a1e25 --- /dev/null +++ b/events/src/utils.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +import json +import os +import logging + +import requests +from eventsapiconf import (LOG_FILE, +                           WEBHOOKS_FILE, +                           DEFAULT_CONFIG_FILE, +                           CUSTOM_CONFIG_FILE) +import eventtypes + +from gluster.cliutils import get_node_uuid + + +# Webhooks list +_webhooks = {} +# Default Log Level +_log_level = "INFO" +# Config Object +_config = {} + +# Init Logger instance +logger = logging.getLogger(__name__) + + +def get_event_type_name(idx): +    """ +    Returns Event Type text from the index. For example, VOLUME_CREATE +    """ +    return eventtypes.all_events[idx].replace("EVENT_", "") + + +def setup_logger(): +    """ +    Logging initialization, Log level by default will be INFO, once config +    file is read, respective log_level will be set. +    """ +    global logger +    logger.setLevel(logging.INFO) + +    # create the logging file handler +    fh = logging.FileHandler(LOG_FILE) + +    formatter = logging.Formatter("[%(asctime)s] %(levelname)s " +                                  "[%(module)s - %(lineno)s:%(funcName)s] " +                                  "- %(message)s") + +    fh.setFormatter(formatter) + +    # add handler to logger object +    logger.addHandler(fh) + + +def load_config(): +    """ +    Load/Reload the config from REST Config files. This function will +    be triggered during init and when SIGUSR2. +    """ +    global _config +    _config = {} +    if os.path.exists(DEFAULT_CONFIG_FILE): +        _config = json.load(open(DEFAULT_CONFIG_FILE)) +    if os.path.exists(CUSTOM_CONFIG_FILE): +        _config.update(json.load(open(CUSTOM_CONFIG_FILE))) + + +def load_log_level(): +    """ +    Reads log_level from Config file and sets accordingly. This function will +    be triggered during init and when SIGUSR2. +    """ +    global logger, _log_level +    new_log_level = _config.get("log_level", "INFO") +    if _log_level != new_log_level: +        logger.setLevel(getattr(logging, new_log_level.upper())) +        _log_level = new_log_level.upper() + + +def load_webhooks(): +    """ +    Load/Reload the webhooks list. This function will +    be triggered during init and when SIGUSR2. +    """ +    global _webhooks +    _webhooks = {} +    if os.path.exists(WEBHOOKS_FILE): +        _webhooks = json.load(open(WEBHOOKS_FILE)) + + +def load_all(): +    """ +    Wrapper function to call all load/reload functions. This function will +    be triggered during init and when SIGUSR2. +    """ +    load_config() +    load_webhooks() +    load_log_level() + + +def publish(ts, event_key, data): +    message = { +        "nodeid": get_node_uuid(), +        "ts": int(ts), +        "event": get_event_type_name(event_key), +        "message": data +    } +    if _webhooks: +        plugin_webhook(message) +    else: +        # TODO: Default action? +        pass + + +def plugin_webhook(message): +    message_json = json.dumps(message, sort_keys=True) +    logger.debug("EVENT: {0}".format(message_json)) +    for url, token in _webhooks.items(): +        http_headers = {"Content-Type": "application/json"} +        if token != "" and token is not None: +            http_headers["Authorization"] = "Bearer " + token + +        try: +            resp = requests.post(url, headers=http_headers, data=message_json) +        except requests.ConnectionError as e: +            logger.warn("Event push failed to URL: {url}, " +                        "Event: {event}, " +                        "Status: {error}".format( +                            url=url, +                            event=message_json, +                            error=e)) +            continue + +        if resp.status_code != 200: +            logger.warn("Event push failed to URL: {url}, " +                        "Event: {event}, " +                        "Status Code: {status_code}".format( +                            url=url, +                            event=message_json, +                            status_code=resp.status_code)) diff --git a/events/tools/Makefile.am b/events/tools/Makefile.am new file mode 100644 index 00000000000..7d5e331e4e1 --- /dev/null +++ b/events/tools/Makefile.am @@ -0,0 +1,3 @@ +scriptsdir = $(datadir)/glusterfs/scripts +scripts_SCRIPTS = eventsdash.py +EXTRA_DIST = eventsdash.py diff --git a/events/tools/eventsdash.py b/events/tools/eventsdash.py new file mode 100644 index 00000000000..47fc56dda6e --- /dev/null +++ b/events/tools/eventsdash.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +#  Copyright (c) 2016 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. +# + +from argparse import ArgumentParser, RawDescriptionHelpFormatter +import logging +from datetime import datetime + +from flask import Flask, request + +app = Flask(__name__) +app.logger.disabled = True +log = logging.getLogger('werkzeug') +log.disabled = True + + +def human_time(ts): +    return datetime.fromtimestamp(float(ts)).strftime("%Y-%m-%d %H:%M:%S") + + +@app.route("/") +def home(): +    return "OK" + + +@app.route("/listen", methods=["POST"]) +def listen(): +    data = request.json +    if data is None: +        return "OK" + +    message = [] +    for k, v in data.get("message", {}).items(): +        message.append("{0}={1}".format(k, v)) + +    print ("{0:20s} {1:20s} {2:36} {3}".format( +        human_time(data.get("ts")), +        data.get("event"), +        data.get("nodeid"), +        " ".join(message))) + +    return "OK" + + +def main(): +    parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, +                            description=__doc__) +    parser.add_argument("--port", type=int, help="Port", default=9000) +    parser.add_argument("--debug", help="Run Server in debug mode", +                        action="store_true") +    args = parser.parse_args() + +    print ("{0:20s} {1:20s} {2:36} {3}".format( +        "TIMESTAMP", "EVENT", "NODE ID", "MESSAGE" +    )) +    print ("{0:20s} {1:20s} {2:36} {3}".format( +        "-"*20, "-"*20, "-"*36, "-"*20 +    )) +    if args.debug: +        app.debug = True + +    app.run(host="0.0.0.0", port=args.port) + + +if __name__ == "__main__": +    main() diff --git a/extras/systemd/Makefile.am b/extras/systemd/Makefile.am index 3fc656b8262..82fa1aac690 100644 --- a/extras/systemd/Makefile.am +++ b/extras/systemd/Makefile.am @@ -1,5 +1,5 @@ - -CLEANFILES = +CLEANFILES = glustereventsd.service +EXTRA_DIST = glustereventsd.service.in  SYSTEMD_DIR = @systemddir@ @@ -8,4 +8,7 @@ install-exec-local:  		$(mkdir_p) $(DESTDIR)$(SYSTEMD_DIR); \  		$(INSTALL_PROGRAM) glusterd.service $(DESTDIR)$(SYSTEMD_DIR)/; \  	fi - +	@if [ @EVENTS_ENABLED@ = 1 ] && [ -d $(SYSTEMD_DIR) ]; then \ +		$(mkdir_p) $(DESTDIR)$(SYSTEMD_DIR); \ +		$(INSTALL_PROGRAM) glustereventsd.service $(DESTDIR)$(SYSTEMD_DIR)/; \ +	fi diff --git a/extras/systemd/glustereventsd.service.in b/extras/systemd/glustereventsd.service.in new file mode 100644 index 00000000000..2be3f25ac18 --- /dev/null +++ b/extras/systemd/glustereventsd.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=Gluster Events Notifier +After=syslog.target network.target + +[Service] +Type=simple +ExecStart=@SBIN_DIR@/glustereventsd +ExecReload=/bin/kill -SIGUSR2 $MAINPID +KillMode=control-group + +[Install] +WantedBy=multi-user.target diff --git a/glusterfs.spec.in b/glusterfs.spec.in index 6a29895a093..4f388f74ba3 100644 --- a/glusterfs.spec.in +++ b/glusterfs.spec.in @@ -90,6 +90,11 @@  %global _with_tmpfilesdir --without-tmpfilesdir  %endif +# Eventing +%if ( 0%{?rhel} && 0%{?rhel} < 6 ) +%global _without_events --disable-events +%endif +  # From https://fedoraproject.org/wiki/Packaging:Python#Macros  %if ( 0%{?rhel} && 0%{?rhel} <= 5 )  %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} @@ -569,6 +574,23 @@ is in user space and easily manageable.  This package provides the translators needed on any GlusterFS client. +%if ( 0%{!?_without_events:1} ) +%package events +Summary:          GlusterFS Events +Group:            Applications/File +Requires:         %{name}-server%{?_isa} = %{version}-%{release} +Requires:         python python-fasteners python-requests python-flask +Requires:         python-prettytable +Requires:         python-gluster = %{version}-%{release} +%if ( 0%{?rhel} && 0%{?rhel} <= 6 ) +Requires:         python-argparse +%endif + +%description events +GlusterFS Events + +%endif +  %prep  %setup -q -n %{name}-%{version}%{?prereltag} @@ -595,7 +617,8 @@ export CFLAGS          %{?_without_ocf} \          %{?_without_rdma} \          %{?_without_syslog} \ -        %{?_without_tiering} +        %{?_without_tiering} \ +        %{?_without_events}  # fix hardening and remove rpath in shlibs  %if ( 0%{?fedora} && 0%{?fedora} > 17 ) || ( 0%{?rhel} && 0%{?rhel} > 6 ) @@ -1195,7 +1218,25 @@ exit 0  %{_sbindir}/gf_logdump  %{_sbindir}/gf_recon +# Events +%if ( 0%{!?_without_events:1} ) +%files events +%config %attr(0600, root, root) %{_sysconfdir}/glusterfs/eventsconfig.json +%dir %attr(0755,-,-) %{_sharedstatedir}/glusterd/events +%{_libexecdir}/glusterfs/events +%{_libexecdir}/glusterfs/peer_eventsapi.py* +%{_sbindir}/glustereventsd +%{_sbindir}/gluster-eventsapi +%{_datadir}/glusterfs/scripts/eventsdash.py* +%if ( 0%{?_with_systemd:1} ) +%{_unitdir}/glustereventsd.service +%endif +%endif +  %changelog +* Wed Jul 15 2016 Aravinda VK <avishwan@redhat.com> +- Added new subpackage events(glusterfs-events) (#1334044) +  * Fri Jul 15 2016 Aravinda VK <avishwan@redhat.com>  - Removed ".py" extension from symlink(S57glusterfind-delete-post)(#1356868) diff --git a/libglusterfs/src/Makefile.am b/libglusterfs/src/Makefile.am index 2ec0f34a670..1dbea92b2bb 100644 --- a/libglusterfs/src/Makefile.am +++ b/libglusterfs/src/Makefile.am @@ -71,6 +71,12 @@ libglusterfs_la_SOURCES += $(CONTRIBDIR)/uuid/clear.c \  	$(CONTRIBDIR)/uuid/unpack.c  endif +if BUILD_EVENTS +libglusterfs_la_SOURCES += events.c + +libglusterfs_la_HEADERS += events.h eventtypes.h +endif +  libgfchangelog_HEADERS = changelog.h  EXTRA_DIST = graph.l graph.y defaults-tmpl.c diff --git a/libglusterfs/src/events.c b/libglusterfs/src/events.c new file mode 100644 index 00000000000..9d781874a8a --- /dev/null +++ b/libglusterfs/src/events.c @@ -0,0 +1,83 @@ +/* +  Copyright (c) 2016 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. +*/ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <stdarg.h> +#include <string.h> +#include "syscall.h" +#include "mem-pool.h" +#include "events.h" + +int +gf_event (int event, char *fmt, ...) +{ +        int      sock                     = -1; +        char     eventstr[EVENTS_MSG_MAX] = ""; +        struct   sockaddr_un server; +        va_list  arguments; +        char     *msg                     = NULL; +        int      ret                      = 0; +        size_t   eventstr_size            = 0; + +        if (event < 0 || event >= EVENT_LAST) { +                ret = EVENT_ERROR_INVALID_INPUTS; +                goto out; +        } + +        sock = socket(AF_UNIX, SOCK_STREAM, 0); +        if (sock < 0) { +                ret = EVENT_ERROR_SOCKET; +                goto out; +        } +        server.sun_family = AF_UNIX; +        strcpy(server.sun_path, EVENT_PATH); + +        if (connect(sock, +                    (struct sockaddr *) &server, +                    sizeof(struct sockaddr_un)) < 0) { +                ret = EVENT_ERROR_CONNECT; +                goto out; +        } + +        va_start (arguments, fmt); +        ret = gf_vasprintf (&msg, fmt, arguments); +        va_end (arguments); +        if (ret < 0) { +                ret = EVENT_ERROR_INVALID_INPUTS; +                goto out; +        } + +        eventstr_size = snprintf(NULL, 0, "%u %d %s", (unsigned)time(NULL), +                                 event, msg); + +        if (eventstr_size + 1 > EVENTS_MSG_MAX) { +                eventstr_size = EVENTS_MSG_MAX - 1; +        } + +        snprintf(eventstr, eventstr_size+1, "%u %d %s", +                 (unsigned)time(NULL), event, msg); + +        if (sys_write(sock, eventstr, strlen(eventstr)) <= 0) { +                ret = EVENT_ERROR_SEND; +                goto out; +        } + +        ret = EVENT_SEND_OK; + + out: +        sys_close(sock); +        GF_FREE(msg); +        return ret; +} diff --git a/libglusterfs/src/events.h.in b/libglusterfs/src/events.h.in new file mode 100644 index 00000000000..37692bef732 --- /dev/null +++ b/libglusterfs/src/events.h.in @@ -0,0 +1,23 @@ +/* +  Copyright (c) 2016 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. +*/ + +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#include <stdio.h> + +#include "eventtypes.h" + +#define EVENT_PATH "@localstatedir@/run/gluster/events.sock" +#define EVENTS_MSG_MAX 2048 + +extern int gf_event(int key, char *fmt, ...); + +#endif /* __EVENTS_H__ */ diff --git a/libglusterfs/src/eventtypes.h b/libglusterfs/src/eventtypes.h new file mode 100644 index 00000000000..874f8ccf114 --- /dev/null +++ b/libglusterfs/src/eventtypes.h @@ -0,0 +1,22 @@ +#ifndef __EVENTTYPES_H__ +#define __EVENTTYPES_H__ + +typedef enum { +    EVENT_SEND_OK, +    EVENT_ERROR_INVALID_INPUTS, +    EVENT_ERROR_SOCKET, +    EVENT_ERROR_CONNECT, +    EVENT_ERROR_SEND, +} event_errors_t; + +typedef enum { +    EVENT_PEER_ATTACH, +    EVENT_PEER_DETACH, +    EVENT_VOLUME_CREATE, +    EVENT_VOLUME_START, +    EVENT_VOLUME_STOP, +    EVENT_VOLUME_DELETE, +    EVENT_LAST +} eventtypes_t; + +#endif /* __EVENTTYPES_H__ */ diff --git a/libglusterfs/src/glusterfs.h b/libglusterfs/src/glusterfs.h index 8d387bafb3b..d11cbdcb8ee 100644 --- a/libglusterfs/src/glusterfs.h +++ b/libglusterfs/src/glusterfs.h @@ -37,6 +37,10 @@  #include "lkowner.h"  #include "compat-uuid.h" +#if (USE_EVENTS) +#include "events.h" +#endif +  #define GF_YES 1  #define GF_NO  0  | 
