#!/usr/bin/env ruby require 'monitor' require 'socket' require 'chronic_duration' require 'rexml/document' require 'xmpp4r/query' require 'xmpp4r/muc' require 'flapjack/redis_proxy' require 'flapjack/record_queue' require 'flapjack/utility' require 'flapjack/exceptions' require 'flapjack/version' require 'flapjack/data/alert' require 'flapjack/data/check' require 'flapjack/data/event' require 'flapjack/data/scheduled_maintenance' require 'flapjack/data/unscheduled_maintenance' require 'flapjack/data/tag' module Flapjack module Gateways module Jabber class Notifier include Flapjack::Utility attr_accessor :siblings def initialize(options = {}) @lock = options[:lock] @config = options[:config] # TODO support for config reloading @queue = Flapjack::RecordQueue.new(@config['queue'] || 'jabber_notifications', Flapjack::Data::Alert) end def start begin Zermelo.redis = Flapjack.redis loop do @lock.synchronize do @queue.foreach {|alert| handle_alert(alert) } end @queue.wait end ensure Flapjack.redis.quit end end def stop_type :exception end private def handle_alert(alert) @bot ||= @siblings && @siblings.detect {|sib| sib.respond_to?(:announce) } if @bot.nil? Flapjack.logger.warn("jabber bot not running, won't announce") return end check = alert.check check_name = check.name address = alert.address state = alert.state Flapjack.logger.debug("processing jabber notification address: #{address}, " + "check: '#{check_name}', state: #{state}, summary: #{alert.summary}") # event_count = alert.event_count @ack_str = if state.eql?('ok') || ['test', 'acknowledgement'].include?(alert.type) nil else "#{@bot.alias}: ACKID #{alert.event_hash}" end message_type = alert.rollup ? 'rollup' : 'alert' message_template_erb, message_template = load_template(@config['templates'], message_type, 'text', File.join(File.dirname(__FILE__), 'jabber')) @alert = alert bnd = binding message = nil begin message = message_template_erb.result(bnd).chomp rescue Flapjack.logger.error "Error while executing the ERB for a jabber message, " + "ERB being executed: #{message_template}" raise end # FIXME: should also check if presence has been established in any group chat rooms that are # configured before starting to process events, otherwise the first few may get lost (send # before joining the group chat rooms) @bot.announce(address, message) end end class Interpreter CHECK_MATCH_FRAGMENT = "(?:checks\s+(?:matching\s+\/(.+?)\/|with\s+tag\s+(.+?))|(.+?))" attr_accessor :siblings include Flapjack::Utility def initialize(opts = {}) @lock = opts[:lock] @stop_cond = opts[:stop_condition] @config = opts[:config] @boot_time = opts[:boot_time] @should_quit = false @messages = [] end def start Zermelo.redis = Flapjack.redis @lock.synchronize do @bot = self.siblings ? self.siblings.detect {|sib| sib.respond_to?(:announce)} : nil until @messages.empty? && @should_quit while msg = @messages.pop Flapjack.logger.info "interpreter received #{msg.inspect}" interpret(msg[:room], msg[:nick], msg[:time], msg[:message]) end @stop_cond.wait_while { @messages.empty? && !@should_quit } end end Flapjack.redis.quit end def stop_type :signal end def receive_message(room, nick, time, msg) @lock.synchronize do @messages += [{:room => room, :nick => nick, :time => time, :message => msg}] @stop_cond.signal end end def get_check_details(check, at_time) start_range = Zermelo::Filters::IndexRange.new(nil, at_time, :by_score => true) end_range = Zermelo::Filters::IndexRange.new(at_time, nil, :by_score => true) sched = check.scheduled_maintenances.intersect(:start_time => start_range, :end_time => end_range).all.max_by(&:end_time) unsched = check.unscheduled_maintenances.intersect(:start_time => start_range, :end_time => end_range).all.max_by(&:end_time) out = '' if sched.nil? && unsched.nil? out += "Not in scheduled or unscheduled maintenance.\n" else if sched.nil? out += "Not in scheduled maintenance.\n" else remain = time_period_in_words( (sched.end_time - at_time).ceil ) # TODO a simpler time format? out += "In scheduled maintenance: #{sched.start_time} -> #{sched.end_time} (#{remain} remaining)\n" end if unsched.nil? out += "Not in unscheduled maintenance.\n" else remain = time_period_in_words( (unsched.end_time - at_time).ceil ) # TODO a simpler time format? out += "In unscheduled maintenance: #{unsched.start_time} -> #{unsched.end_time} (#{remain} remaining)\n" end end out end def derive_check_ids_for(pattern, tag_name, check_name, options = {}) lock_klasses = options[:lock_klasses] || [] deriver = if !pattern.nil? && !pattern.strip.empty? proc { checks = begin Flapjack::Data::Check.intersect(:name => Regexp.new(pattern.strip)) rescue RegexpError nil end if checks.nil? "Error parsing /#{pattern.strip}/" else num_checks = checks.count if num_checks == 0 "No checks match /#{pattern.strip}/" else checks = checks.sort(:name, :limit => options[:limit]) if options[:limit] yield(checks.ids, "matching /#{pattern.strip}/", num_checks) if block_given? end end } elsif !tag_name.nil? && !tag_name.strip.empty? lock_klasses.unshift(Flapjack::Data::Tag) proc { tag = Flapjack::Data::Tag.intersect(:name => tag_name.strip).all.first if tag.nil? "No tag '#{tag_name.strip}'" else checks = tag.checks num_checks = checks.count if num_checks == 0 "No checks with tag '#{tag_name.strip}'" else checks = checks.sort(:name, :limit => options[:limit]) if options[:limit] yield(checks.ids, "with tag '#{tag_name}'", num_checks) if block_given? end end } elsif !check_name.nil? && !check_name.strip.empty? proc { checks = Flapjack::Data::Check.intersect(:name => check_name.strip) if checks.empty? "No check exists with name '#{check_name.strip}'" else num_checks = checks.count checks = checks.sort(:name, :limit => options[:limit]) if options[:limit] yield(checks.ids, "with name '#{check_name.strip}'", num_checks) if block_given? end } end return if deriver.nil? if lock_klasses.empty? Flapjack::Data::Check.lock(&deriver) else Flapjack::Data::Check.lock(*lock_klasses, &deriver) end end def interpret(room, nick, time, command) msg = nil action = nil check = nil begin case command when /^help\s*$/ msg = "commands: \n" + " find (number) checks matching /pattern/\n" + " find (number) checks with tag \n" + " state of \n" + " state of checks matching /pattern/\n" + " state of checks with tag \n" + " tell me about \n" + " tell me about (number) checks matching /pattern/\n" + " tell me about (number) checks with tag \n" + " ACKID [ duration: