lib/flapjack/data/notification.rb in flapjack-1.6.0 vs lib/flapjack/data/notification.rb in flapjack-2.0.0b1
- old
+ new
@@ -1,298 +1,39 @@
#!/usr/bin/env ruby
-require 'flapjack/data/contact'
-require 'flapjack/data/event'
-require 'flapjack/data/message'
+# 'Notification' refers to the template object created when an event occurs,
+# from which individual 'Message' objects are created, one for each
+# contact+media recipient.
+require 'zermelo/records/redis'
+
module Flapjack
module Data
class Notification
- attr_reader :type, :event_id, :state
+ include Zermelo::Records::RedisSet
+ include Flapjack::Data::Extensions::ShortName
- 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
+ define_attributes :severity => :string,
+ :duration => :integer,
+ :condition_duration => :float,
+ :event_hash => :string
- def self.severity_for_event(event, max_notified_severity)
- if ([event.state, max_notified_severity] & ['critical', 'test_notifications']).any?
- 'critical'
- elsif [event.state, max_notified_severity].include?('warning')
- 'warning'
- elsif [event.state, max_notified_severity].include?('unknown')
- 'unknown'
- else
- 'ok'
- end
- end
+ belongs_to :check, :class_name => 'Flapjack::Data::Check',
+ :inverse_of => :notifications
- def self.add(queue, event, opts = {})
- raise "Redis connection not set" unless redis = opts[:redis]
+ has_one :state, :class_name => 'Flapjack::Data::State',
+ :inverse_of => :notification, :after_clear => :destroy_state
- last_state = opts[:last_state] || {}
-
- tag_data = event.tags.is_a?(Set) ? event.tags.to_a : nil
- notif = {'event_id' => event.id,
- 'event_hash' => event.id_hash,
- 'state' => event.state,
- 'summary' => event.summary,
- 'details' => event.details,
- 'time' => event.time,
- 'duration' => event.duration,
- 'count' => event.counter,
- 'last_state' => last_state[:state],
- 'last_summary' => last_state[:summary],
- 'state_duration' => opts[:state_duration],
- 'type' => opts[:type] || type_for_event(event),
- 'severity' => opts[:severity],
- 'tags' => tag_data }
-
- redis.rpush(queue, Flapjack.dump_json(notif))
+ def self.destroy_state(notification_id, st_id)
+ # won't be deleted if still referenced elsewhere -- see the State
+ # before_destroy callback
+ Flapjack::Data::State.intersect(:id => st_id).destroy_all
end
- def self.next(queue, opts = {})
- raise "Redis connection not set" unless redis = opts[:redis]
+ validates :severity,
+ :inclusion => {:in => Flapjack::Data::Condition.unhealthy.keys +
+ Flapjack::Data::Condition.healthy.keys }
- 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 = ::Flapjack.load_json( raw )
- rescue Oj::Error => e
- if options[:logger]
- options[:logger].warn("Error deserialising notification json: #{e}, raw json: #{raw.inspect}")
- end
- return
- end
- return if 'shutdown'.eql?(parsed['type'])
- self.new( parsed )
- end
-
- def ok?
- @state && ['ok', 'up'].include?(@state)
- end
-
- def acknowledgement?
- @state && ['acknowledgement'].include?(@state)
- end
-
- def test?
- @state && ['test_notifications'].include?(@state)
- end
-
- def contents
- @contents ||= {'event_id' => @event_id,
- 'event_hash' => @event_hash,
- 'state' => @state,
- 'summary' => @summary,
- 'duration' => @duration,
- 'last_state' => @last_state,
- 'last_summary' => @last_summary,
- 'state_duration' => @state_duration,
- 'details' => @details,
- 'time' => @time,
- 'notification_type' => @type,
- 'event_count' => @count,
- 'tags' => @tags
- }
- end
-
- def messages(contacts, opts = {})
- return [] if contacts.nil? || contacts.empty?
-
- default_timezone = opts[:default_timezone]
- logger = opts[:logger]
-
- @messages ||= contacts.collect do |contact|
- contact_id = contact.id
- rules = contact.notification_rules
- media = contact.media
-
- logger.debug "Notification#messages: creating messages for contact: #{contact_id} " +
- "event_id: \"#{@event_id}\" state: #{@state} event_tags: #{Flapjack.dump_json(@tags)} media: #{media.inspect}"
- rlen = rules.length
- logger.debug "found #{rlen} rule#{(rlen == 1) ? '' : 's'} for contact #{contact_id}"
-
- media_to_use = if rules.empty?
- media
- else
- # matchers are rules of the contact that have matched the current event
- # for time, entity and tags
- matchers = rules.select do |rule|
- begin
- logger.debug("considering rule with entities: #{rule.entities}, entities regex: #{rule.regex_entities},
- tags: #{Flapjack.dump_json(rule.tags)} and regex tags: #{Flapjack.dump_json(rule.regex_tags)}")
- rule_has_tags = rule.tags ? (rule.tags.length > 0) : false
- rule_has_regex_tags = rule.regex_tags ? (rule.regex_tags.length > 0) : false
- rule_has_entities = rule.entities ? (rule.entities.length > 0) : false
- rule_has_regex_entities = rule.regex_entities ? (rule.regex_entities.length > 0) : false
-
- matches_tags = rule_has_tags ? rule.match_tags?(@tags) : true
- matches_regex_tags = rule_has_regex_tags ? rule.match_regex_tags?(@tags) : true
- matches_entity = rule_has_entities ? rule.match_entity?(@event_id) : true
- matches_regex_entities = rule_has_regex_entities ? rule.match_regex_entities?(@event_id) : true
-
- ((matches_entity && matches_regex_entities && matches_tags && matches_regex_tags) || ! rule.is_specific?) &&
- rule_occurring_now?(rule, :contact => contact, :default_timezone => default_timezone,
- :logger => logger)
- rescue RegexpError => e
- logger.error "rule with entities regex: #{rule.regex_entities} and regex tags: #{Flapjack.dump_json(rule.regex_tags)} has invalid regex: #{e.message}"
- false
- end
- end
-
- logger.debug "#{matchers.length} matchers remain for this contact after time, entity and tags are matched:"
- matchers.each do |matcher|
- logger.debug " - #{matcher.to_jsonapi}"
- end
-
- # delete any general matchers if there are more specific matchers left
- if matchers.any? {|matcher| matcher.is_specific? }
-
- num_matchers = matchers.length
-
- matchers.reject! {|matcher| !matcher.is_specific? }
-
- if num_matchers != matchers.length
- logger.debug("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}")
- matchers.each do |matcher|
- logger.debug " - #{matcher.to_jsonapi}"
- end
- end
- end
-
- # delete media based on blackholes
- blackhole_matchers = matchers.map {|matcher| matcher.blackhole?(@severity) ? matcher : nil }.compact
- if blackhole_matchers.length > 0
- logger.debug "dropping this media as #{blackhole_matchers.length} blackhole matchers are present:"
- blackhole_matchers.each {|bm|
- logger.debug " - #{bm.to_jsonapi}"
- }
- next
- else
- logger.debug "no blackhole matchers matched"
- end
-
- rule_media = matchers.collect{|matcher|
- matcher.media_for_severity(@severity)
- }.flatten.uniq
-
- logger.debug "collected media_for_severity(#{@severity}): #{rule_media}"
- rule_media = rule_media.reject {|medium|
- contact.drop_notifications?(:media => medium,
- :check => @event_id,
- :state => @state)
- }
-
- logger.debug "media after contact_drop?: #{rule_media}"
-
- media.select {|medium, address| rule_media.include?(medium) }
- end
-
- logger.debug "media_to_use: #{media_to_use}"
-
- # here begins rollup madness
- media_to_use.each_pair.inject([]) do |ret, (media, address)|
- rollup_type = nil
-
- contact.add_alerting_check_for_media(media, @event_id) unless ok? || acknowledgement? || test?
-
- # expunge checks in (un)scheduled maintenance from the alerting set
- recovered = contact.clean_alerting_checks_for_media(media)
- logger.debug("cleaned alerting checks for #{media}: recovered? #{recovered}")
-
- # pagerduty is an example of a medium which should never be rolled up
- unless ['pagerduty'].include?(media)
- alerting_checks = contact.count_alerting_checks_for_media(media)
- rollup_threshold = contact.rollup_threshold_for_media(media)
-
- case
- when rollup_threshold.nil?
- # back away slowly
- when alerting_checks >= rollup_threshold
- next ret if contact.drop_rollup_notifications_for_media?(media)
- contact.update_sent_rollup_alert_keys_for_media(media, :delete => ok?)
- rollup_type = 'problem'
- when recovered
- # alerting checks was just cleaned such that it is now below the rollup threshold
- contact.update_sent_rollup_alert_keys_for_media(media, :delete => true)
- rollup_type = 'recovery'
- end
- logger.debug "rollup decisions: #{@event_id} #{@state} #{media} #{address} rollup_type: #{rollup_type}"
- end
-
- m = Flapjack::Data::Message.for_contact(contact,
- :medium => media, :address => address, :rollup => rollup_type)
- ret << m
- ret
- end
- end.compact.flatten # @messages ||= contacts.collect do ...
- end
-
- private
-
- # created from parsed JSON, so opts keys are in strings
- def initialize(opts = {})
- @event_id = opts['event_id']
- @state = opts['state']
- @summary = opts['summary']
- @details = opts['details']
- @time = opts['time']
- @count = opts['count']
- @duration = opts['duration']
- @last_state = opts['last_state']
- @last_summary = opts['last_summary']
- @state_duration = opts['state_duration']
- @type = opts['type']
- @severity = opts['severity']
- @tags = opts['tags'].is_a?(Array) ? Set.new(opts['tags']) : nil
- 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]
- def_tz = options[:default_timezone]
-
- return true if rule.time_restrictions.nil? or rule.time_restrictions.empty?
-
- time_zone = contact.time_zone || def_tz
- usertime = time_zone.now
-
- rule.time_restrictions.any? do |tr|
- # add contact's time_zone to the time restriction schedule
- schedule = Flapjack::Data::NotificationRule.
- time_restriction_to_icecube_schedule(tr, time_zone, :logger => options[:logger])
- schedule && schedule.occurring_at?(usertime)
- end
- end
-
end
end
end
-