Kaynağa Gözat

Merge remote-tracking branch 'github/tokkee/sh/check_dbi'

Holger Weiss 13 yıl önce
ebeveyn
işleme
800a868713
6 değiştirilmiş dosya ile 941 ekleme ve 1 silme
  1. 1 0
      .gitignore
  2. 4 0
      REQUIREMENTS
  3. 13 0
      configure.in
  4. 3 1
      plugins/Makefile.am
  5. 817 0
      plugins/check_dbi.c
  6. 103 0
      plugins/t/check_dbi.t

+ 1 - 0
.gitignore

@@ -134,6 +134,7 @@ NP-VERSION-FILE
 /plugins/check_by_ssh
 /plugins/check_clamd
 /plugins/check_cluster
+/plugins/check_dbi
 /plugins/check_dig
 /plugins/check_disk
 /plugins/check_dns

+ 4 - 0
REQUIREMENTS

@@ -46,6 +46,10 @@ check_pqsql:
 	- Requires the PostgreSQL libraries available from
 	  http://www.postgresql.org/
 
+check_dbi:
+	- Requires the DBI libraries available from
+	  http://libdbi.sourceforge.net/
+
 check_radius:
 	- Requires the radiusclient-ng library available from:
 	  http://developer.berlios.de/projects/radiusclient-ng/

+ 13 - 0
configure.in

@@ -255,6 +255,19 @@ fi
 LIBS="$_SAVEDLIBS"
 CPPFLAGS="$_SAVEDCPPFLAGS"
 
+dnl Check for DBI libraries
+_SAVEDLIBS="$LIBS"
+AC_CHECK_LIB(dbi,dbi_initialize)
+if test "$ac_cv_lib_dbi_dbi_initialize" = "yes"; then
+  EXTRAS="$EXTRAS check_dbi"
+	DBILIBS="-ldbi"
+  AC_SUBST(DBILIBS)
+else
+  AC_MSG_WARN([Skipping dbi plugin])
+  AC_MSG_WARN([install DBI libs to compile this plugin (see REQUIREMENTS).])
+fi
+LIBS="$_SAVEDLIBS"
+
 dnl Check for radius libraries
 _SAVEDLIBS="$LIBS"
 AC_CHECK_LIB(radiusclient,rc_read_config)

+ 3 - 1
plugins/Makefile.am

@@ -37,7 +37,7 @@ check_tcp_programs = check_ftp check_imap check_nntp check_pop \
 EXTRA_PROGRAMS = check_mysql check_radius check_pgsql check_snmp check_hpjd \
 	check_swap check_fping check_ldap check_game check_dig \
 	check_nagios check_by_ssh check_dns check_nt check_ide_smart	\
-	check_procs check_mysql_query check_apt
+	check_procs check_mysql_query check_apt check_dbi
 
 EXTRA_DIST = t tests utils.c netutils.c sslutils.c popen.c utils.h netutils.h \
 	popen.h common.h runcmd.c runcmd.h
@@ -64,6 +64,7 @@ test-debug:
 
 check_apt_LDADD = $(BASEOBJS) runcmd.o
 check_cluster_LDADD = $(BASEOBJS)
+check_dbi_LDADD = $(NETLIBS) $(DBILIBS)
 check_dig_LDADD = $(NETLIBS) runcmd.o 
 check_disk_LDADD = $(BASEOBJS) popen.o
 check_dns_LDADD = $(NETLIBS) runcmd.o
@@ -109,6 +110,7 @@ urlize_LDADD = $(BASEOBJS) popen.o
 
 check_apt_DEPENDENCIES = check_apt.c $(BASEOBJS) runcmd.o $(DEPLIBS)
 check_cluster_DEPENDENCIES = check_cluster.c $(BASEOBJS) $(DEPLIBS)
+check_dbi_DEPENDENCIES = check_dbi.c $(NETOBJS) $(DEPLIBS)
 check_dig_DEPENDENCIES = check_dig.c $(NETOBJS) runcmd.o $(DEPLIBS)
 check_disk_DEPENDENCIES = check_disk.c $(BASEOBJS) popen.o $(DEPLIBS) 
 check_dns_DEPENDENCIES = check_dns.c $(NETOBJS) runcmd.o $(DEPLIBS)

+ 817 - 0
plugins/check_dbi.c

