[libvirt] [libvirt-snmp][PATCH v2 0/3] Add SNMP trap/notification support.

The second version, without formatting stuff. Finally, we have a proof-of-concept implementation of SNMP trap. Two new files were first generated using mib2c then edited, so be careful when re-generating them. Destination of traps is defined in snmpd.conf (trap2sink), and to receive them snmptrapd must be configured and up. The idea behind is - create a thread in which we poll for domain events. Once we receive event notification, we just fire up trap sending. Michal Privoznik (3): Add notification-type object to libvirt MIB Create functions to fill in and send notification packets. Add SNMP trap/notification support. INSTALL | 9 +- configure.ac | 7 + src/LIBVIRT-MIB.txt | 9 ++ src/Makefile.am | 3 + src/README.txt | 9 +- src/libvirtNotifications.c | 109 +++++++++++++++++ src/libvirtNotifications.h | 21 ++++ src/libvirtSnmp.c | 277 +++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 src/libvirtNotifications.c create mode 100644 src/libvirtNotifications.h -- 1.7.4

--- src/LIBVIRT-MIB.txt | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-) diff --git a/src/LIBVIRT-MIB.txt b/src/LIBVIRT-MIB.txt index 607d441..932dec9 100644 --- a/src/LIBVIRT-MIB.txt +++ b/src/LIBVIRT-MIB.txt @@ -201,5 +201,14 @@ libvirtGuestGroup OBJECT-GROUP guests." ::= { libvirtGroups 1 } +libvirtGuestNotif NOTIFICATION-TYPE + STATUS current + OBJECTS { libvirtGuestName, + libvirtGuestUUID, + libvirtGuestState, + libvirtGuestRowStatus } + DESCRIPTION + "Guest lifecycle notification." + ::= { libvirtNotifications 1 } END -- 1.7.4

