Просмотр исходного кода

Add dbus and snmp notifier

This is to send dbus events on major cluster events:
 - membership changes
 - application connect/dissconnet from corosync
 - quorum changes

dbus events can then be converted into snmp traps by foghorn or
corosync-notifyd can be run to directly send snmp traps.

Signed-off-by: Angus Salkeld <asalkeld@redhat.com>
Signed-off-by: Lon Hohberger <lhh@redhat.com>
Reviewed-by: Steven Dake <sdake@redhat.com>
Reviewed-by: Russell Bryant <russell@russellbryant.net>
Reviewed-by: Fabio M. Di Nitto <fdinitto@redhat.com>
Angus Salkeld 15 лет назад
Родитель
Сommit
2a568d6e79

+ 50 - 0
INSTALL

@@ -123,6 +123,56 @@ balance:~/corosync/trunk% ./configure --enable-rdma
 Hopefully pkgconfig support is added in your distribution's version of
 libibverbs and librdmacm soon if you run into this problem.
 
+-----------------------------------------
+* Building with SNMP/DBUS support *
+-----------------------------------------
+
+You can get SNMP traps on the following corosync events:
+1) node joine/leave
+2) application connect/dissconnect from corosync
+3) quorum gain/lost
+
+There are 2 modes of achieving this DBUS + foghorn and snmp-agentx.
+
+Setting up to get dbus events.
+------------------------------
+foghorn (http://git.fedorahosted.org/git/foghorn.git) converts
+dbus signals into snmp traps. So install foghorn.
+
+$ ./configure --enable-dbus
+$ make && sudo make install
+$ /etc/init.d/corosync start
+$ echo "OPTIONS=\"-d\"" > /etc/sysconfig/corosync-notifyd
+$ /etc/init.d/corosync-notifyd start
+Start foghorn
+
+to see the dbus signals getting sent try:
+$ dbus-monitor --system
+
+Setting up snmp-agentx.
+-----------------------
+If you don't want to use dbus then you can use snmp-agentx.
+
+$ ./configure --enable-snmp
+$ make && sudo make install
+$ /etc/init.d/corosync start
+$ vim /etc/snmp/snmptrapd.conf
+
+Add the following:
+authCommunity   log,execute,net public
+$ /etc/init.d/snmptrapd start
+$ echo "OPTIONS=\"-s\"" > /etc/sysconfig/corosync-notifyd
+$ /etc/init.d/corosync-notifyd start
+
+I start up wireshark to see if there are any snmp traps been sent
+as I am too lazy to setup a manager to receive traps.
+
+run a program that talks to corosync e.g.
+$ corosync-objctl
+
+And you should get traps
+
+
 ------------------------
 * Configuring Corosync *
 ------------------------

+ 1 - 1
Makefile.am

@@ -65,7 +65,7 @@ corolenstest_DATA	= conf/lenses/tests/test_corosync.aug
 endif
 
 SUBDIRS			= include lcr lib exec services tools test cts pkgconfig \
-			  man init
+			  man init conf
 
 install-exec-local:
 	$(INSTALL) -d $(DESTDIR)/${COROSYSCONFDIR}/service.d

+ 3 - 2
TODO

@@ -46,13 +46,14 @@ automatically reenable a redundant ring when it has been back in service.
 ------------------------------------------------------------------------------
 topic-snmp
 ------------------------------------------------------------------------------
-Main Developer: Steven Dake
+Main Developer: Angus Salkeld
 Started: Not Started
-Finished: 0%
+Finished: 100%
 target: needle
 Description:
 This topic involves investigation of adding SNMP support into Corosync.
 
+
 ------------------------------------------------------------------------------
 topic-udpu
 ------------------------------------------------------------------------------

+ 186 - 0
conf/COROSYNC-MIB.txt

@@ -0,0 +1,186 @@
+COROSYNC-MIB DEFINITIONS ::= BEGIN
+
+--
+-- MIB objects for the corosync
+--
+
+IMPORTS
+    MODULE-IDENTITY,NOTIFICATION-TYPE,
+    Integer32,enterprises                       FROM SNMPv2-SMI
+    TEXTUAL-CONVENTION                          FROM SNMPv2-TC
+    SnmpAdminString                             FROM SNMP-FRAMEWORK-MIB
+    netSnmp                                     FROM NET-SNMP-MIB
+    InetAddressType, InetAddress                FROM INET-ADDRESS-MIB
+;
+
+corosync MODULE-IDENTITY
+    LAST-UPDATED    "200911061318Z"
+    ORGANIZATION    "www.corosync.org"
+    CONTACT-INFO    "name:  Yuki Sato
+                     email: openais@lists.linux-foundation.org"
+    DESCRIPTION     "MIB objects for the corosync"
+    REVISION        "200911061318Z"
+    DESCRIPTION     "First draft"
+    REVISION        "201003251209Z"
+    DESCRIPTION
+        "Private Enterprise Number has been assigned."
+        ::= { enterprises 35488 }
+
+--
+-- top level structure
+--
+corosyncNotice OBJECT IDENTIFIER ::= { corosync 1 }
+
+--
+--  corosync MIB entries
+--
+
+--
+-- Node Information
+--
+corosyncNoticeNodeStatusTable OBJECT-TYPE
+    SYNTAX      SEQUENCE OF corosyncNoticeNodeEntry
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The table contains information about the nodes in the corosync."
+::= { corosyncNotice 1 }
+
+corosyncNoticeNodeEntry OBJECT-TYPE
+    SYNTAX      corosyncNoticeNodeEntry
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The entry containing information about the iface."
+    INDEX   { corosyncNoticeNodeIndex }
+::= { corosyncNoticeNodeStatusTable 1 }
+
+corosyncNoticeNodeEntry ::= SEQUENCE {
+    corosyncNoticeNodeIndex     Integer32,
+    corosyncNoticeNodeid        Integer32,
+    corosyncNoticeNode          OCTET STRING,
+    corosyncNoticeNodeStatus    INTEGER
+}
+
+corosyncNoticeNodeIndex OBJECT-TYPE
+    SYNTAX      Integer32
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION "The unique integer of the node."
+::= { corosyncNoticeNodeEntry 1 }
+
+corosyncNoticeNodeid OBJECT-TYPE
+    SYNTAX      Integer32
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION "The id of the node."
+::= { corosyncNoticeNodeEntry 2 }
+
+corosyncNoticeNode OBJECT-TYPE
+    SYNTAX      OCTET STRING (SIZE(1..64))
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The iface of the node."
+::= { corosyncNoticeNodeEntry 3 }
+
+corosyncNoticeNodeStatus OBJECT-TYPE
+    SYNTAX      INTEGER {
+                unknown (0),
+                joined  (1),
+                left    (2)
+            }
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The status change of the node."
+::= { corosyncNoticeNodeEntry 4 }
+
+--
+-- Iface(s) Information
+--
+corosyncNoticeIfaceStatusTable OBJECT-TYPE
+    SYNTAX      SEQUENCE OF corosyncNoticeIfaceEntry
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The table describes the iface(s) that are used by the corosync."
+::= { corosyncNotice 2 }
+
+corosyncNoticeIfaceEntry OBJECT-TYPE
+    SYNTAX      corosyncNoticeIfaceEntry
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The entry containing information about the iface."
+    INDEX   { corosyncNoticeIfaceIndex }
+::= { corosyncNoticeIfaceStatusTable 1 }
+
+corosyncNoticeIfaceEntry ::= SEQUENCE {
+    corosyncNoticeIfaceIndex    INTEGER,
+    corosyncNoticeIface         OCTET STRING,
+    corosyncNoticeIfaceStatus   OCTET STRING
+}
+
+corosyncNoticeIfaceIndex OBJECT-TYPE
+    SYNTAX      Integer32
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The unique integer of the iface(s)."
+::= { corosyncNoticeIfaceEntry 1 }
+
+corosyncNoticeIface OBJECT-TYPE
+    SYNTAX      OCTET STRING (SIZE(1..64))
+    MAX-ACCESS  accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The iface(s) of the change happened node."
+::= { corosyncNoticeIfaceEntry 2 }
+
+corosyncNoticeIfaceStatus OBJECT-TYPE
+    SYNTAX      INTEGER {
+                unknown (0),
+                up      (1),
+                down    (2),
+                faulty  (3)
+            }
+    MAX-ACCESS accessible-for-notify
+    STATUS      current
+    DESCRIPTION
+        "The status change of the iface."
+::= { corosyncNoticeIfaceEntry 3 }
+
+--corosyncNoticeIfaceStatus OBJECT-TYPE
+--   SYNTAX     OCTET STRING (SIZE(1..1024))
+--   MAX-ACCESS accessible-for-notify
+--   STATUS     current
+--   DESCRIPTION
+--       "The iface(s) status of the change happened node."
+--::= { corosyncNoticeIfaceEntry 3 }
+
+--
+-- Trap Information
+--
+corosyncNoticeTrap OBJECT IDENTIFIER ::= { corosync 100 }
+
+corosyncNoticeNodeTrap NOTIFICATION-TYPE
+    OBJECTS
+        { corosyncNoticeNodeid corosyncNoticeNode corosyncNoticeNodeStatus }
+    STATUS      current
+    DESCRIPTION
+        "The node status change event just happened."
+::= { corosyncNoticeTrap 1 }
+
+corosyncNoticeIfaceTrap NOTIFICATION-TYPE
+    OBJECTS
+        { corosyncNoticeNodeid corosyncNoticeIface corosyncNoticeIfaceStatus }
+    STATUS  current
+    DESCRIPTION
+        "The iface status change event just happened."
+::= { corosyncNoticeTrap 2 }
+
+END
+
+
+

+ 44 - 0
conf/Makefile.am

@@ -0,0 +1,44 @@
+# Copyright (c) 2009 Red Hat, Inc.
+#
+# Authors: Andrew Beekhof
+#         Steven Dake (sdake@redhat.com)
+#
+# This software licensed under BSD license, the text of which follows:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# - Neither the name of the MontaVista Software, Inc. nor the names of its
+#   contributors may be used to endorse or promote products derived from this
+#   software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+EXTRA_DIST     = COROSYNC-MIB.txt corosync-signals.conf
+
+MAINTAINERCLEANFILES    = Makefile.in
+
+if INSTALL_MIB
+mibdir = $(datadir)/snmp/mibs
+mib_DATA = COROSYNC-MIB.txt
+endif
+
+if INSTALL_DBUSCONF
+dbusdir = $(sysconfdir)/dbus-1/system.d
+dbus_DATA = corosync-signals.conf
+endif

+ 26 - 0
conf/corosync-signals.conf

@@ -0,0 +1,26 @@
+<!DOCTYPE busconfig PUBLIC
+	  "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+	  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+	<!-- Only root can own the corosync service. -->
+	<policy user="root">
+		<allow own="org.corosync"/>
+	</policy>
+
+	<policy context="default">
+		<allow send_destination="org.corosync"
+		       send_path="/org/corosync"
+		       send_interface="org.corosync"
+		       send_member="NodeStateChange"/>
+		<allow send_destination="org.corosync"
+		       send_path="/org/corosync"
+		       send_interface="org.corosync"
+		       send_member="ConnectionStateChange"/>
+		<allow send_destination="org.corosync"
+		       send_path="/org/corosync"
+		       send_interface="org.corosync"
+		       send_member="QuorumStateChange"/>
+	</policy>
+
+</busconfig>

+ 73 - 1
configure.ac

@@ -130,7 +130,8 @@ AC_CONFIG_FILES([Makefile
 		 cts/Makefile
 		 cts/agents/Makefile
 		 cts/CTSvars.py
-		 tools/Makefile])
+		 tools/Makefile
+		 conf/Makefile])
 
 ### Local business
 
