3604df
From 2be99b28595ffab1d503354db832f981ded2671f Mon Sep 17 00:00:00 2001
3604df
From: Aravinda VK <avishwan@redhat.com>
3604df
Date: Thu, 5 May 2016 18:34:41 +0530
3604df
Subject: [PATCH 29/79] eventsapi: Gluster Eventing Feature implementation
3604df
3604df
[Depends on http://review.gluster.org/14627]
3604df
3604df
Design is available in `glusterfs-specs`, A change from the design
3604df
is support of webhook instead of Websockets as discussed in the design
3604df
3604df
http://review.gluster.org/13115
3604df
3604df
Since Websocket support depends on REST APIs, I will add Websocket support
3604df
once REST APIs patch gets merged
3604df
3604df
Usage:
3604df
Run following command to start/stop Eventsapi server in all Peers,
3604df
which will collect the notifications from any Gluster daemon and emits
3604df
to configured client.
3604df
3604df
    gluster-eventsapi start|stop|restart|reload
3604df
3604df
Status of running services can be checked using,
3604df
3604df
    gluster-eventsapi status
3604df
3604df
Events listener is a HTTP(S) server which listens to events emited by
3604df
the Gluster. Create a HTTP Server to listen on POST and register that
3604df
URL using,
3604df
3604df
    gluster-eventsapi webhook-add <URL> [--bearer-token <TOKEN>]
3604df
3604df
For example, if HTTP Server running in `http://192.168.122.188:9000`
3604df
then add that URL using,
3604df
3604df
    gluster-eventsapi webhook-add http://192.168.122.188:9000
3604df
3604df
If it expects a Token then specify it using `--bearer-token` or `-t`
3604df
3604df
We can also test Webhook if all peer nodes can send message or not
3604df
using,
3604df
3604df
    gluster-eventsapi webhook-test <URL> [--bearer-token <TOKEN>]
3604df
3604df
Configurations can be viewed/updated using,
3604df
3604df
    gluster-eventsapi config-get [--name]
3604df
    gluster-eventsapi config-set <NAME> <VALUE>
3604df
    gluster-eventsapi config-reset <NAME|all>
3604df
3604df
If any one peer node was down during config-set/reset or webhook
3604df
modifications, Run sync command from good node when a peer node comes
3604df
back. Automatic update is not yet implemented.
3604df
3604df
    gluster-eventsapi sync
3604df
3604df
Basic Events Client(HTTP Server) is included with the code, Start
3604df
running the client with required port and start listening to the
3604df
events.
3604df
3604df
    /usr/share/glusterfs/scripts/eventsdash.py --port 8080
3604df
3604df
Default port is 9000, if no port is specified, once it started running
3604df
then configure gluster-eventsapi to send events to that client.
3604df
3604df
Eventsapi Client can be outside of the Cluster, it can be run event on
3604df
Windows. But only requirement is the client URL should be accessible
3604df
by all peer nodes.(Or ngrok(https://ngrok.com) like tools can be used)
3604df
3604df
Events implemented with this patch,
3604df
- Volume Create
3604df
- Volume Start
3604df
- Volume Stop
3604df
- Volume Delete
3604df
- Peer Attach
3604df
- Peer Detach
3604df
3604df
It is easy to add/support more events, since it touches Gluster cmd
3604df
code and to avoid merge conflicts I will add support for more events
3604df
once this patch merges.
3604df
3604df
> Reviewed-on: http://review.gluster.org/14248
3604df
> Smoke: Gluster Build System <jenkins@build.gluster.org>
3604df
> NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
3604df
> CentOS-regression: Gluster Build System <jenkins@build.gluster.org>
3604df
> Reviewed-by: Jeff Darcy <jdarcy@redhat.com>
3604df
3604df
BUG: 1351589
3604df
Change-Id: I316827ac9dd1443454df7deffe4f54835f7f6a08
3604df
Signed-off-by: Aravinda VK <avishwan@redhat.com>
3604df
Reviewed-on: https://code.engineering.redhat.com/gerrit/84620
3604df
Reviewed-by: Milind Changire <mchangir@redhat.com>
3604df
Reviewed-by: Atin Mukherjee <amukherj@redhat.com>
3604df
---
3604df
 Makefile.am                              |    2 +-
3604df
 cli/src/cli-cmd-peer.c                   |   12 +
3604df
 cli/src/cli-cmd-volume.c                 |   24 ++-
3604df
 configure.ac                             |   49 +++
3604df
 events/Makefile.am                       |    6 +
3604df
 events/eventskeygen.py                   |   65 ++++
3604df
 events/src/Makefile.am                   |   24 ++
3604df
 events/src/__init__.py                   |   10 +
3604df
 events/src/eventsapiconf.py.in           |   22 ++
3604df
 events/src/eventsconfig.json             |    3 +
3604df
 events/src/eventtypes.py                 |    9 +
3604df
 events/src/glustereventsd.py             |  151 +++++++++
3604df
 events/src/handlers.py                   |   21 ++
3604df
 events/src/peer_eventsapi.py             |  521 ++++++++++++++++++++++++++++++
3604df
 events/src/utils.py                      |  150 +++++++++
3604df
 events/tools/Makefile.am                 |    3 +
3604df
 events/tools/eventsdash.py               |   74 +++++
3604df
 extras/systemd/Makefile.am               |    8 +-
3604df
 extras/systemd/glustereventsd.service.in |   12 +
3604df
 glusterfs.spec.in                        |   59 ++++-
3604df
 libglusterfs/src/Makefile.am             |    6 +
3604df
 libglusterfs/src/events.c                |   83 +++++
3604df
 libglusterfs/src/events.h.in             |   23 ++
3604df
 libglusterfs/src/eventtypes.h            |   22 ++
3604df
 libglusterfs/src/glusterfs.h             |    4 +
3604df
 27 files changed, 1378 insertions(+), 5 deletions(-)
3604df
 create mode 100644 events/Makefile.am
3604df
 create mode 100644 events/eventskeygen.py
3604df
 create mode 100644 events/src/Makefile.am
3604df
 create mode 100644 events/src/__init__.py
3604df
 create mode 100644 events/src/eventsapiconf.py.in
3604df
 create mode 100644 events/src/eventsconfig.json
3604df
 create mode 100644 events/src/eventtypes.py
3604df
 create mode 100644 events/src/glustereventsd.py
3604df
 create mode 100644 events/src/handlers.py
3604df
 create mode 100644 events/src/peer_eventsapi.py
3604df
 create mode 100644 events/src/utils.py
3604df
 create mode 100644 events/tools/Makefile.am
3604df
 create mode 100644 events/tools/eventsdash.py
3604df
 create mode 100644 extras/systemd/glustereventsd.service.in
3604df
 create mode 100644 libglusterfs/src/events.c
3604df
 create mode 100644 libglusterfs/src/events.h.in
3604df
 create mode 100644 libglusterfs/src/eventtypes.h
3604df
3604df
diff --git a/Makefile.am b/Makefile.am
3604df
index d36f530..37cbb86 100644
3604df
--- a/Makefile.am
3604df
+++ b/Makefile.am
3604df
@@ -12,7 +12,7 @@ EXTRA_DIST = autogen.sh \
3604df
 
3604df
 SUBDIRS = $(ARGP_STANDALONE_DIR) libglusterfs rpc api xlators glusterfsd \
3604df
 	$(FUSERMOUNT_SUBDIR) doc extras cli heal @SYNCDAEMON_SUBDIR@ \
3604df
-	@UMOUNTD_SUBDIR@ tools
3604df
+	@UMOUNTD_SUBDIR@ tools @EVENTS_SUBDIR@
3604df
 
3604df
 pkgconfigdir = @pkgconfigdir@
3604df
 pkgconfig_DATA = glusterfs-api.pc libgfchangelog.pc
3604df
diff --git a/cli/src/cli-cmd-peer.c b/cli/src/cli-cmd-peer.c
3604df
index d6b4ab1..36c328a 100644
3604df
--- a/cli/src/cli-cmd-peer.c
3604df
+++ b/cli/src/cli-cmd-peer.c
3604df
@@ -90,6 +90,12 @@ out:
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_PEER_ATTACH, "host=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
+
3604df
         return ret;
3604df
 }
3604df
 
3604df
@@ -160,6 +166,12 @@ out:
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_PEER_DETACH, "host=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
+
3604df
         return ret;
3604df
 }
3604df
 
3604df
diff --git a/cli/src/cli-cmd-volume.c b/cli/src/cli-cmd-volume.c
3604df
index 129d9b9..5296093 100644
3604df
--- a/cli/src/cli-cmd-volume.c
3604df
+++ b/cli/src/cli-cmd-volume.c
3604df
@@ -243,7 +243,11 @@ out:
3604df
         }
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
-
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_VOLUME_CREATE, "name=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
         return ret;
3604df
 }
3604df
 
3604df
@@ -318,6 +322,12 @@ out:
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_VOLUME_DELETE, "name=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
+
3604df
         return ret;