Before sending snmp notification we need to fill in useful data, like domain status, UUID, etc. --- src/Makefile.am | 2 + src/README.txt | 7 ++- src/libvirtNotifications.c | 109 ++++++++++++++++++++++++++++++++++++++++++++ src/libvirtNotifications.h | 21 ++++++++ 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 src/libvirtNotifications.c create mode 100644 src/libvirtNotifications.h diff --git a/src/Makefile.am b/src/Makefile.am index c781e23..d68f174 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,12 +15,14 @@ USER_SRCS = \ libvirtGuestTable_data_get.c \ libvirtGuestTable_data_set.c \ libvirtGuestTable_data_access.c \ + libvirtNotifications.c \ libvirtSnmp.c USER_HDRS = \ libvirtGuestTable_data_get.h \ libvirtGuestTable_data_set.h \ libvirtGuestTable_data_access.h \ + libvirtNotifications.h \ libvirtSnmp.h SRCS = \ diff --git a/src/README.txt b/src/README.txt index 22dd2af..6d010f6 100644 --- a/src/README.txt +++ b/src/README.txt @@ -31,8 +31,11 @@ libvirtGuestTable* - Generated by this command: 'MIBDIRS="+." MIBS="+LIBVIRT-MIB" mib2c libvirtMIB'. - It asks few questions and produces the mentioned files. - libvirtGuestTable.h, libvirtGuestTable.c, libvirtGuestTable_data_access.c, libvirtGuestTable_data_get.c and libvirtGuestTable_data_set.c are manually edited to set correct data structures and their handling. It's nicely documented, see libvirtGuestTable-README-FIRST.txt and libvirtGuestTable-README-libvirtGuestTable.txt + TODOs in the code. It took me ~2 hours to update these files (but I've done this several times for different MIBs, so I know what to do :). Do not touch the rest of the files! -- I've done everything necessary for simple read-write access. -- This code must be regenerated when the definition of libvirtGuestTable in the MIB file is changed! There is some automatic merging tool, which merges my changes with newly generated code, but I wouldn't rely on it. + +libvirtNotifications.[c|h] +- The SNMP trap (notification) stuff +- Generated by this command: 'MIBDIRS="+." MIBS="+LIBVIRT-MIB" mib2c -c mib2c.notify.conf libvirtNotifications' +- and slightly modified (especially filling up trap variables which are going to be send). Usage (tested on Fedora 14 and RHEL6) diff --git a/src/libvirtNotifications.c b/src/libvirtNotifications.c new file mode 100644 index 0000000..545ba66 --- /dev/null +++ b/src/libvirtNotifications.c @@ -0,0 +1,109 @@ +/* + * libvirtNotifications.c: Fill in trap packets + * + * Copyright (C) 2011 Red Hat, Inc. + * + * See COPYING for the license of this software + * + * Michal Privoznik <mprivozn@redhat.com> + */ + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include <string.h> +#include "libvirtNotifications.h" +#include "libvirtGuestTable_enums.h" + +static const oid snmptrap_oid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; + +int +send_libvirtGuestNotif_trap(virDomainPtr dom) +{ + netsnmp_variable_list *var_list = NULL; + const oid libvirtGuestNotif_oid[] = { 1, 3, 6, 1, 4, 1, 12345, 0, 1 }; + const oid libvirtGuestName_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 2, 0 }; + const oid libvirtGuestUUID_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 1, 1 }; + const oid libvirtGuestState_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 3, 2 }; + const oid libvirtGuestRowStatus_oid[] = + { 1, 3, 6, 1, 4, 1, 12345, 1, 1, 1, 9, 3 }; + + + const char *domName = virDomainGetName(dom); + char domUUID[VIR_UUID_BUFLEN]; + virDomainInfo info; + int rowstatus = ROWSTATUS_ACTIVE; + + if (virDomainGetUUID(dom, domUUID)) { + fprintf(stderr, "Failed to get domain UUID\n"); + return SNMP_ERR_GENERR; + } + + if (virDomainGetInfo(dom, &info)) { + fprintf(stderr, "Failed to get domain info\n"); + return SNMP_ERR_GENERR; + } + + /* + * If domain is shutting down, row in libvirtGuestTable will + * not be accessible anymore. + */ + switch (info.state) { + case VIR_DOMAIN_SHUTDOWN: + case VIR_DOMAIN_SHUTOFF: + case VIR_DOMAIN_CRASHED: + rowstatus = ROWSTATUS_NOTINSERVICE; + break; + + default: + rowstatus = ROWSTATUS_ACTIVE; + break; + }; + + /* + * Set the snmpTrapOid.0 value + */ + snmp_varlist_add_variable(&var_list, + snmptrap_oid, OID_LENGTH(snmptrap_oid), + ASN_OBJECT_ID, + libvirtGuestNotif_oid, + sizeof(libvirtGuestNotif_oid)); + + /* + * Add any objects from the trap definition + */ + snmp_varlist_add_variable(&var_list, + libvirtGuestName_oid, + OID_LENGTH(libvirtGuestName_oid), + ASN_OCTET_STR, domName, strlen(domName)); + snmp_varlist_add_variable(&var_list, + libvirtGuestUUID_oid, + OID_LENGTH(libvirtGuestUUID_oid), + ASN_OCTET_STR, domUUID, sizeof(domUUID)); + snmp_varlist_add_variable(&var_list, + libvirtGuestState_oid, + OID_LENGTH(libvirtGuestState_oid), + ASN_INTEGER, + (u_char *) & info.state, sizeof(info.state)); + snmp_varlist_add_variable(&var_list, + libvirtGuestRowStatus_oid, + OID_LENGTH(libvirtGuestRowStatus_oid), + ASN_INTEGER, + (u_char *) & rowstatus, sizeof(rowstatus)); + + /* + * Add any extra (optional) objects here + */ + + /* + * Send the trap to the list of configured destinations + * and clean up + */ + send_v2trap(var_list); + snmp_free_varbind(var_list); + + return SNMP_ERR_NOERROR; +} diff --git a/src/libvirtNotifications.h b/src/libvirtNotifications.h new file mode 100644 index 0000000..37d90a3 --- /dev/null +++ b/src/libvirtNotifications.h @@ -0,0 +1,21 @@ +/* + * libvirtNotifications.c: Fill in trap packets + * + * Copyright (C) 2011 Red Hat, Inc. + * + * See COPYING for the license of this software + * + * Michal Privoznik <mprivozn@redhat.com> + */ + +#ifndef LIBVIRTNOTIFICATIONS_H +#define LIBVIRTNOTIFICATIONS_H + +#include "libvirtSnmp.h" + +/* + * function declarations + */ +int send_libvirtGuestNotif_trap(virDomainPtr dom); + +#endif /* LIBVIRTNOTIFICATIONS_H */ -- 1.7.4

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
participants (1)
-
Michal Privoznik