Volans has submitted this change and it was merged.

Change subject: Monitoring: add event handler for RAID checks
......................................................................


Monitoring: add event handler for RAID checks

Add an event handler to Icinga RAID checks to automatically handle their
alarms. If a CRITICAL state is detected a Phabricator task with the
appropriate DC-Ops tag will be created and the Icinga alarm
acknowledged.

Bug: T142085
Change-Id: I3f2b4899089b8eed2f70f1b7b95182b9a3fb67bb
Depends-On: I585f1cc8d4eaff408c8ebc43252e09770d10f3cd
---
M manifests/role/icinga.pp
A modules/icinga/files/raid_handler.py
A modules/icinga/manifests/event_handlers/raid.pp
A modules/icinga/templates/event_handlers/raid_handler.cfg.erb
M modules/monitoring/manifests/service.pp
M modules/nrpe/manifests/monitor_service.pp
M modules/raid/manifests/init.pp
7 files changed, 301 insertions(+), 6 deletions(-)

Approvals:
  Volans: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/manifests/role/icinga.pp b/manifests/role/icinga.pp
index f109823..29423b7 100644
--- a/manifests/role/icinga.pp
+++ b/manifests/role/icinga.pp
@@ -27,6 +27,7 @@
     include icinga::monitor::commons
     include icinga::monitor::elasticsearch
     include icinga::monitor::wdqs
+    include icinga::event_handlers::raid
 
     include role::authdns::monitoring
     include netops::monitoring
