diff options
author | Patrick J Cherry <patrick@bytemark.co.uk> | 2011-06-15 19:44:07 +0100 |
---|---|---|
committer | Patrick J Cherry <patrick@bytemark.co.uk> | 2011-06-15 19:44:07 +0100 |
commit | 42f14de6ab50d0e05f49f038e40b94b686701bf1 (patch) | |
tree | 33dc459cf3ae32de8956690a990f080321a67a83 | |
parent | 92e5d4e652aae161640359b41c0ba8dcaedac0ad (diff) |
* Added Alert#subject to return source when subject unspecified
* Added extra arg to acknowledge!
* Alert#all_raised / all_cleared does not returned ack'd alerts
* Added sanitising for source/subject
* Catch empty alert_group notifications in AlertChanged
* Tidied up alert_group
* Added skip bytemark_auth when RACK_ENV is development
* Tidied up logging in MauveThread, Timer, UdpServer
* Added extra convenience methods to Time and Date
* Moved working/daylight/dead_zone hours into Time
-rw-r--r-- | lib/mauve/alert.rb | 56 | ||||
-rw-r--r-- | lib/mauve/alert_group.rb | 58 | ||||
-rw-r--r-- | lib/mauve/auth_bytemark.rb | 2 | ||||
-rw-r--r-- | lib/mauve/http_server.rb | 7 | ||||
-rw-r--r-- | lib/mauve/mauve_thread.rb | 3 | ||||
-rw-r--r-- | lib/mauve/mauve_time.rb | 210 | ||||
-rw-r--r-- | lib/mauve/notification.rb | 4 | ||||
-rw-r--r-- | lib/mauve/timer.rb | 8 | ||||
-rw-r--r-- | lib/mauve/udp_server.rb | 15 |
9 files changed, 297 insertions, 66 deletions
diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index d0f134b..9cb2521 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -164,14 +164,19 @@ module Mauve end + # + # AlertGroup.matches must always return a an array of groups. + # def alert_group - AlertGroup.matches(self)[0] + AlertGroup.matches(self).first end def level self.alert_group.level end - + + def subject; attribute_get(:subject) || attribute_get(:source) ; end + def subject=(subject); set_changed_if_different( :subject, subject ); end def summary=(summary); set_changed_if_different( :summary, summary ); end @@ -188,11 +193,11 @@ module Mauve public - def acknowledge!(person) + def acknowledge!(person, ack_until = Time.now+3600) self.acknowledged_by = person.username self.acknowledged_at = MauveTime.now self.update_type = :acknowledged - self.will_unacknowledge_at = MauveTime.parse(acknowledged_at.to_s) + + self.will_unacknowledge_at = ack_until logger.error("Couldn't save #{self}") unless save AlertGroup.notify([self]) end @@ -252,7 +257,21 @@ module Mauve def cleared? !raised? end - + + def sort_tuple + # + # raised > cleared + # unacknowldged > acknowledged + # raise / clear time + # level + # + [(self.raised? ? 1 : 0), AlertGroup::LEVELS.index(self.level), (self.raised? ? self.raised_at : self.cleared_at), self.subject, self.summary].collect{|x| x.nil? ? "" : x } + end + + def <=>(other) + self.sort_tuple <=> other.sort_tuple + end + class << self # @@ -277,7 +296,7 @@ module Mauve # # def all_raised - all(:raised_at.not => nil, :cleared_at => nil) + all(:raised_at.not => nil, :cleared_at => nil) - all_acknowledged end def all_acknowledged @@ -285,7 +304,7 @@ module Mauve end def all_cleared - all(:cleared_at.not => nil) + all(:cleared_at.not => nil) - all_acknowledged end # Returns a hash of all the :urgent, :normal and :low alerts. @@ -303,6 +322,7 @@ module Mauve return hash end + # # Returns the next Alert that will have a timed action due on it, or nil # if none are pending. # @@ -317,6 +337,7 @@ module Mauve end end + # # Receive an AlertUpdate buffer from the wire. # def receive_update(update, reception_time = MauveTime.now) @@ -340,9 +361,15 @@ module Mauve logger.debug("Update received from a host #{time_offset}s behind") if time_offset.abs > 5 + # + # Make sure there is no HTML in the update source. + # + update.source = Alert.remove_html(update.source) + # Update each alert supplied # update.alert.each do |alert| + # # Infer some actions from our pure data structure (hmm, wonder if # this belongs in our protobuf-derived class? # @@ -368,7 +395,7 @@ module Mauve alert_db = first(:alert_id => alert.id, :source => update.source) || new(:alert_id => alert.id, :source => update.source) - + # # Work out what state the alert was in before receiving this update. # @@ -416,15 +443,12 @@ module Mauve alert_db.update_type = :raised end - # - # Changing any of these attributes causes the alert to be sent back - # out to the notification system with an update_type of :changed. - # - # Each of these should be just text, no HTML, so remove any tags we - # find. - # + if alert.subject and !alert.subject.empty? + alert_db.subject = Alert.remove_html(alert.subject) + else + alert_db.subject = alert_db.source + end - alert_db.subject = Alert.remove_html(alert.subject) if alert.subject && !alert.subject.empty? alert_db.summary = Alert.remove_html(alert.summary) if alert.summary && !alert.summary.empty? # diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb index adb9909..a8b1482 100644 --- a/lib/mauve/alert_group.rb +++ b/lib/mauve/alert_group.rb @@ -4,13 +4,26 @@ require 'log4r' module Mauve class AlertGroup < Struct.new(:name, :includes, :acknowledgement_time, :level, :notifications) - def to_s - "#<AlertGroup:#{name} (level #{level})>" - end + + # + # Define some constants, and the ordering. + # + URGENT = :urgent + NORMAL = :normal + LOW = :low + LEVELS = [LOW, NORMAL, URGENT] class << self + def matches(alert) - all.select { |alert_group| alert_group.matches_alert?(alert) } + grps = all.select { |alert_group| alert_group.matches_alert?(alert) } + + # + # Make sure we always match the last (and therefore default) group. + # + grps << all.last unless grps.include?(all.last) + + grps end # If there is any significant change to a set of alerts, the Alert @@ -26,18 +39,19 @@ module Mauve # # Make sure we've got a matching group # - logger.warn "no groups found for #{alert.id}" if groups.empty? + if groups.empty? + logger.warn "no groups found for #{alert}" + next + end # # Notify just the group that thinks this alert is the most urgent. # - %w(urgent normal low).each do |lvl| - this_group = groups.find{|grp| grp.level.to_s == lvl} - next if this_group.nil? - logger.info("notifying group #{this_group} of AlertID.#{alert.id} (matching #{lvl})") - this_group.notify(alert) - break - end + logger.warn "Found #{groups.length} matching groups for #{alert}" if groups.length > 1 + + this_group = groups.first + logger.info("notifying group #{this_group} of #{alert} (matching #{this_group.level})") + this_group.notify(alert) end end @@ -70,6 +84,10 @@ module Mauve self.level = :normal self.includes = Proc.new { true } end + + def to_s + "#<AlertGroup:#{name} (level #{level})>" + end # The list of current raised alerts in this group. # @@ -98,6 +116,14 @@ module Mauve # def notify(alert) # + # If there are no notifications defined. + # + if notifications.nil? + logger.warn("No notifications found for #{alert}") + return + end + + # # The notifications are specified in the config file. # notifications.each do |notification| @@ -105,6 +131,14 @@ module Mauve end end + # + # This sorts by priority (urgent first), and then alphabetically, so the + # first match is the most urgent. + # + def <=>(other) + [LEVELS.index(self.level), self.name] <=> [LEVELS.index(other.level), other.name] + end + end end diff --git a/lib/mauve/auth_bytemark.rb b/lib/mauve/auth_bytemark.rb index 9e0a6d1..52fa610 100644 --- a/lib/mauve/auth_bytemark.rb +++ b/lib/mauve/auth_bytemark.rb @@ -35,6 +35,8 @@ class AuthBytemark raise ArgumentError.new("Password must be a string, not a #{password.class}") if String != password.class raise ArgumentError.new("Login or/and password is/are empty.") if login.empty? || password.empty? + return false if ENV['RACK_ENV'].to_s == "development" + client = XMLRPC::Client.new(@srv,"/",@port,nil,nil,nil,nil,true,@timeout).proxy("bytemark.auth") begin diff --git a/lib/mauve/http_server.rb b/lib/mauve/http_server.rb index 4fd8b60..21c89e5 100644 --- a/lib/mauve/http_server.rb +++ b/lib/mauve/http_server.rb @@ -57,7 +57,6 @@ class RackErrorsProxy def initialize(l); @logger = l; end def write(msg) - #@logger.debug "NEXT LOG LINE COURTESY OF: "+caller.join("\n") case msg when String then @logger.info(msg.chomp) when Array then @logger.info(msg.join("\n")) @@ -68,6 +67,7 @@ class RackErrorsProxy alias_method :<<, :write alias_method :puts, :write + def flush; end end @@ -84,7 +84,7 @@ module Mauve include Singleton attr_accessor :port, :ip, :document_root - attr_accessor :session_secret # not used yet + attr_accessor :session_secret def initialize @port = 1288 @@ -94,6 +94,9 @@ module Mauve end def main_loop + # + # Sessions are kept for 8 days. + # @server = ::Thin::Server.new(@ip, @port, Rack::Session::Cookie.new(WebInterface.new, {:key => "mauvealert", :secret => @session_secret, :expire_after => 691200}), :signals => false) @server.start end diff --git a/lib/mauve/mauve_thread.rb b/lib/mauve/mauve_thread.rb index bd036db..3aba6fe 100644 --- a/lib/mauve/mauve_thread.rb +++ b/lib/mauve/mauve_thread.rb @@ -9,7 +9,7 @@ module Mauve end def logger - Log4r::Logger.new(self.class.to_s) + @logger ||= Log4r::Logger.new(self.class.to_s) end def run_thread(interval = 0.2) @@ -68,6 +68,7 @@ module Mauve end def start + @logger = nil logger.debug("Starting") @stop = false @thread = Thread.new{ self.run_thread { self.main_loop } } diff --git a/lib/mauve/mauve_time.rb b/lib/mauve/mauve_time.rb index 504e6ae..2251c53 100644 --- a/lib/mauve/mauve_time.rb +++ b/lib/mauve/mauve_time.rb @@ -1,34 +1,204 @@ - +require 'date' require 'time' -module Mauve +class Date + def to_mauvetime + Mauve::MauveTime.parse(self.to_s) + end +end - class MauveTime < Time - - def to_s - self.iso8601 +class DateTime + def to_mauvetime + Mauve::MauveTime.parse(self.to_s) + end + + def to_s_relative(*args) + self.to_time.to_s_relative(*args) + end + + def to_s_human + self.to_time.to_s_human + end + + def in_x_hours(*args) + self.to_s_human(*args) + end + +end + +class Time + def to_mauvetime + Mauve::MauveTime.at(self.to_i,self.usec) + end + + def in_x_hours(n, type="wallclock") + t = self.dup + # + # Do this in minutes rather than hours + # + n = n.to_i*3600 + + test = case type + when "working" + "working_hours?" + when "daytime" + "daytime_hours?" + else + "wallclock_hours?" end - def to_s_relative(now = MauveTime.now) - diff = (self.to_f - now.to_f).to_i - case diff - when -5184000..-17200 then "in #{-diff/86400} days" - when -172799..-3600 then "in #{-diff/3600} hours" - when -3599..-300 then "in #{-diff/60} minutes" - when -299..-1 then "very soon" - when 0..299 then "just now" - when 300..3599 then "#{diff/60} minutes ago" - when 3600..172799 then "#{diff/3600} hours ago" - when 172800..5184000 then "#{diff/86400} days ago" + step = 3600 + + # + # Work out how much time to subtract now + # + while n >= 0 + # + # If we're currently OK, and we won't be OK after the next step (or + # vice-versa) decrease step size, and try again + # + if (t.__send__(test) != (t+step).__send__(test)) + # + # Unless we're on the smallest step, try a smaller one. + # + unless step == 1 + step /= 60 + else - diff > 518400 ? - "#{diff/2592000} months ago" : - "in #{-diff/2592000} months" + n -= step if t.__send__(test) + t += step + + # + # Set the step size back to an hour + # + step = 3600 + end + + next end + + # + # Decrease the time by the step size if we're currently OK. + # + n -= step if t.__send__(test) + t += step + end + + # + # Substract any overshoot. + # + t += n if n < 0 + + t + end + + # + # The working day is from 8.30am until 17:00 + # + def working_hours? + (1..5).include?(self.wday) and ((9..16).include?(self.hour) or (self.hour == 8 && self.min >= 30)) + end + + # + # The daytime day is 14 hours long + # + def daytime_hours? + (8..21).include?(self.hour) + end + + # + # The daytime day is 14 hours long + # + def wallclock_hours? + true + end + + # + # In the DEAD ZONE! + # + def dead_zone? + (3..6).include?(self.hour) + end + + def to_s_relative(now = Time.now) + # + # Make sure now is the correct class + # + now now.to_time if now.is_a?(DateTime) + + raise ArgumentError, "now must be a Time" unless now.is_a?(Time) + + diff = (now.to_f - self.to_f).round.to_i.abs + n = nil + + if diff < 120 + n = nil + elsif diff < 3600 + n = diff/60.0 + unit = "minute" + elsif diff < 172800 + n = diff/3600.0 + unit = "hour" + elsif diff < 5184000 + n = diff/86400.0 + unit = "day" + else + n = diff/2592000.0 + unit = "month" + end + + unless n.nil? + n = n.round.to_i + unit += "s" if n != 1 + end + + # The FUTURE + if self > now + return "shortly" if n.nil? + "in #{n} #{unit}" + else + return "just now" if n.nil? + "#{n} #{unit}"+" ago" + end + end + + def to_s_human + _now = Time.now + + if _now.strftime("%F") == self.strftime("%F") + self.strftime("%R today") + + # Tomorrow is in 24 hours + elsif (_now + 86400).strftime("%F") == self.strftime("%F") + self.strftime("%R tomorrow") + + # Yesterday is in 24 ago + elsif (_now - 86400).strftime("%F") == self.strftime("%F") + self.strftime("%R yesterday") + + # Next week starts in 6 days. + elsif self > _now and self < (_now + 86400 * 6) + self.strftime("%R on %A") + + else + self.strftime("%R on %a %d %b %Y") + end end end +module Mauve + class MauveTime < Time + + def to_s + self.iso8601 + end + def to_mauvetime + self + end + + end +end diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb index 089180b..c5e462b 100644 --- a/lib/mauve/notification.rb +++ b/lib/mauve/notification.rb @@ -84,7 +84,7 @@ module Mauve def working_hours? now = (@time || MauveTime.now) - (8..17).include?(now.hour) and (1..5).include?(now.wday) + now.to_mauvetime.working_hours? end # Return true in the dead zone between 3 and 7 in the morning. @@ -94,7 +94,7 @@ module Mauve # @return [Boolean] Whether now is a in the dead zone or not. def dead_zone? now = (@time || MauveTime.now) - (3..6).include?(now.hour) + now.to_mauvetime.dead_zone? end end diff --git a/lib/mauve/timer.rb b/lib/mauve/timer.rb index 35e7aa8..40abe43 100644 --- a/lib/mauve/timer.rb +++ b/lib/mauve/timer.rb @@ -14,8 +14,6 @@ module Mauve attr_accessor :sleep_interval, :last_run_at def initialize - @logger = Log4r::Logger.new self.class.to_s - @logger.info("Timer singleton created.") @initial_sleep = 300 @initial_sleep_threshold = 300 end @@ -31,7 +29,7 @@ module Mauve # look for the next alert_changed object. # if next_alert.nil? or next_alert.due_at > MauveTime.now - @logger.debug("Next alert was #{next_alert} due at #{next_alert.due_at}") unless next_alert.nil? + logger.debug("Next alert was #{next_alert} due at #{next_alert.due_at}") unless next_alert.nil? next_alert_changed = AlertChanged.find_next_with_event end @@ -53,12 +51,12 @@ module Mauve # # Sleep indefinitely # - @logger.debug("Nothing to notify about -- snoozing indefinitely.") + logger.debug("Nothing to notify about -- snoozing indefinitely.") else # # La la la nothing to do. # - @logger.debug("Next to notify: #{next_to_notify} -- snoozing until #{next_to_notify.due_at}") + logger.debug("Next to notify: #{next_to_notify} -- snoozing until #{next_to_notify.due_at}") end # diff --git a/lib/mauve/udp_server.rb b/lib/mauve/udp_server.rb index 9e5f41c..ba49bd4 100644 --- a/lib/mauve/udp_server.rb +++ b/lib/mauve/udp_server.rb @@ -18,19 +18,18 @@ module Mauve # # Set the logger up # - @logger = Log4r::Logger.new(self.class.to_s) @ip = "127.0.0.1" @port = 32741 @socket = nil @closing_now = false @sleep_interval = 0 end - + def open_socket @socket = UDPSocket.new @closing_now = false - @logger.debug("Trying to increase Socket::SO_RCVBUF to 10M.") + logger.debug("Trying to increase Socket::SO_RCVBUF to 10M.") old = @socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF).unpack("i").first @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, 10*1024*1024) @@ -38,11 +37,11 @@ module Mauve raise "Could not increase Socket::SO_RCVBUF. Had #{old} ended up with #{new}!" if old > new - @logger.debug("Successfully increased Socket::SO_RCVBUF from #{old} to #{new}.") + logger.debug("Successfully increased Socket::SO_RCVBUF from #{old} to #{new}.") @socket.bind(@ip, @port) - @logger.debug("Successfully opened UDP socket on #{@ip}:#{@port}") + logger.debug("Successfully opened UDP socket on #{@ip}:#{@port}") end def close_socket @@ -52,10 +51,10 @@ module Mauve @socket.close rescue IOError => ex # Just in case there is some sort of explosion! - @logger.debug("Caught IOError #{ex.to_s}") + logger.debug("Caught IOError #{ex.to_s}") end - @logger.debug("Successfully closed UDP socket") + logger.debug("Successfully closed UDP socket") end def main_loop @@ -81,7 +80,7 @@ module Mauve return if packet.nil? - @logger.debug("Got new packet: #{packet.inspect}") + logger.debug("Got new packet: #{packet.inspect}") # # If we get a zero length packet, and we've been flagged to stop, we stop! |