diff --git a/Changes b/Changes index 1edf622f..dcb544cc 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ This file documents the revision history for mod_gearman. 0.8 + - added send_multi - fixed checks with popen/exec 0.7 Wed Nov 3 14:01:23 CET 2010 diff --git a/Makefile.am b/Makefile.am index 5d514aa3..22ceb376 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,7 @@ mod_gearman_so_SOURCES = $(common_SOURCES) \ bin_PROGRAMS = mod_gearman_worker \ send_gearman \ + send_multi \ check_gearman \ gearman_top @@ -44,6 +45,10 @@ send_gearman_SOURCES = $(common_SOURCES) \ tools/tools_logger.c \ tools/send_gearman.c +send_multi_SOURCES = $(common_SOURCES) \ + tools/tools_logger.c \ + tools/send_multi.c + check_gearman_SOURCES = $(common_SOURCES) \ tools/tools_logger.c \ tools/check_gearman.c @@ -146,13 +151,14 @@ install-data-local: install-local-state-dir @echo "################################################################" @echo "" @echo " Installation completed:" - @echo " neb module: $(DESTDIR)$(pkglibdir)/mod_gearman.o" + @echo " neb module: $(DESTDIR)$(pkglibdir)/mod_gearman.o" @echo "" - @echo " worker: $(DESTDIR)$(bindir)/mod_gearman_worker" - @echo " init script: $(DESTDIR)$(initrddir)/mod_gearman_worker" + @echo " worker: $(DESTDIR)$(bindir)/mod_gearman_worker" + @echo " init script: $(DESTDIR)$(initrddir)/mod_gearman_worker" @echo "" - @echo " check bin: $(DESTDIR)$(bindir)/check_gearman" - @echo " send bin: $(DESTDIR)$(bindir)/send_gearman" + @echo " check bin: $(DESTDIR)$(bindir)/check_gearman" + @echo " send bin: $(DESTDIR)$(bindir)/send_gearman" + @echo " send multi bin: $(DESTDIR)$(bindir)/send_multi" @echo "" @echo "just add the broker line to your nagios.cfg:" @echo "broker_module=$(DESTDIR)$(pkglibdir)/mod_gearman.o config=$(DESTDIR)$(sysconfdir)/mod_gearman.conf" diff --git a/README b/README index 6a3325c0..cacc14e6 100644 --- a/README +++ b/README @@ -153,7 +153,7 @@ NEB Module A sample broker in your nagios.cfg could look like: ====== - broker=/usr/local/share/nagios/mod_gearman.o keyfile=/usr/local/share/nagios/secret.txt server=localhost eventhandler=yes hosts=yes services=yes + broker_module=/usr/local/share/nagios/mod_gearman.o keyfile=/usr/local/share/nagios/secret.txt server=localhost eventhandler=yes hosts=yes services=yes ====== See the following list for a detailed explaination of available @@ -476,6 +476,29 @@ check would do. +How to Submit check_multi Results +--------------------------------- + +check_multi is a plugin which executes multiple child checks. +See more details about the feed_passive mode at: +http://www.my-plugin.de/wiki/projects/check_multi/feed_passive + +You can pass such child checks to Nagios via the mod_gearman +neb module: + +....... + %> check_multi -f multi.cmd -r 256 | ./send_multi --server= --encryption=no --host="" --service="" +....... + +If you want to use only check_multi and no other workers, you can +achieve this with the following neb module settings: + + broker_module=/usr/local/share/nagios/mod_gearman.o server=localhost encryption=no eventhandler=no hosts=no services=no hostgroups=does_not_exist + +Note: encryption is not necessary if you both run the check_multi checks +and the nagios check_results queue on the same server. + + What About Notifications ------------------------ Notifications are very difficult to distribute. And i think its not diff --git a/README.html b/README.html index 738b8682..e725f3fd 100644 --- a/README.html +++ b/README.html @@ -2110,6 +2110,32 @@

How to Submit Passive Checks

 %> ./send_gearman --server=<job server> --encryption=no --host="<hostname>" --service="<service>" --message="message"
+

How to Submit check_multi Results

+
+

+check_multi is a plugin which executes multiple child checks. +See more details about the feed_passive mode at: +http://www.my-plugin.de/wiki/projects/check_multi/feed_passive +You can pass such child checks to Nagios via the mod_gearman +neb module: +