3604df
 }
3604df
 
3604df
@@ -392,6 +402,12 @@ out:
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_VOLUME_START, "name=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
+
3604df
         return ret;
3604df
 }
3604df
 
3604df
@@ -524,6 +540,12 @@ out:
3604df
 
3604df
         CLI_STACK_DESTROY (frame);
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+        if (ret == 0) {
3604df
+                gf_event (EVENT_VOLUME_STOP, "name=%s", (char *)words[2]);
3604df
+        }
3604df
+#endif
3604df
+
3604df
         return ret;
3604df
 }
3604df
 
3604df
diff --git a/configure.ac b/configure.ac
3604df
index 9025114..2e0323d 100644
3604df
--- a/configure.ac
3604df
+++ b/configure.ac
3604df
@@ -38,6 +38,7 @@ AC_CONFIG_HEADERS([config.h])
3604df
 AC_CONFIG_FILES([Makefile
3604df
                 libglusterfs/Makefile
3604df
                 libglusterfs/src/Makefile
3604df
+                libglusterfs/src/events.h
3604df
                 libglusterfs/src/gfdb/Makefile
3604df
                 geo-replication/src/peer_gsec_create
3604df
                 geo-replication/src/peer_mountbroker
3604df
@@ -206,6 +207,7 @@ AC_CONFIG_FILES([Makefile
3604df
                 extras/ganesha/ocf/Makefile
3604df
                 extras/systemd/Makefile
3604df
                 extras/systemd/glusterd.service
3604df
+                extras/systemd/glustereventsd.service
3604df
                 extras/run-gluster.tmpfiles
3604df
                 extras/benchmarking/Makefile
3604df
                 extras/hook-scripts/Makefile
3604df
@@ -229,6 +231,10 @@ AC_CONFIG_FILES([Makefile
3604df
                 extras/hook-scripts/reset/post/Makefile
3604df
                 extras/hook-scripts/reset/pre/Makefile
3604df
                 extras/snap_scheduler/Makefile
3604df
+                events/Makefile
3604df
+                events/src/Makefile
3604df
+                events/src/eventsapiconf.py
3604df
+                events/tools/Makefile
3604df
                 contrib/fuse-util/Makefile
3604df
                 contrib/umountd/Makefile
3604df
                 contrib/uuid/uuid_types.h
3604df
@@ -700,6 +706,43 @@ fi
3604df
 AC_SUBST(GEOREP_EXTRAS_SUBDIR)
3604df
 AM_CONDITIONAL(USE_GEOREP, test "x$enable_georeplication" != "xno")
3604df
 
3604df
+# Events section
3604df
+AC_ARG_ENABLE([events],
3604df
+              AC_HELP_STRING([--disable-events],
3604df
+                             [Do not install Events components]))
3604df
+
3604df
+BUILD_EVENTS=no
3604df
+EVENTS_ENABLED=0
3604df
+EVENTS_SUBDIR=
3604df
+have_python2=no
3604df
+if test "x$enable_events" != "xno"; then
3604df
+  EVENTS_SUBDIR=events
3604df
+  EVENTS_ENABLED=1
3604df
+
3604df
+  BUILD_EVENTS="yes"
3604df
+  AM_PATH_PYTHON()
3604df
+  dnl Check if version matches that we require
3604df
+  if echo $PYTHON_VERSION | grep ^2; then
3604df
+     have_python2=yes
3604df
+  fi
3604df
+
3604df
+  if test "x$have_python2" = "xno"; then
3604df
+     if test "x$enable_events" = "xyes"; then
3604df
+        AC_MSG_ERROR([python 2.x packages required. exiting..])
3604df
+     fi
3604df
+     AC_MSG_WARN([python 2.x not found, disabling events])
3604df
+     EVENTS_SUBDIR=
3604df
+     EVENTS_ENABLED=0
3604df
+     BUILD_EVENTS="no"
3604df
+  else
3604df
+    AC_DEFINE(USE_EVENTS, 1, [define if events enabled])
3604df
+  fi
3604df
+fi
3604df
+AC_SUBST(EVENTS_ENABLED)
3604df
+AC_SUBST(EVENTS_SUBDIR)
3604df
+AM_CONDITIONAL([BUILD_EVENTS], [test x$BUILD_EVENTS = xyes])
3604df
+# end Events section
3604df
+
3604df
 # CDC xlator - check if libz is present if so enable HAVE_LIB_Z
3604df
 BUILD_CDC=yes
3604df
 PKG_CHECK_MODULES([ZLIB], [zlib >= 1.2.0],,
3604df
@@ -1079,10 +1122,15 @@ eval sbintemp=\"${sbintemp}\"
3604df
 eval sbintemp=\"${sbintemp}\"
3604df
 SBIN_DIR=${sbintemp}
3604df
 
3604df
+sysconfdirtemp="${sysconfdir}"
3604df
+eval sysconfdirtemp=\"${sysconfdirtemp}\"
3604df
+SYSCONF_DIR=${sysconfdirtemp}
3604df
+
3604df
 prefix=$prefix_temp
3604df
 exec_prefix=$exec_prefix_temp
3604df
 
3604df
 AC_SUBST(SBIN_DIR)
3604df
+AC_SUBST(SYSCONF_DIR)
3604df
 
3604df
 # lazy umount emulation
3604df
 UMOUNTD_SUBDIR=""
3604df
@@ -1359,4 +1407,5 @@ echo "POSIX ACLs           : $BUILD_POSIX_ACLS"
3604df
 echo "Data Classification  : $BUILD_GFDB"
3604df
 echo "firewalld-config     : $BUILD_FIREWALLD"
3604df
 echo "Experimental xlators : $BUILD_EXPERIMENTAL"
3604df
+echo "Events               : $BUILD_EVENTS"
3604df
 echo
3604df
diff --git a/events/Makefile.am b/events/Makefile.am
3604df
new file mode 100644
3604df
index 0000000..04a74ef
3604df
--- /dev/null
3604df
+++ b/events/Makefile.am
3604df
@@ -0,0 +1,6 @@
3604df
+SUBDIRS = src tools
3604df
+
3604df
+noinst_PYTHON = eventskeygen.py
3604df
+
3604df
+install-data-hook:
3604df
+	$(INSTALL) -d -m 755 $(DESTDIR)@GLUSTERD_WORKDIR@/events
3604df
diff --git a/events/eventskeygen.py b/events/eventskeygen.py
3604df
new file mode 100644
3604df
index 0000000..656a7dc
3604df
--- /dev/null
3604df
+++ b/events/eventskeygen.py
3604df
@@ -0,0 +1,65 @@
3604df
+#!/usr/bin/env python
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+import os
3604df
+
3604df
+GLUSTER_SRC_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
3604df
+eventtypes_h = os.path.join(GLUSTER_SRC_ROOT, "libglusterfs/src/eventtypes.h")
3604df
+eventtypes_py = os.path.join(GLUSTER_SRC_ROOT, "events/src/eventtypes.py")
3604df
+
3604df
+# When adding new keys add it to the END
3604df
+keys = (
3604df
+    "EVENT_PEER_ATTACH",
3604df
+    "EVENT_PEER_DETACH",
3604df
+
3604df
+    "EVENT_VOLUME_CREATE",
3604df
+    "EVENT_VOLUME_START",
3604df
+    "EVENT_VOLUME_STOP",
3604df
+    "EVENT_VOLUME_DELETE",
3604df
+)
3604df
+
3604df
+LAST_EVENT = "EVENT_LAST"
3604df
+
3604df
+ERRORS = (
3604df
+    "EVENT_SEND_OK",
3604df
+    "EVENT_ERROR_INVALID_INPUTS",
3604df
+    "EVENT_ERROR_SOCKET",
3604df
+    "EVENT_ERROR_CONNECT",
3604df
+    "EVENT_ERROR_SEND"
3604df
+)
3604df
+
3604df
+# Generate eventtypes.h
3604df
+with open(eventtypes_h, "w") as f:
3604df
+    f.write("#ifndef __EVENTTYPES_H__\n")
3604df
+    f.write("#define __EVENTTYPES_H__\n\n")
3604df
+    f.write("typedef enum {\n")
3604df
+    for k in ERRORS:
3604df
+        f.write("    {0},\n".format(k))
3604df
+    f.write("} event_errors_t;\n")
3604df
+
3604df
+    f.write("\n")
3604df
+
3604df
+    f.write("typedef enum {\n")
3604df
+    for k in keys:
3604df
+        f.write("    {0},\n".format(k))
3604df
+
3604df
+    f.write("    {0}\n".format(LAST_EVENT))
3604df
+    f.write("} eventtypes_t;\n")
3604df
+    f.write("\n#endif /* __EVENTTYPES_H__ */\n")
3604df
+
3604df
+# Generate eventtypes.py
3604df
+with open(eventtypes_py, "w") as f:
3604df
+    f.write("# -*- coding: utf-8 -*-\n")
3604df
+    f.write("all_events = [\n")
3604df
+    for ev in keys:
3604df
+        f.write('    "{0}",\n'.format(ev))
3604df
+    f.write("]\n")
3604df
diff --git a/events/src/Makefile.am b/events/src/Makefile.am
3604df
new file mode 100644
3604df
index 0000000..528f020
3604df
--- /dev/null
3604df
+++ b/events/src/Makefile.am
3604df
@@ -0,0 +1,24 @@
3604df
+EXTRA_DIST = glustereventsd.py __init__.py  eventsapiconf.py.in eventtypes.py \
3604df
+	handlers.py utils.py peer_eventsapi.py eventsconfig.json
3604df
+
3604df
+eventsdir = $(libexecdir)/glusterfs/events
3604df
+eventspeerscriptdir = $(libexecdir)/glusterfs
3604df
+eventsconfdir = $(sysconfdir)/glusterfs
3604df
+eventsconf_DATA = eventsconfig.json
3604df
+
3604df
+events_PYTHON = __init__.py eventsapiconf.py eventtypes.py handlers.py utils.py
3604df
+events_SCRIPTS = glustereventsd.py
3604df
+eventspeerscript_SCRIPTS = peer_eventsapi.py
3604df
+
3604df
+install-exec-hook:
3604df
+	$(mkdir_p) $(DESTDIR)$(sbindir)
3604df
+	rm -f $(DESTDIR)$(sbindir)/glustereventsd
3604df
+	ln -s $(libexecdir)/glusterfs/events/glustereventsd.py \
3604df
+		$(DESTDIR)$(sbindir)/glustereventsd
3604df
+	rm -f $(DESTDIR)$(sbindir)/gluster-eventing
3604df
+	ln -s $(libexecdir)/glusterfs/peer_eventsapi.py \
3604df
+		$(DESTDIR)$(sbindir)/gluster-eventsapi
3604df
+
3604df
+uninstall-hook:
3604df
+	rm -f $(DESTDIR)$(sbindir)/glustereventsd
3604df
+	rm -f $(DESTDIR)$(sbindir)/gluster-eventsapi
3604df
diff --git a/events/src/__init__.py b/events/src/__init__.py
3604df
new file mode 100644
3604df
index 0000000..f27c53a
3604df
--- /dev/null
3604df
+++ b/events/src/__init__.py
3604df
@@ -0,0 +1,10 @@
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
diff --git a/events/src/eventsapiconf.py.in b/events/src/eventsapiconf.py.in
3604df
new file mode 100644
3604df
index 0000000..702e1d2
3604df
--- /dev/null
3604df
+++ b/events/src/eventsapiconf.py.in
3604df
@@ -0,0 +1,22 @@
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+SERVER_ADDRESS = "@localstatedir@/run/gluster/events.sock"
3604df
+DEFAULT_CONFIG_FILE = "@SYSCONF_DIR@/glusterfs/eventsconfig.json"
3604df
+CUSTOM_CONFIG_FILE_TO_SYNC = "/events/config.json"
3604df
+CUSTOM_CONFIG_FILE = "@GLUSTERD_WORKDIR@" + CUSTOM_CONFIG_FILE_TO_SYNC
3604df
+WEBHOOKS_FILE_TO_SYNC = "/events/webhooks.json"
3604df
+WEBHOOKS_FILE = "@GLUSTERD_WORKDIR@" + WEBHOOKS_FILE_TO_SYNC
3604df
+LOG_FILE = "@localstatedir@/log/glusterfs/events.log"
3604df
+EVENTSD = "glustereventsd"
3604df
+CONFIG_KEYS = ["log_level"]
3604df
+BOOL_CONFIGS = []
3604df
+RESTART_CONFIGS = []
3604df
diff --git a/events/src/eventsconfig.json b/events/src/eventsconfig.json
3604df
new file mode 100644
3604df
index 0000000..ce2c775
3604df
--- /dev/null
3604df
+++ b/events/src/eventsconfig.json
3604df
@@ -0,0 +1,3 @@
3604df
+{
3604df
+    "log_level": "INFO"
3604df
+}
3604df
diff --git a/events/src/eventtypes.py b/events/src/eventtypes.py
3604df
new file mode 100644
3604df
index 0000000..4812e65
3604df
--- /dev/null
3604df
+++ b/events/src/eventtypes.py
3604df
@@ -0,0 +1,9 @@
3604df
+# -*- coding: utf-8 -*-
3604df
+all_events = [
3604df
+    "EVENT_PEER_ATTACH",
3604df
+    "EVENT_PEER_DETACH",
3604df
+    "EVENT_VOLUME_CREATE",
3604df
+    "EVENT_VOLUME_START",
3604df
+    "EVENT_VOLUME_STOP",
3604df
+    "EVENT_VOLUME_DELETE",
3604df
+]
3604df
diff --git a/events/src/glustereventsd.py b/events/src/glustereventsd.py
3604df
new file mode 100644
3604df
index 0000000..3fa5768
3604df
--- /dev/null
3604df
+++ b/events/src/glustereventsd.py
3604df
@@ -0,0 +1,151 @@
3604df
+#!/usr/bin/env python
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+from __future__ import print_function
3604df
+import asyncore
3604df
+import socket
3604df
+import os
3604df
+from multiprocessing import Process, Queue
3604df
+import sys
3604df
+import signal
3604df
+
3604df
+from eventtypes import all_events
3604df
+import handlers
3604df
+import utils
3604df
+from eventsapiconf import SERVER_ADDRESS
3604df
+from utils import logger
3604df
+
3604df
+# Global Queue, EventsHandler will add items to the queue
3604df
+# and process_event will gets each item and handles it
3604df
+events_queue = Queue()
3604df
+events_server_pid = None
3604df
+
3604df
+
3604df
+def process_event():
3604df
+    """
3604df
+    Seperate process which handles all the incoming events from Gluster
3604df
+    processes.
3604df
+    """
3604df
+    while True:
3604df
+        data = events_queue.get()
3604df
+        logger.debug("EVENT: {0}".format(repr(data)))
3604df
+        try:
3604df
+            # Event Format <TIMESTAMP> <TYPE> <DETAIL>
3604df
+            ts, key, value = data.split(" ", 2)
3604df
+        except ValueError:
3604df
+            logger.warn("Invalid Event Format {0}".format(data))
3604df
+            continue
3604df
+
3604df
+        data_dict = {}
3604df
+        try:
3604df
+            # Format key=value;key=value
3604df
+            data_dict = dict(x.split('=') for x in value.split(';'))
3604df
+        except ValueError:
3604df
+            logger.warn("Unable to parse Event {0}".format(data))
3604df
+            continue
3604df
+
3604df
+        try:
3604df
+            # Event Type to Function Map, Recieved event data will be in
3604df
+            # the form <TIMESTAMP> <TYPE> <DETAIL>, Get Event name for the
3604df
+            # recieved Type/Key and construct a function name starting with
3604df
+            # handle_ For example: handle_event_volume_create
3604df
+            func_name = "handle_" + all_events[int(key)].lower()
3604df
+        except IndexError:
3604df
+            # This type of Event is not handled?
3604df
+            logger.warn("Unhandled Event: {0}".format(key))
3604df
+            func_name = None
3604df
+
3604df
+        if func_name is not None:
3604df
+            # Get function from handlers module
3604df
+            func = getattr(handlers, func_name, None)
3604df
+            # If func is None, then handler unimplemented for that event.
3604df
+            if func is not None:
3604df
+                func(ts, int(key), data_dict)
3604df
+            else:
3604df
+                # Generic handler, broadcast whatever received
3604df
+                handlers.generic_handler(ts, int(key), data_dict)
3604df
+
3604df
+
3604df
+def process_event_wrapper():
3604df
+    try:
3604df
+        process_event()
3604df
+    except KeyboardInterrupt:
3604df
+        return
3604df
+
3604df
+
3604df
+class GlusterEventsHandler(asyncore.dispatcher_with_send):
3604df
+
3604df
+    def handle_read(self):
3604df
+        data = self.recv(8192)
3604df
+        if data:
3604df
+            events_queue.put(data)
3604df
+            self.send(data)
3604df
+
3604df
+
3604df
+class GlusterEventsServer(asyncore.dispatcher):
3604df
+
3604df
+    def __init__(self):
3604df
+        global events_server_pid
3604df
+        asyncore.dispatcher.__init__(self)
3604df
+        # Start the Events listener process which listens to
3604df
+        # the global queue
3604df
+        p = Process(target=process_event_wrapper)
3604df
+        p.start()
3604df
+        events_server_pid = p.pid
3604df
+
3604df
+        # Create UNIX Domain Socket, bind to path
3604df
+        self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
3604df
+        self.bind(SERVER_ADDRESS)
3604df
+        self.listen(5)
3604df
+
3604df
+    def handle_accept(self):
3604df
+        pair = self.accept()
3604df
+        if pair is not None:
3604df
+            sock, addr = pair
3604df
+            GlusterEventsHandler(sock)
3604df
+
3604df
+
3604df
+def signal_handler_sigusr2(sig, frame):
3604df
+    if events_server_pid is not None:
3604df
+        os.kill(events_server_pid, signal.SIGUSR2)
3604df
+    utils.load_all()
3604df
+
3604df
+
3604df
+def init_event_server():
3604df
+    utils.setup_logger()
3604df
+
3604df
+    # Delete Socket file if Exists
3604df
+    try:
3604df
+        os.unlink(SERVER_ADDRESS)
3604df
+    except OSError:
3604df
+        if os.path.exists(SERVER_ADDRESS):
3604df
+            print ("Failed to cleanup socket file {0}".format(SERVER_ADDRESS),
3604df
+                   file=sys.stderr)
3604df
+            sys.exit(1)
3604df
+
3604df
+    utils.load_all()
3604df
+
3604df
+    # Start the Eventing Server, UNIX DOMAIN SOCKET Server
3604df
+    GlusterEventsServer()
3604df
+    asyncore.loop()
3604df
+
3604df
+
3604df
+def main():
3604df
+    try:
3604df
+        init_event_server()
3604df
+    except KeyboardInterrupt:
3604df
+        sys.exit(1)
3604df
+
3604df
+
3604df
+if __name__ == "__main__":
3604df
+    signal.signal(signal.SIGUSR2, signal_handler_sigusr2)
3604df
+    main()
3604df
diff --git a/events/src/handlers.py b/events/src/handlers.py
3604df
new file mode 100644
3604df
index 0000000..9b756a9
3604df
--- /dev/null
3604df
+++ b/events/src/handlers.py
3604df
@@ -0,0 +1,21 @@
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+import utils
3604df
+
3604df
+
3604df
+def generic_handler(ts, key, data):
3604df
+    """
3604df
+    Generic handler to broadcast message to all peers, custom handlers
3604df
+    can be created by func name handler_<event_name>
3604df
+    Ex: handle_event_volume_create(ts, key, data)
3604df
+    """
3604df
+    utils.publish(ts, key, data)
3604df
diff --git a/events/src/peer_eventsapi.py b/events/src/peer_eventsapi.py
3604df
new file mode 100644
3604df
index 0000000..7887d77
3604df
--- /dev/null
3604df
+++ b/events/src/peer_eventsapi.py
3604df
@@ -0,0 +1,521 @@
3604df
+#!/usr/bin/env python
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+from __future__ import print_function
3604df
+import os
3604df
+import json
3604df
+from errno import EEXIST
3604df
+
3604df
+import requests
3604df
+import fasteners
3604df
+from prettytable import PrettyTable
3604df
+
3604df
+from gluster.cliutils import (Cmd, execute, node_output_ok, node_output_notok,
3604df
+                              sync_file_to_peers, GlusterCmdException,
3604df
+                              output_error, execute_in_peers, runcli)
3604df
+
3604df
+from events.eventsapiconf import (WEBHOOKS_FILE_TO_SYNC,
3604df
+                                  WEBHOOKS_FILE,
3604df
+                                  DEFAULT_CONFIG_FILE,
3604df
+                                  CUSTOM_CONFIG_FILE,
3604df
+                                  CUSTOM_CONFIG_FILE_TO_SYNC,
3604df
+                                  EVENTSD,
3604df
+                                  CONFIG_KEYS,
3604df
+                                  BOOL_CONFIGS,
3604df
+                                  RESTART_CONFIGS)
3604df
+
3604df
+
3604df
+def file_content_overwrite(fname, data):
3604df
+    with open(fname + ".tmp", "w") as f:
3604df
+        f.write(json.dumps(data))
3604df
+
3604df
+    os.rename(fname + ".tmp", fname)
3604df
+
3604df
+
3604df
+def create_custom_config_file_if_not_exists():
3604df
+    mkdirp(os.path.dirname(CUSTOM_CONFIG_FILE))
3604df
+    if not os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+        with open(CUSTOM_CONFIG_FILE, "w") as f:
3604df
+            f.write("{}")
3604df
+
3604df
+
3604df
+def create_webhooks_file_if_not_exists():
3604df
+    mkdirp(os.path.dirname(WEBHOOKS_FILE))
3604df
+    if not os.path.exists(WEBHOOKS_FILE):
3604df
+        with open(WEBHOOKS_FILE, "w") as f:
3604df
+            f.write("{}")
3604df
+
3604df
+
3604df
+def boolify(value):
3604df
+    val = False
3604df
+    if value.lower() in ["enabled", "true", "on", "yes"]:
3604df
+        val = True
3604df
+    return val
3604df
+
3604df
+
3604df
+def mkdirp(path, exit_on_err=False, logger=None):
3604df
+    """
3604df
+    Try creating required directory structure
3604df
+    ignore EEXIST and raise exception for rest of the errors.
3604df
+    Print error in stderr and exit
3604df
+    """
3604df
+    try:
3604df
+        os.makedirs(path)
3604df
+    except (OSError, IOError) as e:
3604df
+        if e.errno == EEXIST and os.path.isdir(path):
3604df
+            pass
3604df
+        else:
3604df
+            output_error("Fail to create dir %s: %s" % (path, e))
3604df
+
3604df
+
3604df
+def is_enabled(service):
3604df
+    rc, out, err = execute(["systemctl", "is-enabled", service])
3604df
+    return rc == 0
3604df
+
3604df
+
3604df
+def is_active(service):
3604df
+    rc, out, err = execute(["systemctl", "is-active", service])
3604df
+    return rc == 0
3604df
+
3604df
+
3604df
+def enable_service(service):
3604df
+    if not is_enabled(service):
3604df
+        cmd = ["systemctl", "enable", service]
3604df
+        return execute(cmd)
3604df
+
3604df
+    return (0, "", "")
3604df
+
3604df
+
3604df
+def disable_service(service):
3604df
+    if is_enabled(service):
3604df
+        cmd = ["systemctl", "disable", service]
3604df
+        return execute(cmd)
3604df
+
3604df
+    return (0, "", "")
3604df
+
3604df
+
3604df
+def start_service(service):
3604df
+    rc, out, err = enable_service(service)
3604df
+    if rc != 0:
3604df
+        return (rc, out, err)
3604df
+
3604df
+    cmd = ["systemctl", "start", service]
3604df
+    return execute(cmd)
3604df
+
3604df
+
3604df
+def stop_service(service):
3604df
+    rc, out, err = disable_service(service)
3604df
+    if rc != 0:
3604df
+        return (rc, out, err)
3604df
+
3604df
+    cmd = ["systemctl", "stop", service]
3604df
+    return execute(cmd)
3604df
+
3604df
+
3604df
+def restart_service(service):
3604df
+    rc, out, err = stop_service(service)
3604df
+    if rc != 0:
3604df
+        return (rc, out, err)
3604df
+
3604df
+    return start_service(service)
3604df
+
3604df
+
3604df
+def reload_service(service):
3604df
+    if is_active(service):
3604df
+        cmd = ["systemctl", "reload", service]
3604df
+        return execute(cmd)
3604df
+
3604df
+    return (0, "", "")
3604df
+
3604df
+
3604df
+def sync_to_peers(restart=False):
3604df
+    if os.path.exists(WEBHOOKS_FILE):
3604df
+        try:
3604df
+            sync_file_to_peers(WEBHOOKS_FILE_TO_SYNC)
3604df
+        except GlusterCmdException as e:
3604df
+            output_error("Failed to sync Webhooks file: [Error: {0}]"
3604df
+                         "{1}".format(e[0], e[2]))
3604df
+
3604df
+    if os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+        try:
3604df
+            sync_file_to_peers(CUSTOM_CONFIG_FILE_TO_SYNC)
3604df
+        except GlusterCmdException as e:
3604df
+            output_error("Failed to sync Config file: [Error: {0}]"
3604df
+                         "{1}".format(e[0], e[2]))
3604df
+
3604df
+    action = "node-reload"
3604df
+    if restart:
3604df
+        action = "node-restart"
3604df
+
3604df
+    out = execute_in_peers(action)
3604df
+    table = PrettyTable(["NODE", "NODE STATUS", "SYNC STATUS"])
3604df
+    table.align["NODE STATUS"] = "r"
3604df
+    table.align["SYNC STATUS"] = "r"
3604df
+
3604df
+    for p in out:
3604df
+        table.add_row([p.hostname,
3604df
+                       "UP" if p.node_up else "DOWN",
3604df
+                       "OK" if p.ok else "NOT OK: {0}".format(
3604df
+                           p.error)])
3604df
+
3604df
+    print (table)
3604df
+
3604df
+
3604df
+def node_output_handle(resp):
3604df
+    rc, out, err = resp
3604df
+    if rc == 0:
3604df
+        node_output_ok(out)
3604df
+    else:
3604df
+        node_output_notok(err)
3604df
+
3604df
+
3604df
+def action_handle(action):
3604df
+    out = execute_in_peers("node-" + action)
3604df
+    column_name = action.upper()
3604df
+    if action == "status":
3604df
+        column_name = EVENTSD.upper()
3604df
+
3604df
+    table = PrettyTable(["NODE", "NODE STATUS", column_name + " STATUS"])
3604df
+    table.align["NODE STATUS"] = "r"
3604df
+    table.align[column_name + " STATUS"] = "r"
3604df
+
3604df
+    for p in out:
3604df
+        status_col_val = "OK" if p.ok else "NOT OK: {0}".format(
3604df
+            p.error)
3604df
+        if action == "status":
3604df
+            status_col_val = "DOWN"
3604df
+            if p.ok:
3604df
+                status_col_val = p.output
3604df
+
3604df
+        table.add_row([p.hostname,
3604df
+                       "UP" if p.node_up else "DOWN",
3604df
+                       status_col_val])
3604df
+
3604df
+    print (table)
3604df
+
3604df
+
3604df
+class NodeStart(Cmd):
3604df
+    name = "node-start"
3604df
+
3604df
+    def run(self, args):
3604df
+        node_output_handle(start_service(EVENTSD))
3604df
+
3604df
+
3604df
+class StartCmd(Cmd):
3604df
+    name = "start"
3604df
+
3604df
+    def run(self, args):
3604df
+        action_handle("start")
3604df
+
3604df
+
3604df
+class NodeStop(Cmd):
3604df
+    name = "node-stop"
3604df
+
3604df
+    def run(self, args):
3604df
+        node_output_handle(stop_service(EVENTSD))
3604df
+
3604df
+
3604df
+class StopCmd(Cmd):
3604df
+    name = "stop"
3604df
+
3604df
+    def run(self, args):
3604df
+        action_handle("stop")
3604df
+
3604df
+
3604df
+class NodeRestart(Cmd):
3604df
+    name = "node-restart"
3604df
+
3604df
+    def run(self, args):
3604df
+        node_output_handle(restart_service(EVENTSD))
3604df
+
3604df
+
3604df
+class RestartCmd(Cmd):
3604df
+    name = "restart"
3604df
+
3604df
+    def run(self, args):
3604df
+        action_handle("restart")
3604df
+
3604df
+
3604df
+class NodeReload(Cmd):
3604df
+    name = "node-reload"
3604df
+
3604df
+    def run(self, args):
3604df
+        node_output_handle(reload_service(EVENTSD))
3604df
+
3604df
+
3604df
+class ReloadCmd(Cmd):
3604df
+    name = "reload"
3604df
+
3604df
+    def run(self, args):
3604df
+        action_handle("reload")
3604df
+
3604df
+
3604df
+class NodeStatus(Cmd):
3604df
+    name = "node-status"
3604df
+
3604df
+    def run(self, args):
3604df
+        node_output_ok("UP" if is_active(EVENTSD) else "DOWN")
3604df
+
3604df
+
3604df
+class StatusCmd(Cmd):
3604df
+    name = "status"
3604df
+
3604df
+    def run(self, args):
3604df
+        webhooks = {}
3604df
+        if os.path.exists(WEBHOOKS_FILE):
3604df
+            webhooks = json.load(open(WEBHOOKS_FILE))
3604df
+
3604df
+        print ("Webhooks: " + ("" if webhooks else "None"))
3604df
+        for w in webhooks:
3604df
+            print (w)
3604df
+
3604df
+        print ()
3604df
+        action_handle("status")
3604df
+
3604df
+
3604df
+class WebhookAddCmd(Cmd):
3604df
+    name = "webhook-add"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("url", help="URL of Webhook")
3604df
+        parser.add_argument("--bearer_token", "-t", help="Bearer Token",
3604df
+                            default="")
3604df
+
3604df
+    def run(self, args):
3604df
+        create_webhooks_file_if_not_exists()
3604df
+
3604df
+        with fasteners.InterProcessLock(WEBHOOKS_FILE):
3604df
+            data = json.load(open(WEBHOOKS_FILE))
3604df
+            if data.get(args.url, None) is not None:
3604df
+                output_error("Webhook already exists")
3604df
+
3604df
+            data[args.url] = args.bearer_token
3604df
+            file_content_overwrite(WEBHOOKS_FILE, data)
3604df
+
3604df
+        sync_to_peers()
3604df
+
3604df
+
3604df
+class WebhookModCmd(Cmd):
3604df
+    name = "webhook-mod"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("url", help="URL of Webhook")
3604df
+        parser.add_argument("--bearer_token", "-t", help="Bearer Token",
3604df
+                            default="")
3604df
+
3604df
+    def run(self, args):
3604df
+        create_webhooks_file_if_not_exists()
3604df
+
3604df
+        with fasteners.InterProcessLock(WEBHOOKS_FILE):
3604df
+            data = json.load(open(WEBHOOKS_FILE))
3604df
+            if data.get(args.url, None) is None:
3604df
+                output_error("Webhook does not exists")
3604df
+
3604df
+            data[args.url] = args.bearer_token
3604df
+            file_content_overwrite(WEBHOOKS_FILE, data)
3604df
+
3604df
+        sync_to_peers()
3604df
+
3604df
+
3604df
+class WebhookDelCmd(Cmd):
3604df
+    name = "webhook-del"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("url", help="URL of Webhook")
3604df
+
3604df
+    def run(self, args):
3604df
+        create_webhooks_file_if_not_exists()
3604df
+
3604df
+        with fasteners.InterProcessLock(WEBHOOKS_FILE):
3604df
+            data = json.load(open(WEBHOOKS_FILE))
3604df
+            if data.get(args.url, None) is None:
3604df
+                output_error("Webhook does not exists")
3604df
+
3604df
+            del data[args.url]
3604df
+            file_content_overwrite(WEBHOOKS_FILE, data)
3604df
+
3604df
+        sync_to_peers()
3604df
+
3604df
+
3604df
+class NodeWebhookTestCmd(Cmd):
3604df
+    name = "node-webhook-test"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("url")
3604df
+        parser.add_argument("bearer_token")
3604df
+
3604df
+    def run(self, args):
3604df
+        http_headers = {}
3604df
+        if args.bearer_token != ".":
3604df
+            http_headers["Authorization"] = "Bearer " + args.bearer_token
3604df
+
3604df
+        try:
3604df
+            resp = requests.post(args.url, headers=http_headers)
3604df
+        except requests.ConnectionError as e:
3604df
+            node_output_notok("{0}".format(e))
3604df
+
3604df
+        if resp.status_code != 200:
3604df
+            node_output_notok("{0}".format(resp.status_code))
3604df
+
3604df
+        node_output_ok()
3604df
+
3604df
+
3604df
+class WebhookTestCmd(Cmd):
3604df
+    name = "webhook-test"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("url", help="URL of Webhook")
3604df
+        parser.add_argument("--bearer_token", "-t", help="Bearer Token")
3604df
+
3604df
+    def run(self, args):
3604df
+        url = args.url
3604df
+        bearer_token = args.bearer_token
3604df
+        if not args.url:
3604df
+            url = "."
3604df
+        if not args.bearer_token:
3604df
+            bearer_token = "."
3604df
+
3604df
+        out = execute_in_peers("node-webhook-test", [url, bearer_token])
3604df
+
3604df
+        table = PrettyTable(["NODE", "NODE STATUS", "WEBHOOK STATUS"])
3604df
+        table.align["NODE STATUS"] = "r"
3604df
+        table.align["WEBHOOK STATUS"] = "r"
3604df
+
3604df
+        for p in out:
3604df
+            table.add_row([p.hostname,
3604df
+                           "UP" if p.node_up else "DOWN",
3604df
+                           "OK" if p.ok else "NOT OK: {0}".format(
3604df
+                               p.error)])
3604df
+
3604df
+        print (table)
3604df
+
3604df
+
3604df
+class ConfigGetCmd(Cmd):
3604df
+    name = "config-get"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("--name", help="Config Name")
3604df
+
3604df
+    def run(self, args):
3604df
+        data = json.load(open(DEFAULT_CONFIG_FILE))
3604df
+        if os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+            data.update(json.load(open(CUSTOM_CONFIG_FILE)))
3604df
+
3604df
+        if args.name is not None and args.name not in CONFIG_KEYS:
3604df
+            output_error("Invalid Config item")
3604df
+
3604df
+        table = PrettyTable(["NAME", "VALUE"])
3604df
+        if args.name is None:
3604df
+            for k, v in data.items():
3604df
+                table.add_row([k, v])
3604df
+        else:
3604df
+            table.add_row([args.name, data[args.name]])
3604df
+
3604df
+        print (table)
3604df
+
3604df
+
3604df
+def read_file_content_json(fname):
3604df
+    content = "{}"
3604df
+    with open(fname) as f:
3604df
+        content = f.read()
3604df
+        if content.strip() == "":
3604df
+            content = "{}"
3604df
+
3604df
+    return json.loads(content)
3604df
+
3604df
+
3604df
+class ConfigSetCmd(Cmd):
3604df
+    name = "config-set"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("name", help="Config Name")
3604df
+        parser.add_argument("value", help="Config Value")
3604df
+
3604df
+    def run(self, args):
3604df
+        if args.name not in CONFIG_KEYS:
3604df
+            output_error("Invalid Config item")
3604df
+
3604df
+        with fasteners.InterProcessLock(CUSTOM_CONFIG_FILE):
3604df
+            data = json.load(open(DEFAULT_CONFIG_FILE))
3604df
+            if os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+                config_json = read_file_content_json(CUSTOM_CONFIG_FILE)
3604df
+                data.update(config_json)
3604df
+
3604df
+            # Do Nothing if same as previous value
3604df
+            if data[args.name] == args.value:
3604df
+                return
3604df
+
3604df
+            # TODO: Validate Value
3604df
+            create_custom_config_file_if_not_exists()
3604df
+            new_data = read_file_content_json(CUSTOM_CONFIG_FILE)
3604df
+
3604df
+            v = args.value
3604df
+            if args.name in BOOL_CONFIGS:
3604df
+                v = boolify(args.value)
3604df
+
3604df
+            new_data[args.name] = v
3604df
+            file_content_overwrite(CUSTOM_CONFIG_FILE, new_data)
3604df
+
3604df
+            # If any value changed which requires restart of REST server
3604df
+            restart = False
3604df
+            if args.name in RESTART_CONFIGS:
3604df
+                restart = True
3604df
+
3604df
+            sync_to_peers(restart=restart)
3604df
+
3604df
+
3604df
+class ConfigResetCmd(Cmd):
3604df
+    name = "config-reset"
3604df
+
3604df
+    def args(self, parser):
3604df
+        parser.add_argument("name", help="Config Name or all")
3604df
+
3604df
+    def run(self, args):
3604df
+        with fasteners.InterProcessLock(CUSTOM_CONFIG_FILE):
3604df
+            changed_keys = []
3604df
+            data = {}
3604df
+            if os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+                data = read_file_content_json(CUSTOM_CONFIG_FILE)
3604df
+
3604df
+            if not data:
3604df
+                return
3604df
+
3604df
+            if args.name.lower() == "all":
3604df
+                for k, v in data.items():
3604df
+                    changed_keys.append(k)
3604df
+
3604df
+                # Reset all keys
3604df
+                file_content_overwrite(CUSTOM_CONFIG_FILE, {})
3604df
+            else:
3604df
+                changed_keys.append(args.name)
3604df
+                del data[args.name]
3604df
+                file_content_overwrite(CUSTOM_CONFIG_FILE, data)
3604df
+
3604df
+            # If any value changed which requires restart of REST server
3604df
+            restart = False
3604df
+            for key in changed_keys:
3604df
+                if key in RESTART_CONFIGS:
3604df
+                    restart = True
3604df
+                    break
3604df
+
3604df
+            sync_to_peers(restart=restart)
3604df
+
3604df
+
3604df
+class SyncCmd(Cmd):
3604df
+    name = "sync"
3604df
+
3604df
+    def run(self, args):
3604df
+        sync_to_peers()
3604df
+
3604df
+
3604df
+if __name__ == "__main__":
3604df
+    runcli()
3604df
diff --git a/events/src/utils.py b/events/src/utils.py
3604df
new file mode 100644
3604df
index 0000000..772221a
3604df
--- /dev/null
3604df
+++ b/events/src/utils.py
3604df
@@ -0,0 +1,150 @@
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+import json
3604df
+import os
3604df
+import logging
3604df
+
3604df
+import requests
3604df
+from eventsapiconf import (LOG_FILE,
3604df
+                           WEBHOOKS_FILE,
3604df
+                           DEFAULT_CONFIG_FILE,
3604df
+                           CUSTOM_CONFIG_FILE)
3604df
+import eventtypes
3604df
+
3604df
+from gluster.cliutils import get_node_uuid
3604df
+
3604df
+
3604df
+# Webhooks list
3604df
+_webhooks = {}
3604df
+# Default Log Level
3604df
+_log_level = "INFO"
3604df
+# Config Object
3604df
+_config = {}
3604df
+
3604df
+# Init Logger instance
3604df
+logger = logging.getLogger(__name__)
3604df
+
3604df
+
3604df
+def get_event_type_name(idx):
3604df
+    """
3604df
+    Returns Event Type text from the index. For example, VOLUME_CREATE
3604df
+    """
3604df
+    return eventtypes.all_events[idx].replace("EVENT_", "")
3604df
+
3604df
+
3604df
+def setup_logger():
3604df
+    """
3604df
+    Logging initialization, Log level by default will be INFO, once config
3604df
+    file is read, respective log_level will be set.
3604df
+    """
3604df
+    global logger
3604df
+    logger.setLevel(logging.INFO)
3604df
+
3604df
+    # create the logging file handler
3604df
+    fh = logging.FileHandler(LOG_FILE)
3604df
+
3604df
+    formatter = logging.Formatter("[%(asctime)s] %(levelname)s "
3604df
+                                  "[%(module)s - %(lineno)s:%(funcName)s] "
3604df
+                                  "- %(message)s")
3604df
+
3604df
+    fh.setFormatter(formatter)
3604df
+
3604df
+    # add handler to logger object
3604df
+    logger.addHandler(fh)
3604df
+
3604df
+
3604df
+def load_config():
3604df
+    """
3604df
+    Load/Reload the config from REST Config files. This function will
3604df
+    be triggered during init and when SIGUSR2.
3604df
+    """
3604df
+    global _config
3604df
+    _config = {}
3604df
+    if os.path.exists(DEFAULT_CONFIG_FILE):
3604df
+        _config = json.load(open(DEFAULT_CONFIG_FILE))
3604df
+    if os.path.exists(CUSTOM_CONFIG_FILE):
3604df
+        _config.update(json.load(open(CUSTOM_CONFIG_FILE)))
3604df
+
3604df
+
3604df
+def load_log_level():
3604df
+    """
3604df
+    Reads log_level from Config file and sets accordingly. This function will
3604df
+    be triggered during init and when SIGUSR2.
3604df
+    """
3604df
+    global logger, _log_level
3604df
+    new_log_level = _config.get("log_level", "INFO")
3604df
+    if _log_level != new_log_level:
3604df
+        logger.setLevel(getattr(logging, new_log_level.upper()))
3604df
+        _log_level = new_log_level.upper()
3604df
+
3604df
+
3604df
+def load_webhooks():
3604df
+    """
3604df
+    Load/Reload the webhooks list. This function will
3604df
+    be triggered during init and when SIGUSR2.
3604df
+    """
3604df
+    global _webhooks
3604df
+    _webhooks = {}
3604df
+    if os.path.exists(WEBHOOKS_FILE):
3604df
+        _webhooks = json.load(open(WEBHOOKS_FILE))
3604df
+
3604df
+
3604df
+def load_all():
3604df
+    """
3604df
+    Wrapper function to call all load/reload functions. This function will
3604df
+    be triggered during init and when SIGUSR2.
3604df
+    """
3604df
+    load_config()
3604df
+    load_webhooks()
3604df
+    load_log_level()
3604df
+
3604df
+
3604df
+def publish(ts, event_key, data):
3604df
+    message = {
3604df
+        "nodeid": get_node_uuid(),
3604df
+        "ts": int(ts),
3604df
+        "event": get_event_type_name(event_key),
3604df
+        "message": data
3604df
+    }
3604df
+    if _webhooks:
3604df
+        plugin_webhook(message)
3604df
+    else:
3604df
+        # TODO: Default action?
3604df
+        pass
3604df
+
3604df
+
3604df
+def plugin_webhook(message):
3604df
+    message_json = json.dumps(message, sort_keys=True)
3604df
+    logger.debug("EVENT: {0}".format(message_json))
3604df
+    for url, token in _webhooks.items():
3604df
+        http_headers = {"Content-Type": "application/json"}
3604df
+        if token != "" and token is not None:
3604df
+            http_headers["Authorization"] = "Bearer " + token
3604df
+
3604df
+        try:
3604df
+            resp = requests.post(url, headers=http_headers, data=message_json)
3604df
+        except requests.ConnectionError as e:
3604df
+            logger.warn("Event push failed to URL: {url}, "
3604df
+                        "Event: {event}, "
3604df
+                        "Status: {error}".format(
3604df
+                            url=url,
3604df
+                            event=message_json,
3604df
+                            error=e))
3604df
+            continue
3604df
+
3604df
+        if resp.status_code != 200:
3604df
+            logger.warn("Event push failed to URL: {url}, "
3604df
+                        "Event: {event}, "
3604df
+                        "Status Code: {status_code}".format(
3604df
+                            url=url,
3604df
+                            event=message_json,
3604df
+                            status_code=resp.status_code))
3604df
diff --git a/events/tools/Makefile.am b/events/tools/Makefile.am
3604df
new file mode 100644
3604df
index 0000000..7d5e331
3604df
--- /dev/null
3604df
+++ b/events/tools/Makefile.am
3604df
@@ -0,0 +1,3 @@
3604df
+scriptsdir = $(datadir)/glusterfs/scripts
3604df
+scripts_SCRIPTS = eventsdash.py
3604df
+EXTRA_DIST = eventsdash.py
3604df
diff --git a/events/tools/eventsdash.py b/events/tools/eventsdash.py
3604df
new file mode 100644
3604df
index 0000000..47fc56d
3604df
--- /dev/null
3604df
+++ b/events/tools/eventsdash.py
3604df
@@ -0,0 +1,74 @@
3604df
+#!/usr/bin/env python
3604df
+# -*- coding: utf-8 -*-
3604df
+#
3604df
+#  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+#  This file is part of GlusterFS.
3604df
+#
3604df
+#  This file is licensed to you under your choice of the GNU Lesser
3604df
+#  General Public License, version 3 or any later version (LGPLv3 or
3604df
+#  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+#  cases as published by the Free Software Foundation.
3604df
+#
3604df
+
3604df
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
3604df
+import logging
3604df
+from datetime import datetime
3604df
+
3604df
+from flask import Flask, request
3604df
+
3604df
+app = Flask(__name__)
3604df
+app.logger.disabled = True
3604df
+log = logging.getLogger('werkzeug')
3604df
+log.disabled = True
3604df
+
3604df
+
3604df
+def human_time(ts):
3604df
+    return datetime.fromtimestamp(float(ts)).strftime("%Y-%m-%d %H:%M:%S")
3604df
+
3604df
+
3604df
+@app.route("/")
3604df
+def home():
3604df
+    return "OK"
3604df
+
3604df
+
3604df
+@app.route("/listen", methods=["POST"])
3604df
+def listen():
3604df
+    data = request.json
3604df
+    if data is None:
3604df
+        return "OK"
3604df
+
3604df
+    message = []
3604df
+    for k, v in data.get("message", {}).items():
3604df
+        message.append("{0}={1}".format(k, v))
3604df
+
3604df
+    print ("{0:20s} {1:20s} {2:36} {3}".format(
3604df
+        human_time(data.get("ts")),
3604df
+        data.get("event"),
3604df
+        data.get("nodeid"),
3604df
+        " ".join(message)))
3604df
+
3604df
+    return "OK"
3604df
+
3604df
+
3604df
+def main():
3604df
+    parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
3604df
+                            description=__doc__)
3604df
+    parser.add_argument("--port", type=int, help="Port", default=9000)
3604df
+    parser.add_argument("--debug", help="Run Server in debug mode",
3604df
+                        action="store_true")
3604df
+    args = parser.parse_args()
3604df
+
3604df
+    print ("{0:20s} {1:20s} {2:36} {3}".format(
3604df
+        "TIMESTAMP", "EVENT", "NODE ID", "MESSAGE"
3604df
+    ))
3604df
+    print ("{0:20s} {1:20s} {2:36} {3}".format(
3604df
+        "-"*20, "-"*20, "-"*36, "-"*20
3604df
+    ))
3604df
+    if args.debug:
3604df
+        app.debug = True
3604df
+
3604df
+    app.run(host="0.0.0.0", port=args.port)
3604df
+
3604df
+
3604df
+if __name__ == "__main__":
3604df
+    main()
3604df
diff --git a/extras/systemd/Makefile.am b/extras/systemd/Makefile.am
3604df
index 3f0ec89..5b9b117 100644
3604df
--- a/extras/systemd/Makefile.am
3604df
+++ b/extras/systemd/Makefile.am
3604df
@@ -1,7 +1,11 @@
3604df
-CLEANFILES = glusterd.service
3604df
-EXTRA_DIST = glusterd.service.in
3604df
+CLEANFILES = glusterd.service glustereventsd.service
3604df
+EXTRA_DIST = glusterd.service.in glustereventsd.service.in
3604df
 
3604df
 if USE_SYSTEMD
3604df
 # systemddir is already defined through configure.ac
3604df
 systemd_DATA = glusterd.service
3604df
+
3604df
+if BUILD_EVENTS
3604df
+systemd_DATA += glustereventsd.service
3604df
+endif
3604df
 endif
3604df
diff --git a/extras/systemd/glustereventsd.service.in b/extras/systemd/glustereventsd.service.in
3604df
new file mode 100644
3604df
index 0000000..2be3f25
3604df
--- /dev/null
3604df
+++ b/extras/systemd/glustereventsd.service.in
3604df
@@ -0,0 +1,12 @@
3604df
+[Unit]
3604df
+Description=Gluster Events Notifier
3604df
+After=syslog.target network.target
3604df
+
3604df
+[Service]
3604df
+Type=simple
3604df
+ExecStart=@SBIN_DIR@/glustereventsd
3604df
+ExecReload=/bin/kill -SIGUSR2 $MAINPID
3604df
+KillMode=control-group
3604df
+
3604df
+[Install]
3604df
+WantedBy=multi-user.target
3604df
diff --git a/glusterfs.spec.in b/glusterfs.spec.in
3604df
index 7e40f75..cb90eef 100644
3604df
--- a/glusterfs.spec.in
3604df
+++ b/glusterfs.spec.in
3604df
@@ -108,6 +108,13 @@
3604df
 %global _with_tmpfilesdir --without-tmpfilesdir
3604df
 %endif
3604df
 
3604df
+# Eventing
3604df
+%if 0%{?_build_server}
3604df
+%if ( 0%{?rhel} && 0%{?rhel} < 6 )
3604df
+%global _without_events --disable-events
3604df
+%endif
3604df
+%endif
3604df
+
3604df
 # From https://fedoraproject.org/wiki/Packaging:Python#Macros
3604df
 %if ( 0%{?rhel} && 0%{?rhel} <= 5 )
3604df
 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
3604df
@@ -604,6 +611,25 @@ is in user space and easily manageable.
3604df
 
3604df
 This package provides the translators needed on any GlusterFS client.
3604df
 
3604df
+%if 0%{?_build_server}
3604df
+%if ( 0%{!?_without_events:1} )
3604df
+%package events
3604df
+Summary:          GlusterFS Events
3604df
+Group:            Applications/File
3604df
+Requires:         %{name}-server%{?_isa} = %{version}-%{release}
3604df
+Requires:         python python-fasteners python-requests python-flask
3604df
+Requires:         python-prettytable
3604df
+Requires:         python-gluster = %{version}-%{release}
3604df
+%if ( 0%{?rhel} && 0%{?rhel} <= 6 )
3604df
+Requires:         python-argparse
3604df
+%endif
3604df
+
3604df
+%description events
3604df
+GlusterFS Events
3604df
+
3604df
+%endif
3604df
+%endif
3604df
+
3604df
 %prep
3604df
 %setup -q -n %{name}-%{version}%{?prereltag}
3604df
 
3604df
@@ -643,7 +669,8 @@ export LDFLAGS
3604df
         %{?_without_ocf} \
3604df
         %{?_without_rdma} \
3604df
         %{?_without_syslog} \
3604df
-        %{?_without_tiering}
3604df
+        %{?_without_tiering} \
3604df
+        %{?_without_events}
3604df
 
3604df
 # fix hardening and remove rpath in shlibs
3604df
 %if ( 0%{?fedora} && 0%{?fedora} > 17 ) || ( 0%{?rhel} && 0%{?rhel} > 6 )
3604df
@@ -963,6 +990,17 @@ exit 0
3604df
 %exclude %{_libexecdir}/glusterfs/glusterfind
3604df
 %exclude %{_bindir}/glusterfind
3604df
 %exclude %{_libexecdir}/glusterfs/peer_add_secret_pub
3604df
+# exclude eventsapi files
3604df
+%exclude %{_sysconfdir}/glusterfs/eventsconfig.json
3604df
+%exclude %{_sharedstatedir}/glusterd/events
3604df
+%exclude %{_libexecdir}/glusterfs/events
3604df
+%exclude %{_libexecdir}/glusterfs/peer_eventsapi.py*
3604df
+%exclude %{_sbindir}/glustereventsd
3604df
+%exclude %{_sbindir}/gluster-eventsapi
3604df
+%exclude %{_datadir}/glusterfs/scripts/eventsdash.py*
3604df
+%if ( 0%{?_with_systemd:1} )
3604df
+%exclude %{_unitdir}/glustereventsd.service
3604df
+%endif
3604df
 # exclude server files
3604df
 %exclude %{_sharedstatedir}/glusterd/*
3604df
 %exclude %{_sysconfdir}/glusterfs
3604df
@@ -1349,6 +1387,22 @@ exit 0
3604df
 %endif
3604df
 %endif
3604df
 
3604df
+# Events
3604df
+%if 0%{?_build_server}
3604df
+%if ( 0%{!?_without_events:1} )
3604df
+%files events
3604df
+%config %attr(0600, root, root) %{_sysconfdir}/glusterfs/eventsconfig.json
3604df
+%dir %attr(0755,-,-) %{_sharedstatedir}/glusterd/events
3604df
+%{_libexecdir}/glusterfs/events
3604df
+%{_libexecdir}/glusterfs/peer_eventsapi.py*
3604df
+%{_sbindir}/glustereventsd
3604df
+%{_sbindir}/gluster-eventsapi
3604df
+%{_datadir}/glusterfs/scripts/eventsdash.py*
3604df
+%if ( 0%{?_with_systemd:1} )
3604df
+%{_unitdir}/glustereventsd.service
3604df
+%endif
3604df
+%endif
3604df
+%endif
3604df
 
3604df
 ##-----------------------------------------------------------------------------
3604df
 ## All %pretrans should be placed here and keep them sorted
3604df
@@ -1940,6 +1994,9 @@ end
3604df
 %endif
3604df
 
3604df
 %changelog
3604df
+* Thu Sep 15 2016 Aravinda VK <avishwan@redhat.com>
3604df
+- Added new subpackage events(glusterfs-events) (#1334044)
3604df
+
3604df
 * Mon Aug 22 2016 Milind Changire <mchangir@redhat.com>
3604df
 - Add psmisc as dependency for glusterfs-fuse for killall command (#1367665)
3604df
 
3604df
diff --git a/libglusterfs/src/Makefile.am b/libglusterfs/src/Makefile.am
3604df
index 89c7fa0..54b6194 100644
3604df
--- a/libglusterfs/src/Makefile.am
3604df
+++ b/libglusterfs/src/Makefile.am
3604df
@@ -72,6 +72,12 @@ libglusterfs_la_SOURCES += $(CONTRIBDIR)/uuid/clear.c \
3604df
 	$(CONTRIBDIR)/uuid/unpack.c
3604df
 endif
3604df
 
3604df
+if BUILD_EVENTS
3604df
+libglusterfs_la_SOURCES += events.c
3604df
+
3604df
+libglusterfs_la_HEADERS += events.h eventtypes.h
3604df
+endif
3604df
+
3604df
 libgfchangelog_HEADERS = changelog.h
3604df
 
3604df
 EXTRA_DIST = graph.l graph.y defaults-tmpl.c
3604df
diff --git a/libglusterfs/src/events.c b/libglusterfs/src/events.c
3604df
new file mode 100644
3604df
index 0000000..9d78187
3604df
--- /dev/null
3604df
+++ b/libglusterfs/src/events.c
3604df
@@ -0,0 +1,83 @@
3604df
+/*
3604df
+  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+  This file is part of GlusterFS.
3604df
+
3604df
+  This file is licensed to you under your choice of the GNU Lesser
3604df
+  General Public License, version 3 or any later version (LGPLv3 or
3604df
+  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+  cases as published by the Free Software Foundation.
3604df
+*/
3604df
+
3604df
+#include <sys/types.h>
3604df
+#include <sys/socket.h>
3604df
+#include <sys/un.h>
3604df
+#include <stdio.h>
3604df
+#include <unistd.h>
3604df
+#include <time.h>
3604df
+#include <stdarg.h>
3604df
+#include <string.h>
3604df
+#include "syscall.h"
3604df
+#include "mem-pool.h"
3604df
+#include "events.h"
3604df
+
3604df
+int
3604df
+gf_event (int event, char *fmt, ...)
3604df
+{
3604df
+        int      sock                     = -1;
3604df
+        char     eventstr[EVENTS_MSG_MAX] = "";
3604df
+        struct   sockaddr_un server;
3604df
+        va_list  arguments;
3604df
+        char     *msg                     = NULL;
3604df
+        int      ret                      = 0;
3604df
+        size_t   eventstr_size            = 0;
3604df
+
3604df
+        if (event < 0 || event >= EVENT_LAST) {
3604df
+                ret = EVENT_ERROR_INVALID_INPUTS;
3604df
+                goto out;
3604df
+        }
3604df
+
3604df
+        sock = socket(AF_UNIX, SOCK_STREAM, 0);
3604df
+        if (sock < 0) {
3604df
+                ret = EVENT_ERROR_SOCKET;
3604df
+                goto out;
3604df
+        }
3604df
+        server.sun_family = AF_UNIX;
3604df
+        strcpy(server.sun_path, EVENT_PATH);
3604df
+
3604df
+        if (connect(sock,
3604df
+                    (struct sockaddr *) &server,
3604df
+                    sizeof(struct sockaddr_un)) < 0) {
3604df
+                ret = EVENT_ERROR_CONNECT;
3604df
+                goto out;
3604df
+        }
3604df
+
3604df
+        va_start (arguments, fmt);
3604df
+        ret = gf_vasprintf (&msg, fmt, arguments);
3604df
+        va_end (arguments);
3604df
+        if (ret < 0) {
3604df
+                ret = EVENT_ERROR_INVALID_INPUTS;
3604df
+                goto out;
3604df
+        }
3604df
+
3604df
+        eventstr_size = snprintf(NULL, 0, "%u %d %s", (unsigned)time(NULL),
3604df
+                                 event, msg);
3604df
+
3604df
+        if (eventstr_size + 1 > EVENTS_MSG_MAX) {
3604df
+                eventstr_size = EVENTS_MSG_MAX - 1;
3604df
+        }
3604df
+
3604df
+        snprintf(eventstr, eventstr_size+1, "%u %d %s",
3604df
+                 (unsigned)time(NULL), event, msg);
3604df
+
3604df
+        if (sys_write(sock, eventstr, strlen(eventstr)) <= 0) {
3604df
+                ret = EVENT_ERROR_SEND;
3604df
+                goto out;
3604df
+        }
3604df
+
3604df
+        ret = EVENT_SEND_OK;
3604df
+
3604df
+ out:
3604df
+        sys_close(sock);
3604df
+        GF_FREE(msg);
3604df
+        return ret;
3604df
+}
3604df
diff --git a/libglusterfs/src/events.h.in b/libglusterfs/src/events.h.in
3604df
new file mode 100644
3604df
index 0000000..37692be
3604df
--- /dev/null
3604df
+++ b/libglusterfs/src/events.h.in
3604df
@@ -0,0 +1,23 @@
3604df
+/*
3604df
+  Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
3604df
+  This file is part of GlusterFS.
3604df
+
3604df
+  This file is licensed to you under your choice of the GNU Lesser
3604df
+  General Public License, version 3 or any later version (LGPLv3 or
3604df
+  later), or the GNU General Public License, version 2 (GPLv2), in all
3604df
+  cases as published by the Free Software Foundation.
3604df
+*/
3604df
+
3604df
+#ifndef __EVENTS_H__
3604df
+#define __EVENTS_H__
3604df
+
3604df
+#include <stdio.h>
3604df
+
3604df
+#include "eventtypes.h"
3604df
+
3604df
+#define EVENT_PATH "@localstatedir@/run/gluster/events.sock"
3604df
+#define EVENTS_MSG_MAX 2048
3604df
+
3604df
+extern int gf_event(int key, char *fmt, ...);
3604df
+
3604df
+#endif /* __EVENTS_H__ */
3604df
diff --git a/libglusterfs/src/eventtypes.h b/libglusterfs/src/eventtypes.h
3604df
new file mode 100644
3604df
index 0000000..874f8cc
3604df
--- /dev/null
3604df
+++ b/libglusterfs/src/eventtypes.h
3604df
@@ -0,0 +1,22 @@
3604df
+#ifndef __EVENTTYPES_H__
3604df
+#define __EVENTTYPES_H__
3604df
+
3604df
+typedef enum {
3604df
+    EVENT_SEND_OK,
3604df
+    EVENT_ERROR_INVALID_INPUTS,
3604df
+    EVENT_ERROR_SOCKET,
3604df
+    EVENT_ERROR_CONNECT,
3604df
+    EVENT_ERROR_SEND,
3604df
+} event_errors_t;
3604df
+
3604df
+typedef enum {
3604df
+    EVENT_PEER_ATTACH,
3604df
+    EVENT_PEER_DETACH,
3604df
+    EVENT_VOLUME_CREATE,
3604df
+    EVENT_VOLUME_START,
3604df
+    EVENT_VOLUME_STOP,
3604df
+    EVENT_VOLUME_DELETE,
3604df
+    EVENT_LAST
3604df
+} eventtypes_t;
3604df
+
3604df
+#endif /* __EVENTTYPES_H__ */
3604df
diff --git a/libglusterfs/src/glusterfs.h b/libglusterfs/src/glusterfs.h
3604df
index 0cee2ba..cbad737 100644
3604df
--- a/libglusterfs/src/glusterfs.h
3604df
+++ b/libglusterfs/src/glusterfs.h
3604df
@@ -37,6 +37,10 @@
3604df
 #include "lkowner.h"
3604df
 #include "compat-uuid.h"
3604df
 
3604df
+#if (USE_EVENTS)
3604df
+#include "events.h"
3604df
+#endif
3604df
+
3604df
 #define GF_YES 1
3604df
 #define GF_NO  0
3604df
 
3604df
-- 
3604df
1.7.1
3604df