summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README21
-rw-r--r--config/Makefile.am1
-rw-r--r--config/gluster-commands.cfg13
-rw-r--r--config/gluster-contacts.cfg15
-rw-r--r--config/gluster-templates.cfg52
-rw-r--r--plugins/Makefile.am1
-rwxr-xr-xplugins/notify_ovirt_engine_handler.py158
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/test_notify_ovirt_engine_handler.py92
9 files changed, 336 insertions, 18 deletions
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 = "<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)