+
+
+

+ %> check_multi -f multi.cmd -r 256 | ./send_multi --server=<job server> --encryption=no --host="<hostname>" --service="<service>"
+
+
+

+If you want to use only check_multi and no other workers, you can achieve this with the following neb module settings: +

+
+

+ broker_module=/usr/local/share/nagios/mod_gearman.o server=localhost encryption=no eventhandler=no hosts=no services=no hostgroups=does_not_exist
+ 
+
+

+Note: encryption is not necessary if you both run the check_multi checks on one host. +

What About Notifications

Notifications are very difficult to distribute. And i think its not diff --git a/common/utils.c b/common/utils.c index 9a568bf0..039b4574 100644 --- a/common/utils.c +++ b/common/utils.c @@ -451,6 +451,12 @@ int parse_args_line(mod_gm_opt_t *opt, char * arg, int recursion_level) { } /* timeout */ + else if ( !strcmp( key, "timeout" ) ) { + opt->timeout = atoi( value ); + if(opt->timeout < 0) { opt->timeout = 10; } + } + + /* job_timeout */ else if ( !strcmp( key, "job_timeout" ) ) { opt->job_timeout = atoi( value ); if(opt->job_timeout < 1) { opt->job_timeout = 1; } diff --git a/include/send_multi.h b/include/send_multi.h new file mode 100644 index 00000000..53515139 --- /dev/null +++ b/include/send_multi.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * + * mod_gearman - distribute checks with gearman + * + * Copyright (c) 2010 Sven Nierlein - sven.nierlein@consol.de + * Copyright (c) 2010 Matthias Flacke - matthias.flacke@gmx.de + * + * This file is part of mod_gearman. + * + * mod_gearman 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 3 of the License, or + * (at your option) any later version. + * + * mod_gearman 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 mod_gearman. If not, see . + * + *****************************************************************************/ + +#define MOD_GM_SEND_MULTI + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +mod_gm_opt_t *mod_gm_opt; + +int main (int argc, char **argv); +int parse_arguments(int argc, char **argv); +void print_usage(void); +void print_version(void); +int verify_options(mod_gm_opt_t *opt); +int send_result(void); +void alarm_sighandler(int sig); +int read_multi_stream(FILE *stream); +int read_child_check(char *bufstart, char *bufend); +char *read_multi_attribute(char *bufstart, char *bufend, char *attname); +char *decode_xml(char *); + + diff --git a/tools/send_multi.c b/tools/send_multi.c new file mode 100644 index 00000000..ef1eaf46 --- /dev/null +++ b/tools/send_multi.c @@ -0,0 +1,441 @@ +/****************************************************************************** + * + * mod_gearman - distribute checks with gearman + * + * Copyright (c) 2010 Sven Nierlein - sven.nierlein@consol.de + * Copyright (c) 2010 Matthias Flacke - matthias.flacke@gmx.de + * + * This file is part of mod_gearman. + * + * mod_gearman 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 3 of the License, or + * (at your option) any later version. + * + * mod_gearman 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 mod_gearman. If not, see . + * + *****************************************************************************/ + +/* include header */ +#include "send_multi.h" +#include "utils.h" +#include "worker_logger.h" +#include "gearman.h" + +gearman_client_st client; + +/* work starts here */ +int main (int argc, char **argv) { + int rc; + + /* + * allocate options structure + * and parse command line + */ + if(parse_arguments(argc, argv) != GM_OK) { + print_usage(); + exit( EXIT_FAILURE ); + } + + /* init crypto functions */ + if(mod_gm_opt->encryption == GM_ENABLED) { + mod_gm_crypt_init(mod_gm_opt->crypt_key); + } else { + mod_gm_opt->transportmode = GM_ENCODE_ONLY; + } + + /* create client */ + if ( create_client( mod_gm_opt->server_list, &client ) != GM_OK ) { + printf( "cannot start client\n" ); + exit( EXIT_FAILURE ); + } + + /* send result message */ + signal(SIGALRM, alarm_sighandler); + rc = read_multi_stream(stdin); + /* if rc > 0, it contains the number of checks being submitted, + otherwise its an error code (-1 - WARNING, -2 - CRITICAL, -3 - UNKNOWN) */ + if (rc >= 0) { + gm_log( GM_LOG_INFO, "%d check_multi child check%s submitted\n", rc, (rc>1)?"s":"" ); + } else { + rc*=-1; + } + + gearman_client_free( &client ); + mod_gm_free_opt(mod_gm_opt); + exit( rc ); +} + + +/* parse command line arguments */ +int parse_arguments(int argc, char **argv) { + int i; + int verify; + int errors = 0; + mod_gm_opt = malloc(sizeof(mod_gm_opt_t)); + set_default_options(mod_gm_opt); + + /* special default: encryption disabled */ + mod_gm_opt->encryption = GM_DISABLED; + + for(i=1;ikeyfile != NULL && read_keyfile(mod_gm_opt) != GM_OK) { + errors++; + } + + if(errors > 0 || verify != GM_OK) { + return(GM_ERROR); + } + + return(GM_OK); +} + + +/* verify our option */ +int verify_options(mod_gm_opt_t *opt) { + + /* no server specified? then default to localhost */ + if(opt->server_num == 0) { + opt->server_list[opt->server_num] = strdup("localhost"); + opt->server_num++; + } + + /* host is mandatory */ + if(opt->host == NULL) { + printf("got no hostname, please use --host=...\n" ); + return(GM_ERROR); + } + + /* encryption without key? */ + if(opt->encryption == GM_ENABLED) { + if(opt->crypt_key == NULL && opt->keyfile == NULL) { + printf("no encryption key provided, please use --key=... or keyfile=... or disable encryption\n"); + return(GM_ERROR); + } + } + + if ( mod_gm_opt->result_queue == NULL ) + mod_gm_opt->result_queue = GM_DEFAULT_RESULT_QUEUE; + + return(GM_OK); +} + + +/* print usage */ +void print_usage() { + printf("usage:\n"); + printf("\n"); + printf("send_multi [ --debug= ]\n"); + printf(" [ --help|-h ]\n"); + printf("\n"); + printf(" [ --config= ]\n"); + printf("\n"); + printf(" [ --server= ]\n"); + printf(" default:localhost \n"); + printf("\n"); + printf(" [ --encryption= ]\n"); + printf(" default:no \n"); + printf(" [ --key= ]\n"); + printf(" [ --keyfile= ]\n"); + printf("\n"); + printf(" [ --host= ]\n"); + printf(" [ --result_queue= ]\n"); + printf(" default:result_queue \n"); + printf(" [ --timeout= ]\n"); + printf("\n"); + printf("For details see\n"); + printf("http://my-plugin.de/wiki/projects/check_multi/feed_passive\n"); + printf("\n"); + + exit( EXIT_SUCCESS ); +} + + +/* send message to job server */ +int send_result() { + char * buf; + char temp_buffer1[GM_BUFFERSIZE]; + char temp_buffer2[GM_BUFFERSIZE]; + + gm_log( GM_LOG_TRACE, "send_result()\n" ); + + if(mod_gm_opt->result_queue == NULL) { + printf( "got no result queue, please use --result_queue=...\n" ); + return(GM_ERROR); + } + + /* escape newline */ + buf = escape_newlines(mod_gm_opt->message); + free(mod_gm_opt->message); + mod_gm_opt->message = malloc(GM_BUFFERSIZE); + snprintf(mod_gm_opt->message, GM_BUFFERSIZE, "%s", buf); + free(buf); + + gm_log( GM_LOG_TRACE, "queue: %s\n", mod_gm_opt->result_queue ); + temp_buffer1[0]='\x0'; + snprintf( temp_buffer1, sizeof( temp_buffer1 )-1, "type=%s\nhost_name=%s\nstart_time=%i.%i\nfinish_time=%i.%i\nlatency=%i.%i\nreturn_code=%i\n", + mod_gm_opt->active == GM_ENABLED ? "active" : "passive", + mod_gm_opt->host, + (int)mod_gm_opt->starttime.tv_sec, + (int)mod_gm_opt->starttime.tv_usec, + (int)mod_gm_opt->finishtime.tv_sec, + (int)mod_gm_opt->finishtime.tv_usec, + (int)mod_gm_opt->latency.tv_sec, + (int)mod_gm_opt->latency.tv_usec, + mod_gm_opt->return_code + ); + + if(mod_gm_opt->service != NULL) { + temp_buffer2[0]='\x0'; + strncat(temp_buffer2, "service_description=", (sizeof(temp_buffer2)-1)); + strncat(temp_buffer2, mod_gm_opt->service, (sizeof(temp_buffer2)-1)); + strncat(temp_buffer2, "\n", (sizeof(temp_buffer2)-1)); + strncat(temp_buffer1, temp_buffer2, (sizeof(temp_buffer1)-1)); + } + + if(mod_gm_opt->message != NULL) { + temp_buffer2[0]='\x0'; + strncat(temp_buffer2, "output=", (sizeof(temp_buffer2)-1)); + strncat(temp_buffer2, mod_gm_opt->message, (sizeof(temp_buffer2)-1)); + strncat(temp_buffer2, "\n", (sizeof(temp_buffer2)-1)); + strncat(temp_buffer1, temp_buffer2, (sizeof(temp_buffer1)-1)); + } + strncat(temp_buffer1, "\n", (sizeof(temp_buffer1)-2)); + + gm_log( GM_LOG_TRACE, "data:\n%s\n", temp_buffer1); + + if(add_job_to_queue( &client, + mod_gm_opt->server_list, + mod_gm_opt->result_queue, + NULL, + temp_buffer1, + GM_JOB_PRIO_NORMAL, + GM_DEFAULT_JOB_RETRIES, + mod_gm_opt->transportmode + ) == GM_OK) { + gm_log( GM_LOG_TRACE, "send_result_back() finished successfully\n" ); + } + else { + gm_log( GM_LOG_TRACE, "send_result_back() finished unsuccessfully\n" ); + return(GM_ERROR); + } + return(GM_OK); +} + +/* called when check runs into timeout */ +void alarm_sighandler(int sig) { + gm_log( GM_LOG_TRACE, "alarm_sighandler(%i)\n", sig ); + + printf("got no input! Send plugin output to stdin.\n"); + + exit(EXIT_FAILURE); +} + + +/* print version */ +void print_version() { + printf("send_gearman: version %s running on libgearman %s\n", GM_VERSION, gearman_version()); + printf("\n"); + exit( STATE_UNKNOWN ); +} + +int read_multi_stream(FILE *stream) { + char buffer[GM_BUFFERSIZE+1]; + unsigned long buflen=0L; + unsigned long bytes_read=0L; + char *bufstart=NULL; + char *bufend=NULL; + int count=0; + + //char outbuf[GM_BUFFERSIZE+1]; + + do { + /* opening tag found? read from buffer start with maximum buffer len */ + if ((bufstart=(char *)memmem(buffer,buflen,"",strlen(""))) != NULL) { + + /* closing tag found? read after with rest of buffer len */ + if ((bufend=(char *)memmem(bufstart,buflen-(bufstart-buffer),"",strlen(""))) != NULL) { + + gm_log( GM_LOG_TRACE, "\tXML chunk %d found: buffer position %3d-%3d length %d bytes\n", count, bufstart-buffer, bufend-buffer, bufend-bufstart); + /* count valid chunks */ + count++; + + /* identify CHILD chunk */ + bufstart+=strlen(""); + + /* if valid check_multi chunk found, send the result*/ + if (read_child_check(bufstart,bufend)) { + if (send_result() == GM_ERROR) { + count--; + } + } else { + count--; + } + + /* shift input rest to buffer start, create space for subsequent reads */ + memmove(buffer,bufend+strlen(""),buflen-(bufend+strlen("")-buffer)); + buflen-=bufend+strlen("")-buffer; + + /* start tag found, but no closing tag , buffer too small? */ + } else { + buflen=0L; + gm_log( GM_LOG_ERROR, "Error: no closing tag within buffer, buffer size too small? discarding buffer, %ld bytes now\n", buflen); + return -1; + } + gm_log( GM_LOG_TRACE, "\tbuflen after XML chunk parsing:%ld\n", buflen); + + /* neither nor found, discard buffer */ + } else { + /* discard whole buffer but continue */ + buflen=0L; + gm_log( GM_LOG_TRACE, "Error: no starting tag within buffer - discarding buffer, buflen now %ld bytes\n", buflen); + } + + gm_log( GM_LOG_TRACE, "\ttrying to fill up buffer with %ld bytes from offset %ld\n", GM_BUFFERSIZE-buflen, buflen); + + /* read one block of data, or less bytes, if there is still data left */ + alarm(mod_gm_opt->timeout); + if ((bytes_read=fread(buffer+buflen, 1, GM_BUFFERSIZE-buflen, stream)) == 0L) { + + /* break if zero read was caused by an error */ + if (!feof(stream)) { + perror("fread"); + return -2; + } + } else { + + /* adjust block len */ + buflen+=bytes_read; + } + gm_log( GM_LOG_TRACE, "\tread %ld bytes, %ld bytes remaining in buffer\n", bytes_read, buflen); + } while (buflen > 0); + return count; +} + +int read_child_check(char *bufstart, char *bufend) { + char *attribute=NULL; + + /* child check number */ + if ((attribute=read_multi_attribute(bufstart,bufend,"no")) == NULL) { + return 0; + } else { + /* skip parent check */ + if (!strcmp(attribute,"0")) { + return 0; + } + gm_log( GM_LOG_TRACE, "child check: %d\n", atoi(attribute)); + } + + /* service description */ + if ((attribute=read_multi_attribute(bufstart,bufend,"name")) == NULL) { + return 0; + } else { + mod_gm_opt->service=strdup(attribute); + gm_log( GM_LOG_TRACE, "service_description: %s\n", mod_gm_opt->service); + } + + /* return code */ + if ((attribute=read_multi_attribute(bufstart,bufend,"rc")) == NULL) { + return 0; + } else { + mod_gm_opt->return_code=atoi(attribute); + gm_log( GM_LOG_TRACE, "mod_gm_opt->return_code: %d\n", mod_gm_opt->return_code); + } + + /* start time */ + if ((attribute=read_multi_attribute(bufstart,bufend,"starttime")) == NULL) { + return 0; + } else { + mod_gm_opt->starttime.tv_sec=atoi(strtok(attribute, ".")); + mod_gm_opt->starttime.tv_usec=atoi(strtok(NULL, ".")); + gm_log( GM_LOG_TRACE, "starttime: %d.%d\n", mod_gm_opt->starttime.tv_sec, mod_gm_opt->starttime.tv_usec); + } + + /* end time */ + if ((attribute=read_multi_attribute(bufstart,bufend,"endtime")) == NULL) { + return 0; + } else { + mod_gm_opt->finishtime.tv_sec=atoi(strtok(attribute, ".")); + mod_gm_opt->finishtime.tv_usec=atoi(strtok(NULL, ".")); + gm_log( GM_LOG_TRACE, "endtime: %d.%d\n", mod_gm_opt->finishtime.tv_sec, mod_gm_opt->finishtime.tv_usec); + } + + /* message */ + if ((attribute=read_multi_attribute(bufstart,bufend,"output")) == NULL) { + return 0; + } else { + mod_gm_opt->message=strdup(decode_xml(attribute)); + gm_log( GM_LOG_TRACE, "mod_gm_opt->message: %s\n", mod_gm_opt->message); + } + return 1; +} + +char *read_multi_attribute(char *bufstart, char *bufend, char *element) { + char start_element[GM_BUFFERSIZE], end_element[GM_BUFFERSIZE]; + sprintf(start_element, "<%s>", element); + sprintf(end_element, "", element); + + if ((bufstart=(char *)memmem(bufstart,bufend-bufstart,start_element,strlen(start_element))) == NULL) { + gm_log( GM_LOG_TRACE, "\tread_multi_attribute: start element \'%s\' not found\n", start_element); + return NULL; + } + bufstart+=strlen(start_element); + if ((bufend=(char *)memmem(bufstart,bufend-bufstart,end_element,strlen(end_element))) == NULL) { + gm_log( GM_LOG_TRACE, "\tread_multi_attribute: end element \'%s\' not found\n", end_element); + return NULL; + } + *bufend='\0'; + return bufstart; +} + +char *decode_xml(char *string) { + struct decode{ + char c; + char *enc_string; + } dtab[] = { + { '>', ">" }, + { '<', "<" }, + { '&', "&" }, + }; + int i; + char *found; + + /* foreach XML decode pair */ + for (i=0; i<(int)(sizeof(dtab)/sizeof(struct decode)); i++) { + /* while XML encoding strings found */ + while ((found=strstr(string, dtab[i].enc_string)) != NULL) { + /* replace string with character */ + *found=dtab[i].c; + /* shift rest of string after character */ + strcpy(found+1, found+strlen(dtab[i].enc_string)); + } + } + return string; +}