diff options
Diffstat (limited to 'lib')
-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 |
16 files changed, 371 insertions, 110 deletions
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 |