Browse Source

Added monit support.

Martin Edenhofer 7 years ago
parent
commit
e7fab8d5c4

+ 32 - 0
app/assets/javascripts/app/controllers/_integration/monit.coffee

@@ -0,0 +1,32 @@
+class Index extends App.ControllerIntegrationBase
+  featureIntegration: 'monit_integration'
+  featureName: 'Monit'
+  featureConfig: 'monit_config'
+  description: [
+    ['This service receives emails from %s and creates tickets with host and service.', 'Monit']
+    ['If the host and service is recovered again, the ticket will be closed automatically.']
+  ]
+
+  render: =>
+    super
+    new App.SettingsForm(
+      area: 'Integration::Monit'
+      el: @$('.js-form')
+    )
+
+class State
+  @current: ->
+    App.Setting.get('monit_integration')
+
+App.Config.set(
+  'IntegrationMonit'
+  {
+    name: 'Monit'
+    target: '#system/integration/monit'
+    description: 'An open source monitoring tool.'
+    controller: Index
+    state: State
+    permission: ['admin.integration.monit']
+  }
+  'NavBarIntegrations'
+)

+ 7 - 0
app/models/channel/filter/monit.rb

@@ -0,0 +1,7 @@
+# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
+
+class Channel::Filter::Monit < Channel::Filter::MonitoringBase
+  def self.integration_name
+    'monit'
+  end
+end

+ 21 - 3
app/models/channel/filter/monitoring_base.rb

@@ -30,7 +30,7 @@ class Channel::Filter::MonitoringBase
 
     # get mail attibutes like host and state
     result = {}
-    mail[:body].gsub(%r{(Service|Host|State|Address|Date/Time|Additional\sInfo|Info):(.+?)\n}i) do |_match|
+    mail[:body].gsub(%r{(Service|Host|State|Address|Date/Time|Additional\sInfo|Info|Action|Description):(.+?)\n}i) do |_match|
       key = $1
       if key
         key = key.downcase
@@ -45,14 +45,14 @@ class Channel::Filter::MonitoringBase
     # check min. params
     return if result['host'].blank?
 
-    # get state by body - ichinga new templates
+    # icinga - get state by body - new templates
     if result['state'].blank?
       if mail[:body] =~ /==>.*\sis\s(.+?)\!\s+?<==/
         result['state'] = $1
       end
     end
 
-    # get state by subject - ichinga new templates "state:" is not in body anymore
+    # icinga - get state by subject - new templates "state:" is not in body anymore
     # Subject: [PROBLEM] Ping IPv4 on host1234.dc.example.com is WARNING!
     # Subject: [PROBLEM] Host host1234.dc.example.com is DOWN!
     if result['state'].blank?
@@ -61,6 +61,24 @@ class Channel::Filter::MonitoringBase
       end
     end
 
+    # monit - get missing attributes from body
+    if result['service'].blank?
+      if mail[:body] =~ /\sService\s(.+?)\s/
+        result['service'] = $1
+      end
+    end
+
+    # possible event types https://mmonit.com/monit/documentation/#Setting-an-event-filter
+    if result['state'].blank?
+      result['state'] = if mail[:body] =~ /\s(done|recovery|succeeded|bytes\sok|packets\sok)\s/
+                          'OK'
+                        elsif mail[:body] =~ /(instance\schanged\snot|Link\sup|Exists|Saturation\sok|Speed\sok)/
+                          'OK'
+                        else
+                          'CRITICAL'
+                        end
+    end
+
     # check if ticket with host is open
     customer = User.lookup(id: session_user_id)
 

+ 118 - 0
db/migrate/20171024000001_monit_integration.rb