diff --git a/modules/icinga/files/raid_handler.py 
b/modules/icinga/files/raid_handler.py
new file mode 100644
index 0000000..00d2609
--- /dev/null
+++ b/modules/icinga/files/raid_handler.py
@@ -0,0 +1,237 @@
+#!/usr/bin/env python
+"""Nagios/Icinga Event Handler for RAID checks"""
+
+import argparse
+import ConfigParser
+import logging
+import subprocess
+import time
+import zlib
+
+from logging.handlers import RotatingFileHandler
+
+from phabricator import Phabricator
+
+SERVICE_STATES = ('OK', 'UNKNOWN', 'WARNING', 'CRITICAL')
+SERVICE_STATE_TYPES = ('SOFT', 'HARD')
+
+RAID_TYPES = ('megacli', 'hpssacli', 'mpt', 'md')
+COMPRESSED_RAID_TYPES = ('megacli', 'hpssacli')
+
+LOG_PATH = '/var/log/icinga/raid_handler.log'
+COMMAND_FILE = '/var/lib/nagios/rw/nagios.cmd'
+CHECK_NRPE_PATH = '/usr/lib/nagios/plugins/check_nrpe'
+
+NRPE_REMOTE_COMMAND = 'get_raid_status_{}'
+ACK_MESSAGE = 'RAID handler auto-ack: {}'
+ICINGA_URL = ('https://icinga.wikimedia.org/cgi-bin/icinga/extinfo.cgi?type=2&;'
+              'host={host}&service={service}')
+
+PHABRICATOR_CONFIG_FILE = '/etc/phabricator_ops-monitoring-bot.conf'
+PHABRICATOR_TAG_PREFIX = 'ops-'
+PHABRICATOR_TASK_TITLE = "Degraded RAID on {host}"
+PHABRICATOR_TASK_DESCRIPTION_PREFIX = (
+    "TASK AUTO-GENERATED by Nagios/Icinga RAID event handler\n\n"
+    "A degraded RAID [[ {url} | was detected ]] on host `{host}`. An "
+    "automatic snapshot of the current RAID status is attached below.\n\n"
+    "Please **sync with the service owner** to find the appropriate time "
+    "window before actually replacing any failed hardware."
+)
+
+logger = logging.getLogger('raid_handler')
+
+
+def parse_args():
+    """Parse command line arguments"""
+
+    parser = argparse.ArgumentParser(
+        description='Nagios/Icinga event handler for RAID checks')
+    parser.add_argument(
+        '-s', dest='service_state', action='store', required=True,
+        choices=SERVICE_STATES, help='Nagios/Icinga service state')
+    parser.add_argument(
+        '-t', dest='service_state_type', action='store', required=True,
+        choices=SERVICE_STATE_TYPES, help='Nagios/Icinga service state type')
+    parser.add_argument(
+        '-a', dest='service_attempts', action='store', required=True, type=int,
+        help='Nagios/Icinga service retry attemp counter')
+    parser.add_argument(
+        '-H', dest='host_address', action='store', required=True,
+        help='Hostname/address of the monitored host')
+    parser.add_argument(
+        '-r', dest='raid_type', action='store', required=True,
+        choices=RAID_TYPES, help='The RAID type')
+    parser.add_argument(
+        '-D', dest='service_description', action='store', required=True,
+        help='The Nagios/Icinga service description')
+    parser.add_argument(
+        '-c', dest='datacenter', action='store', required=True,
+        help='The name of the datacenter the host is located in')
+    parser.add_argument(
+        '-d', dest='debug', action='store_true', help='Debug level logging')
+
+    return parser.parse_args()
+
+
+def get_raid_status(host, raid_type):
+    """ Get and return the RAID status of a remote host
+
+        Arguments:
+        host      -- hostname to be passed to the NRPE check
+        raid_type -- the RAID type to check, see RAID_TYPES for accepted values
+    """
+
+    try:
+        nrpe_command = [CHECK_NRPE_PATH, '-H', host,
+                        '-c', NRPE_REMOTE_COMMAND.format(raid_type)]
+        proc = subprocess.Popen(nrpe_command, stdout=subprocess.PIPE)
+        stdout, stderr = proc.communicate()
+    except Exception as e:
+        logger.error("Unable to execute '{}': {}".format(nrpe_command, e))
+        raise
+
+    if raid_type in COMPRESSED_RAID_TYPES:
+        # NRPE doesn't handle NULL bytes, decoding them.
+        # Given the specific domain there was no need of a full yEnc encoding.
+        status = zlib.decompress(stdout.replace('###NULL###', '\x00'))
+    else:
+        status = stdout
+
+    logger.debug(status)
+    return status
+
+
+def get_phabricator_client():
+    """Return a Phabricator client instance"""
+
+    parser = ConfigParser.SafeConfigParser()
+    parser_mode = 'phabricator_bot'
+    parser.read(PHABRICATOR_CONFIG_FILE)
+
+    client = Phabricator(
+        username=parser.get(parser_mode, 'username'),
+        token=parser.get(parser_mode, 'token'),
+        host=parser.get(parser_mode, 'host'))
+
+    return client
+
+
+def get_phabricator_project_ids(phab_client, datacenter):
+    """ Return a list of Phabricator's projectPHID
+
+        Find the project IDs of the datacenter's tag and add the one of
+        Operations group.
+
+        Arguments:
+        phab_client -- a Phabricator client instance
+        datacenter  -- the name of the datacenter the host is located in
+    """
+
+    project_name = '{}{}'.format(PHABRICATOR_TAG_PREFIX, datacenter)
+    projects = phab_client.project.query(names=[project_name, 'Operations'])
+
+    if len(projects.data.keys()) != 2:
+        logger.error("Unable to find PHID for project '{}', found: {}".format(
+            project_name, projects))
+        raise RuntimeError("Unable to find PHID")
+
+    logger.debug("Found PHIDs '{}' for project '{}' and Operations".format(
+        projects.data.keys(), project_name))
+
+    return projects.data.keys()
+
+
+def open_phabricator_task(
+        phab_client, project_ids, host, raid_status, icinga_url):
+    """ Open a task on Phabricator and return it
+
+        Arguments:
+        phab_client -- a Phabricator client instance
+        project_ids -- the PHIDs to tag the task with
+        host        -- the hostname of the affected host
+        raid_status -- the RAID status message to include in the task
+        icinga_url  -- the URL of the Icinga alarm that triggered this handler
+    """
+
+    description_prefix = PHABRICATOR_TASK_DESCRIPTION_PREFIX.format(
+        host=host, url=icinga_url)
+    description = '{description_prefix}\n```\n{raid_status}\n```'.format(
+        description_prefix=description_prefix, raid_status=raid_status)
+
+    task = phab_client.maniphest.createtask(
+        title=PHABRICATOR_TASK_TITLE.format(host=host),
+        projectPHIDs=project_ids, description=description)
+
+    logger.debug('Opened Phabricator task: {}'.format(task))
+    return task
+
+
+def acknowledge_nagios_alert(host, service_description, task_uri):
+    """ Acknowledge the Nagios/Icinga alert
+
+        Arguments:
+        host                -- the hostname of the affected host
+        service_description -- the Nagios/Icinga service description
+        task_uri            -- the URI of the related Phabricator task
+    """
+
+    message = (
+        '[{time}] ACKNOWLEDGE_SVC_PROBLEM;{host};{service};2;1;1;'
+        'nagiosadmin;{message}\n'
+    ).format(time=int(time.time()), host=host, service=service_description,
+             message=ACK_MESSAGE.format(task_uri))
+
+    with open(COMMAND_FILE, 'w') as f:
+        f.write(message)
+
+    logger.debug('Acknowledged Nagios/Icinga alert: {}'.format(message))
+
+
+def main():
+    """Run the Nagios/Icinga Event Handler for RAID checks"""
+
+    log_formatter = logging.Formatter(
+        fmt='%(asctime)s [%(levelname)s] %(name)s::%(funcName)s: %(message)s',
+        datefmt='%F %T')
+    log_handler = RotatingFileHandler(
+        LOG_PATH, maxBytes=5*(1024**2), backupCount=10)
+    log_handler.setFormatter(log_formatter)
+    logger.addHandler(log_handler)
+    logger.raiseExceptions = False
+    logger.setLevel(logging.INFO)
+
+    args = parse_args()
+    if args.debug:
+        logger.setLevel(logging.DEBUG)
+
+    logger.debug('RAID Handler called with args: {}'.format(args))
+
+    if args.service_state != 'CRITICAL' or args.service_state_type != 'HARD':
+        logger.debug('Nothing to do, exiting')
+        return
+
+    raid_status = get_raid_status(args.host_address, args.raid_type)
+    phab_client = get_phabricator_client()
+    project_ids = get_phabricator_project_ids(phab_client, args.datacenter)
+
+    icinga_url = ICINGA_URL.format(
+        host=args.host_address, service=args.service_description)
+    task = open_phabricator_task(
+        phab_client, project_ids, args.host_address, raid_status, icinga_url)
+
+    acknowledge_nagios_alert(
+        args.host_address, args.service_description, task['uri'])
+
+    logger.info(
+        ("RAID Handler executed for host '{}' and RAID type '{}'. "
+         "Created task ID '{}'").format(
+            args.host_address, args.raid_type, task['id']))
+
+    logger.debug('RAID Handler completed')
+
+
+if __name__ == '__main__':
+    try:
+        main()
+    except Exception:
+        logger.exception("Unable to handle RAID check alert")
diff --git a/modules/icinga/manifests/event_handlers/raid.pp 
b/modules/icinga/manifests/event_handlers/raid.pp
new file mode 100644
index 0000000..ea27fb8
--- /dev/null
+++ b/modules/icinga/manifests/event_handlers/raid.pp
@@ -0,0 +1,39 @@
+# = Class: icinga::event_handlers::raid
+#
+# Sets up icinga RAID event handler
+class icinga::event_handlers::raid {
+    include passwords::phabricator
+
+    class { '::phabricator::bot':
+        username => 'ops-monitoring-bot',
+        token    => $passwords::phabricator::ops_monitoring_bot_token,
+        owner    => 'icinga',
+        group    => 'icinga',
+    }
+
+    package { 'python-phabricator':
+        ensure => 'present',
+    }
+
+    file { '/usr/lib/nagios/plugins/eventhandlers/raid_handler':
+        source  => 'puppet:///modules/icinga/raid_handler.py',
+        owner   => 'root',
+        group   => 'root',
+        mode    => '0755',
+        require => [
+            File['/etc/phabricator_ops-monitoring-bot.conf'],
+            File['/var/lib/nagios/rw/nagios.cmd'],
+            File['/usr/lib/nagios/plugins/check_nrpe'],
+            Package['icinga'],
+        ],
+    }
+
+    nagios_common::check_command::config { 'raid_handler':
+        ensure     => present,
+        content    => template('icinga/event_handlers/raid_handler.cfg.erb'),
+        config_dir => '/etc/icinga',
+        owner      => 'icinga',
+        group      => 'icinga',
+        require    => 
File['/usr/lib/nagios/plugins/eventhandlers/raid_handler'],
+    }
+}
diff --git a/modules/icinga/templates/event_handlers/raid_handler.cfg.erb 
b/modules/icinga/templates/event_handlers/raid_handler.cfg.erb
new file mode 100644
index 0000000..a19167b
--- /dev/null
+++ b/modules/icinga/templates/event_handlers/raid_handler.cfg.erb
@@ -0,0 +1,4 @@
+define command{
+    command_name    raid_handler
+    command_line    $USER1$/eventhandlers/raid_handler -s $SERVICESTATE$ -t 
$SERVICESTATETYPE$ -a $SERVICEATTEMPT$ -H $HOSTADDRESS$ -r $ARG1$ -D "$ARG2$" 
-c $ARG3$
+    }
diff --git a/modules/monitoring/manifests/service.pp 
b/modules/monitoring/manifests/service.pp
index b85e85b..2fbba7e 100644
--- a/modules/monitoring/manifests/service.pp
+++ b/modules/monitoring/manifests/service.pp
@@ -12,6 +12,7 @@
     $retry_check_interval  = 1,
     $contact_group         = hiera('contactgroups', 'admins'),
     $config_dir            = '/etc/nagios',
