aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2011-06-15 19:44:07 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2011-06-15 19:44:07 +0100
commit42f14de6ab50d0e05f49f038e40b94b686701bf1 (patch)
tree33dc459cf3ae32de8956690a990f080321a67a83 /lib/mauve
parent92e5d4e652aae161640359b41c0ba8dcaedac0ad (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
Diffstat (limited to 'lib/mauve')
-rw-r--r--lib/mauve/alert.rb56
-rw-r--r--lib/mauve/alert_group.rb58
-rw-r--r--lib/mauve/auth_bytemark.rb2
-rw-r--r--lib/mauve/http_server.rb7
-rw-r--r--lib/mauve/mauve_thread.rb3
-rw-r--r--lib/mauve/mauve_time.rb210
-rw-r--r--lib/mauve/notification.rb4
-rw-r--r--lib/mauve/timer.rb8
-rw-r--r--lib/mauve/udp_server.rb15
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!