@@ -241,6 +242,10 @@ AC_ARG_ENABLE([nss],
 	[  --enable-nss                    : Network Security Services encryption. ],,
 	[ enable_nss="yes" ])
 
+AC_ARG_ENABLE([dbus],
+	[  --enable-dbus                    : dbus events. ],,
+	[ enable_dbus="no" ])
+
 AC_ARG_ENABLE([testagents],
 	[  --enable-testagents             : Install Test Agents. ],,
 	[ default="no" ])
@@ -281,6 +286,10 @@ AC_ARG_WITH([socket-dir],
 	[ SOCKETDIR="$withval" ],
 	[ SOCKETDIR="$localstatedir/run" ])
 
+AC_ARG_ENABLE([snmp],
+       [  --enable-snmp           : SNMP protocol support ],
+       [ default="no" ])
+
 # OS detection
 # THIS SECTION MUST DIE!
 CP=cp
@@ -385,6 +394,13 @@ if test "x${enable_nss}" = xyes; then
 	PACKAGE_FEATURES="$PACKAGE_FEATURES nss"
 fi
 
+# Look for dbus-1
+if test "x${enable_dbus}" = xyes; then
+	PKG_CHECK_MODULES([DBUS],[dbus-1])
+	AC_DEFINE_UNQUOTED([HAVE_DBUS], 1, [have dbus])
+	PACKAGE_FEATURES="$PACKAGE_FEATURES dbus"
+fi
+
 if test "x${enable_testagents}" = xyes; then
 	AC_DEFINE_UNQUOTED([HAVE_TESTAGENTS], 1, [have testagents])
 	PACKAGE_FEATURES="$PACKAGE_FEATURES testagents"
@@ -425,6 +441,60 @@ if test "x${enable_augeas}" = xyes; then
 	PACKAGE_FEATURES="$PACKAGE_FEATURES augeas"
 fi
 
+if test "x${enable_snmp}" = xyes; then
+       SNMPCONFIG=""
+       AC_CHECK_HEADERS(net-snmp/net-snmp-config.h)
+
+       if test "x${ac_cv_header_net_snmp_net_snmp_config_h}" != "xyes"; then
+               enable_snmp=no
+       fi
+
+       if test $enable_snmp != no; then
+               AC_PATH_PROGS(SNMPCONFIG, net-snmp-config)
+               if test "X${SNMPCONFIG}" = "X"; then
+                       AC_MSG_RESULT(You need the net_snmp development package to continue.)
+                       enable_snmp=no
+               fi
+       fi
+
+       if test $enable_snmp != no; then
+               AC_MSG_CHECKING(for special snmp libraries)
+               SNMPLIBS=`$SNMPCONFIG --libs`
+               AC_MSG_RESULT($SNMPLIBS)
+       fi
+
+       if test $enable_snmp != no; then
+               savedLibs=$LIBS
+               LIBS="$LIBS $SNMPLIBS"
+               AC_CHECK_FUNCS(netsnmp_transport_open_client)
+               if test $ac_cv_func_netsnmp_transport_open_client != yes; then
+                       AC_CHECK_FUNCS(netsnmp_tdomain_transport)
+                       if test $ac_cv_func_netsnmp_tdomain_transport != yes; then
+                               enable_snmp=no
+                       fi
+               else
+                       AC_DEFINE_UNQUOTED([NETSNMPV54], $NETSNMP_NEW_SUPPORT, [have net-snmp5.4 over])
+               fi
+               LIBS=$savedLibs
+       fi
+
+       AC_MSG_CHECKING(for snmp)
+       AC_MSG_RESULT($enable_snmp)
+   if test $enable_snmp = no; then
+               enable_snmp=0
+               AC_MSG_ERROR(Unable to support SNMP)
+   else
+               enable_snmp=1
+               PACKAGE_FEATURES="$PACKAGE_FEATURES snmp"
+               AC_DEFINE_UNQUOTED([ENABLE_SNMP], $enable_snmp, [Build in support for sending SNMP traps])
+   fi
+else
+       enable_snmp=0
+fi
+AC_SUBST([SNMPLIBS])
+AC_SUBST([SNMP_LCRSO])
+AM_CONDITIONAL(BUILD_SNMP, test "${enable_snmp}" = "1")
+
 # extra warnings
 EXTRA_WARNINGS=""
 
@@ -521,6 +591,8 @@ AC_SUBST([OS_DYFLAGS])
 
 AC_SUBST([OS_LDL])
 AM_CONDITIONAL(INSTALL_TESTAGENTS, test -n "${enable_testagents}")
+AM_CONDITIONAL(INSTALL_MIB, test "${enable_snmp}" = "1")
+AM_CONDITIONAL(INSTALL_DBUSCONF, test "${enable_dbus}" = "1")
 AM_CONDITIONAL(AUGTOOL, test -n "${AUGTOOL}")
 AC_SUBST([NSS_LDFLAGS])
 