+    $event_handler         = undef,
 )
 {
     # the list of characters is the default for illegal_object_name_chars
@@ -91,6 +92,7 @@
             is_volatile            => $check_volatile,
             check_freshness        => $check_fresh,
             freshness_threshold    => $is_fresh,
+            event_handler          => $event_handler,
         }
     }
     # This is a hack. We detect if we are running on the scope of an icinga
diff --git a/modules/nrpe/manifests/monitor_service.pp 
b/modules/nrpe/manifests/monitor_service.pp
index 43ee28e..3fef03a 100644
--- a/modules/nrpe/manifests/monitor_service.pp
+++ b/modules/nrpe/manifests/monitor_service.pp
@@ -22,6 +22,9 @@
 #    $critical
 #       Defaults to false. It will passed directly to monitoring::service which
 #       will use nagios_service, so extra care, it is not a boolean, it is a 
string
+#    $event_handler
+#       Default to false. If present execute this registered command on the
+#       Nagios server.
 #    $ensure
 #       Defaults to present
 #
@@ -31,10 +34,10 @@
                               $retries               = 3,
                               $timeout               = 10,
                               $critical              = false,
+                              $event_handler         = undef,
                               $normal_check_interval = 1,
                               $retry_check_interval  = 1,
                               $ensure                = 'present') {
-
     nrpe::check { "check_${title}":
         command => $nrpe_command,
         before  => Monitoring::Service[$title],
@@ -47,6 +50,7 @@
         contact_group         => $contact_group,
         retries               => $retries,
         critical              => $critical,
+        event_handler         => $event_handler,
         normal_check_interval => $normal_check_interval,
         retry_check_interval  => $retry_check_interval,
     }
diff --git a/modules/raid/manifests/init.pp b/modules/raid/manifests/init.pp
index a9a4f1f..e49cada 100644
--- a/modules/raid/manifests/init.pp
+++ b/modules/raid/manifests/init.pp
@@ -44,11 +44,13 @@
             command => "/usr/bin/sudo ${get_raid_status_megacli} -c",
         }
 
