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

The third version with micro-version number change. 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.1st | 9 +- configure.ac | 9 ++- libvirt-snmp.spec.in | 5 +- src/LIBVIRT-MIB.txt | 9 ++ src/Makefile.am | 3 + src/README.txt | 9 +- src/libvirtNotifications.c | 121 +++++++++++++++++++ src/libvirtNotifications.h | 33 +++++ src/libvirtSnmp.c | 277 +++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 464 insertions(+), 11 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 607d441051b04bb6ca0cd09ad8a6ff07eafc2f8e..932dec9587c1e7ed97fe50ef1979979e73876195 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

On Wed, Mar 23, 2011 at 11:06:06AM +0100, Michal Privoznik wrote:
--- 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 607d441051b04bb6ca0cd09ad8a6ff07eafc2f8e..932dec9587c1e7ed97fe50ef1979979e73876195 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
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

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 c781e230e6b5f87c6776f40360d580ff0b50fc2b..d68f17499d8768706050a7f38f77fc597fb0db46 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 22dd2af8436de1797afcee37f47218fe46dac4fd..6d010f6d0fedfa6e47333468bfa9dec40e47d29c 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 0000000000000000000000000000000000000000..0001425b0c1c338bca01d958320ee2379d792866 --- /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 0000000000000000000000000000000000000000..84eefc32359320d9a3cb501a7941241731246dc2 --- /dev/null +++ b/src/libvirtNotifications.h @@ -0,0 +1,21 @@ +/* + * libvirtNotifications.h: 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

On Wed, Mar 23, 2011 at 11:06:07AM +0100, Michal Privoznik wrote:
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 c781e230e6b5f87c6776f40360d580ff0b50fc2b..d68f17499d8768706050a7f38f77fc597fb0db46 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 22dd2af8436de1797afcee37f47218fe46dac4fd..6d010f6d0fedfa6e47333468bfa9dec40e47d29c 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 0000000000000000000000000000000000000000..0001425b0c1c338bca01d958320ee2379d792866 --- /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; + }
In the error cases, I thuink you really want to be printing the message from the virErrorPtr object, so the admin or bug triager can diagnose what is causing the failure. I'd suggest creating a helper method for printing the error, eg static void printLibvirtError(const char *msg) { virErrorPtr err = virGetLastError(); fprintf(stderr, "%s: %s", msg, err ? err->message : "Unknown error"); } Also when checking for failure, you should check for "< 0" or "!= NULL", since positive return values are used for non-error conditions. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

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.1st | 9 +- configure.ac | 9 ++- libvirt-snmp.spec.in | 5 +- src/Makefile.am | 1 + src/README.txt | 2 + src/libvirtNotifications.c | 16 +++- src/libvirtNotifications.h | 16 +++- src/libvirtSnmp.c | 277 +++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 322 insertions(+), 13 deletions(-) diff --git a/INSTALL.1st b/INSTALL.1st index 31345d85d91841bf7ef55f41e932360ea5762133..c439bf3a8cce4db7dea48c36541905196c194493 100644 --- a/INSTALL.1st +++ b/INSTALL.1st @@ -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 dcab0aea21b965e03de82181a779aee3dac8d9bb..7cff5f202364afc593c1656c529379882ace5a1f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([libvirt-snmp],[0.0.1],[libvir-list@redhat.com],[],[http://libvirt.org]) +AC_INIT([libvirt-snmp],[0.0.2],[libvir-list@redhat.com],[],[http://libvirt.org]) AM_INIT_AUTOMAKE([-Wall -Werror]) AC_CONFIG_HEADERS([config.h]) @@ -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 docs/Makefile libvirt-snmp.spec) diff --git a/libvirt-snmp.spec.in b/libvirt-snmp.spec.in index bbc5602a4b914ae73f951ed979229ef7c62c7ae2..02b7164257a0bed43d3d285697d50782e50eab25 100644 --- a/libvirt-snmp.spec.in +++ b/libvirt-snmp.spec.in @@ -1,6 +1,6 @@ Name: libvirt-snmp Version: @VERSION@ -Release: 3%{?dist}%{?extra_release} +Release: 1%{?dist}%{?extra_release} Summary: SNMP functionality for libvirt Group: Development/Libraries @@ -36,6 +36,9 @@ make install DESTDIR=$RPM_BUILD_ROOT %changelog +* Wed Mar 23 2011 Michal Privoznik <mprivozn@redhat.com> 0.0.2-1 +- add SNMP trap/notification support + * Fri Mar 11 2011 Michal Privoznik <mprivozn@redhat.com> 0.0.1-3 - remove LIBVIRT-MIB.txt from %doc diff --git a/src/Makefile.am b/src/Makefile.am index d68f17499d8768706050a7f38f77fc597fb0db46..8aa4cd5975a46c640d9d612f94567b63e04f8569 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 6d010f6d0fedfa6e47333468bfa9dec40e47d29c..5e9823a51b4facc8e0ce322d0c2201010ba7e207 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/libvirtNotifications.c b/src/libvirtNotifications.c index 0001425b0c1c338bca01d958320ee2379d792866..c235638b1f49a243f55c67523433b55d624925c1 100644 --- a/src/libvirtNotifications.c +++ b/src/libvirtNotifications.c @@ -3,9 +3,21 @@ * * Copyright (C) 2011 Red Hat, Inc. * - * See COPYING for the license of this software + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * Michal Privoznik <mprivozn@redhat.com> + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> */ #include <net-snmp/net-snmp-config.h> diff --git a/src/libvirtNotifications.h b/src/libvirtNotifications.h index 84eefc32359320d9a3cb501a7941241731246dc2..0be8fdc2193af60d01fca992158e312844215e86 100644 --- a/src/libvirtNotifications.h +++ b/src/libvirtNotifications.h @@ -3,9 +3,21 @@ * * Copyright (C) 2011 Red Hat, Inc. * - * See COPYING for the license of this software + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. * - * Michal Privoznik <mprivozn@redhat.com> + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Michal Privoznik <mprivozn@redhat.com> */ #ifndef LIBVIRTNOTIFICATIONS_H diff --git a/src/libvirtSnmp.c b/src/libvirtSnmp.c index dd1bd33dc3fbc08656d09075a3c1a1741ebc002f..1bc947d85384e2ced111cde301951b4e5c3666f6 100644 --- a/src/libvirtSnmp.c +++ b/src/libvirtSnmp.c @@ -22,12 +22,168 @@ #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) @@ -188,10 +344,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); @@ -201,11 +460,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