@@ -0,0 +1,118 @@
+class MonitIntegration < ActiveRecord::Migration[4.2]
+  def up
+
+    # return if it's a new setup
+    return if !Setting.find_by(name: 'system_init_done')
+
+    Setting.create_if_not_exists(
+      title: 'Monit integration',
+      name: 'monit_integration',
+      area: 'Integration::Switch',
+      description: 'Defines if Monit (https://mmonit.com/monit/) is enabled or not.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'monit_integration',
+            tag: 'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+          },
+        ],
+      },
+      state: false,
+      preferences: {
+        prio: 1,
+        permission: ['admin.integration'],
+      },
+      frontend: false
+    )
+    Setting.create_if_not_exists(
+      title: 'Sender',
+      name: 'monit_sender',
+      area: 'Integration::Monit',
+      description: 'Defines the sender email address of the service emails.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: false,
+            name: 'monit_sender',
+            tag: 'input',
+            placeholder: 'monit@monitoring.example.com',
+          },
+        ],
+      },
+      state: 'monit@monitoring.example.com',
+      preferences: {
+        prio: 2,
+        permission: ['admin.integration'],
+      },
+      frontend: false,
+    )
+    Setting.create_if_not_exists(
+      title: 'Auto close',
+      name: 'monit_auto_close',
+      area: 'Integration::Monit',
+      description: 'Defines if tickets should be closed if service is recovered.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: true,
+            name: 'monit_auto_close',
+            tag: 'boolean',
+            options: {
+              true  => 'yes',
+              false => 'no',
+            },
+            translate: true,
+          },
+        ],
+      },
+      state: true,
+      preferences: {
+        prio: 3,
+        permission: ['admin.integration'],
+      },
+      frontend: false
+    )
+    Setting.create_if_not_exists(
+      title: 'Auto close state',
+      name: 'monit_auto_close_state_id',
+      area: 'Integration::Monit',
+      description: 'Defines the state of auto closed tickets.',
+      options: {
+        form: [
+          {
+            display: '',
+            null: false,
+            name: 'monit_auto_close_state_id',
+            tag: 'select',
+            relation: 'TicketState',
+            translate: true,
+          },
+        ],
+      },
+      state: 4,
+      preferences: {
+        prio: 4,
+        permission: ['admin.integration'],
+      },
+      frontend: false
+    )
+    Setting.create_if_not_exists(
+      title: 'Defines postmaster filter.',
+      name: '5300_postmaster_filter_monit',
+      area: 'Postmaster::PreFilter',
+      description: 'Defines postmaster filter to manage Monit (https://mmonit.com/monit/) emails.',
+      options: {},
+      state: 'Channel::Filter::Monit',
+      frontend: false
+    )
+  end
+
+end

+ 109 - 0
db/seeds/settings.rb

@@ -2708,6 +2708,15 @@ Setting.create_if_not_exists(
   state: 'Channel::Filter::Nagios',
   frontend: false
 )
+Setting.create_if_not_exists(
+  title: 'Defines postmaster filter.',
+  name: '5300_postmaster_filter_monit',
+  area: 'Postmaster::PreFilter',
+  description: 'Defines postmaster filter to manage Monit (https://mmonit.com/monit/) emails.',
+  options: {},
+  state: 'Channel::Filter::Monit',
+  frontend: false
+)
 Setting.create_if_not_exists(
   title: 'Icinga integration',
   name: 'icinga_integration',
@@ -3014,6 +3023,106 @@ Setting.create_if_not_exists(
   },
   frontend: false
 )
