Added nagios integration.

This commit is contained in:
Martin Edenhofer 2016-04-12 15:32:45 +02:00
parent 7c75fd35bd
commit 5ac1bd108a
7 changed files with 482 additions and 82 deletions

View file

@ -1,83 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
module Channel::Filter::Icinga
class Channel::Filter::Icinga < Channel::Filter::MonitoringBase
# rubocop:disable Style/ClassVars
@@integration = 'icinga'
# rubocop:enable Style/ClassVars
# according
# https://github.com/Icinga/icinga2/blob/master/etc/icinga2/scripts/mail-service-notification.sh
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#host-states
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#service-states
def self.run(_channel, mail)
return if !Setting.get("#{@@integration}_integration")
sender = Setting.get("#{@@integration}_sender")
auto_close = Setting.get("#{@@integration}_auto_close")
auto_close_state_id = Setting.get("#{@@integration}_auto_close_state_id")
state_recovery_match = 'OK'
return if !mail[:from]
return if !mail[:body]
sender_user_id = mail[ 'x-zammad-customer-id'.to_sym ]
return if !sender_user_id
# check if sender is monitoring
return if !mail[:from].match(/#{sender}/i)
# get mail attibutes like host and state
result = {}
mail[:body].gsub(%r{(Service|Host|State|Address|Date/Time|Additional\sInfo):(.+?)\n}i) { |_match|
key = $1
if key
key = key.downcase
end
value = $2
if value
value.strip!
end
result[key] = value
}
# check if ticket with host is open
customer = User.lookup(id: sender_user_id)
# follow up detection by meta data
open_states = Ticket::State.by_category('open')
Ticket.where(state: open_states).each {|ticket|
next if !ticket.preferences
next if !ticket.preferences['integration']
next if ticket.preferences['integration'] != @@integration
next if !ticket.preferences[@@integration]
next if !ticket.preferences[@@integration]['host']
next if ticket.preferences[@@integration]['host'] != result['host']
next if ticket.preferences[@@integration]['service'] != result['service']
# found open ticket for service+host
mail[ 'x-zammad-ticket-id'.to_sym ] = ticket.id
# check if service is recovered
if auto_close && result['state'].match(/#{state_recovery_match}/i)
state = Ticket::State.lookup(id: auto_close_state_id)
if state
mail[ 'x-zammad-ticket-followup-state'.to_sym ] = state.name
end
end
return true
}
# new ticket, set meta data
if !mail[ 'x-zammad-ticket-id'.to_sym ]
if !mail[ 'x-zammad-ticket-preferences'.to_sym ]
mail[ 'x-zammad-ticket-preferences'.to_sym ] = {}
end
preferences = {}
preferences['integration'] = @@integration
preferences[@@integration] = result
preferences.each {|key, value|
mail[ 'x-zammad-ticket-preferences'.to_sym ][key] = value
}
end
end
end

View file

@ -0,0 +1,88 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::Filter::MonitoringBase
# rubocop:disable Style/ClassVars
@@integration = 'to_be_defined'
# rubocop:enable Style/ClassVars
# according
# Icinga
# https://github.com/Icinga/icinga2/blob/master/etc/icinga2/scripts/mail-service-notification.sh
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#host-states
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#service-states
# Nagios
# https://github.com/NagiosEnterprises/nagioscore/blob/754218e67653929a58938b99ef6b6039b6474fe4/sample-config/template-object/commands.cfg.in#L35
def self.run(_channel, mail)
return if !Setting.get("#{@@integration}_integration")
sender = Setting.get("#{@@integration}_sender")
auto_close = Setting.get("#{@@integration}_auto_close")
auto_close_state_id = Setting.get("#{@@integration}_auto_close_state_id")
state_recovery_match = 'OK'
return if !mail[:from]
return if !mail[:body]
sender_user_id = mail[ 'x-zammad-customer-id'.to_sym ]
return if !sender_user_id
# check if sender is monitoring
return if !mail[:from].match(/#{sender}/i)
# get mail attibutes like host and state
result = {}
mail[:body].gsub(%r{(Service|Host|State|Address|Date/Time|Additional\sInfo):(.+?)\n}i) { |_match|
key = $1
if key
key = key.downcase
end
value = $2
if value
value.strip!
end
result[key] = value
}
# check if ticket with host is open
customer = User.lookup(id: sender_user_id)
# follow up detection by meta data
open_states = Ticket::State.by_category('open')
Ticket.where(state: open_states).each {|ticket|
next if !ticket.preferences
next if !ticket.preferences['integration']
next if ticket.preferences['integration'] != @@integration
next if !ticket.preferences[@@integration]
next if !ticket.preferences[@@integration]['host']
next if ticket.preferences[@@integration]['host'] != result['host']
next if ticket.preferences[@@integration]['service'] != result['service']
# found open ticket for service+host
mail[ 'x-zammad-ticket-id'.to_sym ] = ticket.id
# check if service is recovered
if auto_close && result['state'].match(/#{state_recovery_match}/i)
state = Ticket::State.lookup(id: auto_close_state_id)
if state
mail[ 'x-zammad-ticket-followup-state'.to_sym ] = state.name
end
end
return true
}
# new ticket, set meta data
if !mail[ 'x-zammad-ticket-id'.to_sym ]
if !mail[ 'x-zammad-ticket-preferences'.to_sym ]
mail[ 'x-zammad-ticket-preferences'.to_sym ] = {}
end
preferences = {}
preferences['integration'] = @@integration
preferences[@@integration] = result
preferences.each {|key, value|
mail[ 'x-zammad-ticket-preferences'.to_sym ][key] = value
}
end
end
end

View file

@ -0,0 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::Filter::Nagios < Channel::Filter::MonitoringBase
# rubocop:disable Style/ClassVars
@@integration = 'nagios'
# rubocop:enable Style/ClassVars
end

View file

@ -0,0 +1,98 @@
class AddNagiosIntegration < ActiveRecord::Migration
def up
Setting.create_if_not_exists(
title: 'Define postmaster filter.',
name: '5100_postmaster_filter_nagios',
area: 'Postmaster::PreFilter',
description: 'Define postmaster filter for manage Nagios (http://www.nagios.org) emails.',
options: {},
state: 'Channel::Filter::Nagios',
frontend: false
)
Setting.create_if_not_exists(
title: 'Nagios integration',
name: 'nagios_integration',
area: 'Integration::Nagios',
description: 'Define if Nagios (http://www.nagios.org) is enabled or not.',
options: {
form: [
{
display: '',
null: true,
name: 'nagios_integration',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
state: false,
preferences: { prio: 1 },
frontend: false
)
Setting.create_if_not_exists(
title: 'Sender',
name: 'nagios_sender',
area: 'Integration::Nagios',
description: 'Define the sender email address of Nagios emails.',
options: {
form: [
{
display: '',
null: false,
name: 'nagios_sender',
tag: 'input',
},
],
},
state: 'nagios@monitoring.example.com',
frontend: false,
preferences: { prio: 2 },
)
Setting.create_if_not_exists(
title: 'Auto close',
name: 'nagios_auto_close',
area: 'Integration::Nagios',
description: 'Define if tickets should be closed if service is recovered.',
options: {
form: [
{
display: '',
null: true,
name: 'nagios_auto_close',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
state: true,
preferences: { prio: 3 },
frontend: false
)
Setting.create_if_not_exists(
title: 'Auto close state',
name: 'nagios_auto_close_state_id',
area: 'Integration::Nagios',
description: 'Define the ticket state of auto closed tickets.',
options: {
form: [
{
display: '',
null: false,
name: 'nagios_auto_close_state_id',
tag: 'select',
relation: 'TicketState',
},
],
},
state: 4,
preferences: { prio: 4 },
frontend: false
)
end
end

View file

@ -1588,6 +1588,15 @@ Setting.create_if_not_exists(
state: 'Channel::Filter::Icinga',
frontend: false
)
Setting.create_if_not_exists(
title: 'Define postmaster filter.',
name: '5100_postmaster_filter_nagios',
area: 'Postmaster::PreFilter',
description: 'Define postmaster filter for manage Nagios (http://www.nagios.org) emails.',
options: {},
state: 'Channel::Filter::Nagios',
frontend: false
)
Setting.create_if_not_exists(
title: 'Icinga integration',
name: 'icinga_integration',
@ -1673,6 +1682,91 @@ Setting.create_if_not_exists(
preferences: { prio: 4 },
frontend: false
)
Setting.create_if_not_exists(
title: 'Nagios integration',
name: 'nagios_integration',
area: 'Integration::Nagios',
description: 'Define if Nagios (http://www.nagios.org) is enabled or not.',
options: {
form: [
{
display: '',
null: true,
name: 'nagios_integration',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
state: false,
preferences: { prio: 1 },
frontend: false
)
Setting.create_if_not_exists(
title: 'Sender',
name: 'nagios_sender',
area: 'Integration::Nagios',
description: 'Define the sender email address of Nagios emails.',
options: {
form: [
{
display: '',
null: false,
name: 'nagios_sender',
tag: 'input',
},
],
},
state: 'nagios@monitoring.example.com',
frontend: false,
preferences: { prio: 2 },
)
Setting.create_if_not_exists(
title: 'Auto close',
name: 'nagios_auto_close',
area: 'Integration::Nagios',
description: 'Define if tickets should be closed if service is recovered.',
options: {
form: [
{
display: '',
null: true,
name: 'nagios_auto_close',
tag: 'boolean',
options: {
true => 'yes',
false => 'no',
},
},
],
},
state: true,
preferences: { prio: 3 },
frontend: false
)
Setting.create_if_not_exists(
title: 'Auto close state',
name: 'nagios_auto_close_state_id',
area: 'Integration::Nagios',
description: 'Define the ticket state of auto closed tickets.',
options: {
form: [
{
display: '',
null: false,
name: 'nagios_auto_close_state_id',
tag: 'select',
relation: 'TicketState',
},
],
},
state: 4,
preferences: { prio: 4 },
frontend: false
)
signature = Signature.create_if_not_exists(
id: 1,

View file

@ -3,6 +3,11 @@ require 'test_helper'
class IntegrationIcingaTest < ActiveSupport::TestCase
# according
# https://github.com/Icinga/icinga2/blob/master/etc/icinga2/scripts/mail-service-notification.sh
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#host-states
# http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/monitoring-basics#service-states
test 'base tests' do
Setting.set('icinga_integration', true)
@ -14,7 +19,7 @@ User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-1@monitoring.znuny.com>
Message-Id: <20160131094621.29ECD400F29C-icinga-1@monitoring.znuny.com>
From: icinga_not_matching@monitoring.example.com (icinga)
***** Icinga *****
@ -46,7 +51,7 @@ User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-2@monitoring.znuny.com>
Message-Id: <20160131094621.29ECD400F29C-icinga-2@monitoring.znuny.com>
From: icinga@monitoring.example.com (icinga)
***** Icinga *****
@ -82,7 +87,7 @@ User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-3@monitoring.znuny.com>
Message-Id: <20160131094621.29ECD400F29C-icinga-3@monitoring.znuny.com>
From: icinga@monitoring.example.com (icinga)
***** Icinga *****
@ -119,7 +124,7 @@ User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-4@monitoring.znuny.com>
Message-Id: <20160131094621.29ECD400F29C-icinga-4@monitoring.znuny.com>
From: icinga@monitoring.example.com (icinga)
***** Icinga *****
@ -156,7 +161,7 @@ User-Agent: Heirloom mailx 12.5 7/5/10
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-5@monitoring.znuny.com>
Message-Id: <20160131094621.29ECD400F29C-icinga-5@monitoring.znuny.com>
From: icinga@monitoring.example.com (icinga)
***** Icinga *****

View file

@ -0,0 +1,184 @@
# encoding: utf-8
require 'test_helper'
class IntegrationNagiosTest < ActiveSupport::TestCase
# according
# https://github.com/NagiosEnterprises/nagioscore/blob/754218e67653929a58938b99ef6b6039b6474fe4/sample-config/template-object/commands.cfg.in#L35
test 'base tests' do
Setting.set('nagios_integration', true)
# not matching sender
email_raw_string = "To: support@example.com
Subject: ** PROBLEM Service Alert: host.internal.loc/CPU Load is WARNING **
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-nagios-1@monitoring.znuny.com>
From: nagios_not_matching@monitoring.example.com (nagios)
***** Nagios *****
Notification Type: PROBLEM
Service: CPU Load
Host: host.internal.loc
Address: 1.1.1.1
State: PROBLEM
Date/Time: 2016-01-31 10:46:20 +0100
Additional Info:
WARNING - load average: 3.44, 0.99, 0.35
"
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
assert_equal('new', ticket_p.state.name)
assert(ticket_p.preferences)
assert_not(ticket_p.preferences['integration'])
assert_not(ticket_p.preferences['nagios'])
# matching sender - CPU Load/host.internal.loc
email_raw_string = "To: support@example.com
Subject: ** PROBLEM Service Alert: host.internal.loc/CPU Load is WARNING **
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-nagios-2@monitoring.znuny.com>
From: nagios@monitoring.example.com (nagios)
***** Nagios *****
Notification Type: PROBLEM
Service: CPU Load
Host: host.internal.loc
Address: 1.1.1.1
State: WARNING
Date/Time: 2016-01-31 10:46:20 +0100
Additional Info:
WARNING - load average: 3.44, 0.99, 0.35
"
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['integration'])
assert_equal('nagios', ticket_1.preferences['integration'])
assert(ticket_1.preferences['nagios'])
assert_equal('host.internal.loc', ticket_1.preferences['nagios']['host'])
assert_equal('CPU Load', ticket_1.preferences['nagios']['service'])
assert_equal('WARNING', ticket_1.preferences['nagios']['state'])
# matching sender - Disk Usage 123/host.internal.loc
email_raw_string = "To: support@example.com
Subject: ** PROBLEM Service Alert: host.internal.loc/Disk Usage 123 is WARNING **
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-nagios-3@monitoring.znuny.com>
From: nagios@monitoring.example.com (nagios)
***** Nagios *****
Notification Type: PROBLEM
Service: Disk Usage 123
Host: host.internal.loc
Address: 1.1.1.1
State: WARNING
Date/Time: 2016-01-31 10:46:20 +0100
Additional Info:
WARNING - load average: 3.44, 0.99, 0.35
"
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['integration'])
assert_equal('nagios', ticket_2.preferences['integration'])
assert(ticket_2.preferences['nagios'])
assert_equal('host.internal.loc', ticket_2.preferences['nagios']['host'])
assert_equal('Disk Usage 123', ticket_2.preferences['nagios']['service'])
assert_equal('WARNING', ticket_2.preferences['nagios']['state'])
assert_not_equal(ticket_2.id, ticket_1.id)
# matching sender - follow up - CPU Load/host.internal.loc
email_raw_string = "To: support@example.com
Subject: ** PROBLEM Service Alert: host.internal.loc/CPU Load is WARNING **
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-nagios-4@monitoring.znuny.com>
From: nagios@monitoring.example.com (nagios)
***** Nagios *****
Notification Type: PROBLEM
Service: CPU Load
Host: host.internal.loc
Address: 1.1.1.1
State: WARNING
Date/Time: 2016-01-31 10:46:20 +0100
Additional Info:
WARNING - load average: 3.44, 0.99, 0.35
"
ticket_1_1, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
assert_equal('new', ticket_1_1.state.name)
assert(ticket_1_1.preferences)
assert(ticket_1_1.preferences['integration'])
assert_equal('nagios', ticket_1_1.preferences['integration'])
assert(ticket_1_1.preferences['nagios'])
assert_equal('host.internal.loc', ticket_1_1.preferences['nagios']['host'])
assert_equal('CPU Load', ticket_1_1.preferences['nagios']['service'])
assert_equal('WARNING', ticket_1_1.preferences['nagios']['state'])
assert_equal(ticket_1.id, ticket_1_1.id)
# matching sender - follow up - recovery - CPU Load/host.internal.loc
email_raw_string = "To: support@example.com
Subject: ** PROBLEM Service Alert: host.internal.loc/CPU Load is WARNING **
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable
Message-Id: <20160131094621.29ECD400F29C-nagios-5@monitoring.znuny.com>
From: nagios@monitoring.example.com (nagios)
***** Nagios *****
Notification Type: PROBLEM
Service: CPU Load
Host: host.internal.loc
Address: 1.1.1.1
State: OK
Date/Time: 2016-01-31 10:48:02 +0100
Additional Info:
"
ticket_1_2, article_p, user_p, mail = Channel::EmailParser.new.process({}, email_raw_string)
assert_equal(ticket_1.id, ticket_1_2.id)
assert_equal('closed', ticket_1_2.state.name)
assert(ticket_1_2.preferences)
assert(ticket_1_2.preferences['integration'])
assert_equal('nagios', ticket_1_2.preferences['integration'])
assert(ticket_1_2.preferences['nagios'])
assert_equal('host.internal.loc', ticket_1_2.preferences['nagios']['host'])
assert_equal('CPU Load', ticket_1_2.preferences['nagios']['service'])
assert_equal('WARNING', ticket_1_2.preferences['nagios']['state'])
#Setting.set('nagios_integration', false)
end
end