+ 23 - 0
corosync.spec.in

@@ -8,6 +8,8 @@
 %bcond_with testagents
 %bcond_with watchdog
 %bcond_with monitoring
+%bcond_with snmp
+%bcond_with dbus
 
 Name: corosync
 Summary: The Corosync Cluster Engine and Application Programming Interfaces
@@ -35,6 +37,12 @@ BuildRequires: autoconf automake
 %endif
 BuildRequires: nss-devel
 BuildRequires: libibverbs-devel librdmacm-devel
+%if %{with snmp}
+BuildRequires: net-snmp-devel
+%endif
+%if %{with dbus}
+BuildRequires: dbus-devel
+%endif
 
 BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
 
@@ -60,6 +68,12 @@ export rdmacm_LIBS=-lrdmacm \
 %endif
 %if %{with monitoring}
 	--enable-monitoring \
+%endif
+%if %{with snmp}
+	--enable-snmp \
+%endif
+%if %{with dbus}
+	--enable-dbus \
 %endif
 	--enable-rdma \
 	--with-initddir=%{_initrddir}
@@ -107,13 +121,21 @@ fi
 %{_sbindir}/corosync-pload
 %{_sbindir}/corosync-cpgtool
 %{_sbindir}/corosync-quorumtool
+%{_sbindir}/corosync-notifyd
 %{_bindir}/corosync-blackbox
 %dir %{_sysconfdir}/corosync
 %dir %{_sysconfdir}/corosync/service.d
 %dir %{_sysconfdir}/corosync/uidgid.d
 %config(noreplace) %{_sysconfdir}/corosync/corosync.conf.example
 %config(noreplace) %{_sysconfdir}/corosync/corosync.conf.example.udpu
+%if %{with dbus}
+%{_sysconfdir}/dbus-1/system.d/corosync-signals.conf
+%endif
+%if %{with snmp}
+%(_datadir)/snmp/mibs/COROSYNC-MIB.txt
+%endif
 %{_initrddir}/corosync
+%{_initrddir}/corosync-notifyd
 %dir %{_libexecdir}/lcrso
 %{_libexecdir}/lcrso/coroparse.lcrso
 %{_libexecdir}/lcrso/objdb.lcrso
@@ -143,6 +165,7 @@ fi
 %{_mandir}/man8/corosync-cpgtool.8*
 %{_mandir}/man8/corosync-fplay.8*
 %{_mandir}/man8/corosync-pload.8*
+%{_mandir}/man8/corosync-notifyd.8*
 %{_mandir}/man8/corosync-quorumtool.8*
 %{_mandir}/man5/corosync.conf.5*
 

+ 1 - 0
cts/.gitignore

@@ -1,2 +1,3 @@
 CTSvars.py
 *_test_agent
+*.pyc

+ 1 - 0
init/.gitignore

@@ -1 +1,2 @@
 generic
+notifyd

+ 4 - 3
init/Makefile.am

@@ -34,9 +34,9 @@
 
 MAINTAINERCLEANFILES	= Makefile.in
 
-EXTRA_DIST		= generic.in
+EXTRA_DIST		= generic.in notifyd.in
 
-target_INIT		= generic
+target_INIT		= generic notifyd
 
 %: %.in Makefile
 	rm -f $@-t $@
@@ -57,7 +57,8 @@ clean-local:
 install-exec-local:
 	$(INSTALL) -d $(DESTDIR)/$(INITDDIR)
 	$(INSTALL) -m 755 generic $(DESTDIR)/$(INITDDIR)/corosync
+	$(INSTALL) -m 755 notifyd $(DESTDIR)/$(INITDDIR)/corosync-notifyd
 
 uninstall-local:
 	cd $(DESTDIR)/$(INITDDIR) && \
-		rm -f corosync
+		rm -f corosync corosync-notifyd

+ 147 - 0
init/notifyd.in

@@ -0,0 +1,147 @@
+#!/bin/bash
+
+# Authors:
+#  Angus Salkeld <asalkeld@redhat.com>
+#
+# License: Revised BSD
+
+# chkconfig: - 23 77
+# description: Corosync Dbus and snmp notifier
+# processname: corosync-notifyd
+#
+### BEGIN INIT INFO
+# Provides:		corosync-notifyd
+# Required-Start:	$corosync $cman
+# Required-Stop:	$corosync $cman
+# Default-Start:
+# Default-Stop:
+# Short-Description:	Starts and stops Corosync Notifier.
+# Description:		Starts and stops Corosync Notifier.
+### END INIT INFO
+
+desc="Corosync Notifier"
+prog="corosync-notifyd"
+
+# set secure PATH
+PATH="/sbin:/bin:/usr/sbin:/usr/bin:@SBINDIR@"
+
+success()
+{
+	echo -ne "[  OK  ]\r"
+}
+
+failure()
+{
+	echo -ne "[FAILED]\r"
+}
+
+status()
+{
+	pid=$(pidof $1 2>/dev/null)
+	rtrn=$?
+	if [ $rtrn -ne 0 ]; then
+		echo "$1 is stopped"
+	else
+		echo "$1 (pid $pid) is running..."
+	fi
+	return $rtrn
+}
+
+# rpm based distros
+if [ -d @SYSCONFDIR@/sysconfig ]; then
+	[ -f @INITDDIR@/functions ] && . @INITDDIR@/functions
+	[ -f @SYSCONFDIR@/sysconfig/$prog ] && . @SYSCONFDIR@/sysconfig/$prog
+	[ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/subsys/$prog"
+fi
+
+# deb based distros
+if [ -d @SYSCONFDIR@/default ]; then
+	[ -f @SYSCONFDIR@/default/$prog ] && . @SYSCONFDIR@/default/$prog
+	[ -z "$LOCK_FILE" ] && LOCK_FILE="@LOCALSTATEDIR@/lock/$prog"
+fi
+
+# The version of __pids_pidof in /etc/init.d/functions calls pidof with -x
+# This means it matches scripts, including this one.
+# Redefine it here so that status (from the same file) works.
+# Otherwise simultaneous calls to stop() will loop forever
+__pids_pidof() {
+        pidof -c -o $$ -o $PPID -o %PPID "$1" || \
+                pidof -c -o $$ -o $PPID -o %PPID "${1##*/}"
+}
+
+start()
+{
+	echo -n "Starting $desc ($prog): "
+
+	# most recent distributions use tmpfs for @LOCALSTATEDIR@/run
+	# to avoid to clean it up on every boot.
+	# they also assume that init scripts will create
+	# required subdirectories for proper operations
+	mkdir -p @LOCALSTATEDIR@/run
+
+	if status $prog > /dev/null 2>&1; then
+		success
+	else
+		$prog $OPTIONS > /dev/null 2>&1
+
+		# give it time to fail
+		sleep 2
+		if status $prog > /dev/null 2>&1; then
+			touch $LOCK_FILE
+			success
+		else
+			failure
+			rtrn=1
+		fi
+	fi
+	echo
+}
+
+stop()
+{
+	! status $prog > /dev/null 2>&1 && return
+
+	echo -n "Signaling $desc ($prog) to terminate: "
+	kill -TERM $(pidof $prog) > /dev/null 2>&1
+	success
+	echo
+
+	rm -f $LOCK_FILE
+	success
+	echo
+}
+
+restart()
+{
+	stop
+	start
+}
+
+rtrn=0
+
+case "$1" in
+start)
+	start
+;;
+restart|reload|force-reload)
+	restart
+;;
+condrestart|try-restart)
+	if status $prog > /dev/null 2>&1; then
+		restart
+	fi
+;;
+status)
+	status $prog
+	rtrn=$?
+;;
+stop)
+	stop
+;;
+*)
+	echo "usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}"
+	rtrn=2
+;;
+esac
+
+exit $rtrn

+ 1 - 0
man/Makefile.am

@@ -47,6 +47,7 @@ dist_man_MANS = \
 	corosync-cpgtool.8 \
 	corosync-fplay.8 \
 	corosync-pload.8 \
+	corosync-notifyd.8 \
 	corosync-quorumtool.8 \
 	corosync_overview.8 \
 	cpg_overview.8 \

+ 145 - 0
man/corosync-notifyd.8

