From 998e1f9ef43f8fd6e2a7a3722bfd4f3f734c450d Mon Sep 17 00:00:00 2001 From: Ramesh Nachimuthu Date: Thu, 13 Mar 2014 19:26:00 +0530 Subject: Notification: Notification handler for ovirt notification Added a new notification handler to notify nagios events to ovirt. A new contact ovirt is added with the handler which can sent events/alerts to ovirt engine using Rest API. By default all gluster related configuration entities like host, volume, gluster, bricks will have this contact. By default contact ovirt will have some default values for ovirt engie rest api and user fields. User has to fill the right values in ovirt contact defined in gluster-contacts.cfg file. Ovirt password has to be configured as a user variable $USER3$ in file /etc/nagios/private/resource.cfg. User variable $USER3$ used in command to pass the password to the handler. Change-Id: I19074af6ae1ee4a8c16d8821b10a1c3a345f321a Signed-off-by: Ramesh Nachimuthu Reviewed-on: https://cuckoo.blr.redhat.com:8443/8 Reviewed-by: Timothy Asir Reviewed-by: Sahina Bose --- README | 21 ++++ config/Makefile.am | 1 + config/gluster-commands.cfg | 13 ++- config/gluster-contacts.cfg | 15 +++ config/gluster-templates.cfg | 52 ++++++---- plugins/Makefile.am | 1 + plugins/notify_ovirt_engine_handler.py | 158 ++++++++++++++++++++++++++++++ tests/Makefile.am | 1 + tests/test_notify_ovirt_engine_handler.py | 92 +++++++++++++++++ 9 files changed, 336 insertions(+), 18 deletions(-) create mode 100644 config/gluster-contacts.cfg create mode 100755 plugins/notify_ovirt_engine_handler.py create mode 100644 tests/test_notify_ovirt_engine_handler.py diff --git a/README b/README index a14ea45..d27ae4b 100644 --- a/README +++ b/README @@ -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 = "Nagios%s" \ + "%s" \ + "%s" \ + % (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', + "NagiosALERT" + "Test-Service in host 'node-1' in cluster" + " 'test-cluster1' is CRITICAL100" + "", '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) -- cgit