@@ -0,0 +1,817 @@
+/*****************************************************************************
+* 
+* Nagios check_dbi plugin
+* 
+* License: GPL
+* Copyright (c) 2011 Nagios Plugins Development Team
+* Author: Sebastian 'tokkee' Harl <sh@teamix.net>
+* 
+* Description:
+* 
+* This file contains the check_dbi plugin
+* 
+* Runs an arbitrary (SQL) command and checks the result.
+* 
+* 
+* 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 3 of the License, or
+* (at your option) any later version.
+* 
+* 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, see <http://www.gnu.org/licenses/>.
+* 
+* 
+*****************************************************************************/
+
+const char *progname = "check_dbi";
+const char *copyright = "2011";
+const char *email = "nagiosplug-devel@lists.sourceforge.net";
+
+#include "common.h"
+#include "utils.h"
+
+#include "netutils.h"
+
+#include "regex.h"
+
+/* required for NAN */
+#ifndef _ISOC99_SOURCE
+#define _ISOC99_SOURCE
+#endif
+
+#include <assert.h>
+#include <math.h>
+
+#include <dbi/dbi.h>
+
+#include <stdarg.h>
+
+typedef enum {
+	METRIC_CONN_TIME,
+	METRIC_SERVER_VERSION,
+	METRIC_QUERY_RESULT,
+	METRIC_QUERY_TIME,
+} np_dbi_metric_t;
+
+typedef enum {
+	TYPE_NUMERIC,
+	TYPE_STRING,
+} np_dbi_type_t;
+
+typedef struct {
+	char *key;
+	char *value;
+} driver_option_t;
+
+char *host = NULL;
+int verbose = 0;
+
+char *warning_range = NULL;
+char *critical_range = NULL;
+thresholds *dbi_thresholds = NULL;
+
+char *expect = NULL;
+
+regex_t expect_re;
+char *expect_re_str = NULL;
+int expect_re_cflags = 0;
+
+np_dbi_metric_t metric = METRIC_QUERY_RESULT;
+np_dbi_type_t type = TYPE_NUMERIC;
+
+char *np_dbi_driver = NULL;
+driver_option_t *np_dbi_options = NULL;
+int np_dbi_options_num = 0;
+char *np_dbi_database = NULL;
+char *np_dbi_query = NULL;
+
+int process_arguments (int, char **);
+int validate_arguments (void);
+void print_usage (void);
+void print_help (void);
+
+double timediff (struct timeval, struct timeval);
+
+void np_dbi_print_error (dbi_conn, char *, ...);
+
+int do_query (dbi_conn, const char **, double *, double *);
+
+int
+main (int argc, char **argv)
+{
+	int status = STATE_UNKNOWN;
+
+	dbi_driver driver;
+	dbi_conn conn;
+
+	unsigned int server_version;
+
+	struct timeval start_timeval, end_timeval;
+	double conn_time = 0.0;
+	double query_time = 0.0;
+
+	const char *query_val_str = NULL;
+	double query_val = 0.0;
+
+	int i;
+
+	setlocale (LC_ALL, "");
+	bindtextdomain (PACKAGE, LOCALEDIR);
+	textdomain (PACKAGE);
+
+	/* Parse extra opts if any */
+	argv = np_extra_opts (&argc, argv, progname);
+
+	if (process_arguments (argc, argv) == ERROR)
+		usage4 (_("Could not parse arguments"));
+
+	/* Set signal handling and alarm */
+	if (signal (SIGALRM, timeout_alarm_handler) == SIG_ERR) {
+		usage4 (_("Cannot catch SIGALRM"));
+	}
+	alarm (timeout_interval);
+
+	if (verbose > 2)
+		printf ("Initializing DBI\n");
+
+	if (dbi_initialize (NULL) < 0) {
+		printf ("UNKNOWN - failed to initialize DBI; possibly you don't have any drivers installed.\n");
+		return STATE_UNKNOWN;
+	}
+
+	if (verbose)
+		printf ("Opening DBI driver '%s'\n", np_dbi_driver);
+
+	driver = dbi_driver_open (np_dbi_driver);
+	if (! driver) {
+		printf ("UNKNOWN - failed to open DBI driver '%s'; possibly it's not installed.\n",
+				np_dbi_driver);
+
+		printf ("Known drivers:\n");
+		for (driver = dbi_driver_list (NULL); driver; driver = dbi_driver_list (driver)) {
+			printf (" - %s\n", dbi_driver_get_name (driver));
+		}
+		return STATE_UNKNOWN;
+	}
+
+	/* make a connection to the database */
+	gettimeofday (&start_timeval, NULL);
+
+	conn = dbi_conn_open (driver);
+	if (! conn) {
+		printf ("UNKNOWN - failed top open connection object.\n");
+		dbi_conn_close (conn);
+		return STATE_UNKNOWN;
+	}
+
+	for (i = 0; i < np_dbi_options_num; ++i) {
+		const char *opt;
+
+		if (verbose > 1)
+			printf ("Setting DBI driver option '%s' to '%s'\n",
+					np_dbi_options[i].key, np_dbi_options[i].value);
+
+		if (! dbi_conn_set_option (conn, np_dbi_options[i].key, np_dbi_options[i].value))
+			continue;
+		/* else: status != 0 */
+
+		np_dbi_print_error (conn, "UNKNOWN - failed to set option '%s' to '%s'",
+				np_dbi_options[i].key, np_dbi_options[i].value);
+		printf ("Known driver options:\n");
+
+		for (opt = dbi_conn_get_option_list (conn, NULL); opt;
+				opt = dbi_conn_get_option_list (conn, opt)) {
+			printf (" - %s\n", opt);
+		}
+		dbi_conn_close (conn);
+		return STATE_UNKNOWN;
+	}
+
+	if (host) {
+		if (verbose > 1)
+			printf ("Setting DBI driver option 'host' to '%s'\n", host);
+		dbi_conn_set_option (conn, "host", host);
+	}
+
+	if (verbose) {
+		const char *dbname, *host;
+
+		dbname = dbi_conn_get_option (conn, "dbname");
+		host = dbi_conn_get_option (conn, "host");
+
+		if (! dbname)
+			dbname = "<unspecified>";
+		if (! host)
+			host = "<unspecified>";
+
+		printf ("Connecting to database '%s' at host '%s'\n",
+				dbname, host);
+	}
+
+	if (dbi_conn_connect (conn) < 0) {
+		np_dbi_print_error (conn, "UNKOWN - failed to connect to database");
+		return STATE_UNKNOWN;
+	}
+
+	gettimeofday (&end_timeval, NULL);
+	conn_time = timediff (start_timeval, end_timeval);
+
+	server_version = dbi_conn_get_engine_version (conn);
+	if (verbose)
+		printf ("Connected to server version %u\n", server_version);
+
+	if (metric == METRIC_SERVER_VERSION)
+		status = get_status (server_version, dbi_thresholds);
+
+	if (verbose)
+		printf ("Time elapsed: %f\n", conn_time);
+
+	if (metric == METRIC_CONN_TIME)
+		status = get_status (conn_time, dbi_thresholds);
+
+	/* select a database */
+	if (np_dbi_database) {
+		if (verbose > 1)
+			printf ("Selecting database '%s'\n", np_dbi_database);
+
+		if (dbi_conn_select_db (conn, np_dbi_database)) {
+			np_dbi_print_error (conn, "UNKOWN - failed to select database '%s'",
+					np_dbi_database);
+			return STATE_UNKNOWN;
+		}
+	}
+
+	if (np_dbi_query) {
+		/* execute query */
+		status = do_query (conn, &query_val_str, &query_val, &query_time);
+		if (status != STATE_OK)
+			/* do_query prints an error message in this case */
+			return status;
+
+		if (metric == METRIC_QUERY_RESULT) {
+			if (expect) {
+				if ((! query_val_str) || strcmp (query_val_str, expect))
+					status = STATE_CRITICAL;
+				else
+					status = STATE_OK;
+			}
+			else if (expect_re_str) {
+				int err;
+
+				err = regexec (&expect_re, query_val_str, 0, NULL, /* flags = */ 0);
+				if (! err)
+					status = STATE_OK;
+				else if (err == REG_NOMATCH)
+					status = STATE_CRITICAL;
+				else {
+					char errmsg[1024];
+					regerror (err, &expect_re, errmsg, sizeof (errmsg));
+					printf ("ERROR - failed to execute regular expression: %s\n",
+							errmsg);
+					status = STATE_CRITICAL;
+				}
+			}
+			else
+				status = get_status (query_val, dbi_thresholds);
+		}
+		else if (metric == METRIC_QUERY_TIME)
+			status = get_status (query_time, dbi_thresholds);
+	}
+
+	if (verbose)
+		printf("Closing connection\n");
+	dbi_conn_close (conn);
+
+	/* In case of METRIC_QUERY_RESULT, isnan(query_val) indicates an error
+	 * which should have been reported and handled (abort) before
+	 * ... unless we expected a string to be returned */
+	assert ((metric != METRIC_QUERY_RESULT) || (! isnan (query_val))
+			|| (type == TYPE_STRING));
+
+	assert ((type != TYPE_STRING) || (expect || expect_re_str));
+
+	printf ("%s - connection time: %fs", state_text (status), conn_time);
+	if (np_dbi_query) {
+		if (type == TYPE_STRING) {
+			assert (expect || expect_re_str);
+			printf (", '%s' returned '%s' in %fs", np_dbi_query,
+					query_val_str ? query_val_str : "<nothing>", query_time);
+			if (status != STATE_OK) {
+				if (expect)
+					printf (" (expected '%s')", expect);
+				else if (expect_re_str)
+					printf (" (expected regex /%s/%s)", expect_re_str,
+							((expect_re_cflags & REG_ICASE) ? "i" : ""));
+			}
+		}
+		else if (isnan (query_val))
+			printf (", '%s' query execution time: %fs", np_dbi_query, query_time);
+		else
+			printf (", '%s' returned %f in %fs", np_dbi_query, query_val, query_time);
+	}
+
+	printf (" | conntime=%fs;%s;%s;0; server_version=%u;%s;%s;0;", conn_time,
+			((metric == METRIC_CONN_TIME) && warning_range) ? warning_range : "",
+			((metric == METRIC_CONN_TIME) && critical_range) ? critical_range : "",
+			server_version,
+			((metric == METRIC_SERVER_VERSION) && warning_range) ? warning_range : "",
+			((metric == METRIC_SERVER_VERSION) && critical_range) ? critical_range : "");
+	if (np_dbi_query) {
+		if (! isnan (query_val)) /* this is also true when -e is used */
+			printf (" query=%f;%s;%s;;", query_val,
+					((metric == METRIC_QUERY_RESULT) && warning_range) ? warning_range : "",
+					((metric == METRIC_QUERY_RESULT) && critical_range) ? critical_range : "");
+		printf (" querytime=%fs;%s;%s;0;", query_time,
+				((metric == METRIC_QUERY_TIME) && warning_range) ? warning_range : "",
+				((metric == METRIC_QUERY_TIME) && critical_range) ? critical_range : "");
+	}
+	printf ("\n");
+	return status;
+}
+
+/* process command-line arguments */
+int
+process_arguments (int argc, char **argv)
+{
+	int c;
+
+	int option = 0;
+	static struct option longopts[] = {
+		STD_LONG_OPTS,
+
+		{"expect", required_argument, 0, 'e'},
+		{"regex", required_argument, 0, 'r'},
+		{"regexi", required_argument, 0, 'R'},
+		{"metric", required_argument, 0, 'm'},
+		{"driver", required_argument, 0, 'd'},
+		{"option", required_argument, 0, 'o'},
+		{"query", required_argument, 0, 'q'},
+		{"database", required_argument, 0, 'D'},
+		{0, 0, 0, 0}
+	};
+
+	while (1) {
+		c = getopt_long (argc, argv, "Vvht:c:w:e:r:R:m:H:d:o:q:D:",
+				longopts, &option);
+
+		if (c == EOF)
+			break;
+
+		switch (c) {
+		case '?':     /* usage */
+			usage5 ();
+		case 'h':     /* help */
+			print_help ();
+			exit (STATE_OK);
+		case 'V':     /* version */
+			print_revision (progname, NP_VERSION);
+			exit (STATE_OK);
+
+		case 'c':     /* critical range */
+			critical_range = optarg;
+			type = TYPE_NUMERIC;
+			break;
+		case 'w':     /* warning range */
+			warning_range = optarg;
+			type = TYPE_NUMERIC;
+			break;
+		case 'e':
+			expect = optarg;
+			type = TYPE_STRING;
+			break;
+		case 'R':
+			expect_re_cflags = REG_ICASE;
+			/* fall through */
+		case 'r':
+			{
+				int err;
+
+				expect_re_cflags |= REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
+				expect_re_str = optarg;
+				type = TYPE_STRING;
+
+				err = regcomp (&expect_re, expect_re_str, expect_re_cflags);
+				if (err) {
+					char errmsg[1024];
+					regerror (err, &expect_re, errmsg, sizeof (errmsg));
+					printf ("ERROR - failed to compile regular expression: %s\n",
+							errmsg);
+					return ERROR;
+				}
+				break;
+			}
+
+		case 'm':
+			if (! strcasecmp (optarg, "CONN_TIME"))
+				metric = METRIC_CONN_TIME;
+			else if (! strcasecmp (optarg, "SERVER_VERSION"))
+				metric = METRIC_SERVER_VERSION;
+			else if (! strcasecmp (optarg, "QUERY_RESULT"))
+				metric = METRIC_QUERY_RESULT;
+			else if (! strcasecmp (optarg, "QUERY_TIME"))
+				metric = METRIC_QUERY_TIME;
+			else
+				usage2 (_("Invalid metric"), optarg);
+			break;
+		case 't':     /* timeout */
+			if (!is_intnonneg (optarg))
+				usage2 (_("Timeout interval must be a positive integer"), optarg);
+			else
+				timeout_interval = atoi (optarg);
+
+		case 'H':     /* host */
+			if (!is_host (optarg))
+				usage2 (_("Invalid hostname/address"), optarg);
+			else
+				host = optarg;
+			break;
+		case 'v':
+			verbose++;
+			break;
+
+		case 'd':
+			np_dbi_driver = optarg;
+			break;
+		case 'o':
+			{
+				driver_option_t *new;
+
+				char *k, *v;
+
+				k = optarg;
+				v = strchr (k, (int)'=');
+
+				if (! v)
+					usage2 (_("Option must be '<key>=<value>'"), optarg);
+
+				*v = '\0';
+				++v;
+
+				new = realloc (np_dbi_options,
+						(np_dbi_options_num + 1) * sizeof (*new));
+				if (! new) {
+					printf ("UNKOWN - failed to reallocate memory\n");
+					exit (STATE_UNKNOWN);
+				}
+
+				np_dbi_options = new;
+				new = np_dbi_options + np_dbi_options_num;
+				++np_dbi_options_num;
+
+				new->key = k;
+				new->value = v;
+			}
+			break;
+		case 'q':
+			np_dbi_query = optarg;
+			break;
+		case 'D':
+			np_dbi_database = optarg;
+			break;
+		}
+	}
+
+	set_thresholds (&dbi_thresholds, warning_range, critical_range);
+
+	return validate_arguments ();
+}
+
+int
+validate_arguments ()
+{
+	if (! np_dbi_driver)
+		usage ("Must specify a DBI driver");
+
+	if (((metric == METRIC_QUERY_RESULT) || (metric == METRIC_QUERY_TIME))
+			&& (! np_dbi_query))
+		usage ("Must specify a query to execute (metric == QUERY_RESULT)");
+
+	if ((metric != METRIC_CONN_TIME)
+			&& (metric != METRIC_SERVER_VERSION)
+			&& (metric != METRIC_QUERY_RESULT)
+			&& (metric != METRIC_QUERY_TIME))
+		usage ("Invalid metric specified");
+
+	if (expect && (warning_range || critical_range || expect_re_str))
+		usage ("Do not mix -e and -w/-c/-r/-R");
+
+	if (expect_re_str && (warning_range || critical_range || expect))
+		usage ("Do not mix -r/-R and -w/-c/-e");
+
+	if (expect && (metric != METRIC_QUERY_RESULT))
+		usage ("Option -e requires metric QUERY_RESULT");
+
+	if (expect_re_str && (metric != METRIC_QUERY_RESULT))
+		usage ("Options -r/-R require metric QUERY_RESULT");
+
+	return OK;
+}
+
+void
+print_help (void)
+{
+	print_revision (progname, NP_VERSION);
+
+	printf (COPYRIGHT, copyright, email);
+
+	printf (_("This program connects to an (SQL) database using DBI and checks the\n"
+			"specified metric against threshold levels. The default metric is\n"
+			"the result of the specified query.\n"));
+
+	printf ("\n\n");
+
+	print_usage ();
+
+	printf (UT_HELP_VRSN);
+/* include this conditionally to avoid 'zero-length printf format string'
+ * compiler warnings */
+#ifdef NP_EXTRA_OPTS
+	printf (UT_EXTRA_OPTS);
+#endif
+	printf ("\n");
+
+	printf (" %s\n", "-d, --driver=STRING");
+	printf ("    %s\n", _("DBI driver to use"));
+	printf (" %s\n", "-o, --option=STRING");
+	printf ("    %s\n", _("DBI driver options"));
+	printf (" %s\n", "-q, --query=STRING");
+	printf ("    %s\n", _("query to execute"));
+	printf ("\n");
+
+	printf (UT_WARN_CRIT_RANGE);
+	printf (" %s\n", "-e, --expect=STRING");
+	printf ("    %s\n", _("String to expect as query result"));
+	printf ("    %s\n", _("Do not mix with -w, -c, -r, or -R!"));
+	printf (" %s\n", "-r, --regex=REGEX");
+	printf ("    %s\n", _("Extended POSIX regular expression to check query result against"));
+	printf ("    %s\n", _("Do not mix with -w, -c, -e, or -R!"));
+	printf (" %s\n", "-R, --regexi=REGEX");
+	printf ("    %s\n", _("Case-insensitive extended POSIX regex to check query result against"));
+	printf ("    %s\n", _("Do not mix with -w, -c, -e, or -r!"));
+	printf (" %s\n", "-m, --metric=METRIC");
+	printf ("    %s\n", _("Metric to check thresholds against. Available metrics:"));
+	printf ("    CONN_TIME    - %s\n", _("time used for setting up the database connection"));
+	printf ("    QUERY_RESULT - %s\n", _("result (first column of first row) of the query"));
+	printf ("    QUERY_TIME   - %s\n", _("time used to execute the query"));
+	printf ("                   %s\n", _("(ignore the query result)"));
+	printf ("\n");
+
+	printf (UT_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
+
+	printf (UT_VERBOSE);
+
+	printf ("\n");
+	printf (" %s\n", _("A DBI driver (-d option) is required. If the specified metric operates"));
+	printf (" %s\n\n", _("on a query, one has to be specified (-q option)."));
+
+	printf (" %s\n", _("This plugin connects to an (SQL) database using libdbi and, optionally,"));
+	printf (" %s\n", _("executes the specified query. The first column of the first row of the"));
+	printf (" %s\n", _("result will be parsed and, in QUERY_RESULT mode, compared with the"));
+	printf (" %s\n", _("warning and critical ranges. The result from the query has to be numeric"));
+	printf (" %s\n\n", _("(strings representing numbers are fine)."));
+
+	printf (" %s\n", _("The number and type of required DBI driver options depends on the actual"));
+	printf (" %s\n", _("driver. See its documentation at http://libdbi-drivers.sourceforge.net/"));
+	printf (" %s\n\n", _("for details."));
+
+	printf (" %s\n", _("Examples:"));
+	printf ("  check_dbi -d pgsql -o username=postgres -m QUERY_RESULT \\\n");
+	printf ("    -q 'SELECT COUNT(*) FROM pg_stat_activity' -w 5 -c 10\n");
+	printf ("  Warning if more than five connections; critical if more than ten.\n\n");
+
+	printf ("  check_dbi -d mysql -H localhost -o username=user -o password=secret \\\n");
+	printf ("    -q 'SELECT COUNT(*) FROM logged_in_users -w 5:20 -c 0:50\n");
+	printf ("  Warning if less than 5 or more than 20 users are logged in; critical\n");
+	printf ("  if more than 50 users.\n\n");
+
+	printf ("  check_dbi -d firebird -o username=user -o password=secret -o dbname=foo \\\n");
+	printf ("    -m CONN_TIME -w 0.5 -c 2\n");
+	printf ("  Warning if connecting to the database takes more than half of a second;\n");
+	printf ("  critical if it takes more than 2 seconds.\n\n");
+
+	printf ("  check_dbi -d mysql -H localhost -o username=user \\\n");
+	printf ("    -q 'SELECT concat(@@version, \" \", @@version_comment)' \\\n");
+	printf ("    -r '^5\\.[01].*MySQL Enterprise Server'\n");
+	printf ("  Critical if the database server is not a MySQL enterprise server in either\n");
+	printf ("  version 5.0.x or 5.1.x.\n\n");
+
+	printf ("  check_dbi -d pgsql -u username=user -m SERVER_VERSION \\\n");
+	printf ("    -w 090000:090099 -c 090000:090199\n");
+	printf ("  Warn if the PostgreSQL server version is not 9.0.x; critical if the version\n");
+	printf ("  is less than 9.x or higher than 9.1.x.\n");
+
+	printf (UT_SUPPORT);
+}
+
+void
+print_usage (void)
+{
+	printf ("%s\n", _("Usage:"));
+	printf ("%s -d <DBI driver> [-o <DBI driver option> [...]] [-q <query>]\n", progname);
+	printf (" [-H <host>] [-c <critical range>] [-w <warning range>] [-m <metric>]\n");
+	printf (" [-e <string>] [-r|-R <regex>]\n");
+}
+
+#define CHECK_IGNORE_ERROR(s) \
+	do { \
+		if (metric != METRIC_QUERY_RESULT) \
+			return (s); \
+	} while (0)
+
+const char *
+get_field_str (dbi_conn conn, dbi_result res, unsigned short field_type)
+{
+	const char *str;
+
+	if (field_type != DBI_TYPE_STRING) {
+		printf ("CRITICAL - result value is not a string\n");
+		return NULL;
+	}
+
+	str = dbi_result_get_string_idx (res, 1);
+	if ((! str) || (strcmp (str, "ERROR") == 0)) {
+		CHECK_IGNORE_ERROR (NULL);
+		np_dbi_print_error (conn, "CRITICAL - failed to fetch string value");
+		return NULL;
+	}
+
+	if ((verbose && (type == TYPE_STRING)) || (verbose > 2))
+		printf ("Query returned string '%s'\n", str);
+	return str;
+}
+
+double
+get_field (dbi_conn conn, dbi_result res, unsigned short *field_type)
+{
+	double val = NAN;
+
+	if (*field_type == DBI_TYPE_INTEGER) {
+		val = (double)dbi_result_get_longlong_idx (res, 1);
+	}
+	else if (*field_type == DBI_TYPE_DECIMAL) {
+		val = dbi_result_get_double_idx (res, 1);
+	}
+	else if (*field_type == DBI_TYPE_STRING) {
+		const char *val_str;
+		char *endptr = NULL;
+
+		val_str = get_field_str (conn, res, *field_type);
+		if (! val_str) {
+			CHECK_IGNORE_ERROR (NAN);
+			*field_type = DBI_TYPE_ERROR;
+			return NAN;
+		}
+
+		val = strtod (val_str, &endptr);
+		if (endptr == val_str) {
+			CHECK_IGNORE_ERROR (NAN);
+			printf ("CRITICAL - result value is not a numeric: %s\n", val_str);
+			*field_type = DBI_TYPE_ERROR;
+			return NAN;
+		}
+		else if ((endptr != NULL) && (*endptr != '\0')) {
+			if (verbose)
+				printf ("Garbage after value: %s\n", endptr);
+		}
+	}
+	else {
+		CHECK_IGNORE_ERROR (NAN);
+		printf ("CRITICAL - cannot parse value of type %s (%i)\n",
+				(*field_type == DBI_TYPE_BINARY)
+					? "BINARY"
+					: (*field_type == DBI_TYPE_DATETIME)
+						? "DATETIME"
+						: "<unknown>",
+				*field_type);
+		*field_type = DBI_TYPE_ERROR;
+		return NAN;
+	}
+	return val;
+}
+
+double
+get_query_result (dbi_conn conn, dbi_result res, const char **res_val_str, double *res_val)
+{
+	unsigned short field_type;
+	double val = NAN;
+
+	if (dbi_result_get_numrows (res) == DBI_ROW_ERROR) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		np_dbi_print_error (conn, "CRITICAL - failed to fetch rows");
+		return STATE_CRITICAL;
+	}
+
+	if (dbi_result_get_numrows (res) < 1) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		printf ("WARNING - no rows returned\n");
+		return STATE_WARNING;
+	}
+
+	if (dbi_result_get_numfields (res) == DBI_FIELD_ERROR) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		np_dbi_print_error (conn, "CRITICAL - failed to fetch fields");
+		return STATE_CRITICAL;
+	}
+
+	if (dbi_result_get_numfields (res) < 1) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		printf ("WARNING - no fields returned\n");
+		return STATE_WARNING;
+	}
+
+	if (dbi_result_first_row (res) != 1) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		np_dbi_print_error (conn, "CRITICAL - failed to fetch first row");
+		return STATE_CRITICAL;
+	}
+
+	field_type = dbi_result_get_field_type_idx (res, 1);
+	if (field_type != DBI_TYPE_ERROR) {
+		if (type == TYPE_STRING)
+			/* the value will be freed in dbi_result_free */
+			*res_val_str = strdup (get_field_str (conn, res, field_type));
+		else
+			val = get_field (conn, res, &field_type);
+	}
+
+	*res_val = val;
+
+	if (field_type == DBI_TYPE_ERROR) {
+		CHECK_IGNORE_ERROR (STATE_OK);
+		np_dbi_print_error (conn, "CRITICAL - failed to fetch data");
+		return STATE_CRITICAL;
+	}
+
+	dbi_result_free (res);
+	return STATE_OK;
+}
+
+#undef CHECK_IGNORE_ERROR
+
+int
+do_query (dbi_conn conn, const char **res_val_str, double *res_val, double *res_time)
+{
+	dbi_result res;
+
+	struct timeval timeval_start, timeval_end;
+	int status = STATE_OK;
+
+	assert (np_dbi_query);
+
+	if (verbose)
+		printf ("Executing query '%s'\n", np_dbi_query);
+
+	gettimeofday (&timeval_start, NULL);
+
+	res = dbi_conn_query (conn, np_dbi_query);
+	if (! res) {
+		np_dbi_print_error (conn, "CRITICAL - failed to execute query '%s'", np_dbi_query);
+		return STATE_CRITICAL;
+	}
+
+	status = get_query_result (conn, res, res_val_str, res_val);
+
+	gettimeofday (&timeval_end, NULL);
+	*res_time = timediff (timeval_start, timeval_end);
+
+	if (verbose)
+		printf ("Time elapsed: %f\n", *res_time);
+
+	return status;
+}
+
+double
+timediff (struct timeval start, struct timeval end)
+{
+	double diff;
+
+	while (start.tv_usec > end.tv_usec) {
+		--end.tv_sec;
+		end.tv_usec += 1000000;
+	}
+	diff = (double)(end.tv_sec - start.tv_sec)
+		+ (double)(end.tv_usec - start.tv_usec) / 1000000.0;
+	return diff;
+}
+
+void
+np_dbi_print_error (dbi_conn conn, char *fmt, ...)
+{
+	const char *errmsg = NULL;
+	va_list ap;
+
+	va_start (ap, fmt);
+
+	dbi_conn_error (conn, &errmsg);
+	vprintf (fmt, ap);
+	printf (": %s\n", errmsg);
+
+	va_end (ap);
+}
+

