diff options
-rw-r--r-- | config/Makefile.am | 1 | ||||
-rw-r--r-- | config/gluster-commands.cfg | 5 | ||||
-rw-r--r-- | config/gluster-host.cfg.template | 34 | ||||
-rw-r--r-- | configure.ac | 3 | ||||
-rw-r--r-- | nagios-server-addons.spec.in | 2 | ||||
-rw-r--r-- | plugins/Makefile.am | 2 | ||||
-rw-r--r-- | plugins/config_generator.py | 186 | ||||
-rw-r--r-- | plugins/constants.py.in | 5 | ||||
-rwxr-xr-x | plugins/discovery.py | 138 | ||||
-rw-r--r-- | tests/Makefile.am | 1 | ||||
-rw-r--r-- | tests/test_config_generator.py | 74 |
11 files changed, 451 insertions, 0 deletions
diff --git a/config/Makefile.am b/config/Makefile.am index 7ee5a11..0f7f73a 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -11,6 +11,7 @@ glusternagiosconf_DATA = \ glusternagiosdefaultconfdir = $(sysconfdir)/nagios/gluster/default glusternagiosdefaultconf_DATA = \ glustercluster.cfg.sample \ + gluster-host.cfg.template \ node1.cfg \ $(NULL) diff --git a/config/gluster-commands.cfg b/config/gluster-commands.cfg index c2d70a7..41aa99b 100644 --- a/config/gluster-commands.cfg +++ b/config/gluster-commands.cfg @@ -62,6 +62,11 @@ define command{ } define command{ + command_name gluster_auto_discovery + command_line $USER1$/gluster/discovery.py -H $ARG1$ -c $ARG2$ +} + +define command{ command_name check_dummy command_line $USER1$/check_dummy 0 } diff --git a/config/gluster-host.cfg.template b/config/gluster-host.cfg.template new file mode 100644 index 0000000..bd1b2af --- /dev/null +++ b/config/gluster-host.cfg.template @@ -0,0 +1,34 @@ +###@GENERATED@### +# AUTO-GENERATED FILE. DO NOT MODIFY. +# +# This confinguration file was automatically generated by the +# Gluster Auto Discovery tool. It should not be modified by hand. +# + +{% if host['use'] == 'gluster-cluster' -%} +define hostgroup{ +hostgroup_name {{host['host_name']}} +alias {{host['alias']}} +} +{%- endif %} + +define host{ +use {{host['use']}} +host_name {{host['host_name']}} +alias {{host['alias']}} +address {{host['address']}} +{% if host['check_command'] != "" -%} +check_command {{host['check_command']}} +{% endif -%} +{% if host['hostgroups'] != "" -%} +hostgroups {{host['hostgroups']}} +{% endif -%} +} + +{% for service in host['host_services'] %} +define service{ +{%- for key, value in service.iteritems() %} +{{ key.ljust(20) }} {{ value }} +{%- endfor %} +} +{% endfor %} diff --git a/configure.ac b/configure.ac index e9067e1..93254a4 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,9 @@ AC_SUBST([nagiosserveraddonstestsdir], ['${datarootdir}/${PACKAGE_NAME}/tests']) AC_SUBST([nagioslivestatussocketpath], ['/var/spool/nagios/cmd/live']) AC_SUBST([nagioscommandfilepath], ['/var/spool/nagios/cmd/nagios.cmd']) AC_SUBST([hostmonitoringserviceslist], ['/etc/nagios/gluster/host-monitoring-services.in']) +AC_SUBST([nrpepath], ['/usr/lib64/nagios/plugins/check_nrpe']) +AC_SUBST([glusterautoconfdir], ['/etc/nagios/gluster']) +AC_SUBST([glusterhostconfigtemplatedir], ['/etc/nagios/gluster/default']) # Checking for pyflakes AC_PATH_PROG([PYFLAKES], [pyflakes]) diff --git a/nagios-server-addons.spec.in b/nagios-server-addons.spec.in index b8d6c44..0fe22a1 100644 --- a/nagios-server-addons.spec.in +++ b/nagios-server-addons.spec.in @@ -47,6 +47,7 @@ BuildRequires: python-pep8 BuildRequires: python-mock BuildRequires: python-nose BuildRequires: python-devel +BuildRequires: python-jinja2 %if ( 0%{?_with_systemd:1} ) BuildRequires: systemd-units Requires(post): systemd-units @@ -74,6 +75,7 @@ Requires: python-pthreading Requires: python-inotify Requires: libselinux-python Requires: rrdtool-perl +Requires: python-jinja2 %description Nagios plugin, scripts, configuration files etc for gluster nodes. diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 3787e9a..5606fb3 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -6,6 +6,8 @@ dist_glusternagiosplugins_PYTHON = \ gluster_host_service_handler.py \ livestatus.py \ notify_ovirt_engine_handler.py \ + discovery.py \ + config_generator.py \ $(NULL) EXTRA_DIST = \ diff --git a/plugins/config_generator.py b/plugins/config_generator.py new file mode 100644 index 0000000..041ee8b --- /dev/null +++ b/plugins/config_generator.py @@ -0,0 +1,186 @@ +#!/usr/bin/python +# +# config_generator.py - Nagios configuration generator for gluster +# entities. 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 +# + +from jinja2 import Environment, FileSystemLoader +import os +import shutil + + +class GlusterNagiosConfManager: + + def __init__(self, configDir, configTemplateDir, hostTemplateName): + self.configDir = configDir + self.configTemplateDir = configTemplateDir + self.hostTemplateName = hostTemplateName + self.__loadJinja() + + def __loadJinja(self): + self.jinjaEnv = Environment( + loader=FileSystemLoader(self.configTemplateDir)) + self.hostTemplate = self.jinjaEnv.get_template(self.hostTemplateName) + + def __createHostConfig(self, host): + hostConfigStr = self.hostTemplate.render(host=host) + hostConfig = {'name': host['host_name'], 'config': hostConfigStr} + return hostConfig + + def createHost(self, hostName, alias, template, + address, hostGroups, checkCommand, services): + host = {} + host['host_name'] = hostName + host['alias'] = alias + host['use'] = template + host['address'] = address + if checkCommand is not None: + host['check_command'] = checkCommand + if hostGroups is not None: + host['hostgroups'] = hostGroups + + if services is not None: + host['host_services'] = services + return host + + def __createVolumeUtilizationService(self, volume, clusterName): + volumeService = {} + volumeService['host_name'] = clusterName + volumeService['use'] = 'gluster-service-with-graph' + serviceDesc = 'Volume Utilization - %s' % (volume['name']) + volumeService['service_description'] = serviceDesc + volumeService['_VOL_NAME'] = volume['name'] + checkCommand = 'check_vol_utilization!%s!%s!70!90' % \ + (clusterName, volume['name']) + volumeService['check_command'] = checkCommand + volumeService['notes'] = "Volume type : %s" % (volume['typeStr']) + return volumeService + + def __createVolumeStatusService(self, volume, clusterName): + volumeService = {} + volumeService['host_name'] = clusterName + volumeService['use'] = 'gluster-service-with-graph' + serviceDesc = 'Volume Status - %s' % (volume['name']) + volumeService['service_description'] = serviceDesc + volumeService['_VOL_NAME'] = volume['name'] + checkCommand = 'check_vol_utilization!%s!%s!70!90' % \ + (clusterName, volume['name']) + volumeService['check_command'] = checkCommand + volumeService['notes'] = "Volume type : %s" % (volume['typeStr']) + return volumeService + + def createClusterUtilizationService(self, clusterName): + service = {} + service['host_name'] = clusterName + service['use'] = 'gluster-service-with-graph' + service['service_description'] = 'Cluster Utilization' + service['check_command'] = 'check_cluster_vol_usage!80!90' + return service + + def createClusterAutoConfigService(self, clusterName, hostIp): + service = {} + service['host_name'] = clusterName + service['use'] = 'generic-service' + service['service_description'] = 'Cluster Auto Config' + service['check_command'] = "gluster_auto_discovery!%s!%s" % ( + hostIp, clusterName) + service['check_interval'] = '1440' + return service + + def createrVolumeServices(self, volumes, clusterName): + volumeServices = [] + for volume in volumes: + volumeService = self.__createVolumeUtilizationService(volume, + clusterName) + volumeServices.append(volumeService) + return volumeServices + + def __createBrickUtilizationService(self, brick, hostName): + brickService = {} + brickService['use'] = 'brick-service' + brickService['host_name'] = hostName + serviceDesc = "Brick-%s:%s" % (hostName, brick['brickpath']) + brickService['service_description'] = serviceDesc + brickService['display_name'] = serviceDesc + brickService['_BRICK_DIR'] = brick['brickpath'] + return brickService + + def __createBrickStatusService(self, brick, hostName): + brickService = {} + brickService['use'] = 'brick-service' + brickService['host_name'] = hostName + serviceDesc = "Brick-%s:%s-Status" % (hostName, brick['brickpath']) + brickService['service_description'] = serviceDesc + brickService['display_name'] = serviceDesc + brickService['_BRICK_DIR'] = brick['brickpath'] + return brickService + + def createBrickServices(self, host): + brickServices = [] + for brick in host['bricks']: + brickService = self.__createBrickUtilizationService( + brick, host['hostip']) + brickServices.append(brickService) + return brickServices + + def generateNagiosConfigFromGlusterCluster(self, cluster): + hostsConfigs = [] + clusterServices = self.createrVolumeServices( + cluster.get('volumes'), cluster['name']) + clusterServices.append(self.createClusterUtilizationService( + cluster['name'])) + clusterServices.append(self.createClusterAutoConfigService( + cluster['name'], cluster['hosts'][0]['hostip'])) + clusterHostConfig = self.createHost( + cluster['name'], cluster['name'], "gluster-cluster", + cluster['name'], "", "check_dummy", clusterServices) + hostsConfigs.append(clusterHostConfig) + for host in cluster['hosts']: + brickServices = self.createBrickServices(host) + hostGroups = "gluster_hosts,%s" % (cluster['name']) + hostConfig = self.createHost( + host['hostip'], host['hostip'], "gluster-host", + host['hostip'], hostGroups, "", brickServices) + hostsConfigs.append(hostConfig) + self.generateConfigFiles(hostsConfigs) + return hostsConfigs + + def generateConfigFiles(self, hosts): + clusterConfig = {'name': None, 'hostConfigs': []} + clusterConfigDir = None + for host in hosts: + if host['use'] == 'gluster-cluster': + clusterConfigDir = self.configDir + "/" + host['host_name'] + self.__prepareConfDir(clusterConfigDir) + clusterConfig['name'] = host['host_name'] + hostConfig = self.__createHostConfig(host) + clusterConfig['hostConfigs'].append(hostConfig) + for hostConfig in clusterConfig['hostConfigs']: + self.__writeHostConfig(clusterConfigDir, hostConfig) + + def __prepareConfDir(self, confDir): + if os.path.exists(confDir): + # Deleting the config dir to write new configs + shutil.rmtree(confDir) + os.mkdir(confDir) + + def __writeHostConfig(self, clusterConfigDir, hostConfig): + if clusterConfigDir is None: + raise Exception("Cluster configuration directory can't None") + configFilePath = clusterConfigDir + "/" + hostConfig['name'] + ".cfg" + with open(configFilePath, 'w') as configFile: + configFile.write(hostConfig['config']) diff --git a/plugins/constants.py.in b/plugins/constants.py.in index 40dd304..bf649de 100644 --- a/plugins/constants.py.in +++ b/plugins/constants.py.in @@ -1 +1,6 @@ LIVESTATUS_SOCKETPATH = "@nagioslivestatussocketpath@" +DEFAULT_AUTO_CONFIG_DIR = "@glusterautoconfdir@" +HOST_TEMPLATE_DIR = "@glusterhostconfigtemplatedir@" +HOST_TEMPLATE_NAME = "gluster-host.cfg.template" +NRPE_PATH = "@nrpepath@" +NAGIOS_COMMAND_FILE_PATH = "@nagioscommandfilepath@" diff --git a/plugins/discovery.py b/plugins/discovery.py new file mode 100755 index 0000000..3fd5573 --- /dev/null +++ b/plugins/discovery.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# discovery.py Nagios plugin to discover Gluster entities using NRPE +# 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 commands +import json +import datetime +import re +from config_generator import GlusterNagiosConfManager + +#from glusternagios import utils +from constants import DEFAULT_AUTO_CONFIG_DIR +from constants import HOST_TEMPLATE_DIR +from constants import HOST_TEMPLATE_NAME +from constants import NRPE_PATH +from constants import NAGIOS_COMMAND_FILE_PATH + + +def excecNRPECommand(command): + """ + This function executes NRPE command and return the result + """ + status = commands.getoutput(command) + return status + + +def discoverhostdetails(host, args): + hostparamsdict = {} + command = NRPE_PATH + " -H " + host + " -c discoverhostparams" + hostparams = excecNRPECommand(command) + #convert to dictionary + try: + hostparamsdict = json.loads(hostparams) + except Exception, e: + e.args += (hostparams,) + raise + return hostparamsdict + + +def discoverlogicalcomponents(host): + componentlist = [] + command = NRPE_PATH + " -H " + host + " -c discoverlogicalcomponents" + components = excecNRPECommand(command) + try: + componentlist = json.loads(components) + except Exception, e: + e.args += (components,) + #print e.args + raise + return componentlist + + +def discovercluster(args): + """ + + :rtype : None + """ + ipPat = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") + clusterdata = {} + #Discover the logical components + componentlist = discoverlogicalcomponents(args.hostip) + #Discover the peers + command = NRPE_PATH + " -H " + args.hostip + " -c discoverpeers" + hosts = excecNRPECommand(command) + hostlist = json.loads(hosts) + + #Add the ip address of the root node to the peer list + #to generate the configuration + hostlist.append({"hostip": args.hostip}) + for host in hostlist: + if(ipPat.match(host['hostip'])): + host.update(discoverhostdetails(host['hostip'], args)) + #Get the list of bricks for this host and add to dictionary + host['bricks'] = \ + [brick for brick in componentlist + if brick["hostip"] == host['hostip']] + clusterdata['hosts'] = hostlist + clusterdata['volumes'] =\ + [volume for volume in componentlist + if volume["srvctype"] == "volume"] + clusterdata['name'] = args.cluster + return clusterdata + + +def parse_input(): + parser = argparse.ArgumentParser(description="Gluster Auto Discover Tool") + parser.add_argument('-c', '--cluster', action='store', dest='cluster', + type=str, required=True, help='Cluster name') + parser.add_argument('-H', '--hostip', action='store', dest='hostip', + type=str, required=True, help='Host IP') + parser.add_argument('-d', '--configdir', action='store', dest='configDir', + type=str, required=False, + help='Configuration directory ' + 'where output files will be written') + args = parser.parse_args() + return args + + +def getConfigManager(args): + configDir = DEFAULT_AUTO_CONFIG_DIR + if args.configDir is not None: + configDir = args.configDir + configManager = GlusterNagiosConfManager( + configDir, HOST_TEMPLATE_DIR, HOST_TEMPLATE_NAME) + return configManager + + +def __restartNagios(): + now = datetime.datetime.now() + cmdStr = "[%s] RESTART_PROGRAM\n" % (now) + with open(NAGIOS_COMMAND_FILE_PATH, "w") as f: + f.write(cmdStr) + + +if __name__ == '__main__': + args = parse_input() + clusterdata = discovercluster(args) + configManager = getConfigManager(args) + clusterConfing = configManager.generateNagiosConfigFromGlusterCluster( + clusterdata) + print " Cluster configurations re-synced successfully from host %s" % \ + (args.hostip) + __restartNagios() diff --git a/tests/Makefile.am b/tests/Makefile.am index 40a09a3..9e860b7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,6 +22,7 @@ test_modules = \ test_check_cluster_volusage.py \ test_check_remote_host.py \ test_notify_ovirt_engine_handler.py \ + test_config_generator.py \ $(NULL) dist_nagiosserveraddonstests_DATA = \ diff --git a/tests/test_config_generator.py b/tests/test_config_generator.py new file mode 100644 index 0000000..21500a9 --- /dev/null +++ b/tests/test_config_generator.py @@ -0,0 +1,74 @@ +#!/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 mock +from plugins import config_generator +from testrunner import PluginsTestCase as TestCaseBase + + +class TestGlusterNagiosConfManager(TestCaseBase): + # Method to test the generateNagiosConfigFromGlusterCluster() method + @mock.patch('plugins.config_generator.GlusterNagiosConfManager.' + 'generateConfigFiles') + def testGenerateConfigFiles(self, mock_generateConfigFiles): + confManager = self.__getGlusterNagiosConfManager() + clusterData = self.__createDummyCluster() + clusterConfig = confManager.generateNagiosConfigFromGlusterCluster( + clusterData) + mock_generateConfigFiles.assert_called() + self.__verifyConfig(clusterConfig, clusterData) + + def __verifyConfig(self, clusterConfig, clusterData): + self.assertTrue(len(clusterConfig), len(clusterData['hosts']) + 1) + self.__verifyClusterConfig(clusterConfig[0], clusterData) + for index in range(0, len(clusterData['hosts'])): + self.__verifyHostConfig(clusterConfig[index + 1], + clusterData['hosts'][index]) + + def __verifyHostConfig(self, hostConfig, hostData): + self.assertEqual(hostConfig['host_name'], hostData['hostip']) + self.assertEqual(hostConfig['alias'], hostData['hostip']) + self.assertEqual(hostConfig['address'], hostData['hostip']) + self.assertEqual(hostConfig['use'], 'gluster-host') + + def __verifyClusterConfig(self, config, clusterData): + self.assertEqual(config['host_name'], clusterData['name']) + self.assertEqual(config['alias'], clusterData['name']) + self.assertEqual(config['address'], clusterData['name']) + self.assertEqual(config['check_command'], "check_dummy") + self.assertEqual(config['use'], 'gluster-cluster') + + def createBricks(self, count): + bricks = [] + for number in range(count): + brickDir = "/mnt/Brick-%s" % (number + 1) + bricks.append({'brickpath': brickDir}) + return bricks + + def __createDummyCluster(self): + cluster = {'name': 'Test-Cluster', 'hosts': [], 'volumes': []} + cluster['hosts'].append({'hostip': '10.70.43.1', + 'bricks': self.createBricks(1)}) + cluster['hosts'].append({'hostip': '10.70.43.2', + 'bricks': self.createBricks(2)}) + cluster['volumes'].append({'name': 'Volume1', "typeStr": "T"}) + return cluster + + def __getGlusterNagiosConfManager(self): + return config_generator.GlusterNagiosConfManager( + "/tmp/nagios/gluster", "../config", "gluster-host.cfg.template") |