diff options
| -rw-r--r-- | config/Makefile.am | 1 | ||||
| -rw-r--r-- | config/gluster-commands.cfg | 2 | ||||
| -rw-r--r-- | config/gluster-host.cfg.template | 34 | ||||
| -rw-r--r-- | nagios-server-addons.spec.in | 4 | ||||
| -rw-r--r-- | plugins/Makefile.am | 1 | ||||
| -rw-r--r-- | plugins/config_generator.py | 176 | ||||
| -rwxr-xr-x | plugins/discovery.py | 291 | ||||
| -rwxr-xr-x | plugins/server_utils.py | 78 | ||||
| -rw-r--r-- | tests/Makefile.am | 2 | ||||
| -rw-r--r-- | tests/test_config_generator.py | 82 | 
10 files changed, 531 insertions, 140 deletions
diff --git a/config/Makefile.am b/config/Makefile.am index 60da34c..3176aa7 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -12,7 +12,6 @@ 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 57b76ce..8c5594f 100644 --- a/config/gluster-commands.cfg +++ b/config/gluster-commands.cfg @@ -73,7 +73,7 @@ define command{  define command{          command_name    gluster_auto_discovery -        command_line    $USER1$/gluster/discovery.py -H $ARG1$ -c $HOSTNAME$ +        command_line    sudo $USER1$/gluster/discovery.py -H $ARG1$ -c $HOSTNAME$ -m auto  }  define command{ diff --git a/config/gluster-host.cfg.template b/config/gluster-host.cfg.template deleted file mode 100644 index 0b5173d..0000000 --- a/config/gluster-host.cfg.template +++ /dev/null @@ -1,34 +0,0 @@ -###@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'] is defined -%} -check_command   {{host['check_command']}} -{% endif -%} -{% if host['hostgroups'] is defined -%} -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/nagios-server-addons.spec.in b/nagios-server-addons.spec.in index 68c6e8d..997c07f 100644 --- a/nagios-server-addons.spec.in +++ b/nagios-server-addons.spec.in @@ -50,10 +50,10 @@ BuildRequires:    python-netaddr  BuildRequires:    libselinux-python  BuildRequires:    python-nose  BuildRequires:    python-devel -BuildRequires:    python-jinja2  BuildRequires:    python-argparse  BuildRequires:    python-ethtool  BuildRequires:    gluster-nagios-common +BuildRequires:    pynag  %if ( 0%{?_with_systemd:1} )  BuildRequires:    systemd-units  Requires(post):   systemd-units @@ -82,7 +82,7 @@ Requires:         python-pthreading  Requires:         python-inotify  Requires:         libselinux-python  Requires:         rrdtool-perl -Requires:         python-jinja2 +Requires:         pynag  Requires:         net-snmp-utils  %description diff --git a/plugins/Makefile.am b/plugins/Makefile.am index a6cc905..5400d5c 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -12,6 +12,7 @@ dist_glusternagiosplugins_PYTHON = \  	config_generator.py \  	servicesnmptrapgenerator.py \  	submit_external_command.py \ +	server_utils.py \  	$(NULL)  EXTRA_DIST = \ diff --git a/plugins/config_generator.py b/plugins/config_generator.py index d383b0b..f76279f 100644 --- a/plugins/config_generator.py +++ b/plugins/config_generator.py @@ -18,29 +18,28 @@  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA  # -from jinja2 import Environment, FileSystemLoader -import os -import shutil +from pynag import Model +import server_utils -class GlusterNagiosConfManager: +""" +Change mode helps to identify the change in the defintion. +"ADD" means the entity and all its sub entities are added. +"REMOVE" means the entity and all its sub entities are removed +"UPDATE" means the entity is changes. It may also means sub entities +are added or removed to the entity. +""" +CHANGE_MODE_ADD = "ADD" +CHANGE_MODE_REMOVE = "REMOVE" +CHANGE_MODE_UPDATE = "UPDATE" -    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) +class GlusterNagiosConfManager: -    def __createHostConfig(self, host): -        hostConfigStr = self.hostTemplate.render(host=host) -        hostConfig = {'name': host['host_name'], 'config': hostConfigStr} -        return hostConfig +    def __init__(self, configDir): +        self.configDir = configDir +    #Create nagios host configuration with the given attributes      def createHost(self, hostName, alias, template,                     address, hostGroups, checkCommand, services):          host = {} @@ -52,7 +51,8 @@ class GlusterNagiosConfManager:              host['check_command'] = checkCommand          if hostGroups:              host['hostgroups'] = hostGroups - +        #Host service is not a field in host configuration. It helps to +        #aggregate all the host services under the host          if services:              host['host_services'] = services          return host @@ -119,12 +119,13 @@ class GlusterNagiosConfManager:      def createClusterAutoConfigService(self, clusterName, hostIp):          service = {}          service['host_name'] = clusterName -        service['use'] = 'generic-service' +        service['use'] = 'gluster-service' +        service['check_interval'] = '1440'          service['service_description'] = 'Cluster Auto Config'          service['check_command'] = "gluster_auto_discovery!%s" % (hostIp) -        service['check_interval'] = '1440'          return service +    #Create all volume related services for the given volume      def createrVolumeServices(self, volumes, clusterName):          volumeServices = []          for volume in volumes: @@ -168,6 +169,7 @@ class GlusterNagiosConfManager:          brickService['notes'] = "Volume : %s" % (brick['volumeName'])          return brickService +    #Create all Brick related service here.      def createBrickServices(self, host):          brickServices = []          for brick in host['bricks']: @@ -179,7 +181,20 @@ class GlusterNagiosConfManager:              brickServices.append(brickService)          return brickServices -    def generateNagiosConfigFromGlusterCluster(self, cluster): +    #Create a host group with the name +    def createHostGroup(self, name): +        return {'hostgroup_name': name, 'alias': name} + +    #Create the Nagios configuration model in run time using list and +    #dictionary +    #Nagios config model hierarchy +    ######################################################################### +    #Hostgroup +    #  --'_host' ---> List of host configurations in the host group +    #    --'host_services' ----> List of services in the host +    ######################################################################### +    def generateNagiosConfig(self, cluster): +        hostGroup = self.createHostGroup(cluster['name'])          hostsConfigs = []          clusterServices = self.createrVolumeServices(              cluster.get('volumes'), cluster['name']) @@ -189,42 +204,107 @@ class GlusterNagiosConfManager:                  cluster['name']))          clusterServices.append(self.createClusterAutoConfigService(              cluster['name'], cluster['hosts'][0]['hostip'])) +        #Create host config for Gluster cluster with volume related services          clusterHostConfig = self.createHost(              cluster['name'], cluster['name'], "gluster-cluster",              cluster['name'], None, None, clusterServices)          hostsConfigs.append(clusterHostConfig) +        #Create host config for all hosts in the cluster with brick related +        #services          for host in cluster['hosts']:              brickServices = self.createBrickServices(host)              hostGroups = "gluster_hosts,%s" % (cluster['name'])              hostConfig = self.createHost(                  host['hostname'], host['hostname'], "gluster-host", -                host['hostip'], hostGroups, "", brickServices) +                host['hostip'], hostGroups, None, brickServices)              hostsConfigs.append(hostConfig) -        self.generateConfigFiles(hostsConfigs) -        return hostsConfigs -    def generateConfigFiles(self, hosts): -        clusterConfig = {'name': None, 'hostConfigs': []} -        clusterConfigDir = None +        hostGroup["_hosts"] = hostsConfigs +        return hostGroup + +    #Get the config file name for the given hostname +    def getCfgFileName(self, hostname): +        return self.configDir + "/" + hostname + ".cfg" + +    #Create Nagios config service for the given host group with all hosts. +    #Host group should contain the delta to be written to the configuration. +    #Delta will be processed using the change mode. +    def writeHostGroup(self, hostgroup): +        changeMode = hostgroup['changeMode'] +        if changeMode == CHANGE_MODE_ADD: +            hostgroupModel = Model.Hostgroup() +            hostgroupModel['hostgroup_name'] = hostgroup['hostgroup_name'] +            hostgroupModel['alias'] = hostgroup['alias'] +            hostgroupModel.set_filename( +                self.getCfgFileName(hostgroup['hostgroup_name'])) +            hostgroupModel.save() +        #Process all the hosts in the hostgroup. ChangeMode of the hostgroup +        #will be used to proces the host if there is not changeMode specified +        #in the host. +        if hostgroup['_hosts']: +            self.writeHosts(hostgroup['_hosts'], changeMode) + +    #Fill the pynag model with the given values. +    #'changeMode' and 'host_services' are special fields which are +    #not meant to be writen to the nagios config, These fields are +    #used to represent the config model and changes. +    def fillModel(self, model, values): +        for key, value in values.iteritems(): +            if key not in ['changeMode', 'host_services']: +                model[key] = value +        return model + +    #Write service to nagios config +    def writeService(self, service, hostname): +        if service['changeMode'] == CHANGE_MODE_ADD: +            serviceModel = Model.Service() +            serviceModel = self.fillModel(serviceModel, service) +            serviceModel.set_filename(self.getCfgFileName(hostname)) +            serviceModel.save() +        elif service['changeMode'] == CHANGE_MODE_REMOVE: +            serviceModel = Model.Service.objects.filter( +                host_name=hostname, +                service_description=service['service_description']) +            if serviceModel: +                serviceModel[0].delete() +        elif service['changeMode'] == CHANGE_MODE_UPDATE: +            serviceModel = server_utils.getServiceConfig( +                service['service_description'], service['host_name']) +            self.fillModel(serviceModel, service) +            serviceModel.save() + +    #Write all services in the host. +    #host_services filed contains the list of services to be written to +    #nagios configuration +    def writeHostServices(self, host): +        for service in host['host_services']: +            if service.get('changeMode') is None: +                service['changeMode'] = host['changeMode'] +            self.writeService(service, host['host_name']) + +    #Write the host configuration with list of services to nagios configuration +    def writeHost(self, host): +        if host['changeMode'] == CHANGE_MODE_REMOVE: +            hostModel = Model.Host.objects.filter( +                host_name=host['host_name']) +            if hostModel: +                hostModel[0].delete(recursive=True) +            return +        if host['changeMode'] == CHANGE_MODE_ADD: +            hostModel = Model.Host() +            hostModel = self.fillModel(hostModel, host) +            hostModel.set_filename(self.getCfgFileName(host['host_name'])) +            hostModel.save() + +        if host['host_services']: +            self.writeHostServices(host) + +    def writeHosts(self, hosts, chageMode):          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 not clusterConfigDir: -            raise Exception("Cluster configuration directory can't None") -        configFilePath = clusterConfigDir + "/" + hostConfig['name'] + ".cfg" -        with open(configFilePath, 'w') as configFile: -            configFile.write(hostConfig['config']) +            if host.get('changeMode') is None: +                host['changeMode'] = chageMode +            self.writeHost(host) + +    #Write the hostgroup delta to nagios configuration. +    def generateConfigFiles(self, delta): +        self.writeHostGroup(delta) diff --git a/plugins/discovery.py b/plugins/discovery.py index b2cc21a..3ee102b 100755 --- a/plugins/discovery.py +++ b/plugins/discovery.py @@ -18,24 +18,27 @@  #  import argparse  import json -import datetime + +import os +import shutil  import sys  from glusternagios import utils  from config_generator import GlusterNagiosConfManager +import server_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 -import submit_external_command -serviceCmdPath = utils.CommandPath("service", "/sbin/service", ) +from config_generator import CHANGE_MODE_ADD +from config_generator import CHANGE_MODE_REMOVE +from config_generator import CHANGE_MODE_UPDATE + +  nrpeCmdPath = utils.CommandPath("nrpe", NRPE_PATH, )  def excecNRPECommand(host, command): -    output = {}      (returncode, outputStr, err) = utils.execCmd([nrpeCmdPath.cmd,                                                    "-H", host, "-c", command])      #convert to dictionary @@ -48,13 +51,36 @@ def excecNRPECommand(host, command):  def discoverCluster(hostip, cluster): +    """ +    This method helps to discover the nodes, volumes and bricks in the given +    gluster. It uses NRPE commands to contact the gluster nodes. + +    Assumptions: +    First node returned by the "discoverpeers" NRPE command should be the same +    node where "discoverpeers" is executed. + +    Parameters +    ---------- +    hostip: Address of a node in the gluster cluster. +    cluster: Cluster Name + +    Returns +    --------- +     Returns cluster details in the following dictinary format +    { +      'name': cluster-name, +     'volumes': [list-volumes], +    'host': [list-hosts] +    } +    each host in the list will a have list of bricks from the host. +    """ +      clusterdata = {}      #Discover the logical components      componentlist = excecNRPECommand(hostip, "discoverlogicalcomponents")      #Discover the peers      hostlist = excecNRPECommand(hostip, "discoverpeers") -    #Add the ip address of the root node to the peer list -    #to generate the configuration +    #Add the ip address of the root node given by the user to the peer list      hostlist[0]['hostip'] = hostip      for host in hostlist:          #Get host names @@ -70,16 +96,22 @@ def discoverCluster(hostip, cluster):      clusterdata['hosts'] = hostlist      clusterdata['volumes'] = componentlist['volumes']      clusterdata['name'] = cluster +    #Host names returned by "discoverhostparams" supposed to be unique. So host +    #name can be used to configure the host_name in nagios host. +    #But if host names are not unique then we have to use IP address to +    #configure host_name in nagios.      if not isHostsNamesUnique(clusterdata):          setHostNameWithIP(clusterdata)      return clusterdata +# Set host address as the hostname  def setHostNameWithIP(clusterdata):      for host in clusterdata['hosts']:          host['hostname'] = host['hostip'] +# Check host names are unique  def isHostsNamesUnique(clusterdata):      hostnames = {}      for host in clusterdata['hosts']: @@ -90,51 +122,244 @@ def isHostsNamesUnique(clusterdata):      return True +def getConfigManager(args): +    configDir = DEFAULT_AUTO_CONFIG_DIR +    if args.configDir is not None: +        configDir = args.configDir +    clusterConfigDir = configDir + "/" + args.cluster +    configManager = GlusterNagiosConfManager(clusterConfigDir) +    return configManager + + +#Find the given service in service list. +def findServiceInList(serviceList, serviceDescription): +    for service in serviceList: +        if service['service_description'] == serviceDescription: +            return service +    return None + + +#Find the given host in host list +def findHostInList(hostList, hostName): +    for host in hostList: +        if host['host_name'] == hostName: +            return host +    return None + + +#Find all deleted services in the host. +def findDeletedServices(host): +    deletedService = [] +    serviceConfigs = server_utils.getServiceConfigByHost(host['host_name']) +    for serviceConfig in serviceConfigs: +        service = findServiceInList(host['host_services'], +                                    serviceConfig['service_description']) +        if service is None: +            deletedService.append( +                {'service_description': serviceConfig['service_description'], +                 'changeMode': CHANGE_MODE_REMOVE}) +    return deletedService + + +#Check if auto config is changed. IP address in the check command will change +#when user runs the auto config using different host. +def findChangeInAutoConfig(newService, oldService): +    if newService['check_command'] != oldService['check_command']: +        changes = {} +        changes['check_command'] = newService['check_command'] +        changes['service_description'] = newService['service_description'] +        changes['host_name'] = newService['host_name'] +        changes['changeMode'] = CHANGE_MODE_UPDATE +        return changes +    return None + + +#Find all Added/Deleted services in the given host. +#Note: 'Cluster Auto Config' is a special service. When user runs the +#auto-config using different host instead what is used previously then we +#have to update the host ip in existing auto-config service. +def findServiceDelta(host): +    serviceDelta = [] +    for service in host['host_services']: +        serviceConfig = server_utils.getServiceConfig( +            service['service_description'], service['host_name']) +        if serviceConfig is None: +            service['changeMode'] = CHANGE_MODE_ADD +            serviceDelta.append(service) +        elif serviceConfig['service_description'] == "Cluster Auto Config": +            changes = findChangeInAutoConfig(service, serviceConfig) +            if changes: +                serviceDelta.append(changes) +    serviceDelta.extend(findDeletedServices(host)) +    return serviceDelta + + +#Find newly added hosts and newly added services to the existing hosts +def findAddUpdateHosts(hosts): +    delta = [] +    for host in hosts: +        hostConfing = server_utils.getHostConfigByName(host['host_name']) +        if hostConfing is None: +            host['changeMode'] = CHANGE_MODE_ADD +            delta.append(host) +        else: +            serviceDelta = findServiceDelta(host) +            if serviceDelta: +                host['changeMode'] = CHANGE_MODE_UPDATE +                host['host_services'] = serviceDelta +                delta.append(host) +    return delta + + +#Find deleted hosts in the given cluster. +def findDeletedHosts(hostgroup, hosts): +    deletedHosts = [] +    hostConfigs = server_utils.getHostConfigsForCluster(hostgroup) +    for hostConfig in hostConfigs: +        host = findHostInList(hosts, hostConfig['host_name']) +        if host is None: +            deletedHosts.append({'host_name': hostConfig['host_name'], +                                 'changeMode': CHANGE_MODE_REMOVE}) +    return deletedHosts + + +#Find Added/Deleted/Updated hosts in cluster +def findHostDelta(clusterConfig): +    hostDelta = [] +    updated = findAddUpdateHosts(clusterConfig['_hosts']) +    hostDelta.extend(updated) +    hostDelta.extend(findDeletedHosts(clusterConfig['hostgroup_name'], +                                      clusterConfig['_hosts'])) +    return hostDelta + + +#Find changes to the cluster +def findDelta(clusterConfig): +    delta = {} +    delta['hostgroup_name'] = clusterConfig['hostgroup_name'] +    delta['alias'] = clusterConfig['alias'] + +    hostgroup = server_utils.getHostGroup(clusterConfig['hostgroup_name']) +    if hostgroup is None: +        delta['changeMode'] = CHANGE_MODE_ADD +        delta['_hosts'] = clusterConfig['_hosts'] +        return delta + +    hostDelta = findHostDelta(clusterConfig) +    delta['_hosts'] = hostDelta +    if hostDelta: +        delta['changeMode'] = CHANGE_MODE_UPDATE +    return delta + +  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('-m', '--mode', action='store', dest='mode', +                        choices=['auto', 'manual'], required=False, +                        default='manual', help='Mode')      parser.add_argument('-d', '--configdir', action='store', dest='configDir',                          type=str, required=False, -                        help='Configuration directory ' -                             'where output files will be written') +                        default=DEFAULT_AUTO_CONFIG_DIR, +                        help='Configuration directory where' +                             ' output files will be written') +    parser.add_argument('-f', '--force', action='store_true', dest='force', +                        help="Force sync the Cluster configuration")      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 +#Clean the config directory +def cleanConfigDir(dir): +    if os.path.exists(dir): +        # Deleting the config dir to write new configs +        shutil.rmtree(dir) +    os.mkdir(dir) -def _restartNagios(): -    now = datetime.datetime.now() -    cmdStr = "[%s] RESTART_PROGRAM\n" % (now) -    submit_external_command.submitExternalCommand(cmdStr) +#Preview the changes to cluster config +def previewChanges(clusterDelta): +    print "Changes :" +    clusterChangeMode = clusterDelta['changeMode'] +    print "Hostgroup %s - %s" % (clusterDelta['hostgroup_name'], +                                   clusterChangeMode) +    for host in clusterDelta['_hosts']: +        if host.get('changeMode'): +            changeMode = host.get('changeMode') +        else: +            changeMode = clusterChangeMode +        print "Host %s - %s" % (host['host_name'], changeMode) +        for service in host.get('host_services'): +            if service.get('changeMode'): +                changeMode = service.get('changeMode') +            print "\t Service - %s -%s " % (service['service_description'], +                                              changeMode) -def _isNagiosRunning(): -    (rc, out, err) = utils.execCmd([serviceCmdPath.cmd, 'nagios', 'status']) -    if rc == 0: -        return True -    else: -        return False +#Write the cluster configurations. If force mode is used then it will clean +#the config directory before writing the changes. +def writeDelta(clusterDelta, configManager, force): +    if force: +        cleanConfigDir(configManager.configDir) +    configManager.generateConfigFiles(clusterDelta) + +def getConfirmation(message, default): +    while True: +        ans = raw_input("%s (Yes, No) [%s]: " % (message, default)) +        if not ans: +            ans = default +        ans = ans.upper() +        if ans not in ['YES', 'NO']: +            print 'please enter Yes or No' +        if ans == 'YES': +            return True +        if ans == 'NO': +            return False  if __name__ == '__main__':      args = parse_input()      clusterdata = discoverCluster(args.hostip, args.cluster)      configManager = getConfigManager(args) -    clusterConfing = configManager.generateNagiosConfigFromGlusterCluster( -        clusterdata) -    print " Cluster configurations re-synced successfully from host %s" % \ -          (args.hostip) -    if _isNagiosRunning(): -        _restartNagios() +    clusterDelta = configManager.generateNagiosConfig(clusterdata) +    if args.force: +        clusterDelta['changeMode'] = CHANGE_MODE_ADD +    else: +        clusterDelta = findDelta(clusterDelta) + +    if clusterDelta.get('changeMode') is None: +        print "Cluster configurations are in sync" +        sys.exit(utils.PluginStatusCode.OK) +    #When auto config is run in manual mode, we will ask confirmation +    #before writing the config file and before restarting the Nagios +    if args.mode == "manual": +        print "Cluster configurations changed" +        previewChanges(clusterDelta) +        confirmation = getConfirmation( +            "Are you sure, you want to commit the changes?", "Yes") +        if confirmation: +            writeDelta(clusterDelta, configManager, args.force) +            print "Cluster configurations synced successfully from host %s" % \ +                  (args.hostip) +            #If Nagios is running then try to restart. Otherwise don't do +            #anything. +            if server_utils.isNagiosRunning(): +                confirmation = getConfirmation( +                    "Do you want to restart Nagios to start monitoring newly " +                    "discovered entities?", "Yes") +                if confirmation: +                    server_utils.restartNagios() +                    print "Nagios re-started successfully" +            else: +                print "Start the Nagios service to monitor" +    #auto mode means write the configurations without asking confirmation +    elif args.mode == "auto": +        writeDelta(clusterDelta, configManager, args.force) +        print "Cluster configurations synced successfully from host %s" % \ +              (args.hostip) +        if server_utils.isNagiosRunning(): +            server_utils.restartNagios()      sys.exit(utils.PluginStatusCode.OK) diff --git a/plugins/server_utils.py b/plugins/server_utils.py new file mode 100755 index 0000000..b3767bd --- /dev/null +++ b/plugins/server_utils.py @@ -0,0 +1,78 @@ +#!/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 datetime +from pynag import Model + +from glusternagios import utils +import submit_external_command + + +serviceCmdPath = utils.CommandPath("service", "/sbin/service", ) + + +def restartNagios(): +    now = datetime.datetime.now() +    cmdStr = "[%s] RESTART_PROGRAM\n" % (now) +    submit_external_command.submitExternalCommand(cmdStr) + + +def isNagiosRunning(): +    (rc, out, err) = utils.execCmd([serviceCmdPath.cmd, 'nagios', 'status']) +    if rc == 0: +        return True +    else: +        return False + + +def getServiceConfig(serviceDesc, hostName): +    serviceConfig = Model.Service.objects.filter( +        service_description=serviceDesc, host_name=hostName) +    if serviceConfig: +        return serviceConfig[0] +    else: +        return None + + +def getServiceConfigByHost(hostName): +    serviceConfigs = Model.Service.objects.filter(host_name=hostName) +    return serviceConfigs + + +def getHostConfigByName(hostName): +    hostConfigs = Model.Host.objects.filter(host_name=hostName) +    if hostConfigs: +        return hostConfigs[0] +    else: +        return None + + +def getHostConfigsForCluster(clusterName): +    hostgroup = getHostGroup(clusterName) +    if hostgroup: +        return hostgroup.get_effective_hosts() +    else: +        return [] + + +def getHostGroup(name): +    hostgroup = Model.Hostgroup.objects.filter(hostgroup_name=name) +    if hostgroup: +        return hostgroup[0] +    else: +        return None diff --git a/tests/Makefile.am b/tests/Makefile.am index be571d6..7527b23 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -23,7 +23,7 @@ test_modules = \  	test_check_remote_host.py \  	test_notify_ovirt_engine_handler.py \  	test_config_generator.py \ -        test_discovery.py +	test_discovery.py \  	$(NULL)  dist_nagiosserveraddonstests_DATA = \ diff --git a/tests/test_config_generator.py b/tests/test_config_generator.py index 6fe62d3..8c79057 100644 --- a/tests/test_config_generator.py +++ b/tests/test_config_generator.py @@ -16,42 +16,85 @@  # 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( +    def testGenerateNagiosConfig(self): +        confManager = self._getGlusterNagiosConfManager() +        clusterData = self._createDummyCluster() +        clusterConfig = confManager.generateNagiosConfig(              clusterData) -        mock_generateConfigFiles.assert_called() -        self.__verifyConfig(clusterConfig, clusterData) +        self._verifyConfig(clusterConfig, clusterData) -    def __verifyConfig(self, clusterConfig, clusterData): +    def _verifyConfig(self, clusterConfig, clusterData):          self.assertTrue(len(clusterConfig), len(clusterData['hosts']) + 1) -        self.__verifyClusterConfig(clusterConfig[0], clusterData) +        self._verifyClusterConfig(clusterConfig["_hosts"][0], clusterData)          for index in range(0, len(clusterData['hosts'])): -            self.__verifyHostConfig(clusterConfig[index + 1], -                                    clusterData['hosts'][index]) +            self._verifyHostConfig(clusterConfig['_hosts'][index + 1], +                                   clusterData['hosts'][index]) -    def __verifyHostConfig(self, hostConfig, hostData): +    def _verifyHostConfig(self, hostConfig, hostData):          self.assertEqual(hostConfig['host_name'], hostData['hostname'])          self.assertEqual(hostConfig['alias'], hostData['hostname'])          self.assertEqual(hostConfig['address'], hostData['hostip'])          self.assertEqual(hostConfig['use'], 'gluster-host') +        self._verifyHostServices(hostConfig, hostData) -    def __verifyClusterConfig(self, config, clusterData): +    def _verifyHostServices(self, hostConfig, hostData): +        for brick in hostData['bricks']: +            serviceDesc = "Brick Status - %s:%s" % (hostData['hostname'], +                                                    brick['brickpath']) +            service = self._findServiceInList(hostConfig['host_services'], +                                              serviceDesc) +            self.assertNotEqual(service, None, +                                "Brick status service is not created") +            serviceDesc = "Brick Utilization - %s:%s" % (hostData['hostname'], +                                                         brick['brickpath']) +            service = self._findServiceInList(hostConfig['host_services'], +                                              serviceDesc) +            self.assertNotEqual(service, None, +                                "Brick Utilization service is not created") + +    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.get('check_command'), None)          self.assertEqual(config['use'], 'gluster-cluster') +        self._verifyClusterServices(config, clusterData) + +    def _verifyClusterServices(self, clusterConfig, clusterData): +        self.assertEqual(len(clusterConfig['host_services']), 6) +        for volume in clusterData['volumes']: +            self._verifyVolumeServices(clusterConfig['host_services'], volume) + +    def _verifyVolumeServices(self, serviceList, volume): +        serviceDesc = 'Volume Utilization - %s' % (volume['name']) +        service = self._findServiceInList(serviceList, serviceDesc) +        self.assertNotEqual(service, None, +                            "Volume utilization service is not created") +        serviceDesc = 'Volume Status - %s' % (volume['name']) +        service = self._findServiceInList(serviceList, serviceDesc) +        self.assertNotEqual(service, None, +                            "Volume Status service is not created") +        serviceDesc = 'Volume Status Quota - %s' % (volume['name']) +        service = self._findServiceInList(serviceList, serviceDesc) +        self.assertNotEqual(service, None, +                            "Volume Status Quota service is not created") +        if 'Replicate' in volume['type']: +            serviceDesc = 'Volume Self-Heal - %s' % (volume['name']) +            service = self._findServiceInList(serviceList, serviceDesc) +            self.assertNotEqual(service, None, +                                "Volume Self-Heal service is not created") + +    def _findServiceInList(self, serviceList, serviceDescription): +        for service in serviceList: +            if service['service_description'] == serviceDescription: +                return service +        return None      def createBricks(self, count, volume, hostip):          bricks = [] @@ -62,7 +105,7 @@ class TestGlusterNagiosConfManager(TestCaseBase):                             'hostip': hostip})          return bricks -    def __createDummyCluster(self): +    def _createDummyCluster(self):          cluster = {'name': 'Test-Cluster', 'hosts': [], 'volumes': []}          cluster['hosts'].append({'hostip': '10.70.43.1',                                   'hostname': 'host-1', @@ -72,9 +115,8 @@ class TestGlusterNagiosConfManager(TestCaseBase):                                   'hostname': 'host-2',                                   'bricks': self.createBricks(2, "Volume1",                                                               '10.70.43.2')}) -        cluster['volumes'].append({'name': 'Volume1', "type": "T"}) +        cluster['volumes'].append({'name': 'Volume1', "type": "Replicate"})          return cluster -    def __getGlusterNagiosConfManager(self): -        return config_generator.GlusterNagiosConfManager( -            "/tmp/nagios/gluster", "../config", "gluster-host.cfg.template") +    def _getGlusterNagiosConfManager(self): +        return config_generator.GlusterNagiosConfManager("/tmp/nagios/gluster")  | 