+ 103 - 0
plugins/t/check_dbi.t

@@ -0,0 +1,103 @@
+#! /usr/bin/perl -w -I ..
+#
+# Database Server Tests via check_dbi
+#
+#
+# Uses the 'sqlite3' DBD driver and command line utility.
+
+use strict;
+use Test::More;
+use NPTest;
+
+use File::Temp;
+
+use vars qw($tests);
+
+plan skip_all => "check_dbi not compiled" unless (-x "check_dbi");
+
+$tests = 20;
+plan tests => $tests;
+
+my $missing_driver_output = "failed to open DBI driver 'sqlite3'";
+
+my $bad_driver_output    = "/failed to open DBI driver 'nodriver'/";
+my $conn_time_output     = "/OK - connection time: [0-9\.]+s \|/";
+my $missing_query_output = "/Must specify a query to execute/";
+my $no_rows_output       = "/WARNING - no rows returned/";
+my $not_numeric_output   = "/CRITICAL - result value is not a numeric:/";
+my $query_time_output    = "/OK - connection time: [0-9\.]+s, 'SELECT 1' returned 1.000000 in [0-9\.]+s \|/";
+my $syntax_error_output  = "/CRITICAL - failed to execute query 'GET ALL FROM test': 1: near \"GET\": syntax error/";
+
+my $result;
+
+SKIP: {
+	my $sqlite3 = qx(which sqlite3 2> /dev/null);
+	chomp($sqlite3);
+
+	skip "No Sqlite3 found", $tests unless $sqlite3;
+
+	my $sqlite3_check = qx(./check_dbi -d sqlite3 -q '');
+	if ($sqlite3_check =~ m/$missing_driver_output/) {
+		skip "No 'sqlite3' DBD driver found", $tests;
+	}
+
+	my $fh = File::Temp->new(
+		TEMPLATE => "/tmp/check_dbi_sqlite3.XXXXXXX",
+		UNLINK   => 1,
+	);
+	my $filename = $fh->filename;
+	$filename =~ s/^\/tmp\///;
+
+	system("$sqlite3 /tmp/$filename 'CREATE TABLE test(a INT, b TEXT)'");
+	system("$sqlite3 /tmp/$filename 'INSERT INTO test VALUES (1, \"text1\")'");
+	system("$sqlite3 /tmp/$filename 'INSERT INTO test VALUES (2, \"text2\")'");
+
+	my $check_cmd = "./check_dbi -d sqlite3 -o sqlite3_dbdir=/tmp -o dbname=$filename";
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT 1'");
+	cmp_ok($result->return_code, '==', 0, "Sqlite3 login okay and can run query");
+
+	$result = NPTest->testCmd("$check_cmd");
+	cmp_ok($result->return_code, '==', 3, "Missing query parameter");
+	like($result->output, $missing_query_output, "Missing query parameter error message");
+
+	$result = NPTest->testCmd("$check_cmd -q 'GET ALL FROM test'");
+	cmp_ok($result->return_code, '==', 2, "Invalid query");
+	like($result->output, $syntax_error_output, "Syntax error message");
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT 2.71828' -w 2 -c 3");
+	cmp_ok($result->return_code, '==', 1, "Got warning");
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT 3.1415' -w 2 -c 3");
+	cmp_ok($result->return_code, '==', 2, "Got critical");
+
+	$result = NPTest->testCmd("$check_cmd -q ''");
+	cmp_ok($result->return_code, '==', 1, "No rows returned");
+	like($result->output, $no_rows_output, "Now rows returned warning message");
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT b FROM test'");
+	cmp_ok($result->return_code, '==', 2, "Value is not a numeric");
+	like($result->output, $not_numeric_output, "Value is not a numeric error message");
+
+	$result = NPTest->testCmd("$check_cmd -m QUERY_RESULT -q 'SELECT b FROM test' -e text1");
+	cmp_ok($result->return_code, '==', 0, "Query result string comparison okay");
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT b FROM test' -r 'eXt[0-9]'");
+	cmp_ok($result->return_code, '==', 2, "Query result case-insensitive regex failure");
+
+	$result = NPTest->testCmd("$check_cmd -q 'SELECT b FROM test' -R 'eXt[0-9]'");
+	cmp_ok($result->return_code, '==', 0, "Query result case-sensitive regex okay");
+
+	$result = NPTest->testCmd("$check_cmd -m CONN_TIME -w 0.5 -c 0.7");
+	cmp_ok($result->return_code, '==', 0, "CONN_TIME metric okay");
+	like($result->output, $conn_time_output, "CONN_TIME metric output okay");
+
+	$result = NPTest->testCmd("$check_cmd -m QUERY_TIME -q 'SELECT 1'");
+	cmp_ok($result->return_code, '==', 0, "QUERY_TIME metric okay");
+	like($result->output, $query_time_output, "QUERY_TIME metric output okay");
+
+	$result = NPTest->testCmd("./check_dbi -d nodriver -q ''");
+	cmp_ok($result->return_code, '==', 3, "Unknown DBI driver");
+	like($result->output, $bad_driver_output, "Correct error message");
+}
+