This patch adds support for domain lifecycle notification support
over SNMP traps. SNMP subagent monitors any domain events and when
something interesting happens, it sends a trap.
Monitoring is done in a joinable thread using polling (used
domain-events example from libvirt) so we won't block the agent itself.
Some debug info can be printed out by setting LIBVIRT_SNMP_VERBOSE
environment variable.
---
INSTALL | 9 +-
configure.ac | 7 ++
src/Makefile.am | 1 +
src/README.txt | 2 +
src/libvirtSnmp.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 289 insertions(+), 7 deletions(-)
diff --git a/INSTALL b/INSTALL
index 31345d8..4616529 100644
--- a/INSTALL
+++ b/INSTALL
@@ -15,14 +15,17 @@ Now it's time for make:
make
su -c "make install"
-This compile all sources producing runable SNMP subagent
-libvirtMib_subagent, which is installed right after.
+This compiles all source producing a runnable SNMP subagent,
+libvirtMib_subagent, which is installed afterward.
But before we run it, we need to edit /etc/snmp/snmpd.conf
-so it contains this two lines:
+so it contains these four lines:
rwcommunity public
master agentx
+trap2sink localhost
+trapcommunity public
+
and then restart snmpd:
/etc/init.d/snmpd restart
diff --git a/configure.ac b/configure.ac
index a5fcb4e..58d26b2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,5 +86,12 @@ fi
AC_SUBST([MIB_DIR])
+dnl pthread
+PTHREAD_LIBS=
+AC_CHECK_HEADERS(pthread.h, [], [AC_MSG_ERROR([pthread.h required])])
+AC_CHECK_LIB(pthread, pthread_create, [PTHREAD_LIBS="-lpthread"])
+
+AC_SUBST([PTHREAD_LIBS])
+
AC_OUTPUT(Makefile src/Makefile libvirt-snmp.spec)
diff --git a/src/Makefile.am b/src/Makefile.am
index d68f174..8aa4cd5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,6 +9,7 @@ AM_CFLAGS = \
AM_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
+ $(PTHREAD_LIBS) \
$(SNMP_LIBS)
USER_SRCS = \
diff --git a/src/README.txt b/src/README.txt
index 6d010f6..5e9823a 100644
--- a/src/README.txt
+++ b/src/README.txt
@@ -47,6 +47,8 @@ $ make
2. use following /etc/snmp/snmpd.conf:
rwcommunity public
master agentx
+trap2sink localhost
+trapcommunity public
3. service snmpd start
diff --git a/src/libvirtSnmp.c b/src/libvirtSnmp.c
index c15431a..1f4604c 100644
--- a/src/libvirtSnmp.c
+++ b/src/libvirtSnmp.c
@@ -9,12 +9,168 @@ gcc -lvirt activeguests.c
#include <stdio.h>
#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <pthread.h>
+#include <signal.h>
#include "libvirtSnmp.h"
-/* include our MIB structures*/
-#include "libvirtGuestTable.h"
+#include "libvirtGuestTable.h" /* include our MIB structures*/
+#include "libvirtNotifications.h"
+#define DEBUG0(fmt) if (verbose) printf("%s:%d :: " fmt "\n", \
+ __func__, __LINE__)
+#define DEBUG(fmt, ...) if (verbose) printf("%s:%d: " fmt "\n", \
+ __func__, __LINE__, __VA_ARGS__)
+#define STREQ(a,b) (strcmp(a,b) == 0)
+
+#ifndef ATTRIBUTE_UNUSED
+#define ATTRIBUTE_UNUSED __attribute__((__unused__))
+#endif
+
+int verbose = 0;
virConnectPtr conn;
+int callbackRet = -1;
+int run = 1;
+pthread_t poll_thread;
+
+/* handle globals */
+int h_fd = 0;
+virEventHandleType h_event = 0;
+virEventHandleCallback h_cb = NULL;
+virFreeCallback h_ff = NULL;
+void *h_opaque = NULL;
+
+/* timeout globals */
+#define TIMEOUT_MS 1000
+int t_active = 0;
+int t_timeout = -1;
+virEventTimeoutCallback t_cb = NULL;
+virFreeCallback t_ff = NULL;
+void *t_opaque = NULL;
+
+
+static int
+domainEventCallback(virConnectPtr conn ATTRIBUTE_UNUSED,
+ virDomainPtr dom, int event, int detail,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ DEBUG("%s EVENT: Domain %s(%d) %d %d\n", __func__,
+ virDomainGetName(dom), virDomainGetID(dom), event, detail);
+
+ send_libvirtGuestNotif_trap(dom);
+ return 0;
+}
+
+static void
+myFreeFunc(void *opaque)
+{
+ if (opaque)
+ free(opaque);
+}
+
+/* EventImpl Functions */
+int
+myEventHandleTypeToPollEvent(virEventHandleType events)
+{
+ int ret = 0;
+
+ if (events & VIR_EVENT_HANDLE_READABLE)
+ ret |= POLLIN;
+ if (events & VIR_EVENT_HANDLE_WRITABLE)
+ ret |= POLLOUT;
+ if (events & VIR_EVENT_HANDLE_ERROR)
+ ret |= POLLERR;
+ if (events & VIR_EVENT_HANDLE_HANGUP)
+ ret |= POLLHUP;
+ return ret;
+}
+
+virEventHandleType
+myPollEventToEventHandleType(int events)
+{
+ virEventHandleType ret = 0;
+
+ if (events & POLLIN)
+ ret |= VIR_EVENT_HANDLE_READABLE;
+ if (events & POLLOUT)
+ ret |= VIR_EVENT_HANDLE_WRITABLE;
+ if (events & POLLERR)
+ ret |= VIR_EVENT_HANDLE_ERROR;
+ if (events & POLLHUP)
+ ret |= VIR_EVENT_HANDLE_HANGUP;
+ return ret;
+}
+
+int
+myEventAddHandleFunc(int fd, int event,
+ virEventHandleCallback cb,
+ void *opaque, virFreeCallback ff)
+{
+ DEBUG("Add handle %d %d %p %p", fd, event, cb, opaque);
+ h_fd = fd;
+ h_event = myEventHandleTypeToPollEvent(event);
+ h_cb = cb;
+ h_ff = ff;
+ h_opaque = opaque;
+ return 0;
+}
+
+void
+myEventUpdateHandleFunc(int fd, int event)
+{
+ DEBUG("Updated Handle %d %d", fd, event);
+ h_event = myEventHandleTypeToPollEvent(event);
+ return;
+}
+
+int
+myEventRemoveHandleFunc(int fd)
+{
+ DEBUG("Removed Handle %d", fd);
+ h_fd = 0;
+ if (h_ff)
+ (h_ff) (h_opaque);
+ return 0;
+}
+
+int
+myEventAddTimeoutFunc(int timeout,
+ virEventTimeoutCallback cb,
+ void *opaque, virFreeCallback ff)
+{
+ DEBUG("Adding Timeout %d %p %p", timeout, cb, opaque);
+ t_active = 1;
+ t_timeout = timeout;
+ t_cb = cb;
+ t_ff = ff;
+ t_opaque = opaque;
+ return 0;
+}
+
+void
+myEventUpdateTimeoutFunc(int timer ATTRIBUTE_UNUSED, int timeout)
+{
+ /*DEBUG("Timeout updated %d %d", timer, timeout); */
+ t_timeout = timeout;
+}
+
+int
+myEventRemoveTimeoutFunc(int timer)
+{
+ DEBUG("Timeout removed %d", timer);
+ t_active = 0;
+ if (t_ff)
+ (t_ff) (t_opaque);
+ return 0;
+}
+
+/* Signal trap function */
+static void
+stop(int sig)
+{
+ run = 0;
+}
static void
showError(virConnectPtr conn)
@@ -175,10 +331,113 @@ out:
return ret;
}
+/* Polling thread function */
+void *
+pollingThreadFunc(void *foo) {
+ int sts;
+
+ while (run) {
+ struct pollfd pfd = {.fd = h_fd,
+ .events = h_event,
+ .revents = 0
+ };
+
+ sts = poll(&pfd, 1, TIMEOUT_MS);
+
+ /* if t_timeout < 0 then t_cb must not be called */
+ if (t_cb && t_active && t_timeout >= 0) {
+ t_cb(t_timeout, t_opaque);
+ }
+
+ if (sts == 0) {
+ /* DEBUG0("Poll timeout"); */
+ continue;
+ }
+ if (sts < 0) {
+ DEBUG0("Poll failed");
+ continue;
+ }
+ if (pfd.revents & POLLHUP) {
+ DEBUG0("Reset by peer");
+ pthread_exit(NULL);
+ }
+
+ if (h_cb) {
+ h_cb(0,
+ h_fd,
+ myPollEventToEventHandleType(pfd.revents & h_event),
+ h_opaque);
+ }
+
+ }
+
+ pthread_exit(NULL);
+}
+
+/* Function to register domain lifecycle events collection */
+int
+libvirtRegisterEvents(virConnectPtr conn) {
+ struct sigaction action_stop;
+ pthread_attr_t thread_attr;
+
+ memset(&action_stop, 0, sizeof action_stop);
+
+ action_stop.sa_handler = stop;
+
+ sigaction(SIGTERM, &action_stop, NULL);
+ sigaction(SIGINT, &action_stop, NULL);
+
+ DEBUG0("Registering domain event callback");
+
+ callbackRet = virConnectDomainEventRegisterAny(conn, NULL,
+ VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ VIR_DOMAIN_EVENT_CALLBACK
+ (domainEventCallback),
+ NULL, myFreeFunc);
+
+ if (callbackRet == -1)
+ return -1;
+
+ /* we need a thread to poll for events */
+ pthread_attr_init(&thread_attr);
+ pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE);
+
+ if (pthread_create
+ (&poll_thread, &thread_attr, pollingThreadFunc, NULL))
+ return -1;
+
+ pthread_attr_destroy(&thread_attr);
+
+ return 0;
+}
+
+/* Unregister domain events collection */
+int
+libvirtUnregisterEvents(virConnectPtr conn)
+{
+ void *status;
+
+ pthread_join(poll_thread, &status);
+ virConnectDomainEventDeregisterAny(conn, callbackRet);
+ callbackRet = -1;
+ return 0;
+}
+
int libvirtSnmpInit(void)
{
- /* virConnectOpenAuth is called here with all default parameters,
- * except, possibly, the URI of the hypervisor. */
+ char *verbose_env = getenv("LIBVIRT_SNMP_VERBOSE");
+
+ verbose = verbose_env != NULL;
+
+ /* if we don't already have registered callback */
+ if (callbackRet == -1)
+ virEventRegisterImpl(myEventAddHandleFunc,
+ myEventUpdateHandleFunc,
+ myEventRemoveHandleFunc,
+ myEventAddTimeoutFunc,
+ myEventUpdateTimeoutFunc,
+ myEventRemoveTimeoutFunc);
+
/* TODO: configure the URI */
/* Use libvirt env variable LIBVIRT_DEFAULT_URI by default*/
conn = virConnectOpenAuth(NULL, virConnectAuthPtrDefault, 0);
@@ -188,11 +447,21 @@ int libvirtSnmpInit(void)
showError(conn);
return -1;
}
+
+ if ((callbackRet == -1) && libvirtRegisterEvents(conn)) {
+ printf("Unable to register domain events\n");
+ return -1;
+ }
+
return 0;
}
void libvirtSnmpShutdown(void)
{
+ if (libvirtUnregisterEvents(conn)) {
+ printf("Failed to unregister domain events\n");
+ }
+
if (0 != virConnectClose(conn)) {
printf("Failed to disconnect from hypervisor\n");
showError(conn);
--
1.7.4