From 1ac431fa21907a2a95d87901825cff3dc462746b Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Fri, 8 Jul 2011 17:24:08 +0100 Subject: Added first basic history functionality, and rejigged when notify is called for an alert. --- lib/mauve/alert.rb | 204 +++++++++++++++------------ lib/mauve/alert_changed.rb | 4 +- lib/mauve/alert_group.rb | 2 +- lib/mauve/history.rb | 26 ++++ lib/mauve/notifiers/email.rb | 6 + lib/mauve/notifiers/sms_aql.rb | 19 ++- lib/mauve/notifiers/templates/email.html.erb | 6 +- lib/mauve/notifiers/templates/email.txt.erb | 6 +- lib/mauve/notifiers/templates/xmpp.txt.erb | 6 +- lib/mauve/notifiers/xmpp.rb | 12 +- lib/mauve/person.rb | 2 +- lib/mauve/server.rb | 2 + views/alert.haml | 10 +- 13 files changed, 192 insertions(+), 113 deletions(-) create mode 100644 lib/mauve/history.rb diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index 7768f50..bea2fc4 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -1,5 +1,6 @@ require 'mauve/proto' require 'mauve/alert_changed' +require 'mauve/history' require 'mauve/datamapper' require 'sanitize' @@ -60,7 +61,7 @@ module Mauve property :id, Serial property :alert_id, String, :required => true, :unique_index => :alert_index, :length=>256 property :source, String, :required => true, :unique_index => :alert_index, :length=>512 - property :subject, String, :length=>512, :length=>512 + property :subject, String, :length=>512 property :summary, String, :length=>1024 property :detail, Text, :length=>65535 property :importance, Integer, :default => 50 @@ -75,11 +76,13 @@ module Mauve property :will_clear_at, DateTime property :will_raise_at, DateTime property :will_unacknowledge_at, DateTime -# property :will_unacknowledge_after, Integer - has n, :changes, :model => AlertChanged + has n, :histories, :model => Mauve::History has 1, :alert_earliest_date + before :save, :take_copy_of_changes + after :save, :notify_if_needed + validates_with_method :check_dates def to_s @@ -141,73 +144,121 @@ module Mauve def subject; attribute_get(:subject) || attribute_get(:source) || "not set" ; end def detail; attribute_get(:detail) || "_No detail set._" ; end - def subject=(subject); set_changed_if_different( :subject, subject ); end - def summary=(summary); set_changed_if_different( :summary, summary ); end + # def subject=(subject); set_changed_if_different( :subject, subject ); end + # def summary=(summary); set_changed_if_different( :summary, summary ); end # def source=(source); attribute_set( :source, source ); end # def detail=(detail); attribute_set( :detail, detail ); end protected - def set_changed_if_different(attribute, value) - return if self.__send__(attribute) == value - self.update_type ||= :changed - attribute_set(attribute.to_sym, value) + #def set_changed_if_different(attribute, value) + # return if self.__send__(attribute) == value + # self.update_type ||= "changed" + # attribute_set(attribute.to_sym, value) + #end + + # + # This allows us to take a copy of the changes before we save. + # + def take_copy_of_changes + @changes_before_save = Hash.new + self.original_attributes.each do |k,v| + @changes_before_save[k.name] = v + end end - + + # + # This sends notifications. It is called after each save. + # + def notify_if_needed + # + # Make sure we don't barf + # + @changes_before_save ||= Hash.new + + is_a_change = [:subject, :summary].any?{|k| @changes_before_save.keys.include?(k)} + + # + # We notify if the update type has changed, or if the update type is + # "raised", and the above is_a_change condition is true + # + if @changes_before_save.has_key?(:update_type) or (self.update_type == "raised" and is_a_change) + self.notify + + h = History.new(:alert => self, :type => "update") + + if self.update_type == "acknowledged" + h.event = "ACKNOWLEDGED by #{self.acknowledged_by} until #{self.will_unacknowledge_at}" + + elsif is_a_change + h.event = "CHANGED: " + h.event += @changes_before_save.keys.collect{|k| "#{k.to_s}: #{@changes_before_save[k]} -> #{self.__send__(k)}"}.join(", ") + + else + h.event = self.update_type.upcase + + end + + h.save + end + + true + end + public + def notify + self.alert_group.notify(self) + end + def acknowledge!(person, ack_until = Time.now+3600) raise ArgumentError unless person.is_a?(Person) raise ArgumentError unless ack_until.is_a?(Time) - + raise ArgumentError, "Cannot acknowledge a cleared alert" if self.cleared? + self.acknowledged_by = person.username self.acknowledged_at = MauveTime.now self.will_unacknowledge_at = ack_until - self.update_type = :acknowledged + self.update_type = "acknowledged" logger.error("Couldn't save #{self}") unless save - AlertGroup.notify([self]) if self.raised? end def unacknowledge! self.acknowledged_by = nil self.acknowledged_at = nil self.will_unacknowledge_at = nil - self.update_type = (raised? ? :raised : :cleared) + self.update_type = (raised? ? "raised" : "cleared") logger.error("Couldn't save #{self}") unless save - AlertGroup.notify([self]) if self.raised? end - def raise! - already_raised = raised? && !acknowledged? + def raise!(at = MauveTime.now) self.acknowledged_by = nil self.acknowledged_at = nil self.will_unacknowledge_at = nil - self.raised_at = MauveTime.now + self.raised_at = at if self.raised_at.nil? self.will_raise_at = nil self.cleared_at = nil # Don't clear will_clear_at - self.update_type = :raised - + self.update_type = "raised" if self.update_type.nil? or self.update_type != "changed" or self.original_attributes[Alert.properties[:update_type]] == "cleared" + + logger.debug("saving #{self.inspect}") logger.error("Couldn't save #{self}") unless save - AlertGroup.notify([self]) unless already_raised end - def clear!(notify=true) - already_cleared = cleared? + def clear!(at = MauveTime.now) self.acknowledged_by = nil self.acknowledged_at = nil self.will_unacknowledge_at = nil self.raised_at = nil # Don't clear will_raise_at - self.cleared_at = MauveTime.now + self.cleared_at = at if self.cleared_at.nil? self.will_clear_at = nil - self.update_type = :cleared + self.update_type = "cleared" logger.error("Couldn't save #{self}") unless save - AlertGroup.notify([self]) unless !notify || already_cleared end # Returns the time at which a timer loop should call poll_event to either @@ -223,19 +274,29 @@ module Mauve (will_raise_at and will_raise_at.to_time <= MauveTime.now) clear! if will_clear_at && will_clear_at.to_time <= MauveTime.now end - + + + # + # Tests to see if an alert is raised/acknowledged given a certain set of + # dates/times. + # + # + def raised? !raised_at.nil? and (cleared_at.nil? or raised_at > cleared_at) end - + def acknowledged? !acknowledged_at.nil? end - + + # + # Cleared is just the opposite of raised. + # def cleared? - !raised? + !raised? end - + class << self # @@ -310,7 +371,7 @@ module Mauve alerts_updated = [] - logger.debug("Alert update received from wire: #{update.inspect.split.join(", ")}") + logger.debug("Alert update received from wire: #{update.inspect.split("\n").join(" ")}") # # Transmission time helps us determine any time offset @@ -357,14 +418,6 @@ 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. - # - was_raised = alert_db.raised? - was_cleared = alert_db.cleared? - was_acknowledged = alert_db.acknowledged? - alert_db.update_type = nil ## @@ -380,7 +433,7 @@ module Mauve # This prevents the raised time constantly changing on alerts # that are already raised. # - alert_db.raised_at = raise_time unless was_raised or alert_db.raised_at.nil? + alert_db.raised_at = raise_time if alert_db.raised_at.nil? alert_db.will_raise_at = nil else alert_db.raised_at = nil @@ -393,7 +446,7 @@ module Mauve # # Don't reset the cleared_at time (see above for raised_at timings). # - alert_db.cleared_at = clear_time unless was_cleared or alert_db.cleared_at.nil? + alert_db.cleared_at = clear_time if alert_db.cleared_at.nil? alert_db.will_clear_at = nil else alert_db.cleared_at = nil @@ -401,33 +454,16 @@ module Mauve end end - # - # Clear old cleared_at time, if the raised_at time is newer - # - if alert_db.cleared_at && alert_db.raised_at && alert_db.cleared_at < alert_db.raised_at - alert_db.cleared_at = nil - end - - if alert_db.cleared? - alert_db.update_type = :cleared - else - alert_db.update_type = :raised - end - - # - # If the alert is cleared ,or has just been raised unset the acknowledge dates. - # - if alert_db.acknowledged? and (alert_db.cleared? or (alert_db.raised? and !was_raised)) - alert_db.acknowledged_at = nil - end - # # Set the subject # if alert.subject and !alert.subject.empty? alert_db.subject = Alert.remove_html(alert.subject) else - alert_db.subject = alert_db.source + # + # Use the source, Luke, but only when the subject hasn't already been set. + # + alert_db.subject = alert_db.source if alert_db.subject.nil? end alert_db.summary = Alert.remove_html(alert.summary) if alert.summary && !alert.summary.empty? @@ -439,35 +475,24 @@ module Mauve alert_db.importance = alert.importance if alert.importance != 0 - alert_db.update_type = :changed unless alert_db.update_type - - # - # This decides if we notify. + # + # This will probably get overwritten below. # - should_notify = case alert_db.update_type.to_sym - when :raised - !was_raised - when :acknowledged - !was_acknowledged - when :cleared - !was_cleared - else - alert_db.raised? - end - - alerts_updated << alert_db if should_notify + # alert_db.update_type = "changed" unless alert_db.update_type alert_db.updated_at = reception_time - logger.debug "Saving #{alert_db}" - - if !alert_db.save - if alert_db.errors.respond_to?("full_messages") - msg = alert_db.errors.full_messages + if alert_db.raised? + # + # If we're acknowledged, just save. + # + if alert_db.acknowledged? + alert_db.save else - msg = alert_db.errors.inspect + alert_db.raise! end - logger.error "Couldn't save update #{alert} because of #{msg}" + else + alert_db.clear! end end @@ -481,15 +506,10 @@ module Mauve :alert_id.not => alert_ids_mentioned, :cleared_at => nil ).each do |alert_db| - logger.debug "Replace: clearing #{alert_db.id}" - alert_db.clear!(false) - alerts_updated << alert_db + alert_db.clear! end end - logger.debug "Got #{alerts_updated.length} alerts to notify about" if alerts_updated.length > 0 - - AlertGroup.notify(alerts_updated) end def logger diff --git a/lib/mauve/alert_changed.rb b/lib/mauve/alert_changed.rb index ebcbbda..0b0f72a 100644 --- a/lib/mauve/alert_changed.rb +++ b/lib/mauve/alert_changed.rb @@ -61,11 +61,11 @@ module Mauve # @return [Boolean] true if it was relevant, false otherwise. def was_relevant_when_raised? - if :acknowledged == update_type.to_sym and true == was_relevant + if "acknowledged" == update_type and true == was_relevant return true end - return was_relevant if update_type.to_sym == :raised + return was_relevant if update_type == "raised" previous = AlertChanged.first(:id.lt => id, :alert_id => alert_id, diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb index 4895200..75f97fd 100644 --- a/lib/mauve/alert_group.rb +++ b/lib/mauve/alert_group.rb @@ -104,7 +104,7 @@ module Mauve unless alert.is_a?(Alert) logger.warn "Got given a #{alert.class} instead of an Alert!" - logger.debug caller.join("\n") + logger.debug caller.join("\n") return false end diff --git a/lib/mauve/history.rb b/lib/mauve/history.rb new file mode 100644 index 0000000..6c4969b --- /dev/null +++ b/lib/mauve/history.rb @@ -0,0 +1,26 @@ +# encoding: UTF-8 +require 'mauve/datamapper' +require 'log4r' + +module Mauve + class History + include DataMapper::Resource + + # so .first always returns the most recent update + default_scope(:default).update(:order => [:created_at.desc, :id.desc]) + + property :id, Serial + property :alert_id, Integer, :required => true + property :type, String, :required => true, :default => "unknown" + property :event, Text, :required => true + property :created_at, DateTime + + belongs_to :alert + + def logger + Log4r::Logger.new self.class.to_s + end + + end + +end diff --git a/lib/mauve/notifiers/email.rb b/lib/mauve/notifiers/email.rb index 75588f4..129afec 100644 --- a/lib/mauve/notifiers/email.rb +++ b/lib/mauve/notifiers/email.rb @@ -47,14 +47,20 @@ module Mauve message = prepare_message(destination, alert, all_alerts, conditions) args = [@server, @port] args += [@username, @password, @login_method.to_sym] if @login_method + history = Mauve::History.new(:alert => alert, :type => :notification) + begin Net::SMTP.start(*args) do |smtp| smtp.send_message(message, @from, destination) end + history.event = "Sent mail to #{destination}." + history.save true rescue StandardError => ex logger.error "SMTP failure: #{ex.to_s}" logger.debug ex.backtrace.join("\n") + history.event = "Failed to send mail to #{destination} due to #{ex.to_s}" + history.save false end end diff --git a/lib/mauve/notifiers/sms_aql.rb b/lib/mauve/notifiers/sms_aql.rb index 856c494..b295e6c 100644 --- a/lib/mauve/notifiers/sms_aql.rb +++ b/lib/mauve/notifiers/sms_aql.rb @@ -41,12 +41,19 @@ module Mauve 'Content-Length' => opts_string.length.to_s }) - raise response unless response.kind_of?(Net::HTTPSuccess) - - # - # Woo -- return true! - # - true + history = Mauve::History.new(:alert => alert, :type => :notification) + if response.kind_of?(Net::HTTPSuccess) + history.event = "Sent SMS via AQL to #{destination}" + history.save + # + # Woo -- return true! + # + true + else + history.event = "Failed to send SMS via AQL to #{destination} due to #{response.class.to_s}" + history.save + false + end end protected diff --git a/lib/mauve/notifiers/templates/email.html.erb b/lib/mauve/notifiers/templates/email.html.erb index ab65b48..bf5aed5 100644 --- a/lib/mauve/notifiers/templates/email.html.erb +++ b/lib/mauve/notifiers/templates/email.html.erb @@ -2,10 +2,10 @@ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<%= alert.update_type.upcase %>: <% -case alert.update_type.to_sym -when :cleared +case alert.update_type +when "cleared" %><%= alert.cleared_at.to_s_relative %><% -when :acknowledged +when "acknowledged" %><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by%><% else %><%= alert.raised_at.to_s_relative %><% diff --git a/lib/mauve/notifiers/templates/email.txt.erb b/lib/mauve/notifiers/templates/email.txt.erb index 57b9309..d112718 100644 --- a/lib/mauve/notifiers/templates/email.txt.erb +++ b/lib/mauve/notifiers/templates/email.txt.erb @@ -1,8 +1,8 @@ <%= alert.update_type.upcase %>: <% -case alert.update_type.to_sym -when :cleared +case alert.update_type +when "cleared" %><%= alert.cleared_at.to_s_relative %><% -when :acknowledged +when "acknowledged" %><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><% else %><%= alert.raised_at.to_s_relative %><% diff --git a/lib/mauve/notifiers/templates/xmpp.txt.erb b/lib/mauve/notifiers/templates/xmpp.txt.erb index 331d7ef..2f86cb6 100644 --- a/lib/mauve/notifiers/templates/xmpp.txt.erb +++ b/lib/mauve/notifiers/templates/xmpp.txt.erb @@ -1,8 +1,8 @@ <%= alert.update_type.upcase %>: <% -case alert.update_type.to_sym -when :cleared +case alert.update_type +when "cleared" %><%= alert.cleared_at.to_s_relative %><% -when :acknowledged +when "acknowledged" %><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><% else %><%= alert.raised_at.to_s_relative %><% diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb index 4c30643..c4e1785 100644 --- a/lib/mauve/notifiers/xmpp.rb +++ b/lib/mauve/notifiers/xmpp.rb @@ -203,7 +203,17 @@ module Mauve alert.to_s end - send_message(destination_jid, txt) + history = Mauve::History.new(:alert_id => alert.id, :type => :notification) + + if send_message(destination_jid, txt) + history.event = "Sent XMPP message to #{destination_jid}" + history.save + true + else + history.event = "Failed to send XMPP message to #{destination_jid}" + history.save + false + end end # Sends a message to the destionation. diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb index 2a6ddee..0290faf 100644 --- a/lib/mauve/person.rb +++ b/lib/mauve/person.rb @@ -217,7 +217,7 @@ module Mauve def current_alerts Alert.all_raised.select do |alert| my_last_update = AlertChanged.first(:person => username, :alert_id => alert.id) - my_last_update && my_last_update.update_type != :cleared + my_last_update && my_last_update.update_type != "cleared" end end diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb index c985e15..51587c0 100644 --- a/lib/mauve/server.rb +++ b/lib/mauve/server.rb @@ -4,6 +4,7 @@ require 'socket' # require 'mauve/datamapper' require 'mauve/proto' require 'mauve/alert' +require 'mauve/history' require 'mauve/mauve_thread' require 'mauve/mauve_time' require 'mauve/timer' @@ -86,6 +87,7 @@ module Mauve # Alert.auto_upgrade! AlertChanged.auto_upgrade! + History.auto_upgrade! Mauve::AlertEarliestDate.create_view! # diff --git a/views/alert.haml b/views/alert.haml index b752f35..7e7503f 100644 --- a/views/alert.haml +++ b/views/alert.haml @@ -56,7 +56,15 @@ = change.person at = change.at.to_s_human - + %tr + %th History + %td + %ul + - @alert.histories.each do |history| + %li + = history.event + at + = (history.created_at.nil? ? "unkown" : history.created_at.to_s_human) %h2 Actions - if !@alert.acknowledged? %form{:method => :post, :action => "/alert/#{@alert.id}/acknowledge"} -- cgit v1.2.1