@@ -0,0 +1,145 @@
+.\"/*
+.\" * Copyright (C) 2010 Red Hat, Inc.
+.\" *
+.\" * All rights reserved.
+.\" *
+.\" * Author: Angus Salkeld <asalkeld@redhat.com>
+.\" *
+.\" * This software licensed under BSD license, the text of which follows:
+.\" *
+.\" * Redistribution and use in source and binary forms, with or without
+.\" * modification, are permitted provided that the following conditions are met:
+.\" *
+.\" * - Redistributions of source code must retain the above copyright notice,
+.\" *   this list of conditions and the following disclaimer.
+.\" * - Redistributions in binary form must reproduce the above copyright notice,
+.\" *   this list of conditions and the following disclaimer in the documentation
+.\" *   and/or other materials provided with the distribution.
+.\" * - Neither the name of Red Hat, Inc. nor the names of its
+.\" *   contributors may be used to endorse or promote products derived from this
+.\" *   software without specific prior written permission.
+.\" *
+.\" * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+.\" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+.\" * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+.\" * THE POSSIBILITY OF SUCH DAMAGE.
+.\" */
+.TH COROSYNC-NOTIFYD 8 2011-01-14
+.SH NAME
+corosync-notifyd \- Listen for important corosync events and send dbus and/or snmp traps.
+.SH SYNOPSIS
+.B "corosync-notifyd [\-f] [\-l] [\-o] [\-s] [\-m] [manager] [\-d] [-h]"
+.SH DESCRIPTION
+.B corosync-notifyd
+uses corosync API to listen for important cluster events and can log them,
+generate dbus signals or genterate snmp traps.
+.SH OPTIONS
+.TP
+.B -f
+Start application in foreground.
+.TP
+.B -l
+Log all events.
+.TP
+.B -o
+Print events to stdout (turns on -l).
+.TP
+.B -s
+Send SNMP traps on all events.
+.TP
+.B -m
+Set the SNMP manager address.
+.TP
+.B -d
+Send DBUS signals on all events.
+.TP
+.B -h
+Print this help
+.SH EXAMPLES
+.br
+$ corosync-notifyd -o
+.br
+corosync-notifyd[18505]: troll[23374016] corosync-notify:18505:12 is now connected to corosync
+.br
+corosync-notifyd[18505]: troll[23374016] corosync-notify:18505:13 is now disconnected from corosync
+.br
+corosync-notifyd[18505]: troll[23374016] is now quorate
+.br
+corosync-notifyd[18505]: r2[1550100672] ip:192.168.100.92 joined
+.br
+corosync-notifyd[18505]: r2[1550100672] ip:192.168.100.92 left
+.br
+
+.br
+$ corosync-notifyd -o
+.br
+
+Note this output below is from "dbus-monitor --system"
+
+.br
+signal sender=:1.216 -> dest=(null destination) serial=2 path=/com/redhat/cluster/corosync;
+ interface=com.redhat.cluster.corosync; member=ConnectionStateChange
+.br
+   string "troll"
+.br
+   uint32 23374016
+.br
+   string "corosync-notify:18900:12"
+.br
+   string "connected"
+.br
+signal sender=:1.216 -> dest=(null destination) serial=3 path=/com/redhat/cluster/corosync;
+ interface=com.redhat.cluster.corosync; member=ConnectionStateChange
+.br
+   string "troll"
+.br
+   uint32 23374016
+.br
+   string "corosync-notify:18900:13"
+.br
+   string "disconnected"
+.br
+signal sender=:1.216 -> dest=(null destination) serial=4 path=/com/redhat/cluster/corosync;
+ interface=com.redhat.cluster.corosync; member=QorumStateChange
+.br
+   string "troll"
+.br
+   uint32 23374016
+.br
+   string "quorate"
+.br
+signal sender=:1.216 -> dest=(null destination) serial=5 path=/com/redhat/cluster/corosync;
+ interface=com.redhat.cluster.corosync; member=NodeStateChange
+.br
+   string "r2"
+.br
+   uint32 1550100672
+.br
+   string "192.168.100.92"
+.br
+   string "joined"
+.br
+signal sender=:1.216 -> dest=(null destination) serial=6 path=/com/redhat/cluster/corosync;
+ interface=com.redhat.cluster.corosync; member=NodeStateChange
+.br
+   string "r2"
+.br
+   uint32 1550100672
+.br
+   string "192.168.100.92"
+.br
+   string "left"
+.SH SEE ALSO
+.BR corosync (8),
+.BR corosync-objctl (8),
+.BR dbus-monitor (1),
+.SH AUTHOR
+Angus Salkeld
+.PP

+ 1 - 0
tools/.gitignore

@@ -5,3 +5,4 @@ corosync-keygen
 corosync-objctl
 corosync-pload
 corosync-quorumtool
+corosync-notifyd

+ 15 - 4
tools/Makefile.am

@@ -30,15 +30,17 @@
 # THE POSSIBILITY OF SUCH DAMAGE.
 
 MAINTAINERCLEANFILES    = Makefile.in
-INCLUDES       		= -I$(top_builddir)/include  -I$(top_srcdir)/include
+INCLUDES       		= -I$(top_builddir)/include  -I$(top_srcdir)/include \
+                          -I$(top_builddir)/include/corosync
 
 sbin_PROGRAMS		= corosync-fplay corosync-cfgtool \
 			  corosync-keygen corosync-objctl \
-			  corosync-pload corosync-cpgtool corosync-quorumtool
+			  corosync-pload corosync-cpgtool corosync-quorumtool \
+			  corosync-notifyd
 
 bin_SCRIPTS		= corosync-blackbox
 
-EXTRA_DIST		= $(bin_SCRIPTS)
+EXTRA_DIST		= $(bin_SCRIPTS) corosync-notifyd.sysconfig.example
 
 corosync_pload_LDADD	= -lpload -lcoroipcc
 corosync_pload_LDFLAGS	= -L../lib
@@ -52,5 +54,14 @@ corosync_quorumtool_LDADD = -lconfdb -lcfg -lquorum \
 			    -lvotequorum -lcoroipcc ../lcr/liblcr.a
 corosync_quorumtool_LDFLAGS = -L../lib
 
+corosync_notifyd_LDADD = -L../lib
+corosync_notifyd_LDFLAGS = -lcfg -lconfdb ../lcr/liblcr.a -lcoroipcc \
+			   ../exec/coropoll.o $(DBUS_LIBS) $(SNMPLIBS) \
+			   -lquorum
+corosync_notifyd_CPPFLAGS = $(DBUS_CFLAGS)
+
+
 lint:
-	-splint $(LINT_FLAGS) $(CFLAGS) *.c
+	-splint $(LINT_FLAGS) $(DBUS_CFLAGS) $(INCLUDES) $(CFLAGS) *.c
+
+

+ 1040 - 0
tools/corosync-notifyd.c