On Wed, Mar 23, 2011 at 11:06:08AM +0100, Michal Privoznik wrote:
diff --git a/src/libvirtSnmp.c b/src/libvirtSnmp.c index dd1bd33dc3fbc08656d09075a3c1a1741ebc002f..1bc947d85384e2ced111cde301951b4e5c3666f6 100644 --- a/src/libvirtSnmp.c +++ b/src/libvirtSnmp.c @@ -22,12 +22,168 @@
#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; +}
What is the oldest version of libvirt that you need to be able to run libvirt-snmp against ? This impl of the file handle / timer callbacks taken from the C example program is really not a very good one to copy / use. If you are able to say that you only need to support the next libvirt 0.9.0 or newer, then you can simply use our new public APIs for this int virEventRegisterDefaultImpl(void); int virEventRunDefaultImpl(void);
+/* 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); +}
This loop would just turn into while (run) { if (virEventRunDefaultImpl() < 0) { ...print error... 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);
And this can be replaced by virEventRegisterDefaultImpl(). On the other hand, if you need to be able to build against older libvirt releases, then you should probably just copy the code from src/util/event_poll.c in the libvirt GIT repo, straight into libvirt-snmp, instead of using the C event example code. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/23/2011 07:30 AM, Daniel P. Berrange wrote:
What is the oldest version of libvirt that you need to be able to run libvirt-snmp against ?
This impl of the file handle / timer callbacks taken from the C example program is really not a very good one to copy / use.
If you are able to say that you only need to support the next libvirt 0.9.0 or newer, then you can simply use our new public APIs for this
int virEventRegisterDefaultImpl(void); int virEventRunDefaultImpl(void);
If you want for upstream libvirt-snmp to compile out of the box against RHEL 5.6 (which shipped with libvirt 0.8.2), then I'd opt for the option of copying the latest libvirt.git event_poll.c implementation (earlier versions had bugs), but avoiding the new 0.9.0 APIs.
On the other hand, if you need to be able to build against older libvirt releases, then you should probably just copy the code from src/util/event_poll.c in the libvirt GIT repo, straight into libvirt-snmp, instead of using the C event example code.
-- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Michal Privoznik