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 |