From 645c47d975e3c34a092acddf4a5f9420010755bc Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Wed, 20 Jul 2011 16:15:03 +0100 Subject: * Added heartbeat to remote mauve * Added URLs to messages * Added example configuration file * Added various docs * XMPP messages now XHTML and TXT * Handling of MUC reconnection a bit better. --- lib/mauve/configuration.rb | 56 +++++++++------------ lib/mauve/heartbeat.rb | 64 ++++++++++++++++++++++++ lib/mauve/http_server.rb | 7 ++- lib/mauve/mauve_thread.rb | 70 +++++++++++++++----------- lib/mauve/notifier.rb | 6 --- lib/mauve/notifiers/templates/email.html.erb | 4 +- lib/mauve/notifiers/templates/email.txt.erb | 2 +- lib/mauve/notifiers/templates/xmpp.html.erb | 21 ++++++++ lib/mauve/notifiers/templates/xmpp.txt.erb | 8 +-- lib/mauve/notifiers/xmpp.rb | 75 +++++++++++++++++----------- lib/mauve/server.rb | 25 ++++------ lib/mauve/timer.rb | 15 +++--- lib/mauve/web_interface.rb | 12 +++-- 13 files changed, 237 insertions(+), 128 deletions(-) create mode 100644 lib/mauve/heartbeat.rb create mode 100644 lib/mauve/notifiers/templates/xmpp.html.erb (limited to 'lib/mauve') diff --git a/lib/mauve/configuration.rb b/lib/mauve/configuration.rb index 0d3e520..2794c03 100644 --- a/lib/mauve/configuration.rb +++ b/lib/mauve/configuration.rb @@ -7,6 +7,7 @@ require 'mauve/notification' require 'mauve/alert_group' require 'mauve/people_list' require 'mauve/source_list' +require 'mauve/heartbeat' # Seconds, minutes, hours, days, and weeks... More than that, we # really should not need it. @@ -64,8 +65,8 @@ module Mauve attr_reader :alert_groups attr_reader :people_lists attr_reader :source_lists - attr_reader :logger - + attr_reader :logger + def initialize @notification_methods = {} @people = {} @@ -115,6 +116,7 @@ module Mauve is_builder "outputter", LoggerOutputterBuilder + def builder_setup logger = Log4r::Logger.new("Mauve") @default_format = nil @@ -166,14 +168,11 @@ module Mauve class ProcessorBuilder < ObjectBuilder is_attribute "sleep_interval" + is_attribute "transmission_cache_expire_time" def builder_setup @result = Processor.instance end - - def method_missing(name, value) - @args[name] = value - end end class UDPServerBuilder < ObjectBuilder @@ -184,10 +183,6 @@ module Mauve def builder_setup @result = UDPServer.instance end - - def method_missing(name, value) - @args[name] = value - end end class TimerBuilder < ObjectBuilder @@ -196,12 +191,15 @@ module Mauve def builder_setup @result = Timer.instance end + end - def method_missing(name, value) - @args[name] = value + class HeartbeatBuilder < ObjectBuilder + is_attribute "destination" + is_attribute "interval" + + def builder_setup + @result = Heartbeat.instance end - - end class HTTPServerBuilder < ObjectBuilder @@ -210,15 +208,11 @@ module Mauve is_attribute "ip" is_attribute "document_root" is_attribute "session_secret" + is_attribute "base_url" def builder_setup @result = HTTPServer.instance end - - def method_missing(name, value) - @args[name] = value - end - end class NotifierBuilder < ObjectBuilder @@ -227,11 +221,6 @@ module Mauve def builder_setup @result = Notifier.instance end - - def method_missing(name, value) - @args[name] = value - end - end class ServerBuilder < ObjectBuilder @@ -241,22 +230,22 @@ module Mauve is_builder "processor", ProcessorBuilder is_builder "timer", TimerBuilder is_builder "notifier", NotifierBuilder - + is_builder "heartbeat", HeartbeatBuilder + + is_attribute "hostname" + is_attribute "database" + is_attribute "initial_sleep" + def builder_setup + @result = Mauve::Server.instance @args = {} end def result - @result = Mauve::Server.instance @result.configure(@args) - @result.web_interface = @web_interface @result end - def method_missing(name, value) - @args[name] = value - end - def created_web_interface(web_interface) @web_interface = web_interface end @@ -272,6 +261,10 @@ module Mauve def created_notifier(notifier) @notifier = notifier end + + def created_heartbeat(heartbeat) + @heartbeat = heartbeat + end end class NotificationMethodBuilder < ObjectBuilder @@ -282,7 +275,6 @@ module Mauve provider("Default") end - def provider(name) notifiers_base = Mauve::Notifiers notifiers_type = notifiers_base.const_get(@notification_type) diff --git a/lib/mauve/heartbeat.rb b/lib/mauve/heartbeat.rb new file mode 100644 index 0000000..0f51f80 --- /dev/null +++ b/lib/mauve/heartbeat.rb @@ -0,0 +1,64 @@ +require 'mauve/sender' +require 'mauve/proto' +require 'mauve/mauve_thread' +require 'log4r' + +# +# This class is responsible for sending a heartbeat to another mauve instance elsewhere. +# +module Mauve + + class Heartbeat < MauveThread + + include Singleton + + attr_accessor :destination, :summary, :detail + attr_reader :sleep_interval, :raise_at + + def initialize + super + + @destination = nil + @summary = "Mauve alert server down." + @detail = "The Mauve server at #{Server.instance.hostname} has failed to send a heartbeat." + self.raise_at = 600 + end + + def raise_at=(i) + @raise_at = i + @sleep_interval = ((i.to_f)/2.5).round.to_i + end + + def logger + @logger ||= Log4r::Logger.new(self.class.to_s) + end + + def main_loop + # + # Don't send if no destination set. + # + return if @destination.nil? + + update = Mauve::Proto::AlertUpdate.new + update.replace = false + update.alert = [] + update.source = Server.instance.hostname + update.transmission_id = rand(2**63) + + message = Mauve::Proto::Alert.new + message.id = "mauve-heartbeat" + message.summary = self.summary + message.detail = self.detail + message.raise_time = (MauveTime.now.to_f+self.raise_at).to_i + message.clear_time = MauveTime.now.to_i + + update.alert << message + + Mauve::Sender.new(self.destination).send(update) + end + + end + +end + + diff --git a/lib/mauve/http_server.rb b/lib/mauve/http_server.rb index 71261c8..7bd4467 100644 --- a/lib/mauve/http_server.rb +++ b/lib/mauve/http_server.rb @@ -87,7 +87,7 @@ module Mauve include Singleton - attr_accessor :port, :ip, :document_root + attr_accessor :port, :ip, :document_root, :base_url attr_accessor :session_secret def initialize @@ -96,6 +96,7 @@ module Mauve @ip = "127.0.0.1" @document_root = "/usr/share/mauvealert" @session_secret = "%x" % rand(2**100) + @server_name = nil end def main_loop @@ -105,6 +106,10 @@ module Mauve @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 + + def base_url + @base_url ||= "http://"+Server.instance.hostname + end def stop @server.stop if @server diff --git a/lib/mauve/mauve_thread.rb b/lib/mauve/mauve_thread.rb index f6c0cbc..6409f98 100644 --- a/lib/mauve/mauve_thread.rb +++ b/lib/mauve/mauve_thread.rb @@ -5,9 +5,11 @@ module Mauve class MauveThread + attr_reader :state + def initialize @thread = nil - @stop = true + @state = :stopped end def logger @@ -18,62 +20,77 @@ module Mauve # # Good to go. # - @frozen = false - @stop = false - - logger.debug("Started") + self.state = :starting @sleep_interval ||= interval - while !@stop do + sleep_loops = (@sleep_interval.to_f / 0.1).round.to_i + sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1 + + while self.state != :stopping do + + self.state = :started if self.state == :starting + # # Schtop! # - if @frozen - logger.debug("Frozen") + if self.state == :freezing + self.state = :frozen Thread.stop - logger.debug("Thawed") + self.state = :started end yield - next if self.should_stop? + # + # Ah-ha! Sleep with a break clause. + # + sleep_loops.times do + + break if self.should_stop? - Kernel.sleep(@sleep_interval) + # + # This is a rate-limiting step + # + Kernel.sleep 0.1 + end end - logger.debug("Stopped") + self.state = :stopped end def should_stop? - @frozen or @stop + [:freezing, :stopping].include?(self.state) + end + + def state=(s) + raise "Bad state for mauve_thread #{s.inspect}" unless [:stopped, :starting, :started, :freezing, :frozen, :stopping, :killing, :killed].include?(s) + unless @state == s + @state = s + logger.debug(s.to_s.capitalize) + end end def freeze - logger.debug("Freezing") unless @frozen + self.state = :freezing - @frozen = true - 20.times { Kernel.sleep 0.1 ; break if @thread.stop? } logger.debug("Thread has not frozen!") unless @thread.stop? end def frozen? - self.stop? + self.stop? and self.state == :frozen end def run if self.alive? - if self.stop? - logger.debug("Thawing") if @frozen - @frozen = false + if self.stop? @thread.wakeup end else @logger = nil - logger.debug("Starting") if @stop - @stop = false + self.state = :starting @thread = Thread.new{ self.run_thread { self.main_loop } } end end @@ -107,9 +124,7 @@ module Mauve end def stop - logger.debug("Stopping") unless @stop - - @stop = true + self.state = :stopping 10.times do break unless self.alive? @@ -127,10 +142,9 @@ module Mauve alias exit stop def kill - logger.debug("Killing") - @frozen = @stop = true + self.state = :killing @thread.kill - logger.debug("Killed") + self.state = :killed end def thread diff --git a/lib/mauve/notifier.rb b/lib/mauve/notifier.rb index 5bedeb9..6099457 100644 --- a/lib/mauve/notifier.rb +++ b/lib/mauve/notifier.rb @@ -5,17 +5,11 @@ require 'mauve/notifiers/xmpp' module Mauve class Notifier < MauveThread - - DEFAULT_XMPP_MESSAGE = "Mauve server started." include Singleton attr_accessor :sleep_interval - def initialize - super - end - def main_loop # # Cycle through the buffer. diff --git a/lib/mauve/notifiers/templates/email.html.erb b/lib/mauve/notifiers/templates/email.html.erb index f0ce1be..24b5e7b 100644 --- a/lib/mauve/notifiers/templates/email.html.erb +++ b/lib/mauve/notifiers/templates/email.html.erb @@ -1,12 +1,12 @@ -

