lib/flapjack/data/notification.rb in flapjack-0.7.18 vs lib/flapjack/data/notification.rb in flapjack-0.7.19

- old
+ new

@@ -1,146 +1,211 @@ #!/usr/bin/env ruby +require 'oj' + require 'flapjack/data/contact' require 'flapjack/data/event' require 'flapjack/data/message' module Flapjack module Data class Notification - attr_reader :event, :type, :max_notified_severity, :contacts, - :default_timezone, :last_state + attr_reader :type, :event_id, :event_state, :event_count - def self.for_event(event, opts = {}) - self.new(:event => event, - :type => opts[:type], - :max_notified_severity => opts[:max_notified_severity], - :contacts => opts[:contacts], - :default_timezone => opts[:default_timezone], - :last_state => opts[:last_state], - :logger => opts[:logger]) + def self.type_for_event(event) + case event.type + when 'service' + case event.state + when 'ok' + 'recovery' + when 'warning', 'critical', 'unknown' + 'problem' + end + when 'action' + case event.state + when 'acknowledgement' + 'acknowledgement' + when 'test_notifications' + 'test' + end + else + 'unknown' + end end - def messages - return [] if contacts.nil? || contacts.empty? - - event_id = event.id - event_state = event.state - - severity = if ([event_state, max_notified_severity] & ['critical', 'unknown', 'test_notifications']).any? + def self.severity_for_event(event, max_notified_severity) + if ([event.state, max_notified_severity] & ['critical', 'unknown', 'test_notifications']).any? 'critical' - elsif [event_state, max_notified_severity].include?('warning') + elsif [event.state, max_notified_severity].include?('warning') 'warning' else 'ok' end + end - contents = {'event_id' => event_id, - 'state' => event_state, - 'summary' => event.summary, - 'last_state' => @last_state ? @last_state[:state] : nil, - 'last_summary' => @last_state ? @last_state[:summary] : nil, - 'details' => event.details, - 'time' => event.time, - 'duration' => event.duration || nil, - 'notification_type' => type, - 'max_notified_severity' => max_notified_severity } + def self.add(queue, event, opts = {}) + raise "Redis connection not set" unless redis = opts[:redis] + last_state = opts[:last_state] || {} + + notif = {'event_id' => event.id, + 'state' => event.state, + 'summary' => event.summary, + 'last_state' => last_state[:state], + 'last_summary' => last_state[:summary], + 'details' => event.details, + 'time' => event.time, + 'duration' => event.duration || nil, + 'type' => opts[:type] || type_for_event(event), + 'severity' => opts[:severity], + 'count' => event.counter } + + redis.rpush(queue, Oj.dump(notif)) + end + + def self.next(queue, opts = {}) + raise "Redis connection not set" unless redis = opts[:redis] + + defaults = { :block => true } + options = defaults.merge(opts) + + if options[:block] + raw = redis.blpop(queue, 0)[1] + else + raw = redis.lpop(queue) + return unless raw + end + begin + parsed = ::Oj.load( raw ) + rescue Oj::Error => e + if options[:logger] + options[:logger].warn("Error deserialising notification json: #{e}, raw json: #{raw.inspect}") + end + return nil + end + self.new( parsed ) + end + + def contents + @contents ||= {'event_id' => @event_id, + 'state' => @event_state, + 'summary' => @event_summary, + 'last_state' => @last_event_state, + 'last_summary' => @last_event_summary, + 'details' => @event_details, + 'time' => @event_time, + 'duration' => @event_duration, + 'notification_type' => @type, + 'event_count' => @event_count + } + end + + def messages(contacts, opts = {}) + return [] if contacts.nil? || contacts.empty? + + default_timezone = opts[:default_timezone] + logger = opts[:logger] + @messages ||= contacts.collect {|contact| contact_id = contact.id rules = contact.notification_rules media = contact.media - @logger.debug "considering messages for contact id #{contact_id} #{event_id} #{event_state} (media) #{media.inspect}" + logger.debug "considering messages for contact id #{contact_id} #{@event_id} #{@event_state} (media) #{media.inspect}" rlen = rules.length - @logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact" + logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact" media_to_use = if rules.empty? media else # matchers are rules of the contact that have matched the current event # for time and entity matchers = rules.select do |rule| - rule.match_entity?(event_id) && + rule.match_entity?(@event_id) && rule_occurring_now?(rule, :contact => contact, :default_timezone => default_timezone) end - @logger.debug "#{matchers.length} matchers remain for this contact:" + logger.debug "#{matchers.length} matchers remain for this contact:" matchers.each do |matcher| - @logger.debug "matcher: #{matcher.to_json}" + logger.debug "matcher: #{matcher.to_json}" end # delete any matchers for all entities if there are more specific matchers if matchers.any? {|matcher| matcher.is_specific? } - @logger.debug("general removal: found #{matchers.length} entity specific matchers") + logger.debug("general removal: found #{matchers.length} entity specific matchers") num_matchers = matchers.length matchers.reject! {|matcher| !matcher.is_specific? } if num_matchers != matchers.length - @logger.debug("notification: removal of general matchers when entity specific matchers are present: number of matchers changed from #{num_matchers} to #{matchers.length} for contact id: #{contact_id}") + logger.debug("notification: removal of general matchers when entity specific matchers are present: number of matchers changed from #{num_matchers} to #{matchers.length} for contact id: #{contact_id}") end end # delete media based on blackholes - next if matchers.any? {|matcher| matcher.blackhole?(event_state) } + next if matchers.any? {|matcher| matcher.blackhole?(@event_state) } - @logger.debug "notification: num matchers after removing blackhole matchers: #{matchers.size}" + logger.debug "notification: num matchers after removing blackhole matchers: #{matchers.size}" rule_media = matchers.collect{|matcher| - matcher.media_for_severity(severity) + matcher.media_for_severity(@severity) }.flatten.uniq - @logger.debug "notification: collected media_for_severity(#{severity}): #{rule_media}" - rule_media = rule_media.flatten.uniq.reject {|medium| + logger.debug "notification: collected media_for_severity(#{@severity}): #{rule_media}" + rule_media = rule_media.reject {|medium| contact.drop_notifications?(:media => medium, - :check => event_id, - :state => event_state) + :check => @event_id, + :state => @event_state) } - @logger.debug "notification: media after contact_drop?: #{rule_media}" + logger.debug "notification: media after contact_drop?: #{rule_media}" media.select {|medium, address| rule_media.include?(medium) } end - @logger.debug "notification: media_to_use: #{media_to_use}" + logger.debug "notification: media_to_use: #{media_to_use}" media_to_use.each_pair.inject([]) { |ret, (k, v)| m = Flapjack::Data::Message.for_contact(contact, - :notification_contents => contents, - :medium => k, :address => v) + :medium => k, :address => v) ret << m ret } }.compact.flatten end private + # created from parsed JSON, so opts keys are in strings def initialize(opts = {}) - raise "Event not passed" unless event = opts[:event] - @event = event - @type = opts[:type] - @max_notified_severity = opts[:max_notified_severity] - @contacts = opts[:contacts] - @default_timezone = opts[:default_timezone] - @last_state = opts[:last_state] - @logger = opts[:logger] + @event_id = opts['event_id'] + @event_state = opts['state'] + @event_summary = opts['summary'] + @event_details = opts['details'] + @event_time = opts['time'] + @event_duration = opts['duration'] + @event_count = opts['count'] + + @last_event_state = opts['last_state'] + @last_event_summary = opts['last_summary'] + + @type = opts['type'] + @severity = opts['severity'] end # # time restrictions match? # nil rule.time_restrictions matches # times (start, end) within time restrictions will have any UTC offset removed and will be # considered to be in the timezone of the contact def rule_occurring_now?(rule, options = {}) contact = options[:contact] - default_timezone = options[:default_timezone] + def_tz = options[:default_timezone] return true if rule.time_restrictions.nil? or rule.time_restrictions.empty? - timezone = contact.timezone(:default => default_timezone) + timezone = contact.timezone(:default => def_tz) usertime = timezone.now rule.time_restrictions.any? do |tr| # add contact's timezone to the time restriction schedule schedule = Flapjack::Data::NotificationRule.