+        $service_description = 'MegaRAID'
         nrpe::monitor_service { 'raid_megaraid':
-            description           => 'MegaRAID',
+            description           => $service_description,
             nrpe_command          => "${check_raid} megacli",
             normal_check_interval => $normal_check_interval,
             retry_check_interval  => $retry_check_interval,
+            event_handler         => 
"raid_handler!megacli!${service_description}!${::site}",
         }
     }
 
@@ -86,12 +88,14 @@
             ],
         }
 
+        $service_description = 'HP RAID'
         nrpe::monitor_service { 'raid_hpssacli':
-            description           => 'HP RAID',
+            description           => $service_description,
             nrpe_command          => 
'/usr/local/lib/nagios/plugins/check_hpssacli',
             timeout               => 50, # can take > 10s on servers with lots 
of disks
             normal_check_interval => $normal_check_interval,
             retry_check_interval  => $retry_check_interval,
+            event_handler         => 
"raid_handler!hpssacli!${service_description}!${::site}",
         }
 
         $get_raid_status_hpssacli = 
'/usr/local/lib/nagios/plugins/get-raid-status-hpssacli'
@@ -123,11 +127,13 @@
             before  => Package['mpt-status'],
         }
 
+        $service_description = 'MPT RAID'
         nrpe::monitor_service { 'raid_mpt':
-            description           => 'MPT RAID',
+            description           => $service_description,
             nrpe_command          => "${check_raid} mpt",
             normal_check_interval => $normal_check_interval,
             retry_check_interval  => $retry_check_interval,
+            event_handler         => 
"raid_handler!mpt!${service_description}!${::site}",
         }
 
         nrpe::check { 'get_raid_status_mpt':
@@ -138,9 +144,11 @@
     if 'md' in $raid {
         # if there is an "md" RAID configured, mdadm is already installed
 
+        $service_description = 'MD RAID'
         nrpe::monitor_service { 'raid_md':
-            description  => 'MD RAID',
-            nrpe_command => "${check_raid} md",
+            description   => $service_description,
+            nrpe_command  => "${check_raid} md",
+            event_handler => 
"raid_handler!md!${service_description}!${::site}",
         }
 
         nrpe::check { 'get_raid_status_md':

-- 
To view, visit https://gerrit.wikimedia.org/r/304026
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I3f2b4899089b8eed2f70f1b7b95182b9a3fb67bb
Gerrit-PatchSet: 8
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Volans <[email protected]>
Gerrit-Reviewer: Faidon Liambotis <[email protected]>
Gerrit-Reviewer: Filippo Giunchedi <[email protected]>
Gerrit-Reviewer: Jcrespo <[email protected]>
Gerrit-Reviewer: Volans <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to