+Setting.create_if_not_exists(
+  title: 'Monit integration',
+  name: 'monit_integration',
+  area: 'Integration::Switch',
+  description: 'Defines if Monit (https://mmonit.com/monit/) is enabled or not.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: true,
+        name: 'monit_integration',
+        tag: 'boolean',
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+      },
+    ],
+  },
+  state: false,
+  preferences: {
+    prio: 1,
+    permission: ['admin.integration'],
+  },
+  frontend: false
+)
+Setting.create_if_not_exists(
+  title: 'Sender',
+  name: 'monit_sender',
+  area: 'Integration::Monit',
+  description: 'Defines the sender email address of the service emails.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: false,
+        name: 'monit_sender',
+        tag: 'input',
+        placeholder: 'monit@monitoring.example.com',
+      },
+    ],
+  },
+  state: 'monit@monitoring.example.com',
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
+  frontend: false,
+)
+Setting.create_if_not_exists(
+  title: 'Auto close',
+  name: 'monit_auto_close',
+  area: 'Integration::Monit',
+  description: 'Defines if tickets should be closed if service is recovered.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: true,
+        name: 'monit_auto_close',
+        tag: 'boolean',
+        options: {
+          true  => 'yes',
+          false => 'no',
+        },
+        translate: true,
+      },
+    ],
+  },
+  state: true,
+  preferences: {
+    prio: 3,
+    permission: ['admin.integration'],
+  },
+  frontend: false
+)
+Setting.create_if_not_exists(
+  title: 'Auto close state',
+  name: 'monit_auto_close_state_id',
+  area: 'Integration::Monit',
+  description: 'Defines the state of auto closed tickets.',
+  options: {
+    form: [
+      {
+        display: '',
+        null: false,
+        name: 'monit_auto_close_state_id',
+        tag: 'select',
+        relation: 'TicketState',
+        translate: true,
+      },
+    ],
+  },
+  state: 4,
+  preferences: {
+    prio: 4,
+    permission: ['admin.integration'],
+  },
+  frontend: false
+)
 
 Setting.create_if_not_exists(
   title: 'LDAP integration',

+ 206 - 0
test/unit/integration_monit_test.rb

@@ -0,0 +1,206 @@
+# encoding: utf-8
+require 'test_helper'
+
+class IntegrationMoniTest < ActiveSupport::TestCase
+
+  # according
+  # https://mmonit.com/monit/documentation/#ALERT-MESSAGES
+
+  setup do
+    Setting.set('monit_integration', true)
+    Setting.set('monit_sender', 'monit@monitoring.example.com')
+  end
+
+  test 'base tests' do
+
+    # Service
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-1-1@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Timeout php-fpm
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Timeout Service php-fpm
+
+    Date:        Thu, 24 Aug 2017 10:30:42
+    Action:      unmonitor
+    Host:        web1.example
+    Description: service restarted 6 times within 3 cycles(s) - unmonitor
+
+Your faithful employee,
+Monit
+"
+
+    ticket_0, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('new', ticket_0.state.name)
+    assert(ticket_0.preferences)
+    assert(ticket_0.preferences['monit'])
+    assert_equal('unmonitor', ticket_0.preferences['monit']['action'])
+    assert_equal('web1.example', ticket_0.preferences['monit']['host'])
+    assert_equal('service restarted 6 times within 3 cycles(s) - unmonitor', ticket_0.preferences['monit']['description'])
+    assert_equal('php-fpm', ticket_0.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_0.preferences['monit']['state'])
+
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-1-2@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Action done php-fpm
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Action done Service php-fpm
+
+    Date:        Thu, 24 Aug 2017 10:37:39
+    Action:      alert
+    Host:        web1.example
+    Description: monitor action done
+
+Your faithful employee,
+Monit"
+
+    ticket_0_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('closed', ticket_0_1.state.name)
+    assert(ticket_0_1.preferences)
+    assert(ticket_0_1.preferences['monit'])
+    assert_equal('unmonitor', ticket_0.preferences['monit']['action'])
+    assert_equal('web1.example', ticket_0_1.preferences['monit']['host'])
+    assert_equal('service restarted 6 times within 3 cycles(s) - unmonitor', ticket_0_1.preferences['monit']['description'])
+    assert_equal('php-fpm', ticket_0_1.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_0_1.preferences['monit']['state'])
+    assert_equal(ticket_0_1.id, ticket_0.id)
+
+    # Service
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-2-1@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Connection failed host.example
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Connection failed Service host.example=20
+
+    Date:        Fri, 25 Aug 2017 02:28:31
+    Action:      alert
+    Host:        web5.host.example
+    Description: failed protocol test [HTTP] at [host.example]:80 [TCP/I=
+P] -- HTTP: Error receiving data -- Resource temporarily unavailable
+
+Your faithful employee,
+Monit"
+
+    ticket_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('new', ticket_1.state.name)
+    assert(ticket_1.preferences)
+    assert(ticket_1.preferences['monit'])
+    assert_equal('alert', ticket_1.preferences['monit']['action'])
+    assert_equal('web5.host.example', ticket_1.preferences['monit']['host'])
+    assert_equal('failed protocol test [HTTP] at [host.example]:80 [TCP/IP] -- HTTP: Error receiving data -- Resource temporarily unavailable', ticket_1.preferences['monit']['description'])
+    assert_equal('host.example', ticket_1.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_1.preferences['monit']['state'])
+
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-2-2@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Connection succeeded host.example
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Connection succeeded Service host.example=20
+
+    Date:        Fri, 25 Aug 2017 02:29:13
+    Action:      alert
+    Host:        web5.host.example
+    Description: connection succeeded to [host.example]:80 [TCP/IP]
+
+Your faithful employee,
+Monit"
+
+    ticket_1_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('closed', ticket_1_1.state.name)
+    assert(ticket_1_1.preferences)
+    assert(ticket_1_1.preferences['monit'])
+    assert_equal('alert', ticket_1.preferences['monit']['action'])
+    assert_equal('web5.host.example', ticket_1_1.preferences['monit']['host'])
+    assert_equal('failed protocol test [HTTP] at [host.example]:80 [TCP/IP] -- HTTP: Error receiving data -- Resource temporarily unavailable', ticket_1_1.preferences['monit']['description'])
+    assert_equal('host.example', ticket_1_1.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_1_1.preferences['monit']['state'])
+    assert_equal(ticket_1_1.id, ticket_1.id)
+
+    # Resource Limit
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-3-1@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Resource limit matched web5.example.net
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Resource limit matched Service web5.example.net=20
+
+    Date:        Fri, 25 Aug 2017 02:02:08
+    Action:      exec
+    Host:        web5.example.net
+    Description: loadavg(1min) of 10.7 matches resource limit [loadavg(1min) >=
+ 6.0]
+
+Your faithful employee,
+Monit"
+
+    ticket_2, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('new', ticket_2.state.name)
+    assert(ticket_2.preferences)
+    assert(ticket_2.preferences['monit'])
+    assert_equal('exec', ticket_2.preferences['monit']['action'])
+    assert_equal('web5.example.net', ticket_2.preferences['monit']['host'])
+    assert_equal('loadavg(1min) of 10.7 matches resource limit [loadavg(1min) > 6.0]', ticket_2.preferences['monit']['description'])
+    assert_equal('web5.example.net', ticket_2.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_2.preferences['monit']['state'])
+
+    email_raw_string = "Message-Id: <20160131094621.29ECD400F29C-monit-3-2@monitoring.znuny.com>
+From: monit@monitoring.example.com
+To: admin@example
+Subject: monit alert --  Resource limit succeeded web5.example.net
+Date: Thu, 24 Aug 2017 08:30:42 GMT
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+X-Mailer: Monit 5.23.0
+MIME-Version: 1.0
+
+Resource limit succeeded Service web5.example.net=20
+
+    Date:        Fri, 25 Aug 2017 02:05:18
+    Action:      alert
+    Host:        web5.example.net
+    Description: loadavg(1min) check succeeded [current loadavg(1min) =3D 4.8]
+
+Your faithful employee,
+Monit"
+
+    ticket_2_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
+    assert_equal('closed', ticket_2_1.state.name)
+    assert(ticket_2_1.preferences)
+    assert(ticket_2_1.preferences['monit'])
+    assert_equal('exec', ticket_2.preferences['monit']['action'])
+    assert_equal('web5.example.net', ticket_2_1.preferences['monit']['host'])
+    assert_equal('loadavg(1min) of 10.7 matches resource limit [loadavg(1min) > 6.0]', ticket_2_1.preferences['monit']['description'])
+    assert_equal('web5.example.net', ticket_2_1.preferences['monit']['service'])
+    assert_equal('CRITICAL', ticket_2_1.preferences['monit']['state'])
+    assert_equal(ticket_2_1.id, ticket_2.id)
+  end
+
+end