@@ -0,0 +1,1040 @@
+/*
+ * Copyright (c) 2011 Red Hat
+ *
+ * All rights reserved.
+ *
+ * Author: Angus Salkeld <asalkeld@redhat.com>
+ *
+ * This software licensed under BSD license, the text of which follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the MontaVista Software, Inc. nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <poll.h>
+#include <signal.h>
+#include <syslog.h>
+
+#include <corosync/corotypes.h>
+#include <corosync/totem/coropoll.h>
+#include <corosync/confdb.h>
+#include <corosync/cfg.h>
+#include <corosync/quorum.h>
+
+/*
+ * generic declarations
+ */
+enum {
+	CS_NTF_LOG,
+	CS_NTF_STDOUT,
+	CS_NTF_SNMP,
+	CS_NTF_DBUS,
+	CS_NTF_FG,
+	CS_NTF_MAX,
+};
+static int conf[CS_NTF_MAX];
+
+typedef void (*node_membership_fn_t)(char *nodename, uint32_t nodeid, char *state, char* ip);
+typedef void (*node_quorum_fn_t)(char *nodename, uint32_t nodeid, const char *state);
+typedef void (*application_connection_fn_t)(char *nodename, uint32_t nodeid, char *app_name, const char *state);
+
+struct notify_callbacks {
+	node_membership_fn_t node_membership_fn;
+	node_quorum_fn_t node_quorum_fn;
+	application_connection_fn_t application_connection_fn;
+};
+
+#define MAX_NOTIFIERS 5
+static int num_notifiers = 0;
+static struct notify_callbacks notifiers[MAX_NOTIFIERS];
+static uint32_t local_nodeid = 0;
+static char local_nodename[CS_MAX_NAME_LENGTH];
+static hdb_handle_t poll_handle;
+static quorum_handle_t quorum_handle;
+
+static void _cs_node_membership_event(char *nodename, uint32_t nodeid, char *state, char* ip);
+static void _cs_node_quorum_event(const char *state);
+static void _cs_application_connection_event(char *app_name, const char *state);
+
+#ifdef HAVE_DBUS
+#include <dbus/dbus.h>
+/*
+ * dbus
+ */
+#define DBUS_CS_NAME	"org.corosync"
+#define DBUS_CS_IFACE	"org.corosync"
+#define DBUS_CS_PATH	"/org/corosync"
+
+static DBusConnection *db = NULL;
+static char _err[512];
+static int err_set = 0;
+static void _cs_dbus_init(void);
+#endif /* HAVE_DBUS */
+
+#ifdef ENABLE_SNMP
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/snmpv3_api.h>
+#include <net-snmp/agent/agent_trap.h>
+#include <net-snmp/library/mib.h>
+#include <net-snmp/library/snmp_api.h>
+#include <net-snmp/library/snmp_client.h>
+#include <net-snmp/library/snmp_debug.h>
+
+enum snmp_node_status {
+       SNMP_NODE_STATUS_UNKNOWN = 0,
+       SNMP_NODE_STATUS_JOINED = 1,
+       SNMP_NODE_STATUS_LEFT = 2
+};
+
+#define SNMP_OID_COROSYNC "1.3.6.1.4.1.35488"
+#define SNMP_OID_NOTICE_ROOT SNMP_OID_COROSYNC ".1"
+#define SNMP_OID_NOTICE_NODE_TABLE  SNMP_OID_NOTICE_ROOT ".1"
+#define SNMP_OID_NOTICE_NODE_ENTRY  SNMP_OID_NOTICE_NODE_TABLE ".1"
+#define SNMP_OID_NOTICE_NODE_INDEX  SNMP_OID_NOTICE_NODE_ENTRY ".1"
+#define SNMP_OID_NOTICE_NODE_ID     SNMP_OID_NOTICE_NODE_ENTRY ".2"
+#define SNMP_OID_NOTICE_NODE        SNMP_OID_NOTICE_NODE_ENTRY ".3"
+#define SNMP_OID_NOTICE_NODE_STATE  SNMP_OID_NOTICE_NODE_ENTRY ".4"
+
+#define SNMP_OID_TRAPS_ROOT  SNMP_OID_COROSYNC ".100"
+#define SNMP_OID_TRAPS_NODE  SNMP_OID_TRAPS_ROOT ".1"
+
+#define CS_TIMESTAMP_STR_LEN 20
+static const char *local_host = "localhost";
+#endif /* ENABLE_SNMP */
+static char snmp_manager_buf[CS_MAX_NAME_LENGTH];
+static char *snmp_manager = NULL;
+
+
+/*
+ * confdb
+ */
+#define SEPERATOR_STR "."
+
+static confdb_handle_t confdb_handle;
+
+static void _cs_confdb_key_changed(confdb_handle_t handle,
+	confdb_change_type_t change_type,
+	hdb_handle_t parent_object_handle,
+	hdb_handle_t object_handle,
+	const void *object_name, size_t object_name_len,
+	const void *key_name, size_t key_name_len,
+	const void *key_value, size_t key_value_len);
+
+static void _cs_confdb_object_created(confdb_handle_t handle,
+	hdb_handle_t parent_object_handle,
+	hdb_handle_t object_handle,
+	const void *name_pt, size_t name_len);
+
+static void _cs_confdb_object_deleted(confdb_handle_t handle,
+	hdb_handle_t parent_object_handle,
+	const void *name_pt, size_t name_len);
+
+static confdb_callbacks_t callbacks = {
+	.confdb_key_change_notify_fn = _cs_confdb_key_changed,
+	.confdb_object_create_change_notify_fn = _cs_confdb_object_created,
+	.confdb_object_delete_change_notify_fn = _cs_confdb_object_deleted,
+};
+
+static int32_t _cs_ip_to_hostname(char* ip, char* name_out)
+{
+	struct sockaddr_in sa;
+	int rc;
+
+	if (strchr(ip, ':') == NULL) {
+		sa.sin_family = AF_INET;
+	} else {
+		sa.sin_family = AF_INET6;
+	}
+
+	rc = inet_pton(sa.sin_family, ip, &sa.sin_addr);
+	if (rc == 0) {
+		return -EINVAL;
+	}
+
+	rc = getnameinfo((struct sockaddr*)&sa, sizeof(sa),
+			name_out, CS_MAX_NAME_LENGTH, NULL, 0, 0);
+	if (rc != 0) {
+		syslog (LOG_ERR, "error looking up %s : %s\n", ip, gai_strerror(rc));
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void
+_cs_confdb_key_changed(confdb_handle_t handle,
+	confdb_change_type_t change_type,
+	hdb_handle_t parent_object_handle,
+	hdb_handle_t object_handle,
+	const void *object_name_pt, size_t  object_name_len,
+	const void *key_name_pt, size_t key_name_len,
+	const void *key_value_pt, size_t key_value_len)
+{
+	char parent_name[CS_MAX_NAME_LENGTH];
+	size_t len = 0;
+	hdb_handle_t real_parent_object_handle;
+	cs_error_t rc = CS_OK;
+	char nodename[CS_MAX_NAME_LENGTH];
+	char nodeid_str[CS_MAX_NAME_LENGTH];
+	uint32_t nodeid;
+	char status[CS_MAX_NAME_LENGTH];
+	char ip[CS_MAX_NAME_LENGTH];
+	size_t ip_len;
+	confdb_value_types_t type;
+	char* open_bracket = NULL;
+	char* close_bracket = NULL;
+
+	rc = confdb_object_parent_get (handle,
+		parent_object_handle, &real_parent_object_handle);
+	assert(rc == CS_OK);
+
+	rc = confdb_object_name_get (handle,
+		real_parent_object_handle,
+		parent_name,
+		&len);
+	parent_name[len] = '\0';
+	assert(rc == CS_OK);
+
+	if (strcmp(parent_name, "members") == 0) {
+		if (strncmp(key_name_pt, "status", strlen("status")) == 0) {
+
+			memcpy(nodeid_str, object_name_pt, object_name_len);
+			nodeid_str[object_name_len] = '\0';
+			nodeid = atoi(nodeid_str);
+
+			memcpy(status, key_value_pt, key_value_len);
+			status[key_value_len] = '\0';
+
+			rc = confdb_key_get_typed(handle, parent_object_handle,
+				"ip", ip, &ip_len, &type);
+			assert(rc == CS_OK);
+			ip[ip_len-1] = '\0';
+
+			/*
+			 * We want the ip out of: "r(0) ip(192.168.100.92)"
+			 */
+			open_bracket = strrchr(ip, '(');
+			open_bracket++;
+			close_bracket = strrchr(open_bracket, ')');
+			*close_bracket = '\0';
+			_cs_ip_to_hostname(open_bracket, nodename);
+
+			_cs_node_membership_event(nodename, nodeid, status, open_bracket);
+		}
+	}
+}
+
+static void
+_cs_confdb_object_created(confdb_handle_t handle,
+	hdb_handle_t parent_object_handle,
+	hdb_handle_t object_handle,
+	const void *name_pt,
+	size_t name_len)
+{
+	char parent_name[CS_MAX_NAME_LENGTH];
+	size_t len = 0;
+	char obj_name[CS_MAX_NAME_LENGTH];
+	hdb_handle_t real_parent_object_handle;
+	cs_error_t rc = CS_OK;
+
+	memcpy(obj_name, name_pt, name_len);
+	obj_name[name_len] = '\0';
+
+	rc = confdb_object_parent_get (handle,
+		parent_object_handle, &real_parent_object_handle);
+	if (rc != CS_OK) {
+		/* this error is normally from our own cfg connection
+		 * which is short lived.
+		 */
+		return;
+	}
+
+	rc = confdb_object_name_get (handle,
+		real_parent_object_handle, parent_name, &len);
+	parent_name[len] = '\0';
+	assert(rc == CS_OK);
+
+	if (strcmp(parent_name, "connections") == 0) {
+		_cs_application_connection_event(obj_name, "connected");
+	}
+}
+
+static void
+_cs_confdb_object_deleted(confdb_handle_t handle,
+	hdb_handle_t parent_object_handle,
+	const void *name_pt,
+	size_t name_len)
+{
+	char obj_name[CS_MAX_NAME_LENGTH];
+	char parent_name[CS_MAX_NAME_LENGTH];
+	size_t len = 0;
+	cs_error_t rc;
+
+	memcpy(obj_name, name_pt, name_len);
+	obj_name[name_len] = '\0';
+
+	rc = confdb_object_name_get (handle,
+		parent_object_handle, parent_name, &len);
+	parent_name[len] = '\0';
+	assert(rc == CS_OK);
+
+	if (strcmp(parent_name, "connections") == 0) {
+		_cs_application_connection_event(obj_name, "disconnected");
+	}
+}
+
+static cs_error_t
+_cs_confdb_find_object (confdb_handle_t handle,
+	const char * name_pt,
+	hdb_handle_t * out_handle)
+{
+	char * obj_name_pt;
+	char * save_pt;
+	hdb_handle_t obj_handle;
+	confdb_handle_t parent_object_handle = OBJECT_PARENT_HANDLE;
+	char tmp_name[CS_MAX_NAME_LENGTH];
+	cs_error_t res = CS_OK;
+
+	strncpy (tmp_name, name_pt, CS_MAX_NAME_LENGTH);
+	obj_name_pt = strtok_r(tmp_name, SEPERATOR_STR, &save_pt);
+
+	while (obj_name_pt != NULL) {
+		res = confdb_object_find_start(handle, parent_object_handle);
+		if (res != CS_OK) {
+			syslog (LOG_ERR, "Could not start object_find %d\n", res);
+			exit (EXIT_FAILURE);
+		}
+
+		res = confdb_object_find(handle, parent_object_handle,
+				obj_name_pt, strlen (obj_name_pt), &obj_handle);
+		if (res != CS_OK) {
+			return res;
+		}
+
+		parent_object_handle = obj_handle;
+		obj_name_pt = strtok_r (NULL, SEPERATOR_STR, &save_pt);
+	}
+
+	*out_handle = parent_object_handle;
+	return res;
+}
+
+static int
+_cs_confdb_dispatch(hdb_handle_t handle,
+	int fd,	int revents, void *data)
+{
+	confdb_dispatch(confdb_handle, CONFDB_DISPATCH_ALL);
+	return 0;
+}
+
+static void _cs_quorum_notification(quorum_handle_t handle,
+	uint32_t quorate, uint64_t ring_seq,
+	uint32_t view_list_entries, uint32_t *view_list)
+{
+	if (quorate) {
+		_cs_node_quorum_event("quorate");
+	} else {
+		_cs_node_quorum_event("not quorate");
+	}
+}
+
+static int
+_cs_quorum_dispatch(hdb_handle_t handle,
+	int fd,	int revents, void *data)
+{
+	quorum_dispatch(quorum_handle, CS_DISPATCH_ALL);
+	return 0;
+}
+
+static void
+_cs_quorum_init(void)
+{
+	cs_error_t rc;
+	int fd;
+
+	quorum_callbacks_t quorum_callbacks = {
+		.quorum_notify_fn = _cs_quorum_notification,
+	};
+
+	rc = quorum_initialize (&quorum_handle, &quorum_callbacks);
+	if (rc != CS_OK) {
+		syslog(LOG_ERR, "Could not connect to corosync(quorum)");
+		return;
+	}
+	quorum_fd_get(quorum_handle, &fd);
+	poll_dispatch_add (poll_handle, fd, POLLIN|POLLNVAL, NULL,
+		_cs_quorum_dispatch);
+	quorum_trackstart(quorum_handle, CS_TRACK_CHANGES);
+}
+
+static void
+_cs_quorum_finalize(void)
+{
+	quorum_finalize (quorum_handle);
+}
+
+
+#ifdef HAVE_DBUS
+/*
+ * dbus notifications
+ */
+static void
+_cs_dbus_auto_flush(void)
+{
+	dbus_connection_ref(db);
+	dbus_connection_read_write(db, 500);
+	dbus_connection_unref(db);
+}
+
+static void
+_cs_dbus_release(void)
+{
+	DBusError err;
+
+	if (!db)
+		return;
+
+	dbus_error_init(&err);
+	dbus_bus_release_name(db, DBUS_CS_NAME, &err);
+	dbus_error_free(&err);
+	dbus_connection_unref(db);
+	db = NULL;
+}
+
+static void
+_cs_dbus_node_quorum_event(char *nodename, uint32_t nodeid, const char *state)
+{
+	DBusMessage *msg = NULL;
+	int ret = -1;
+
+	if (err_set) {
+		syslog (LOG_ERR, "%s\n", _err);
+		err_set = 0;
+	}
+
+	if (!db) {
+		goto out_free;
+	}
+
+	if (dbus_connection_get_is_connected(db) != TRUE) {
+		err_set = 1;
+		snprintf(_err, sizeof(_err), "DBus connection lost");
+		_cs_dbus_release();
+		goto out_unlock;
+	}
+
+	_cs_dbus_auto_flush();
+
+	if (!(msg = dbus_message_new_signal(DBUS_CS_PATH,
+					    DBUS_CS_IFACE,
+					    "QuorumStateChange"))) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	if (!dbus_message_append_args(msg,
+			DBUS_TYPE_STRING, &nodename,
+			DBUS_TYPE_UINT32, &nodeid,
+			DBUS_TYPE_STRING, &state,
+			DBUS_TYPE_INVALID)) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	dbus_connection_send(db, msg, NULL);
+	ret = 0;
+
+out_unlock:
+	if (ret == -1) {
+		syslog (LOG_ERR, "%s() error\n", __func__);
+	}
+	if (msg)
+		dbus_message_unref(msg);
+out_free:
+	return;
+}
+
+static void
+_cs_dbus_node_membership_event(char *nodename, uint32_t nodeid, char *state, char* ip)
+{
+	DBusMessage *msg = NULL;
+	int ret = -1;
+
+	if (err_set) {
+		syslog (LOG_ERR, "%s\n", _err);
+		err_set = 0;
+	}
+
+	if (!db) {
+		goto out_free;
+	}
+
+	if (dbus_connection_get_is_connected(db) != TRUE) {
+		err_set = 1;
+		snprintf(_err, sizeof(_err), "DBus connection lost");
+		_cs_dbus_release();
+		goto out_unlock;
+	}
+
+	_cs_dbus_auto_flush();
+
+	if (!(msg = dbus_message_new_signal(DBUS_CS_PATH,
+					    DBUS_CS_IFACE,
+					    "NodeStateChange"))) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	if (!dbus_message_append_args(msg,
+			DBUS_TYPE_STRING, &nodename,
+			DBUS_TYPE_UINT32, &nodeid,
+			DBUS_TYPE_STRING, &ip,
+			DBUS_TYPE_STRING, &state,
+			DBUS_TYPE_INVALID)) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	dbus_connection_send(db, msg, NULL);
+	ret = 0;
+
+out_unlock:
+	if (ret == -1) {
+		syslog (LOG_ERR, "%s() error\n", __func__);
+	}
+	if (msg)
+		dbus_message_unref(msg);
+out_free:
+	return;
+}
+
+static void
+_cs_dbus_application_connection_event(char *nodename, uint32_t nodeid, char *app_name, const char *state)
+{
+	DBusMessage *msg = NULL;
+	int ret = -1;
+
+	if (err_set) {
+		syslog (LOG_ERR, "%s\n", _err);
+		err_set = 0;
+	}
+
+	if (!db) {
+		goto out_free;
+	}
+
+	if (dbus_connection_get_is_connected(db) != TRUE) {
+		err_set = 1;
+		snprintf(_err, sizeof(_err), "DBus connection lost");
+		_cs_dbus_release();
+		goto out_unlock;
+	}
+
+	_cs_dbus_auto_flush();
+
+	if (!(msg = dbus_message_new_signal(DBUS_CS_PATH,
+				DBUS_CS_IFACE,
+				"ConnectionStateChange"))) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	if (!dbus_message_append_args(msg,
+			DBUS_TYPE_STRING, &nodename,
+			DBUS_TYPE_UINT32, &nodeid,
+			DBUS_TYPE_STRING, &app_name,
+			DBUS_TYPE_STRING, &state,
+			DBUS_TYPE_INVALID)) {
+		syslog (LOG_ERR, "%s(%d) error\n", __func__, __LINE__);
+		goto out_unlock;
+	}
+
+	dbus_connection_send(db, msg, NULL);
+	ret = 0;
+
+out_unlock:
+	if (msg)
+		dbus_message_unref(msg);
+out_free:
+	return;
+}
+
+static void
+_cs_dbus_init(void)
+{
+	DBusConnection *dbc = NULL;
+	DBusError err;
+
+	dbus_error_init(&err);
+
+	dbc = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+	if (!dbc) {
+		snprintf(_err, sizeof(_err),
+			 "dbus_bus_get: %s", err.message);
+		err_set = 1;
+		dbus_error_free(&err);
+		return;
+	}
+
+	dbus_connection_set_exit_on_disconnect(dbc, FALSE);
+
+	db = dbc;
+
+	notifiers[num_notifiers].node_membership_fn =
+		_cs_dbus_node_membership_event;
+	notifiers[num_notifiers].node_quorum_fn =
+		_cs_dbus_node_quorum_event;
+	notifiers[num_notifiers].application_connection_fn =
+		_cs_dbus_application_connection_event;
+	num_notifiers++;
+}
+
+#endif /* HAVE_DBUS */
+
+#ifdef ENABLE_SNMP
+static netsnmp_session *snmp_init (const char *target)
+{
+	static netsnmp_session *session = NULL;
+#ifndef NETSNMPV54
+	char default_port[128];
+	snprintf (default_port, sizeof (default_port), "%s:162", target);
+#endif
+	if (session) {
+		return (session);
+	}
+
+	if (target == NULL) {
+		return NULL;
+	}
+
+	session = malloc (sizeof (netsnmp_session));
+	snmp_sess_init (session);
+	session->version = SNMP_VERSION_2c;
+	session->callback = NULL;
+	session->callback_magic = NULL;
+
+	session = snmp_add(session,
+#ifdef NETSNMPV54
+		netsnmp_transport_open_client ("snmptrap", target),
+#else
+		netsnmp_tdomain_transport (default_port, 0, "udp"),
+#endif
+		NULL, NULL);
+
+	if (session == NULL) {
+		syslog(LOG_ERR, "Could not create snmp transport");
+	}
+	return (session);
+}
+
+static inline void add_field (
+	netsnmp_pdu *trap_pdu,
+	u_char asn_type,
+	const char *prefix,
+	void *value,
+	size_t value_size)
+{
+	oid _oid[MAX_OID_LEN];
+	size_t _oid_len = MAX_OID_LEN;
+	if (snmp_parse_oid(prefix, _oid, &_oid_len)) {
+		snmp_pdu_add_variable (trap_pdu, _oid, _oid_len, asn_type, (u_char *) value, value_size);
+	}
+}
+
+static void
+_cs_snmp_node_membership_event(char *nodename, uint32_t nodeid, char *state, char* ip)
+{
+	int ret;
+	char csysuptime[CS_TIMESTAMP_STR_LEN];
+	static oid snmptrap_oid[]  = { 1,3,6,1,6,3,1,1,4,1,0 };
+	static oid sysuptime_oid[] = { 1,3,6,1,2,1,1,3,0 };
+	time_t now = time (NULL);
+	int node_status;
+
+	netsnmp_pdu *trap_pdu;
+	netsnmp_session *session = snmp_init (snmp_manager);
+	if (session == NULL) {
+		syslog (LOG_NOTICE, "Failed to init SNMP session.\n");
+		return ;
+	}
+
+	trap_pdu = snmp_pdu_create (SNMP_MSG_TRAP2);
+	if (!trap_pdu) {
+		syslog (LOG_NOTICE, "Failed to create SNMP notification.\n");
+		return ;
+	}
+
+	if (strcmp(state, "joined") == 0) {
+		node_status = SNMP_NODE_STATUS_JOINED;
+	} else if (strcmp(state, "left") == 0) {
+		node_status = SNMP_NODE_STATUS_LEFT;
+	} else {
+		node_status = SNMP_NODE_STATUS_UNKNOWN;
+	}
+
+	/* send uptime */
+	snprintf (csysuptime, CS_TIMESTAMP_STR_LEN, "%ld", now);
+	snmp_add_var (trap_pdu, sysuptime_oid, sizeof (sysuptime_oid) / sizeof (oid), 't', csysuptime);
+	snmp_add_var (trap_pdu, snmptrap_oid, sizeof (snmptrap_oid) / sizeof (oid), 'o', SNMP_OID_TRAPS_NODE);
+
+	/* Add extries to the trap */
+	add_field (trap_pdu, ASN_INTEGER, SNMP_OID_NOTICE_NODE_ID, (void*)&nodeid, sizeof (nodeid));
+	add_field (trap_pdu, ASN_OCTET_STR, SNMP_OID_NOTICE_NODE, (void*)ip, strlen (ip));
+	add_field (trap_pdu, ASN_INTEGER, SNMP_OID_NOTICE_NODE_STATE, (void*)&node_status, sizeof (node_status));
+
+	/* Send and cleanup */
+	ret = snmp_send (session, trap_pdu);
+	if (ret == 0) {
+		/* error */
+		syslog (LOG_ERR, "Could not send SNMP trap");
+		snmp_free_pdu (trap_pdu);
+	}
+}
+
+static void
+_cs_snmp_init(void)
+{
+	if (snmp_manager == NULL) {
+		snmp_manager = (char*)local_host;
+	}
+
+	notifiers[num_notifiers].node_membership_fn =
+		_cs_snmp_node_membership_event;
+	notifiers[num_notifiers].node_quorum_fn = NULL;
+	notifiers[num_notifiers].application_connection_fn = NULL;
+	num_notifiers++;
+}
+
+#endif /* ENABLE_SNMP */
+
+static void
+_cs_syslog_node_membership_event(char *nodename, uint32_t nodeid, char *state, char* ip)
+{
+	syslog (LOG_NOTICE, "%s[%d] ip:%s %s\n", nodename, nodeid, ip, state);
+}
+
+static void
+_cs_syslog_node_quorum_event(char *nodename, uint32_t nodeid, const char *state)
+{
+	if (strcmp(state, "quorate") == 0) {
+		syslog (LOG_NOTICE, "%s[%d] is now %s\n", nodename, nodeid, state);
+	} else {
+		syslog (LOG_NOTICE, "%s[%d] has lost quorum\n", nodename, nodeid);
+	}
+}
+
+static void
+_cs_syslog_application_connection_event(char *nodename, uint32_t nodeid, char* app_name, const char *state)
+{
+	if (strcmp(state, "connected") == 0) {
+		syslog (LOG_ERR, "%s[%d] %s is now %s to corosync\n", nodename, nodeid, app_name, state);
+	} else {
+		syslog (LOG_ERR, "%s[%d] %s is now %s from corosync\n", nodename, nodeid, app_name, state);
+	}
+}
+
+static void
+_cs_node_membership_event(char *nodename, uint32_t nodeid, char *state, char* ip)
+{
+	int i;
+
+	for (i = 0; i < num_notifiers; i++) {
+		if (notifiers[i].node_membership_fn) {
+			notifiers[i].node_membership_fn(nodename, nodeid, state, ip);
+		}
+	}
+}
+
+static void
+_cs_local_node_info_get(char **nodename, uint32_t *nodeid)
+{
+	cs_error_t rc;
+	corosync_cfg_handle_t cfg_handle;
+
+	if (local_nodeid == 0) {
+		corosync_cfg_initialize(&cfg_handle, NULL);
+		rc = corosync_cfg_local_get (cfg_handle, &local_nodeid);
+		corosync_cfg_finalize(cfg_handle);
+		if (rc != CS_OK) {
+			local_nodeid = 0;
+			strncpy(local_nodename, "localhost", CS_MAX_NAME_LENGTH);
+		} else {
+			gethostname(local_nodename, CS_MAX_NAME_LENGTH);
+		}
+	}
+	*nodeid = local_nodeid;
+	*nodename = local_nodename;
+}
+
+static void
+_cs_node_quorum_event(const char *state)
+{
+	int i;
+	char *nodename;
+	uint32_t nodeid;
+
+	_cs_local_node_info_get(&nodename, &nodeid);
+
+	for (i = 0; i < num_notifiers; i++) {
+		if (notifiers[i].node_quorum_fn) {
+			notifiers[i].node_quorum_fn(nodename, nodeid, state);
+		}
+	}
+}
+
+static void
+_cs_application_connection_event(char *app_name, const char *state)
+{
+	int i;
+	char *nodename;
+	uint32_t nodeid;
+
+	_cs_local_node_info_get(&nodename, &nodeid);
+
+	for (i = 0; i < num_notifiers; i++) {
+		if (notifiers[i].application_connection_fn) {
+			notifiers[i].application_connection_fn(nodename, nodeid, app_name, state);
+		}
+	}
+}
+
+static void
+sig_exit_handler (int num)
+{
+	poll_stop(poll_handle);
+}
+
+static void
+_cs_confdb_init(void)
+{
+	hdb_handle_t obj_handle;
+	cs_error_t rc;
+	int conf_fd = 0;
+
+	rc = confdb_initialize (&confdb_handle, &callbacks);
+	if (rc != CS_OK) {
+		syslog (LOG_ERR, "Failed to initialize the objdb API. Error %d\n", rc);
+		exit (EXIT_FAILURE);
+	}
+	confdb_fd_get(confdb_handle, &conf_fd);
+
+	poll_dispatch_add (poll_handle, conf_fd, POLLIN|POLLNVAL, NULL,
+		_cs_confdb_dispatch);
+
+	rc = _cs_confdb_find_object (confdb_handle, "runtime.connections.",
+		&obj_handle);
+	if (rc != CS_OK) {
+		syslog (LOG_ERR,
+			"Failed to find the connections object. Error %d\n", rc);
+		exit (EXIT_FAILURE);
+	}
+
+	rc = confdb_track_changes (confdb_handle, obj_handle,
+		CONFDB_TRACK_DEPTH_ONE);
+	if (rc != CS_OK) {
+		syslog (LOG_ERR,
+			"Failed to track the connections object. Error %d\n", rc);
+		exit (EXIT_FAILURE);
+	}
+	rc = _cs_confdb_find_object(confdb_handle,
+		"runtime.totem.pg.mrp.srp.members.", &obj_handle);
+	if (rc != CS_OK) {
+		syslog (LOG_ERR, "Failed to find the object. Error %d\n", rc);
+		exit (EXIT_FAILURE);
+	}
+
+	rc = confdb_track_changes(confdb_handle,
+		obj_handle, CONFDB_TRACK_DEPTH_RECURSIVE);
+	if (rc != CS_OK) {
+		syslog (LOG_ERR,
+			"Failed to track the object. Error %d\n", rc);
+		exit (EXIT_FAILURE);
+	}
+}
+
+static void
+_cs_confdb_finalize(void)
+{
+	confdb_stop_track_changes (confdb_handle);
+	confdb_finalize (confdb_handle);
+}
+
+static void
+_cs_check_config(void)
+{
+	if (conf[CS_NTF_LOG] == 0 &&
+		conf[CS_NTF_STDOUT] == 0 &&
+		conf[CS_NTF_SNMP] == 0 &&
+		conf[CS_NTF_DBUS] == 0) {
+		syslog(LOG_ERR, "no event type enabled, see corosync-notifyd -h, exiting.");
+		exit(EXIT_FAILURE);
+	}
+
+#ifndef ENABLE_SNMP
+	if (conf[CS_NTF_SNMP]) {
+		syslog(LOG_ERR, "Not compiled with SNMP support enabled, exiting.");
+		exit(EXIT_FAILURE);
+	}
+#endif
+#ifndef HAVE_DBUS
+	if (conf[CS_NTF_DBUS]) {
+		syslog(LOG_ERR, "Not compiled with DBus support enabled, exiting.");
+		exit(EXIT_FAILURE);
+	}
+#endif
+
+	if (conf[CS_NTF_STDOUT] && !conf[CS_NTF_FG]) {
+		syslog(LOG_ERR, "configured to print to stdout and run in the background, exiting");
+		exit(EXIT_FAILURE);
+	}
+	if (conf[CS_NTF_SNMP] && conf[CS_NTF_DBUS]) {
+		syslog(LOG_ERR, "configured to send snmp traps and dbus signals - are you sure?.");
+	}
+}
+
+static void
+_cs_usage(void)
+{
+	fprintf(stderr,	"usage:\n"\
+		"        -f     : Start application in foreground.\n"\
+		"        -l     : Log all events.\n"\
+		"        -o     : Print events to stdout (turns on -l).\n"\
+		"        -s     : Send SNMP traps on all events.\n"\
+		"        -m     : SNMP Manager IP address (defaults to localhost).\n"\
+		"        -d     : Send DBUS signals on all events.\n"\
+		"        -h     : Print this help\n\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+	int ch;
+
+	conf[CS_NTF_FG] = 0;
+	conf[CS_NTF_LOG] = 0;
+	conf[CS_NTF_STDOUT] = 0;
+	conf[CS_NTF_SNMP] = 0;
+	conf[CS_NTF_DBUS] = 0;
+
+	while ((ch = getopt (argc, argv, "floshdm:")) != EOF) {
+		switch (ch) {
+			case 'f':
+				conf[CS_NTF_FG] = 1;
+				break;
+			case 'l':
+				conf[CS_NTF_LOG] = 1;
+				break;
+			case 'm':
+				conf[CS_NTF_SNMP] = 1;
+				strncpy(snmp_manager_buf, optarg, CS_MAX_NAME_LENGTH);
+				snmp_manager = snmp_manager_buf;
+				break;
+			case 'o':
+				conf[CS_NTF_LOG] = 1;
+				conf[CS_NTF_STDOUT] = 1;
+				break;
+			case 's':
+				conf[CS_NTF_SNMP] = 1;
+				break;
+			case 'd':
+				conf[CS_NTF_DBUS] = 1;
+				break;
+			case 'h':
+			default:
+				_cs_usage();
+				return EXIT_FAILURE;
+		}
+	}
+
+	if (conf[CS_NTF_STDOUT]) {
+		openlog(NULL, LOG_PID|LOG_PERROR, LOG_DAEMON);
+	} else {
+		openlog(NULL, LOG_PID, LOG_DAEMON);
+	}
+	_cs_check_config();
+
+	if (!conf[CS_NTF_FG]) {
+		daemon(0, 0);
+	}
+
+	num_notifiers = 0;
+	if (conf[CS_NTF_LOG]) {
+		notifiers[num_notifiers].node_membership_fn =
+			_cs_syslog_node_membership_event;
+		notifiers[num_notifiers].node_quorum_fn =
+			_cs_syslog_node_quorum_event;
+		notifiers[num_notifiers].application_connection_fn =
+			_cs_syslog_application_connection_event;
+		num_notifiers++;
+	}
+
+	poll_handle = poll_create();
+
+	_cs_confdb_init();
+	_cs_quorum_init();
+
+#ifdef HAVE_DBUS
+	if (conf[CS_NTF_DBUS]) {
+		_cs_dbus_init();
+	}
+#endif /* HAVE_DBUS */
+
+#ifdef ENABLE_SNMP
+	if (conf[CS_NTF_SNMP]) {
+		_cs_snmp_init();
+	}
+#endif /* ENABLE_SNMP */
+
+	(void)signal (SIGINT, sig_exit_handler);
+	(void)signal (SIGQUIT, sig_exit_handler);
+	(void)signal (SIGTERM, sig_exit_handler);
+
+	poll_run(poll_handle);
+
+#ifdef HAVE_DBUS
+	if (conf[CS_NTF_DBUS]) {
+		_cs_dbus_release();
+	}
+#endif /* HAVE_DBUS */
+
+	_cs_quorum_finalize();
+	_cs_confdb_finalize();
+
+	return 0;
+}
+

+ 9 - 0
tools/corosync-notifyd.sysconfig.example

@@ -0,0 +1,9 @@
+#
+# See "man corosync-notifyd" for descriptions of
+# the options below.
+#
+# OPTIONS="-d -s -l -m <snmp manager address>"
+#
+
+OPTIONS=""
+