diff options
-rw-r--r-- | .hgtags | 8 | ||||
-rw-r--r-- | debian/changelog | 62 | ||||
-rw-r--r-- | debian/mauvealert-client.install | 2 | ||||
-rw-r--r-- | etc/mauveserver.conf | 31 | ||||
-rw-r--r-- | lib/mauve/alert.rb | 15 | ||||
-rw-r--r-- | lib/mauve/alert_changed.rb | 2 | ||||
-rw-r--r-- | lib/mauve/alert_group.rb | 16 | ||||
-rw-r--r-- | lib/mauve/configuration_builders/person.rb | 15 | ||||
-rw-r--r-- | lib/mauve/notification.rb | 4 | ||||
-rw-r--r-- | lib/mauve/notifiers.rb | 1 | ||||
-rw-r--r-- | lib/mauve/notifiers/debug.rb | 2 | ||||
-rw-r--r-- | lib/mauve/notifiers/sms_clockwork.rb | 72 | ||||
l---------[-rw-r--r--] | lib/mauve/notifiers/templates/email_subject.txt.erb | 23 | ||||
l---------[-rw-r--r--] | lib/mauve/notifiers/templates/sms.txt.erb | 23 | ||||
-rw-r--r-- | lib/mauve/notifiers/templates/xmpp.html.erb | 6 | ||||
-rw-r--r-- | lib/mauve/notifiers/templates/xmpp.txt.erb | 6 | ||||
-rw-r--r-- | lib/mauve/person.rb | 123 | ||||
-rw-r--r-- | lib/mauve/quick_update.rb | 166 | ||||
-rw-r--r-- | lib/mauve/server.rb | 5 | ||||
-rw-r--r-- | lib/mauve/version.rb | 2 | ||||
-rw-r--r-- | test/tc_mauve_configuration_builders_person.rb | 6 | ||||
-rw-r--r-- | test/tc_mauve_person.rb | 124 | ||||
-rw-r--r-- | test/th_mauve.rb | 9 | ||||
-rw-r--r-- | views/_alert_actions.haml | 8 |
24 files changed, 599 insertions, 132 deletions
@@ -53,3 +53,11 @@ b245559b3b70af7c72a47bc223d27337af9938dc 3.15.3 bf551ba5805f5ec40002501a6f573f6c877ef84a 3.15.4 4eaf5c4e8b39d842470909f074be8a9393466d82 3.15.5 8f17efb79101ad42e3c835b24dbca3763d38879d 3.15.6 +fed4916f989ec76455450e46d014672826f02c27 3.15.7 +4eb1d57bab0ef97e4f10d620eb394f4c68a47dc7 3.15.8 +4c761e04d53b6367a8ade4edcf5338c38178303a 3.15.9 +63c228b13092c528b72d5117e57fec3a13def7c3 3.15.10 +73b098ffe0165adacf68689428b6116dd9f1f905 3.15.11 +33456d075c9479767ecf12e289fe8e4166910d60 3.15.12 +35b3e9e7eedd3300d11d356112c0f3819edceabc 3.15.13 +68c8c5b836f7e5a1ba49c2e04bbd25ec5823e9ee 3.15.14 diff --git a/debian/changelog b/debian/changelog index 8c54fce..68061fd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,65 @@ -mauvealert (3.15.6-3) stable; urgency=medium - +mauvealert (3.15.14-2) stable; urgency=low + * Relaxed mauvealert-client ruby dependency. - + -- Patrick J Cherry <patrick@bytemark.co.uk> Thu, 03 Jul 2014 11:43:00 +0100 +mauvealert (3.15.14-1) stable; urgency=low + + * Web interface now defaults to wall-clock hours. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Wed, 22 May 2013 11:47:52 +0100 + +mauvealert (3.15.13-1) stable; urgency=low + + * Switched off global socket reverse lookups. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Tue, 21 May 2013 14:34:15 +0100 + +mauvealert (3.15.12-1) stable; urgency=low + + * Removed extra database lookup when receiving alert updates. + * Added extra to_a when checking to see if an alert should be suppressed to + force the database lookup at that point. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Tue, 21 May 2013 12:24:13 +0100 + +mauvealert (3.15.11-1) stable; urgency=low + + * Notifications now generate just one history entry to ensure that the + previous changes regarding notification supression work. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Thu, 16 May 2013 14:13:43 +0100 + +mauvealert (3.15.10-1ubuntu1) stable; urgency=low + + * Database is now queried to see if a notification should be suppressed. + * Notification suppression now takes urgency into account, such that more + urgent alerts are not smothered by less urgent ones. + * Templates now include which level the alert is. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Thu, 16 May 2013 11:47:33 +0100 + +mauvealert (3.15.9-1) stable; urgency=low + + * Added Mauve::QuickUpdate library + + -- Patrick J Cherry <patrick@bytemark.co.uk> Thu, 02 May 2013 14:23:34 +0100 + +mauvealert (3.15.8-1) stable; urgency=low + + * Added a further check to make sure the last alert of the same update type + was being updated by reminders. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Wed, 01 May 2013 15:41:10 +0100 + +mauvealert (3.15.7-1) stable; urgency=low + + * Altered alert_group to only set one reminder. + * Allow alert to clear raised_at/cleared_at times if none specified. + + -- Patrick J Cherry <patrick@bytemark.co.uk> Wed, 01 May 2013 10:16:52 +0100 + mauvealert (3.15.6-2) stable; urgency=low * Added generic_http library that had been missed off. diff --git a/debian/mauvealert-client.install b/debian/mauvealert-client.install index 0744f97..8e6e561 100644 --- a/debian/mauvealert-client.install +++ b/debian/mauvealert-client.install @@ -1,3 +1,3 @@ bin/mauvesend usr/bin/ lib/mauve/sender.rb usr/lib/ruby/1.8/mauve/ - +lib/mauve/quick_update.rb usr/lib/ruby/1.8/mauve/ diff --git a/etc/mauveserver.conf b/etc/mauveserver.conf index 4baec0a..0a7a747 100644 --- a/etc/mauveserver.conf +++ b/etc/mauveserver.conf @@ -75,8 +75,8 @@ notification_method("email") { # password "x" # } -# How to notify by SMS - we use aql.com, you'll need to write a module -# to use any other provider. +# How to notify by SMS - we use aql.com, a provider for clockworksms.com +# is also provided. For another provider, you'll need to write a module. # # notification_method("sms") { # provider "AQL" @@ -88,6 +88,16 @@ notification_method("email") { # # Maximum number of SMS messages to concatenate for one notification # max_messages_per_alert 3 # } +# +# notification_method("sms") { +# provider "Clockwork" +# +# apikey "sssseeeeeeccccccccrrrrreeeeeettttsssssss" +# from "01234567890" +# +# # Maximum number of SMS messages to concatenate for one notification +# max_messages_per_alert 3 +# } # Simple default notification preference for root at this machine, at all # alert levels. You probably want more people, see below for a more complete @@ -101,16 +111,20 @@ person("root") { # # person("johnny") { # +# sms "07111222333" +# email "johnny@example.com" +# xmpp "johnny@example.com.jabber.org" +# # # Johnny wants waking up 24/7 if anything urgent happens -# urgent { sms("07111222333") } +# urgent { sms } # -# # Email him for anything that's not urgent -# normal { email("johnny@example.com") } +# # XMPP, or Email him for anything that's not urgent +# normal { xmpp or email } # # # Anything else can just be a jabber message, which he might miss. # # Email instead if he's unavailable/offline - but give it a try if # # we don't know his status. -# low { xmpp("johnny@example.com.jabber.org", :if_presence => [:available, unknown]) || email("johnny@example.com") } +# low { xmpp("johnny@example.com.jabber.org", :if_presence => [:available, unknown]) || email } # # # SMS messages are expensive, if we're sending more than 5 per minute, # # tell the user we're going to stop until it slows down. @@ -122,7 +136,10 @@ person("root") { # Archie is Johnny's boss # # person("archie") { -# all { email("archie@example.com") } +# +# email "archie@example.com" +# +# all { email } # # # Don't spam Archie, if more than 3 messages per hour come in. # suppress_notifications_after 3 => 1.hour diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index a2033a5..24b9dae 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -824,7 +824,10 @@ module Mauve # alert.id = Alert.remove_html(alert.id.to_s) - alert_db = first(:alert_id => alert.id, :source => update.source) || + # + # Load the database alert, and all its properties, since we're updating. + # + alert_db = first(:alert_id => alert.id, :source => update.source, :fields => Alert.properties) || new(:alert_id => alert.id, :source => update.source) ## @@ -850,6 +853,11 @@ module Mauve alert_db.raised_at = nil alert_db.will_raise_at = raise_time end + else + # + # If no raise time has been set, then update the database to reflect this. + # + alert_db.raised_at = alert_db.will_raise_at = nil end if clear_time @@ -863,6 +871,11 @@ module Mauve alert_db.cleared_at = nil alert_db.will_clear_at = clear_time end + else + # + # If no clear time has been set, then update the database to reflect this. + # + alert_db.cleared_at = alert_db.will_clear_at = nil end # diff --git a/lib/mauve/alert_changed.rb b/lib/mauve/alert_changed.rb index 2b9dfe5..6925085 100644 --- a/lib/mauve/alert_changed.rb +++ b/lib/mauve/alert_changed.rb @@ -110,7 +110,7 @@ module Mauve # # Push this notifitcation onto the queue. # - Server.notification_push([alert, Time.now]) + Server.notification_push([alert, self.remind_at]) end # diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb index fe78ad9..fe17516 100644 --- a/lib/mauve/alert_group.rb +++ b/lib/mauve/alert_group.rb @@ -236,15 +236,21 @@ module Mauve # OK got the next reminder time. # unless remind_at.nil? - this_reminder = AlertChanged.new( - :level => level.to_s, + # + # Find the last reminder, if available for the same alert, update type, and person. + # + this_reminder = AlertChanged.first_or_new( :alert_id => alert.id, :person => self.name, - :at => at, :update_type => alert.update_type, - :remind_at => remind_at, - :was_relevant => true) + :remind_at.not => nil, + :remind_at.gt => at + ) + this_reminder.level = level.to_s + this_reminder.at = at + this_reminder.remind_at = remind_at + this_reminder.was_relevant = true this_reminder.save end diff --git a/lib/mauve/configuration_builders/person.rb b/lib/mauve/configuration_builders/person.rb index f171d10..9cc09ab 100644 --- a/lib/mauve/configuration_builders/person.rb +++ b/lib/mauve/configuration_builders/person.rb @@ -51,10 +51,15 @@ module Mauve def suppress_notifications_after(h) raise ArgumentError.new("notification_threshold must be specified as e.g. (10 => 1.minute)") unless h.kind_of?(Hash) - h.each do |k,v| - raise ArgumentError.new("notification_threshold must be specified as e.g. (10 => 1.minute)") unless k.is_a?(Integer) and v.is_a?(Integer) - - @result.notification_thresholds[v] = Array.new(k) + h.each do |number_of_alerts,in_period| + raise ArgumentError.new("notification_threshold must be specified as e.g. (10 => 1.minute)") unless number_of_alerts.is_a?(Integer) and in_period.is_a?(Integer) + + @result.suppress_notifications_after[in_period] = number_of_alerts + # History.all( + # :limit => number_of_alerts, + # :order => :created_at.desc, + # :type => "notification", + # :event.like => '% succeeded') end end @@ -87,7 +92,7 @@ module Mauve # # Add a default notification threshold # - person.notification_thresholds[600] = Array.new(5) if person.notification_thresholds.empty? + person.suppress_notifications_after[600] = 5 if person.suppress_notifications_after.empty? # # Add a default notify clause diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb index 745660a..c164afb 100644 --- a/lib/mauve/notification.rb +++ b/lib/mauve/notification.rb @@ -319,9 +319,7 @@ module Mauve # # A bit of alert de-bouncing. # - if already_sent_to.include?(person.username) - logger.info("Already sent notification of #{alert} to #{person.username}") - else + unless already_sent_to.include?(person.username) person.send_alert(level, alert) already_sent_to << person.username end diff --git a/lib/mauve/notifiers.rb b/lib/mauve/notifiers.rb index 7276091..5a7acc0 100644 --- a/lib/mauve/notifiers.rb +++ b/lib/mauve/notifiers.rb @@ -1,6 +1,7 @@ require 'mauve/notifiers/email' require 'mauve/notifiers/sms_default' require 'mauve/notifiers/sms_aql' +require 'mauve/notifiers/sms_clockwork' require 'mauve/notifiers/xmpp' module Mauve diff --git a/lib/mauve/notifiers/debug.rb b/lib/mauve/notifiers/debug.rb index a9afc52..b3e88e2 100644 --- a/lib/mauve/notifiers/debug.rb +++ b/lib/mauve/notifiers/debug.rb @@ -69,7 +69,7 @@ module Mauve deliver_to_queue << [Time.now, self.class, destination, message] if deliver_to_queue - if @disable_normal_delivery + if @disable_normal_delivery true # pretend it happened OK if we're just testing else send_alert_without_debug(destination, alert, all_alerts, conditions) diff --git a/lib/mauve/notifiers/sms_clockwork.rb b/lib/mauve/notifiers/sms_clockwork.rb new file mode 100644 index 0000000..b4bd860 --- /dev/null +++ b/lib/mauve/notifiers/sms_clockwork.rb @@ -0,0 +1,72 @@ +require 'mauve/notifiers/debug' +require 'cgi' + +module Mauve + module Notifiers + module Sms + + require 'net/https' + + class Clockwork + GATEWAY = "https://api.clockworksms.com/http/send.aspx" + + attr_writer :apikey, :from + attr_reader :name + + def initialize(name) + @name = name + end + + def send_alert(destination, alert, all_alerts, conditions = {}) + uri = URI.parse(GATEWAY) + + opts_string = { + :key => @apikey, + :to => normalize_number(destination), + :content => prepare_message(destination, alert, all_alerts, conditions), + :from => @from, + }.map { |k,v| "#{k}=#{CGI::escape(v.to_s)}" }.join("&") + + http = Net::HTTP.new(uri.host, uri.port) + if uri.port == 443 + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + response, data = http.post(uri.path, opts_string, { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => opts_string.length.to_s + }) + + if response.kind_of?(Net::HTTPSuccess) + # + # Woo -- return true! + # + true + else + false + end + end + + protected + def prepare_message(destination, alert, all_alerts, conditions={}) + was_suppressed = conditions[:was_suppressed] || false + will_suppress = conditions[:will_suppress] || false + + template_file = File.join(File.dirname(__FILE__),"templates","sms.txt.erb") + + txt = if File.exists?(template_file) + ERB.new(File.read(template_file)).result(binding).chomp + else + logger.error("Could not find sms.txt.erb template") + alert.to_s + end + end + + def normalize_number(n) + n.split("").select { |s| (?0..?9).include?(s[0]) }.join.gsub(/^0/, "44") + end + end + end + end +end + diff --git a/lib/mauve/notifiers/templates/email_subject.txt.erb b/lib/mauve/notifiers/templates/email_subject.txt.erb index 119c742..802c711 100644..120000 --- a/lib/mauve/notifiers/templates/email_subject.txt.erb +++ b/lib/mauve/notifiers/templates/email_subject.txt.erb @@ -1,22 +1 @@ -<%=alert.id %>: <%= alert.update_type.upcase %>: <% -case alert.update_type -when "cleared" -%><%= alert.cleared_at.to_s_relative %><% -when "acknowledged" -%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %> until <%= alert.will_unacknowledge_at.to_s_human %><% -else -%><%= alert.raised_at.to_s_relative %><% -end -%>: <%= alert.subject %>: <%= alert.summary %><% -if alert.source != alert.subject -%> -- from <%= alert.source %><% -end -%>. <%=WebInterface.url_for(alert)%><% -if defined? was_suppressed and defined? will_suppress - if was_suppressed and not will_suppress -%> (Normal service has resumed.)<% - elsif will_suppress and not was_suppressed -%> (Further alerts suppressed until things calm down.)<% - end -end -%> +xmpp.txt.erb
\ No newline at end of file diff --git a/lib/mauve/notifiers/templates/sms.txt.erb b/lib/mauve/notifiers/templates/sms.txt.erb index faec37d..802c711 100644..120000 --- a/lib/mauve/notifiers/templates/sms.txt.erb +++ b/lib/mauve/notifiers/templates/sms.txt.erb @@ -1,22 +1 @@ -<%= alert.update_type.upcase %>: <% -case alert.update_type -when "cleared" -%><%= alert.cleared_at.to_s_relative %><% -when "acknowledged" -%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %> until <%= alert.will_unacknowledge_at.to_s_human %><% -else -%><%= alert.raised_at.to_s_relative %><% -end -%>: <%= alert.subject %>: <%= alert.summary %><% -if alert.source != alert.subject -%> -- from <%= alert.source %><% -end -%>. <%=WebInterface.url_for(alert)%><% -if defined? was_suppressed and defined? will_suppress - if was_suppressed and not will_suppress -%> (Normal service has resumed.)<% - elsif will_suppress and not was_suppressed -%> (Further alerts suppressed until things calm down.)<% - end -end -%> +xmpp.txt.erb
\ No newline at end of file diff --git a/lib/mauve/notifiers/templates/xmpp.html.erb b/lib/mauve/notifiers/templates/xmpp.html.erb index 12354a2..7792bd9 100644 --- a/lib/mauve/notifiers/templates/xmpp.html.erb +++ b/lib/mauve/notifiers/templates/xmpp.html.erb @@ -1,5 +1,5 @@ <html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="http://www.w3.org/1999/xhtml"> -<a href="<%=WebInterface.url_for(alert)%>"><%= alert.id%>: <%= alert.update_type.upcase %></a>: <% +<a href="<%=WebInterface.url_for(alert)%>"><%= alert.id%>: <%= alert.update_type.upcase %></a> (<%= alert.level %>): <% case alert.update_type when "cleared" %><%= alert.cleared_at.to_s_relative %><% @@ -15,9 +15,9 @@ end %>.<% if defined? was_suppressed and defined? will_suppress if was_suppressed and not will_suppress -%><br /><em>Normal service has resumed.</em><% +%><br /><em>Normal service for <%= alert.level %> alerts has resumed.</em><% elsif will_suppress and not was_suppressed -%><br /><em>Further alerts suppressed until things calm down.</em><% +%><br /><em>Further <%= alert.level %> alerts suppressed until things calm down.</em><% end end %></body></html> diff --git a/lib/mauve/notifiers/templates/xmpp.txt.erb b/lib/mauve/notifiers/templates/xmpp.txt.erb index f63c96c..c148f41 100644 --- a/lib/mauve/notifiers/templates/xmpp.txt.erb +++ b/lib/mauve/notifiers/templates/xmpp.txt.erb @@ -1,4 +1,4 @@ -<%=alert.id %>: <%= alert.update_type.upcase %>: <% +<%=alert.id %>: <%= alert.update_type.upcase %> (<%= alert.level %>): <% case alert.update_type when "cleared" %><%= alert.cleared_at.to_s_relative %><% @@ -14,9 +14,9 @@ end %>. <%=WebInterface.url_for(alert)%><% if defined? was_suppressed and defined? will_suppress if was_suppressed and not will_suppress -%> (Normal service has resumed.)<% +%> (Normal service for <%= alert.level %> alerts has resumed.)<% elsif will_suppress and not was_suppressed -%> (Further alerts suppressed until things calm down.)<% +%> (Further <%= alert.level %> alerts suppressed until things calm down.)<% end end %> diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb index 979c0ab..1158cbf 100644 --- a/lib/mauve/person.rb +++ b/lib/mauve/person.rb @@ -6,13 +6,12 @@ module Mauve class Person attr_reader :username, :password, :urgent, :normal, :low, :email, :xmpp, :sms - attr_reader :notification_thresholds, :last_pop3_login, :suppressed, :notifications + attr_reader :last_pop3_login, :suppressed, :notifications attr_reader :notify_when_off_sick, :notify_when_on_holiday # Set up a new Person # def initialize(username) - @notification_thresholds = nil @suppressed = false # # TODO fix up web login so pop3 can be used as a proxy. @@ -107,34 +106,85 @@ module Mauve # Works out if a notification should be suppressed. If no parameters are supplied, it will # + # @param [Symbol] Level of notification that is being tested # @param [Time] Theoretical time of notification # @param [Time] Current time. # @return [Boolean] If suppression is needed. - def should_suppress?(with_notification_at = nil, now = Time.now) + def should_suppress?(level, with_notification_at = nil, now = Time.now) - return self.notification_thresholds.any? do |period, previous_alert_times| + self.suppress_notifications_after.any? do |period, number| # - # This is going to work out if we would be suppressed if - if with_notification_at.nil? - first = previous_alert_times.first - last = previous_alert_times.last + # When testing if the next alert will suppress, we know that if only + # one alert is needed to suppress, then this function should always + # return true. + # + return true if with_notification_at and number <= 1 + + # + # Here are the previous notifications set to this person in the last period. + # + previous_notifications = History.all( + :user => self.username, :type => "notification", + :created_at.gte => now - period, :created_at.lte => now, + :event.like => '% succeeded', + :order => :created_at.desc) + + # + # Defintely not suppressed if no notifications have been found. + # + return false if previous_notifications.count == 0 + + # + # If we're suppressed already, we need to check the time of the last alert sent + # + if @suppressed + + if with_notification_at.is_a?(Time) + latest = with_notification_at + else + latest = previous_notifications.first.created_at + end + + # + # We should not suppress this alert if the last one was sent ages ago + # + if (now - latest) >= period + return false + end + else - first = previous_alert_times[1] - last = with_notification_at + # + # We do not suppress if we can't find a sufficient number of previous alerts + # + if previous_notifications.count < (with_notification_at.nil? ? number : number - 1) + return false + end + end - - (first.is_a?(Time) and (now - first) < period) or - (last.is_a?(Time) and @suppressed and (now - last) < period) + + # + # If we're at the lowest level, return true now. + # + return true if !AlertGroup::LEVELS.include?(level) or AlertGroup::LEVELS.index(level) == 0 + + # + # Suppress this notification if all the last N of the preceeding + # notifications were of a equal or higher level. + # + return previous_notifications.first(number).alerts.to_a.all? do |a| + AlertGroup::LEVELS.index(a.level) >= AlertGroup::LEVELS.index(level) + end + end end - # The notification thresholds for this user # - # @return [Hash] - def notification_thresholds - @notification_thresholds ||= { } + # + # + def suppress_notifications_after + @suppress_notifications_after ||= { } end - + # This class implements an instance_eval context to execute the blocks # for running a notification block for each person. # @@ -203,10 +253,8 @@ module Mauve # # Log the result - note = "#{@alert.update_type.capitalize} #{name} notification to #{@person.username} (#{destination}) " + (res ? "succeeded" : "failed" ) - logger.info note+" about #{@alert}." - h = History.new(:alerts => [@alert], :type => "notification", :event => note, :user => @person.username) - h.save + # + logger.info "#{@alert.update_type.capitalize} #{name} notification to #{@person.username} (#{destination}) " + (res ? "succeeded" : "failed" ) +" about #{@alert}." return res end @@ -222,17 +270,17 @@ module Mauve def send_alert(level, alert, now=Time.now) was_suppressed = @suppressed - @suppressed = self.should_suppress? - will_suppress = self.should_suppress?(now) + @suppressed = self.should_suppress?(level) + will_suppress = self.should_suppress?(level, now) logger.info "Starting to send notifications again for #{username}." if was_suppressed and not @suppressed - + # # We only suppress notifications if we were suppressed before we started, # and are still suppressed. # if @suppressed or self.is_on_holiday?(now) or self.is_off_sick?(now) - note = "#{alert.update_type.capitalize} notification to #{self.username} suppressed" + note = "#{alert.update_type.capitalize} #{level} notification to #{self.username} suppressed" logger.info note + " about #{alert}." History.create(:alerts => [alert], :type => "notification", :event => note, :user => self.username) return true @@ -253,24 +301,13 @@ module Mauve :was_suppressed => was_suppressed, } ).instance_eval(&__send__(level)) end + + res = [result].flatten.any? + + note = "#{alert.update_type.capitalize} #{level} notification to #{self.username} " + (res ? "succeeded" : "failed" ) + History.create(:alerts => [alert], :type => "notification", :event => note, :user => self.username) - if [result].flatten.any? - # - # Remember that we've sent an alert - # - self.notification_thresholds.each do |period, previous_alert_times| - # - # Hmm.. not sure how to make this thread-safe. - # - self.notification_thresholds[period].push now - self.notification_thresholds[period].shift - end - - - return true - end - - return false + return res end # diff --git a/lib/mauve/quick_update.rb b/lib/mauve/quick_update.rb new file mode 100644 index 0000000..d5ecc8a --- /dev/null +++ b/lib/mauve/quick_update.rb @@ -0,0 +1,166 @@ +# encoding: UTF-8 +require 'mauve/proto' +require 'mauve/sender' + +module Mauve + # + # This class can be used in simple cases where all the program needs to do is + # send an update about a single alert. + # + # In its simplest form, this could be something like + # + # Mauve::QuickUpdate.new("foo").raise! + # + # sends a "raise" to the default mauve destination about alert ID "foo". + # + # It can be used to do set more details about the alert. + # + # update = Mauve::QuickUpdate.new("foo") + # update.summary = "Foo backups failed" + # update.detail = cmd_output + # update.raise! + # + # Another example might be a heartbeat. + # + # update = Mauve::QuickUpdate.new("heartbeat") + # update.summary = "Heartbeat for this.host.name not received" + # update.detail = "Maybe this host is down, or if not, cron has stopped running." + # update.raise_at = Time.now + 600 + # update.clear_at = now + # update.suppress_until = Time.now + 900 + # update.send + # + class QuickUpdate + + def initialize(alert_id) + raise ArgumentError, "alert_id must be a String, or respond to to_s" unless alert_id.is_a?(String) or alert_id.respond_to?("to_s") + + @verbose = false + + @update = Mauve::Proto::AlertUpdate.new + @update.replace = false + @update.alert = [] + + @alert = Mauve::Proto::Alert.new + @alert.id = alert_id.to_s + + @update << @alert + end + + # + # Sets the replace flag for the whole update. Defaults to false. + # + def replace=(bool) + raise ArgumentError, "replace must either be true or false" unless bool.is_a?(TrueClass) or bool.is_a?(FalseClass) + + @update.replace = bool + end + + # + # Sets the verbose flag for the update process. Defaults to false. + # + def verbose=(bool) + raise ArgumentError, "verbose must either be true or false" unless bool.is_a?(TrueClass) or bool.is_a?(FalseClass) + + @verbose = bool + end + + # + # Sets the source of the alert. Defaults to the machine's hostname. + # + def source=(s) + raise ArgumentError, "source must be a String, or respond to to_s" unless s.is_a?(String) or s.respond_to?("to_s") + + @update.source = s.to_s + end + + # + # Sets the alert summary. Must be a string or something that can convert to a string. + # + def summary=(s) + raise ArgumentError, "summary must be a String, or respond to to_s" unless s.is_a?(String) or s.respond_to?("to_s") + + @alert.summary = s.to_s + end + + # + # Sets the alert detail. Must be a string or something that can convert to a string. + # + def detail=(s) + raise ArgumentError, "detail must be a String, or respond to to_s" unless s.is_a?(String) or s.respond_to?("to_s") + + @alert.detail = s + end + + # + # Sets the alert summary. Must be a string or something that can convert to a string. + # + def subject=(s) + raise ArgumentError, "subject must be a String, or respond to to_s" unless s.is_a?(String) or s.respond_to?("to_s") + + @alert.subject = s + end + + # + # Sets the raise time. Must be an Integer (epoch time) or a Time. + # + def raise_time=(t) + raise ArgumentError, "raise_time must be a Time or an Integer" unless t.is_a?(Time) or t.is_a?(Integer) + t = t.to_i if t.is_a?(Time) + + @alert.raise_time = t + end + + alias raise_at= raise_time= + + # + # Sets the clear time. Must be an Integer (epoch time) or a Time. + # + def clear_time=(t) + clear ArgumentError, "clear_time must be a Time or an Integer" unless t.is_a?(Time) or t.is_a?(Integer) + t = t.to_i if t.is_a?(Time) + + @alert.clear_time = t + end + + alias clear_at= clear_time= + + # + # Sets the time after which alerts will get sent. Must be an Integer (epoch time) or a Time. + # + def suppress_until=(t) + clear ArgumentError, "suppress_until must be a Time or an Integer" unless t.is_a?(Time) or t.is_a?(Integer) + t = t.to_i if t.is_a?(Time) + + @alert.suppress_until = t + end + + # + # Immediately send a raise message. The raise_time defaults to Time#now. + # + def raise!(t = Time.now) + self.raise_time = t + self.send + end + + # + # Immediately send a clear message. The clear_time defaults to Time#now. + # + def clear!(t = Time.now) + self.clear_time = t + self.send + end + + # + # This sends the alert. If destinations are left as nil, then the default + # as per Mauve::Sender are used. + # + def send(destinations = nil) + Mauve::Sender.new(destinations).send(@update, @verbose) + end + + end + +end + + diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb index bb514ce..8f8cec4 100644 --- a/lib/mauve/server.rb +++ b/lib/mauve/server.rb @@ -56,6 +56,11 @@ module Mauve @bank_holidays = nil # + # Turn off unwanted reverse DNS lookups across the board. + # + BasicSocket.do_not_reverse_lookup = true + + # # Set up a blank config. # Configuration.current = Configuration.new if Mauve::Configuration.current.nil? diff --git a/lib/mauve/version.rb b/lib/mauve/version.rb index 0014105..fa2d75a 100644 --- a/lib/mauve/version.rb +++ b/lib/mauve/version.rb @@ -5,6 +5,6 @@ module Mauve # # Current version - VERSION="3.15.6" + VERSION="3.15.14" end diff --git a/test/tc_mauve_configuration_builders_person.rb b/test/tc_mauve_configuration_builders_person.rb index 48ceafd..76841a3 100644 --- a/test/tc_mauve_configuration_builders_person.rb +++ b/test/tc_mauve_configuration_builders_person.rb @@ -65,9 +65,9 @@ EOF assert_kind_of(Proc, person.normal) assert_kind_of(Proc, person.urgent) - assert_kind_of(Hash, person.notification_thresholds) - assert_equal(1,person.notification_thresholds.keys.length) - assert(person.notification_thresholds.all?{|k,v| k.is_a?(Integer) and v.is_a?(Array)}) + assert_kind_of(Hash, person.suppress_notifications_after) + assert_equal(1,person.suppress_notifications_after.keys.length) + assert(person.suppress_notifications_after.all?{|k,v| k.is_a?(Integer) and v.is_a?(Integer)}) assert_kind_of(Array, person.notifications) assert_equal(1, person.notifications.length) diff --git a/test/tc_mauve_person.rb b/test/tc_mauve_person.rb index 9e6f9ac..0d4b695 100644 --- a/test/tc_mauve_person.rb +++ b/test/tc_mauve_person.rb @@ -97,12 +97,10 @@ EOF # Pop the notification off the buffer. # notification_buffer.pop - assert_equal(Time.now, person.notification_thresholds[60][-1], "Notification thresholds not updated at #{Time.now}.") else assert_equal(0, notification_buffer.length, "Notification sent when it should not have been at #{Time.now}.") end - logger_pop end @@ -176,7 +174,6 @@ EOF # Pop the notification off the buffer. # notification_buffer.pop - assert_equal(Time.now, person.notification_thresholds[60][-1], "Notification thresholds not updated at #{Time.now}.") else assert_equal(0, notification_buffer.length, "Notification sent when it should not have been at #{Time.now}.") end @@ -185,6 +182,127 @@ EOF end end + + def test_send_alert_suppression_as_alerts_get_more_urgent + # + # This configuration is a bit different. We only want one alert per + # minute. + # + config =<<EOF +notification_method("email") { + debug! + deliver_to_queue [] + disable_normal_delivery! +} + +person ("test") { + email "test@example.com" + all { email } + suppress_notifications_after( 2 => 1.minute ) +} + +alert_group("low") { + level LOW + includes { alert_id =~ /^low-/ } + + notify("test") { + every 10.seconds + } +} + +alert_group("normal") { + level NORMAL + includes { alert_id =~ /^normal-/ } + + notify("test") { + every 10.seconds + } +} + +alert_group("default") { + level URGENT + + notify("test") { + every 10.seconds + } +} +EOF + + Configuration.current = ConfigurationBuilder.parse(config) + notification_buffer = Configuration.current.notification_methods["email"].deliver_to_queue + Server.instance.setup + + person = Configuration.current.people["test"] + + alerts = [ + Alert.new( + :alert_id => "low-test", + :source => "test", + :subject => "test" + ), + Alert.new( + :alert_id => "normal-test", + :source => "test", + :subject => "test" + ), + Alert.new( + :alert_id => "urgent-test", + :source => "test", + :subject => "test" + ) + ] + + # + # Raise the alerts + # + alerts.each{|a| a.raise!} + assert_equal(false, person.suppressed?, "Person suppressed before we even begin!") + + assert_equal(:low, alerts[0].level) + assert_equal(:normal, alerts[1].level) + assert_equal(:urgent, alerts[2].level) + + start_time = Time.now + + # + # + # + [ [0, true, alerts.first ], + [1, true, alerts.first ], + [2, false, alerts.first ], + [3, false, alerts.first ], + [4, true, alerts[1]], + [5, false, alerts.first], + [6, true, alerts[1]], + [7, false, alerts[1]], + [8, false, alerts[1]], + [9, false, alerts[1]], + [10, true, alerts[2]], + [11, true, alerts[2]], + [12, false, alerts[2]], + [13, false, alerts.first] + ].each do |offset, notification_sent, alert| + # + # Advance in to the future! + # + Timecop.freeze(start_time + offset) + + person.send_alert(alert.level, alert) + + if notification_sent + assert_equal(1, notification_buffer.length, "#{alert.level.to_s.capitalize} notification not sent when it should have been at #{Time.now}.") + # + # Pop the notification off the buffer. + # + notification_buffer.pop + else + assert_equal(0, notification_buffer.length, "#{alert.level.to_s.capitalize} notification sent when it should not have been at #{Time.now}.") + end + + logger_pop + end + + end def test_current_alerts diff --git a/test/th_mauve.rb b/test/th_mauve.rb index 4ec8dc5..49e714b 100644 --- a/test/th_mauve.rb +++ b/test/th_mauve.rb @@ -69,7 +69,14 @@ module Mauve @logger = Log4r::Logger.new 'Mauve' @outputter = Mauve::TestOutputter.new("test") @outputter.formatter = Log4r::PatternFormatter.new( :pattern => "%d %l %m" ) - @outputter.level = ($debug ? Log4r::DEBUG : Log4r::WARN) + @outputter.level = case ENV['LOGLEVEL'] + when "DEBUG" + Log4r::DEBUG + when "INFO" + Log4r::INFO + else + Log4r::WARN + end @logger.outputters << @outputter return @logger end diff --git a/views/_alert_actions.haml b/views/_alert_actions.haml index 2f4eb57..db64d7f 100644 --- a/views/_alert_actions.haml +++ b/views/_alert_actions.haml @@ -19,10 +19,10 @@ for %input#n_hours{ :name => 'n_hours', :type => "number", :min => 0, :max => 48, :value => 2, :style => "min-width: 6ex;"} %select#type_hours{:name => 'type_of_hours' } - -# Default to daytime hours - %option{ :value => "working" } working - %option{ :value => "daytime", :selected => "selected"} daytime - %option{ :value => "wallclock" } wall-clock + -# Default to wall-clock hours. + %option{ :value => "working" } working + %option{ :value => "daytime"} daytime + %option{ :value => "wallclock", :selected => "selected"} wall-clock hours %span#ack_until_text %input#ack_until{ :value => '', :type => :hidden, :name => 'ack_until' } |