<%= alert.update_type.upcase %>: <% +

<%= 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%><% +%><%= 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 diff --git a/lib/mauve/notifiers/templates/email.txt.erb b/lib/mauve/notifiers/templates/email.txt.erb index dc5762b..aab44a8 100644 --- a/lib/mauve/notifiers/templates/email.txt.erb +++ b/lib/mauve/notifiers/templates/email.txt.erb @@ -3,7 +3,7 @@ case alert.update_type when "cleared" %><%= alert.cleared_at.to_s_relative %><% when "acknowledged" -%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><% +%><%= 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 diff --git a/lib/mauve/notifiers/templates/xmpp.html.erb b/lib/mauve/notifiers/templates/xmpp.html.erb new file mode 100644 index 0000000..c6bfaed --- /dev/null +++ b/lib/mauve/notifiers/templates/xmpp.html.erb @@ -0,0 +1,21 @@ + +<%= 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 +%>.<% +if was_suppressed and not is_suppressed +%> Normal service has resumed.<% +elsif is_suppressed and not was_suppressed +%> Further alerts suppressed until things calm down.<% +end +%> diff --git a/lib/mauve/notifiers/templates/xmpp.txt.erb b/lib/mauve/notifiers/templates/xmpp.txt.erb index 837fd67..a73f41f 100644 --- a/lib/mauve/notifiers/templates/xmpp.txt.erb +++ b/lib/mauve/notifiers/templates/xmpp.txt.erb @@ -3,7 +3,7 @@ case alert.update_type when "cleared" %><%= alert.cleared_at.to_s_relative %><% when "acknowledged" -%><%= alert.acknowledged_at.to_s_relative %> by <%= alert.acknowledged_by %><% +%><%= 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 @@ -11,10 +11,10 @@ end if alert.source != alert.subject %> -- from <%= alert.source %><% end -%>.<% +%>. <%=WebInterface.url_for(alert)%><% if was_suppressed and not is_suppressed -%> Normal service has resumed.<% +%> (Normal service has resumed.)<% elsif is_suppressed and not was_suppressed -%> Further alerts suppressed until things calm down.<% +%> (Further alerts suppressed until things calm down.)<% end %> diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb index 7fe1e39..991194d 100644 --- a/lib/mauve/notifiers/xmpp.rb +++ b/lib/mauve/notifiers/xmpp.rb @@ -34,9 +34,11 @@ module Jabber # mean the other parts of the method fail to execute. # That would be bad. So kill parser_thread last @tbcbmutex.synchronize { @processing = 0 } - @fd.close if @fd and !@fd.closed? + if @fd and !@fd.closed? + @fd.close + stop + end @status = DISCONNECTED - stop end end end @@ -66,15 +68,15 @@ module Mauve include Jabber # Atrtribute. - attr_reader :name, :jid + attr_reader :name # Atrtribute. attr_accessor :password def initialize(name) Jabber::logger = self.logger - #Jabber::debug = true - #Jabber::warnings = true +# Jabber::debug = true +# Jabber::warnings = true @name = name @mucs = {} @@ -138,10 +140,11 @@ module Mauve # unless ex.nil? or @closing logger.warn(["Caught",ex.class,ex.to_s,"during XMPP",where].join(" ")) + @closing = true connect @mucs.each do |jid, muc| @mucs.delete(jid) - join_muc(jid) + join_muc(muc[:jid], muc[:password]) end end end @@ -156,11 +159,13 @@ module Mauve def close @closing = true - if @client and @client.is_connected? - @mucs.each do |jid, muc| - muc.exit("Goodbye!") if muc.active? - end - @client.send(Presence.new(nil, "Goodbye!").set_type(:unavailable)) + if @client + if @client.is_connected? + @mucs.each do |jid, muc| + muc[:client].exit("Goodbye!") if muc[:client].active? + end + @client.send(Presence.new(nil, "Goodbye!").set_type(:unavailable)) + end @client.close! end end @@ -202,7 +207,16 @@ module Mauve alert.to_s end - send_message(destination_jid, txt) + template_file = File.join(File.dirname(__FILE__),"templates","xmpp.html.erb") + + xhtml = if File.exists?(template_file) + ERB.new(File.read(template_file)).result(binding).chomp + else + logger.error("Could not find xmpp.txt.erb template") + alert.to_s + end + + send_message(destination_jid, txt, xhtml) end # Sends a message to the destionation. @@ -210,20 +224,23 @@ module Mauve # @param [String] destination The (full) JID to send to. # @param [String] msg The (formatted) message to send. # @return [NIL] nada. - def send_message(jid, msg) + def send_message(jid, msg, html_msg=nil) jid = JID.new(jid) unless jid.is_a?(JID) message = Message.new(jid) - - #if msg.is_a?(XHTML::HTML) - # message.add_element(msg) - #else message.body = msg - #end + if html_msg + begin + html_msg = REXML::Document.new(html_msg) unless html_msg.is_a?(REXML::Document) + message.add_element(html_msg) + rescue REXML::ParseException + logger.error "Bad XHTML: #{html_msg.inspect}" + end + end if is_muc?(jid) jid = join_muc(jid.strip) - muc = @mucs[jid] + muc = @mucs[jid][:client] if muc message.to = muc.jid @@ -272,28 +289,28 @@ module Mauve logger.debug("Adding new MUC client for #{jid}") - @mucs[jid.strip] = Jabber::MUC::MUCClient.new(@client) + @mucs[jid.strip] = {:jid => jid, :password => password, :client => Jabber::MUC::MUCClient.new(@client)} # Add some callbacks - @mucs[jid.strip].add_message_callback do |m| + @mucs[jid.strip][:client].add_message_callback do |m| receive_message(m) end - @mucs[jid.strip].add_private_message_callback do |m| + @mucs[jid.strip][:client].add_private_message_callback do |m| receive_message(m) end end - if !@mucs[jid.strip].active? + if !@mucs[jid.strip][:client].active? # # Make sure we have a resource. # - @mucs[jid.strip].join(jid, password) + @mucs[jid.strip][:client].join(jid, password) - logger.info("Joined #{jid}") + logger.info("Joined #{jid.strip}") else - logger.debug("Already joined #{jid}.") + logger.debug("Already joined #{jid.strip}.") end # @@ -410,10 +427,10 @@ module Mauve # that we've not sent ourselves, that are not historical, and that # match our resource or node in the body. # - if @mucs[msg.from.strip].is_a?(MUC::MUCClient) and - msg.from != @mucs[msg.from.strip].jid and + if @mucs[msg.from.strip][:client].is_a?(MUC::MUCClient) and + msg.from != @mucs[msg.from.strip][:client].jid and msg.x("jabber:x:delay") == nil and - (msg.body =~ /\b#{Regexp.escape(@mucs[msg.from.strip].jid.resource)}\b/i or + (msg.body =~ /\b#{Regexp.escape(@mucs[msg.from.strip][:client].jid.resource)}\b/i or msg.body =~ /\b#{Regexp.escape(@client.jid.node)}\b/i) receive_normal_message(msg) end diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb index 57ddca8..20f7045 100644 --- a/lib/mauve/server.rb +++ b/lib/mauve/server.rb @@ -11,29 +11,23 @@ require 'mauve/timer' require 'mauve/udp_server' require 'mauve/processor' require 'mauve/http_server' +require 'mauve/heartbeat' require 'log4r' module Mauve class Server - DEFAULT_CONFIGURATION = { - :ip => "127.0.0.1", - :port => 32741, - :database => "sqlite3:///./mauvealert.db", - :log_file => "stdout", - :log_level => 1, - :transmission_cache_expire_time => 600 - } + DEFAULT_CONFIGURATION = { } # # This is the order in which the threads should be started. # - THREAD_CLASSES = [UDPServer, HTTPServer, Processor, Timer, Notifier] + THREAD_CLASSES = [UDPServer, HTTPServer, Processor, Timer, Notifier, Heartbeat] - attr_accessor :web_interface - attr_reader :stopped_at, :started_at, :initial_sleep, :packet_buffer, :notification_buffer + attr_accessor :hostname, :database, :initial_sleep + attr_reader :stopped_at, :started_at, :packet_buffer, :notification_buffer include Singleton @@ -43,8 +37,11 @@ module Mauve # Sleep time between pooling the @buffer buffer. @sleep = 1 - @frozen = false - @stop = false + @frozen = false + @stop = false + @hostname = "localhost" + @database = "sqlite3:///./mauvealert.db" + @stopped_at = MauveTime.now @started_at = MauveTime.now @@ -79,7 +76,7 @@ module Mauve end # - DataMapper.setup(:default, @config[:database]) + DataMapper.setup(:default, @database) # DataObjects::Sqlite3.logger = Log4r::Logger.new("Mauve::DataMapper") # diff --git a/lib/mauve/timer.rb b/lib/mauve/timer.rb index f6ada88..91dea18 100644 --- a/lib/mauve/timer.rb +++ b/lib/mauve/timer.rb @@ -13,12 +13,6 @@ module Mauve attr_accessor :sleep_interval, :last_run_at - def initialize - super - @initial_sleep = 300 - @initial_sleep_threshold = 300 - end - def main_loop # # Get the next alert. @@ -51,22 +45,27 @@ module Mauve # # Sleep indefinitely # - logger.info("Nothing to notify about -- snoozing indefinitely.") + logger.info("Nothing to notify about -- snoozing for a while.") + sleep_loops = 600 else # # La la la nothing to do. # logger.info("Next to notify: #{next_to_notify} -- snoozing until #{next_to_notify.due_at}") + sleep_loops = ((next_to_notify.due_at - MauveTime.now).to_f / 0.1).round.to_i end + sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1 + # # Ah-ha! Sleep with a break clause. # - while next_to_notify.nil? or MauveTime.now <= next_to_notify.due_at + sleep_loops.times do # # Start again if the situation has changed. # break if self.should_stop? + # # This is a rate-limiting step for alerts. # diff --git a/lib/mauve/web_interface.rb b/lib/mauve/web_interface.rb index 4594e10..4bb9517 100644 --- a/lib/mauve/web_interface.rb +++ b/lib/mauve/web_interface.rb @@ -22,7 +22,14 @@ module Mauve def self._logger Log4r::Logger.new(self.to_s) end - + + # + # Generic URL for + # + def self.url_for(obj) + [Mauve::HTTPServer.instance.base_url, obj.class.to_s.split("::").last.downcase, obj.id.to_s].join("/") + end + use Rack::CommonLogger use Rack::Chunked use Rack::ContentLength @@ -519,7 +526,6 @@ EOF env['rack.errors'] = RackErrorsProxy.new(logger) super(env) end - - end + end end -- cgit v1.2.1 From d3a3cfef9650b08f62db62bd7e86b673f9d77d0b Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Wed, 20 Jul 2011 16:15:48 +0100 Subject: Bumped version --- lib/mauve/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/mauve') diff --git a/lib/mauve/version.rb b/lib/mauve/version.rb index 139b8eb..a4be324 100644 --- a/lib/mauve/version.rb +++ b/lib/mauve/version.rb @@ -1,5 +1,5 @@ module Mauve - VERSION="3.1.5" + VERSION="3.1.6" end -- cgit v1.2.1