diff options
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | config/Makefile.am | 1 | ||||
-rw-r--r-- | config/gluster-commands.cfg | 13 | ||||
-rw-r--r-- | config/gluster-contacts.cfg | 15 | ||||
-rw-r--r-- | config/gluster-templates.cfg | 52 | ||||
-rw-r--r-- | plugins/Makefile.am | 1 | ||||
-rwxr-xr-x | plugins/notify_ovirt_engine_handler.py | 158 | ||||
-rw-r--r-- | tests/Makefile.am | 1 | ||||
-rw-r--r-- | tests/test_notify_ovirt_engine_handler.py | 92 |
9 files changed, 336 insertions, 18 deletions
@@ -21,6 +21,27 @@ Packaging The 'nagios-server-addons.spec' file demonstrates how to distribute this as an RPM package. +Sending Notifications to Ovirt Engine +===================================== + + Nagios can be configured to send notifications to Ovirt engine to notify important nagios events. + +Configure the 'Ovirt' Contact defined in /etc/nagios/gluster-gluster-contacts.cfg with actual +ovirt details. + +# Ovirt Engine Rest API URL + +_ovirt_rest_api #http://[ovirt-engine-address]:[port]/ovirt-engine/api +_ovirt_user # username@domain + +# Ovirt password has to be configured as a user variable USER3$ in file /etc/nagios/private/resource.cfg. + +$USER3$ = [password for the user defined in contact 'Ovirt'] + +User variable $USER3$ is used in commands to pass the password to the handler. If some other variable is +used to configure password than it has to be updated in the nagios commands 'notify-host-to-ovirt' and +'notify-service-to-ovirt' defined in file /etc/nagios/gluster/gluster-commands.cfg + Getting Help ============ diff --git a/config/Makefile.am b/config/Makefile.am index 7c3c8be..7e19136 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -4,6 +4,7 @@ glusternagiosconf_DATA = \ gluster-host-groups.cfg \ gluster-host-services.cfg \ gluster-templates.cfg \ + gluster-contacts.cfg \ $(NULL) glusternagiosdefaultconfdir = $(sysconfdir)/nagios/gluster/default diff --git a/config/gluster-commands.cfg b/config/gluster-commands.cfg index 5c335c7..ccf2998 100644 --- a/config/gluster-commands.cfg +++ b/config/gluster-commands.cfg @@ -31,5 +31,16 @@ define command { define command { command_name host_service_handler - command_line $USER1$/gluster_host_service_handler.py -s $SERVICESTATE$ -t $SERVICESTATETYPE$ -a $SERVICEATTEMPT$ -l $HOSTADDRESS$ -n $SERVICEDESC$ + command_line $USER1$/gluster_host_service_handler.py -s $SERVICESTATE$ -t $SERVICESTATETYPE$ -a $SERVICEATTEMPT$ -l $HOSTADDRESS$ -n "$SERVICEDESC$" +} + +define command { + command_name notify-host-to-ovirt + command_line $USER1$/gluster/notify_ovirt_engine_handler.py -c $HOSTGROUPNAME$ -H $HOSTNAME$ -g $_HOSTGLUSTER_ENTITY$ -t $HOSTSTATE$ -o $_CONTACTOVIRT_REST_API$ -u $_CONTACTOVIRT_USER$ -p $USER3$ +} + +define command { + command_name notify-service-to-ovirt + command_line $USER1$/gluster/notify_ovirt_engine_handler.py -c $HOSTGROUPNAME$ -H $HOSTNAME$ -g $_SERVICEGLUSTER_ENTITY$ -s "$SERVICEDESC$" -t $SERVICESTATE$ -o $_CONTACTOVIRT_REST_API$ -u $_CONTACTOVIRT_USER$ -p $USER3$ + } diff --git a/config/gluster-contacts.cfg b/config/gluster-contacts.cfg new file mode 100644 index 0000000..758a102 --- /dev/null +++ b/config/gluster-contacts.cfg @@ -0,0 +1,15 @@ +define contact { + contact_name ovirt + alias Ovirt Engine + email admin@ovirt.com + service_notification_period 24x7 + service_notification_options w,u,c,r + service_notification_commands notify-service-to-ovirt + host_notification_period 24x7 + host_notification_options d,u,r + host_notification_commands notify-host-to-ovirt + can_submit_commands 1 + _ovirt_rest_api http://ovirt.com:8080/ovirt-engine/api + _ovirt_user admin@internal +} + diff --git a/config/gluster-templates.cfg b/config/gluster-templates.cfg index 3ee66d4..ce7e307 100644 --- a/config/gluster-templates.cfg +++ b/config/gluster-templates.cfg @@ -1,32 +1,50 @@ +define host{ + name gluster-generic-host + use linux-server + register 0 + contacts +ovirt +} + define host { - name gluster-host - use linux-server - check_command check_remote_host - register 0 + name gluster-host + use gluster-generic-host + check_command check_remote_host + register 0 + _gluster_entity Host } define host { - name gluster-cluster - use linux-server - register 0 + name gluster-cluster + use gluster-generic-host + register 0 + _gluster_entity Cluster } define host{ - name gluster-volume - use linux-server - register 0 + name gluster-volume + use gluster-generic-host + register 0 + _gluster_entity Volume +} + +define service { + name gluster-service + use generic-service + register 0 + contacts +ovirt + _gluster_entity Service } define service { - name gluster-service-with-graph - use generic-service - action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$' class='tips' rel='/pnp4nagios/index.php/popup?host=$HOSTNAME$&srv=$SERVICEDESC$ - register 0 + name gluster-service-with-graph + use gluster-service + action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$' class='tips' rel='/pnp4nagios/index.php/popup?host=$HOSTNAME$&srv=$SERVICEDESC$ + register 0 } define service { - name gluster-service-without-graph - use generic-service - register 0 + name gluster-service-without-graph + use gluster-service + register 0 } diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 329af89..fa515d7 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1,6 +1,7 @@ dist_glusternagiosplugins_PYTHON = \ check_remote_host.py \ gluster_host_service_handler.py \ + notify_ovirt_engine_handler.py \ $(NULL) EXTRA_DIST = \ diff --git a/plugins/notify_ovirt_engine_handler.py b/plugins/notify_ovirt_engine_handler.py new file mode 100755 index 0000000..272666b --- /dev/null +++ b/plugins/notify_ovirt_engine_handler.py @@ -0,0 +1,158 @@ +#!/usr/bin/python +# +# notify_ovirt_engine_handler.py -- Event handler which notifies +# nagios events to Ovirt engine using external events Rest API in Ovirt +# +# Copyright (C) 2014 Red Hat Inc +# +# 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. +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA +# + +import argparse +import cpopen +import datetime +from glusternagios.utils import HostStatus +from glusternagios.utils import PluginStatus +import json +from subprocess import PIPE +import sys + + +COOKIES_FILE = "/tmp/cookies.txt" + + +class OvirtEventSeverity: + NORMAL = "NORMAL" + ALERT = "ALERT" + + +def postOvirtExternalEvent(serverUrl, username, password, bodyData, + certFile, cookie): + externalCommand = ["curl", "--request", "POST", "--header", + "Accept: application/json", "--header", + "Content-Type: application/xml", "--header", + "Prefer: persistent-auth"] + if certFile is not None: + externalCommand.extend(["--cacert", certFile]) + else: + externalCommand.append("--insecure") + externalCommand.extend(["--user", "%s:%s" % (username, password), + "--cookie", cookie, "--cookie-jar", + cookie, "--data", bodyData, + "%s/events" % (serverUrl)]) + process = cpopen.Popen(externalCommand, stdout=PIPE, stderr=PIPE) + output = process.communicate()[0] + return output + + +def composeEventMessage(glusterEntity, service, host, cluster, status): + messages = {'Host': "Host '%s' in cluster '%s' is %s" % + (host, cluster, status), + 'Volume': "Volume '%s' in cluster '%s' is %s" % + (host, cluster, status), + 'Brick': "Brick '%s' in host '%s' in cluster '%s' is %s" % + (service, host, cluster, status), + 'Cluster': "Cluster '%s' is %s" % (host, status), + 'Service': "%s in host '%s' in cluster '%s' is %s" % + (service, host, cluster, status), + } + + return messages.get(glusterEntity) + + +def processNagiosEvent(cluster, host, service, glusterEntity, status, + globalEventId, ovirtEngineUrl, + username, password, certFile): + severity = OvirtEventSeverity.NORMAL + + if status == PluginStatus.CRITICAL or status == HostStatus.DOWN: + severity = OvirtEventSeverity.ALERT + + description = composeEventMessage(glusterEntity, service, + host, cluster, status) + bodyData = "<event><origin>Nagios</origin><severity>%s</severity>" \ + "<description>%s</description>" \ + "<custom_id>%s</custom_id></event>" \ + % (severity, description, globalEventId) + return postOvirtExternalEvent(ovirtEngineUrl, username, password, + bodyData, certFile, COOKIES_FILE) + + +def handleNagiosEvent(args): + # Notifies Ovirt Engine about service/host state change + exitStatus = 0 + if args.eventId is None: + t = datetime.datetime.now() + args.eventId = int(t.strftime("%s")) + try: + response = processNagiosEvent(args.cluster, args.host, + args.service, args.glusterEntity, + args.status, args.eventId, + args.ovirtEngineUrl, args.username, + args.password, args.certFile) + responseData = json.loads(response) + if responseData.get("id") is None: + print "Failed to submit event %s to ovirt engine at %s" \ + % (args.eventId, args.ovirtEngineUrl) + exitStatus = -1 + else: + print "Nagios event %s posted to ovirt engine %s " \ + % (args.eventId, responseData['href']) + + except Exception as exp: + print (str(exp)) + exitStatus = -1 + + return exitStatus + + +# Method to parse the arguments +def createParser(): + parser = argparse.ArgumentParser(prog="notify_ovirt_engine_handler.py", + description="Notifies Nagios events to " + "ovirt engine through external events " + "REAT API") + parser.add_argument('-c', '--cluster', action='store', dest='cluster', + type=str, required=True, help='Cluster name') + parser.add_argument('-H', '--host', action='store', dest='host', + type=str, required=False, help='Host name') + parser.add_argument('-g', '--glusterEntity', action='store', + dest='glusterEntity', type=str, + required=True, help='Gluster entity') + parser.add_argument('-s', '--service', action='store', dest='service', + type=str, required=False, help='Service name') + parser.add_argument('-t', '--status', action='store', dest='status', + type=str, required=True, help='Status') + parser.add_argument('-e', '--eventId', action='store', dest='eventId', + type=str, required=False, + help='Global Nagios event ID') + parser.add_argument('-o', '--ovirtServer', action='store', + dest='ovirtEngineUrl', type=str, + required=True, help='Ovirt Engine Rest API URL') + parser.add_argument('-u', '--username', action='store', dest='username', + type=str, required=True, help='Ovirt user name') + parser.add_argument('-p', '--password', action='store', dest='password', + type=str, required=False, help='Ovirt password') + parser.add_argument('-C', '--cert_file', action='store', dest='certFile', + type=str, required=False, + help='CA certificate of the Ovirt Engine') + return parser + +# Main Method +if __name__ == "__main__": + parser = createParser() + arguments = parser.parse_args() + return_status = handleNagiosEvent(arguments) + sys.exit(return_status) diff --git a/tests/Makefile.am b/tests/Makefile.am index 1a5887a..e4543f7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,6 +20,7 @@ test_modules = \ test_check_remote_host.py \ + test_notify_ovirt_engine_handler.py \ $(NULL) dist_nagiosserveraddonstests_DATA = \ diff --git a/tests/test_notify_ovirt_engine_handler.py b/tests/test_notify_ovirt_engine_handler.py new file mode 100644 index 0000000..556bfd9 --- /dev/null +++ b/tests/test_notify_ovirt_engine_handler.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# Copyright 2014 Red Hat, Inc. +# +# 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. +# +# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA +# + +import cpopen +import mock +from plugins import notify_ovirt_engine_handler +import subprocess +from testrunner import PluginsTestCase as TestCaseBase + + +class TestOvirtNotificationHandler(TestCaseBase): + # Method to test the post_ovirt_external_event() method + @mock.patch('plugins.notify_ovirt_engine_handler.cpopen.Popen') + def testPostOvirtExternalEvent(self, mock_popen): + reference = cpopen.Popen('any command', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = "sample output" + err = "" + reference.communicate.return_value = (out, err) + response = notify_ovirt_engine_handler.postOvirtExternalEvent( + "http://ovirt.com", "test", "test", "test", + "test/test-cert", "test/cookie") + reference.communicate.assert_called_with() + mock_popen.assert_called_with( + ['curl', '--request', 'POST', '--header', + 'Accept: application/json', '--header', + 'Content-Type: application/xml', '--header', + 'Prefer: persistent-auth', '--cacert', 'test/test-cert', + '--user', 'test:test', '--cookie', 'test/cookie', '--cookie-jar', + 'test/cookie', '--data', 'test', 'http://ovirt.com/events'], + stderr=-1, stdout=-1) + self.assertEquals(out, response, "Invalid response") + + # Method to test the post_ovirt_external_event() method + @mock.patch('plugins.notify_ovirt_engine_handler.postOvirtExternalEvent') + def testProcessNagiosEvent(self, mock_postOvirtExternalEvent): + notify_ovirt_engine_handler.processNagiosEvent( + "test-cluster1", "node-1", "Test-Service", "Service", + "CRITICAL", "100", "http://ovirt.com", "test-user", + "test-pwd", "test/cert") + + mock_postOvirtExternalEvent.assert_called_with( + 'http://ovirt.com', 'test-user', 'test-pwd', + "<event><origin>Nagios</origin><severity>ALERT</severity>" + "<description>Test-Service in host 'node-1' in cluster" + " 'test-cluster1' is CRITICAL</description><custom_id>100" + "</custom_id></event>", 'test/cert', '/tmp/cookies.txt') + + def _verifyArguments(self, args, argsExpected): + self.assertEquals(args.cluster, argsExpected["cluster"]) + self.assertEquals(args.host, argsExpected["host"]) + self.assertEquals(args.glusterEntity, argsExpected["glusterEntity"]) + self.assertEquals(args.service, argsExpected["service"]) + self.assertEquals(args.status, argsExpected["status"]) + self.assertEquals(args.eventId, argsExpected["eventId"]) + self.assertEquals(args.ovirtEngineUrl, argsExpected["ovirtEngineUrl"]) + self.assertEquals(args.username, argsExpected["username"]) + self.assertEquals(args.password, argsExpected["password"]) + self.assertEquals(args.certFile, argsExpected["certFile"]) + + # Method to test the argument parsing + def testArgumentParser(self): + parser = notify_ovirt_engine_handler.createParser() + argsInput = {"cluster": "Test-Cluster", "host": "Test-Node", + "glusterEntity": "Service", "service": "Test-Service", + "status": "OK", "eventId": "123", + "ovirtEngineUrl": "http://test.com:8080", + "username": "test", "password": "test", + "certFile": "/test/certfile"} + args = parser.parse_args( + ['-c', argsInput["cluster"], '-H', argsInput["host"], + '-g', argsInput["glusterEntity"], '-s', argsInput["service"], + '-t', argsInput["status"], '-e', argsInput["eventId"], + '-o', argsInput["ovirtEngineUrl"], '-u', argsInput["username"], + '-p', argsInput["password"], '-C', argsInput["certFile"]]) + self._verifyArguments(args, argsInput) |