From fd23821950f0562a8995735105cd31fdc6d55933 Mon Sep 17 00:00:00 2001 From: Patrick J Cherry Date: Fri, 22 Jul 2011 16:55:01 +0100 Subject: * Rejigged configuration * Added --test and --verbose flags for the server config * Started proper test suite * Config parsing now gives more sensible errors + backtrace * Rejigged people and source lists --- lib/mauve/alert.rb | 13 + lib/mauve/alert_group.rb | 15 +- lib/mauve/configuration.rb | 454 +-------------------- lib/mauve/configuration_builder.rb | 38 ++ lib/mauve/configuration_builders.rb | 6 + lib/mauve/configuration_builders/alert_group.rb | 81 ++++ lib/mauve/configuration_builders/logger.rb | 120 ++++++ .../configuration_builders/notification_method.rb | 50 +++ lib/mauve/configuration_builders/person.rb | 60 +++ lib/mauve/configuration_builders/server.rb | 102 +++++ lib/mauve/datamapper.rb | 3 +- lib/mauve/heartbeat.rb | 34 +- lib/mauve/http_server.rb | 50 ++- lib/mauve/mauve_resolv.rb | 35 ++ lib/mauve/mauve_thread.rb | 17 +- lib/mauve/notifier.rb | 10 +- lib/mauve/notifiers/email.rb | 1 - lib/mauve/notifiers/sms_aql.rb | 26 +- lib/mauve/notifiers/xmpp.rb | 4 + lib/mauve/people_list.rb | 38 +- lib/mauve/processor.rb | 7 +- lib/mauve/sender.rb | 43 +- lib/mauve/server.rb | 198 ++++----- lib/mauve/source_list.rb | 175 +++++--- lib/mauve/timer.rb | 2 - lib/mauve/udp_server.rb | 35 +- lib/mauve/version.rb | 2 +- lib/object_builder.rb | 39 +- 28 files changed, 910 insertions(+), 748 deletions(-) create mode 100644 lib/mauve/configuration_builder.rb create mode 100644 lib/mauve/configuration_builders.rb create mode 100644 lib/mauve/configuration_builders/alert_group.rb create mode 100644 lib/mauve/configuration_builders/logger.rb create mode 100644 lib/mauve/configuration_builders/notification_method.rb create mode 100644 lib/mauve/configuration_builders/person.rb create mode 100644 lib/mauve/configuration_builders/server.rb create mode 100644 lib/mauve/mauve_resolv.rb (limited to 'lib') diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb index 8778892..6bde73a 100644 --- a/lib/mauve/alert.rb +++ b/lib/mauve/alert.rb @@ -2,6 +2,7 @@ require 'mauve/proto' require 'mauve/alert_changed' require 'mauve/history' require 'mauve/datamapper' +require 'mauve/source_list' require 'sanitize' module Mauve @@ -128,6 +129,18 @@ module Mauve @alert_group ||= AlertGroup.matches(self).first end + # + # Pick out the source lists that match this alert by subject. + # + def source_lists + Mauve::Configuration.current.source_lists.select{|label, list| list.includes?(self.subject)}.collect{|sl| sl.first} + end + + def in_source_list?(g) + list = Mauve::Configuration.current.source_lists[g] + list.includes?(self.subject) + end + # # # diff --git a/lib/mauve/alert_group.rb b/lib/mauve/alert_group.rb index bfdb3cf..6cff33b 100644 --- a/lib/mauve/alert_group.rb +++ b/lib/mauve/alert_group.rb @@ -16,7 +16,7 @@ module Mauve class << self def matches(alert) - grps = all.select { |alert_group| alert_group.matches_alert?(alert) } + grps = all.select { |alert_group| alert_group.includes?(alert) } # # Make sure we always match the last (and therefore default) group. @@ -90,7 +90,7 @@ module Mauve # The list of current raised alerts in this group. # def current_alerts - Alert.all(:cleared_at => nil, :raised_at.not => nil).select { |a| matches_alert?(a) } + Alert.all(:cleared_at => nil, :raised_at.not => nil).select { |a| includes?(a) } end # Decides whether a given alert belongs in this group according to its @@ -98,7 +98,7 @@ module Mauve # # @param [Alert] alert An alert to test for belongness to group. # @return [Boolean] Success or failure. - def matches_alert?(alert) + def includes?(alert) unless alert.is_a?(Alert) logger.error "Got given a #{alert.class} instead of an Alert!" @@ -106,14 +106,11 @@ module Mauve return false end - result = alert.instance_eval(&self.includes) - if true == result or - true == result.instance_of?(MatchData) - return true - end - return false + alert.instance_eval(&self.includes) ? true : false end + alias matches_alert? includes? + def logger ; self.class.logger ; end # Signals that a given alert (which is assumed to belong in this group) diff --git a/lib/mauve/configuration.rb b/lib/mauve/configuration.rb index 2794c03..67b76ab 100644 --- a/lib/mauve/configuration.rb +++ b/lib/mauve/configuration.rb @@ -1,13 +1,5 @@ -# encoding: UTF-8 -require 'object_builder' -require 'mauve/server' -require 'mauve/web_interface' -require 'mauve/person' -require 'mauve/notification' -require 'mauve/alert_group' -require 'mauve/people_list' require 'mauve/source_list' -require 'mauve/heartbeat' +require 'mauve/people_list' # Seconds, minutes, hours, days, and weeks... More than that, we # really should not need it. @@ -23,6 +15,7 @@ class Integer alias_method :week, :weeks end + module Mauve ## Configuration object for Mauve. @@ -30,28 +23,6 @@ module Mauve # # @TODO Write some more documentation. This is woefully inadequate. # - # - # == How to add a new class to the configuration? - # - # - Add a method to ConfigurationBuilder such that your new object - # maybe created. Call it created_NEW_OBJ. - # - # - Create a new class inheriting from ObjectBuilder with at least a - # builder_setup() method. This should create the new object you want. - # - # - Define attributes for the new class are defined as "is_attribute". - # - # - Methods for the new class are defined as methods or missing_method - # depending on what one wishes to do. Remember to define a method - # with "instance_eval(&block)" if you want to call said block within - # the new class. - # - # - Add a "is_builder "", BuilderCLASS" clause in the - # ConfigurationBuilder class. - # - # That should be it. - # - # @author Matthew Bloch, Yann Golanski class Configuration class << self @@ -60,426 +31,19 @@ module Mauve attr_accessor :server attr_accessor :last_alert_group - attr_reader :notification_methods - attr_reader :people - attr_reader :alert_groups - attr_reader :people_lists - attr_reader :source_lists - attr_reader :logger + attr_reader :notification_methods + attr_reader :people + attr_reader :alert_groups + attr_reader :people_lists + attr_reader :source_lists def initialize @notification_methods = {} @people = {} - @people_lists = {} + @people_lists = Hash.new{|h,k| h[k] = Mauve::PeopleList.new(k)} + @source_lists = Hash.new{|h,k| h[k] = Mauve::SourceList.new(k)} @alert_groups = [] - @source_lists = SourceList.new() - @logger = Log4r::Logger.new("Mauve") - end - - end - - class LoggerOutputterBuilder < ObjectBuilder - - def builder_setup(outputter) - @outputter = outputter.capitalize+"Outputter" - - begin - Log4r.const_get(@outputter) - rescue - require "log4r/outputter/#{@outputter.downcase}" - end - - @outputter_name = "Mauve-"+5.times.collect{rand(36).to_s(36)}.join - - @args = {} - end - - def result - @result ||= Log4r.const_get(@outputter).new("Mauve", @args) - end - - def format(f) - result.formatter = Log4r::PatternFormatter.new(:pattern => f) - end - - def method_missing(name, value=nil) - if value.nil? - result.send(name.to_sym) - else - @args[name.to_sym] = value - end - end - - end - - class LoggerBuilder < ObjectBuilder - - is_builder "outputter", LoggerOutputterBuilder - - - def builder_setup - logger = Log4r::Logger.new("Mauve") - @default_format = nil - @default_level = Log4r::RootLogger.instance.level - end - - def result - @result = Log4r::Logger['Mauve'] - end - - def default_format(f) - @default_formatter = Log4r::PatternFormatter.new(:pattern => f) - # - # Set all current outputters - # - result.outputters.each do |o| - o.formatter = @default_formatter if o.formatter.is_a?(Log4r::DefaultFormatter) - end - end - - def default_level(l) - if Log4r::Log4rTools.valid_level?(l) - @default_level = l - else - raise "Bad default level set for the logger #{l}.inspect" - end - - result.outputters.each do |o| - o.level = @default_level if o.level == Log4r::RootLogger.instance.level - end - end - - def created_outputter(outputter) - # - # Set the formatter and level for any newly created outputters - # - if @default_formatter - outputter.formatter = @default_formatter if outputter.formatter.is_a?(Log4r::DefaultFormatter) - end - - if @default_level - outputter.level = @default_level if outputter.level == Log4r::RootLogger.instance.level - end - - result.outputters << outputter - end - - end - - class ProcessorBuilder < ObjectBuilder - is_attribute "sleep_interval" - is_attribute "transmission_cache_expire_time" - - def builder_setup - @result = Processor.instance - end - end - - class UDPServerBuilder < ObjectBuilder - is_attribute "port" - is_attribute "ip" - is_attribute "sleep_interval" - - def builder_setup - @result = UDPServer.instance - end - end - - class TimerBuilder < ObjectBuilder - is_attribute "sleep_interval" - - def builder_setup - @result = Timer.instance - end - end - - class HeartbeatBuilder < ObjectBuilder - is_attribute "destination" - is_attribute "interval" - - def builder_setup - @result = Heartbeat.instance - end - end - - class HTTPServerBuilder < ObjectBuilder - - is_attribute "port" - is_attribute "ip" - is_attribute "document_root" - is_attribute "session_secret" - is_attribute "base_url" - - def builder_setup - @result = HTTPServer.instance - end - end - - class NotifierBuilder < ObjectBuilder - is_attribute "sleep_interval" - - def builder_setup - @result = Notifier.instance - end - end - - class ServerBuilder < ObjectBuilder - - is_builder "web_interface", HTTPServerBuilder - is_builder "listener", UDPServerBuilder - 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.configure(@args) - @result - end - - def created_web_interface(web_interface) - @web_interface = web_interface - end - - def created_listener(listener) - @listener = listener - end - - def created_processor(processor) - @processor = processor - end - - def created_notifier(notifier) - @notifier = notifier - end - - def created_heartbeat(heartbeat) - @heartbeat = heartbeat - end - end - - class NotificationMethodBuilder < ObjectBuilder - - def builder_setup(name) - @notification_type = name.capitalize - @name = name - provider("Default") - end - - def provider(name) - notifiers_base = Mauve::Notifiers - notifiers_type = notifiers_base.const_get(@notification_type) - @provider_class = notifiers_type.const_get(name) - end - - def result - @result ||= @provider_class.new(@name) - end - - def method_missing(name, value=nil) - if value - result.send("#{name}=".to_sym, value) - else - result.send(name.to_sym) - end - end - - end - - class PersonBuilder < ObjectBuilder - - def builder_setup(username) - @result = Person.new(username) - end - - is_block_attribute "urgent" - is_block_attribute "normal" - is_block_attribute "low" - - def all(&block); urgent(&block); normal(&block); low(&block); end - - def password (pwd) - @result.password = pwd.to_s - end - - def holiday_url (url) - @result.holiday_url = url.to_s - end - - def email(e) - @result.email = e.to_s - end - - def xmpp(x) - @result.xmpp = x.to_s - end - - def sms(x) - @result.sms = x.to_s - end - - 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.keys[0].kind_of?(Integer) && h.values[0].kind_of?(Integer) - @result.notification_thresholds[h.values[0]] = Array.new(h.keys[0]) - end - - end - - class NotificationBuilder < ObjectBuilder - - def builder_setup(*who) - who = who.map do |username| - #raise BuildException.new("You haven't declared who #{username} is") unless - # @context.people[username] - #@context.people[username] - if @context.people[username] - @context.people[username] - elsif @context.people_lists[username] - @context.people_lists[username] - else - raise BuildException.new("You have not declared who #{username} is") - end - end - @result = Notification.new(who, @context.last_alert_group.level) - end - - is_attribute "every" - is_block_attribute "during" - ##is_attribute "hours_in_day" - ##is_attribute "unacknowledged" - - end - - class AlertGroupBuilder < ObjectBuilder - - def builder_setup(name=anonymous_name) - @result = AlertGroup.new(name) - @context.last_alert_group = @result end - is_block_attribute "includes" - is_attribute "acknowledgement_time" - is_attribute "level" - - is_builder "notify", NotificationBuilder - - def created_notify(notification) - @result.notifications ||= [] - @result.notifications << notification - end - end - - # New list of persons. - # @author Yann Golanski - class PeopleListBuilder < ObjectBuilder - - # Create a new instance and adds it. - def builder_setup(label) - @result = PeopleList.new(label) - end - - is_attribute "list" - - end - - # New list of sources. - # @author Yann Golanski - class AddSourceListBuilder < ObjectBuilder - - # Create the temporary object. - def builder_setup(label) - @result = AddSoruceList.new(label) - end - - # List of IP addresses or hostnames. - is_attribute "list" - - end - - - # this should live in AlertGroupBuilder but can't due to - # http://briancarper.net/blog/ruby-instance_eval_constant_scoping_broken - # - module ConfigConstants - URGENT = :urgent - NORMAL = :normal - LOW = :low - end - - class ConfigurationBuilder < ObjectBuilder - - include ConfigConstants - - is_builder "server", ServerBuilder - is_builder "notification_method", NotificationMethodBuilder - is_builder "person", PersonBuilder - is_builder "alert_group", AlertGroupBuilder - is_builder "people_list", PeopleListBuilder - is_builder "add_source_list", AddSourceListBuilder - is_builder "logger", LoggerBuilder - - def initialize - @context = @result = Configuration.new - # FIXME: need to test blocks that are not immediately evaluated - end - - def created_server(server) - raise BuildError.new("Only one 'server' clause can be specified") if - @result.server - @result.server = server - end - - def created_notification_method(notification_method) - name = notification_method.name - raise BuildException.new("Duplicate notification '#{name}'") if - @result.notification_methods[name] - @result.notification_methods[name] = notification_method - end - - def created_person(person) - name = person.username - raise BuildException.new("Duplicate person '#{name}'") if - @result.people[name] - @result.people[person.username] = person - end - - def created_alert_group(alert_group) - name = alert_group.name - raise BuildException.new("Duplicate alert_group '#{name}'") unless - @result.alert_groups.select { |g| g.name == name }.empty? - @result.alert_groups << alert_group - end - - # Create a new instance of people_list. - # - # @param [PeopleList] people_list The new list of persons. - # @return [NULL] nada. - def created_people_list(people_list) - label = people_list.label - raise BuildException.new("Duplicate people_list '#{label}'") if @result.people_lists[label] - @result.people_lists[label] = people_list - end - - # Create a new list of sources. - # - # @param [] add_source_list - # @return [NULL] nada. - def created_add_source_list(add_source_list) - @result.source_lists.create_new_list(add_source_list.label, - add_source_list.list) - end - - end - end diff --git a/lib/mauve/configuration_builder.rb b/lib/mauve/configuration_builder.rb new file mode 100644 index 0000000..fb3c781 --- /dev/null +++ b/lib/mauve/configuration_builder.rb @@ -0,0 +1,38 @@ +# encoding: UTF-8 +require 'object_builder' +require 'mauve/configuration' +require 'mauve/people_list' +require 'mauve/source_list' + +module Mauve + class ConfigurationBuilder < ObjectBuilder + + # + # This overwrites the default ObjectBuilder initialize method, such that + # the context is set as a new configuration + # + def initialize + @context = @result = Configuration.new + # FIXME: need to test blocks that are not immediately evaluated + end + + def source_list(label, *list) + _logger.warn "Duplicate source_list '#{label}'" if @result.source_lists.has_key?(label) + @result.source_lists[label] += list + end + + def people_list(label, *list) + _logger.warn("Duplicate people_list '#{label}'") if @result.people_lists.has_key?(label) + @result.people_lists[label] += list + end + + # + # Have to use the method _logger here, cos logger is defined as a builder elsewhere. + # + def _logger + @logger ||= Log4r::Logger.new(self.class.to_s) + end + + end + +end diff --git a/lib/mauve/configuration_builders.rb b/lib/mauve/configuration_builders.rb new file mode 100644 index 0000000..03d666e --- /dev/null +++ b/lib/mauve/configuration_builders.rb @@ -0,0 +1,6 @@ + +require 'mauve/configuration_builders/logger' +require 'mauve/configuration_builders/notification_method' +require 'mauve/configuration_builders/person' +require 'mauve/configuration_builders/server' +require 'mauve/configuration_builders/alert_group' diff --git a/lib/mauve/configuration_builders/alert_group.rb b/lib/mauve/configuration_builders/alert_group.rb new file mode 100644 index 0000000..9561d4d --- /dev/null +++ b/lib/mauve/configuration_builders/alert_group.rb @@ -0,0 +1,81 @@ +# encoding: UTF-8 +require 'object_builder' +require 'mauve/notification' +require 'mauve/configuration_builder' +require 'mauve/alert_group' +require 'mauve/notification' + +module Mauve + module ConfigurationBuilders + + class Notification < ObjectBuilder + + def builder_setup(*who) + who = who.map do |username| + #raise BuildException.new("You haven't declared who #{username} is") unless + # @context.people[username] + #@context.people[username] + if @context.people[username] + @context.people[username] + elsif @context.people_lists[username] + @context.people_lists[username] + else + raise BuildException.new("You have not declared who #{username} is") + end + end + @result = Mauve::Notification.new(who, @context.last_alert_group.level) + end + + is_attribute "every" + is_block_attribute "during" + ##is_attribute "hours_in_day" + ##is_attribute "unacknowledged" + end + + class AlertGroup < ObjectBuilder + + def builder_setup(name=anonymous_name) + @result = Mauve::AlertGroup.new(name) + @context.last_alert_group = @result + end + + is_block_attribute "includes" + is_attribute "acknowledgement_time" + is_attribute "level" + + is_builder "notify", Notification + + def created_notify(notification) + @result.notifications ||= [] + @result.notifications << notification + end + + end + + end + + # this should live in AlertGroup but can't due to + # http://briancarper.net/blog/ruby-instance_eval_constant_scoping_broken + # + module AlertGroupConstants + URGENT = :urgent + NORMAL = :normal + LOW = :low + end + + + class ConfigurationBuilder < ObjectBuilder + + include AlertGroupConstants + + is_builder "alert_group", ConfigurationBuilders::AlertGroup + + def created_alert_group(alert_group) + name = alert_group.name + raise BuildException.new("Duplicate alert_group '#{name}'") unless @result.alert_groups.select { |g| g.name == name }.empty? + @result.alert_groups << alert_group + end + + end + +end diff --git a/lib/mauve/configuration_builders/logger.rb b/lib/mauve/configuration_builders/logger.rb new file mode 100644 index 0000000..a1d0388 --- /dev/null +++ b/lib/mauve/configuration_builders/logger.rb @@ -0,0 +1,120 @@ +# encoding: UTF-8 +require 'log4r' +require 'mauve/configuration_builder' + +module Mauve + module ConfigurationBuilders + + class LoggerOutputter < ObjectBuilder + + def builder_setup(outputter) + @outputter = outputter.capitalize+"Outputter" + + begin + Log4r.const_get(@outputter) + rescue + require "log4r/outputter/#{@outputter.downcase}" + end + + @outputter_name = "Mauve-"+5.times.collect{rand(36).to_s(36)}.join + + @args = {} + end + + def result + @result ||= Log4r.const_get(@outputter).new("Mauve", @args) + end + + def format(f) + result.formatter = Log4r::PatternFormatter.new(:pattern => f) + end + + # + # This is needed to be able to pass arbitrary arguments to Log4r + # outputters. + # + def method_missing(name, value=nil) + if value.nil? + result.send(name.to_sym) + else + @args[name.to_sym] = value + end + end + + end + + class Logger < ObjectBuilder + + is_builder "outputter", LoggerOutputter + + def builder_setup + @result = Log4r::Logger.new('Mauve') + @default_format = nil + @default_level = Log4r::RootLogger.instance.level + end + + def default_format(f) + begin + @default_formatter = Log4r::PatternFormatter.new(:pattern => f) + rescue SyntaxError + raise BuildException.new "Bad log format #{f.inspect}" + end + # + # Set all current outputters + # + result.outputters.each do |o| + o.formatter = @default_formatter if o.formatter.is_a?(Log4r::DefaultFormatter) + end + end + + def default_level(l) + if Log4r::Log4rTools.valid_level?(l) + @default_level = l + else + raise "Bad default level set for the logger #{l}.inspect" + end + + result.outputters.each do |o| + o.level = @default_level if o.level == Log4r::RootLogger.instance.level + end + end + + def created_outputter(outputter) + # + # Set the formatter and level for any newly created outputters + # + if @default_formatter + outputter.formatter = @default_formatter if outputter.formatter.is_a?(Log4r::DefaultFormatter) + end + + if @default_level + outputter.level = @default_level if outputter.level == Log4r::RootLogger.instance.level + end + + result.outputters << outputter + end + end + end + + # + # this should live in Logger but can't due to + # http://briancarper.net/blog/ruby-instance_eval_constant_scoping_broken + # + module LoggerConstants + Log4r.define_levels(*Log4r::Log4rConfig::LogLevels) # ensure levels are loaded + + DEBUG = Log4r::DEBUG + INFO = Log4r::INFO + WARN = Log4r::WARN + ERROR = Log4r::ERROR + FATAL = Log4r::FATAL + end + + class ConfigurationBuilder < ObjectBuilder + + include LoggerConstants + + is_builder "logger", ConfigurationBuilders::Logger + + end +end diff --git a/lib/mauve/configuration_builders/notification_method.rb b/lib/mauve/configuration_builders/notification_method.rb new file mode 100644 index 0000000..1192078 --- /dev/null +++ b/lib/mauve/configuration_builders/notification_method.rb @@ -0,0 +1,50 @@ +require 'mauve/notifiers' +require 'mauve/configuration_builder' + +# encoding: UTF-8 +module Mauve + module ConfigurationBuilders + class NotificationMethod < ObjectBuilder + + def builder_setup(name) + @notification_type = name.capitalize + @name = name + provider("Default") + end + + def provider(name) + notifiers_base = Mauve::Notifiers + notifiers_type = notifiers_base.const_get(@notification_type) + @provider_class = notifiers_type.const_get(name) + end + + def result + @result ||= @provider_class.new(@name) + end + + def method_missing(name, value=nil) + if value + result.send("#{name}=".to_sym, value) + else + result.send(name.to_sym) + end + end + end + end + + # + # Add notification_method to our top-level config builder + # + class ConfigurationBuilder < ObjectBuilder + is_builder "notification_method", ConfigurationBuilders::NotificationMethod + + def created_notification_method(notification_method) + name = notification_method.name + raise BuildException.new("Duplicate notification '#{name}'") if @result.notification_methods[name] + @result.notification_methods[name] = notification_method + end + + end + +end + diff --git a/lib/mauve/configuration_builders/person.rb b/lib/mauve/configuration_builders/person.rb new file mode 100644 index 0000000..1f2af71 --- /dev/null +++ b/lib/mauve/configuration_builders/person.rb @@ -0,0 +1,60 @@ +# encoding: UTF-8 +require 'object_builder' +require 'mauve/person' +require 'mauve/configuration_builder' + +module Mauve + module ConfigurationBuilders + + class Person < ObjectBuilder + + def builder_setup(username) + @result = Mauve::Person.new(username) + end + + is_block_attribute "urgent" + is_block_attribute "normal" + is_block_attribute "low" + + def all(&block); urgent(&block); normal(&block); low(&block); end + + def password (pwd) + @result.password = pwd.to_s + end + + def holiday_url (url) + @result.holiday_url = url.to_s + end + + def email(e) + @result.email = e.to_s + end + + def xmpp(x) + @result.xmpp = x.to_s + end + + def sms(x) + @result.sms = x.to_s + end + + 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.keys[0].kind_of?(Integer) && h.values[0].kind_of?(Integer) + @result.notification_thresholds[h.values[0]] = Array.new(h.keys[0]) + end + end + end + + class ConfigurationBuilder < ObjectBuilder + + is_builder "person", ConfigurationBuilders::Person + + def created_person(person) + name = person.username + raise BuildException.new("Duplicate person '#{name}'") if @result.people[name] + @result.people[person.username] = person + end + + end +end diff --git a/lib/mauve/configuration_builders/server.rb b/lib/mauve/configuration_builders/server.rb new file mode 100644 index 0000000..0fa811b --- /dev/null +++ b/lib/mauve/configuration_builders/server.rb @@ -0,0 +1,102 @@ +# encoding: UTF-8 +require 'mauve/server' +require 'mauve/configuration_builder' + +module Mauve + module ConfigurationBuilders + + class HTTPServer < ObjectBuilder + is_attribute "port" + is_attribute "ip" + is_attribute "document_root" + is_attribute "session_secret" + is_attribute "base_url" + + def builder_setup + @result = Mauve::HTTPServer.instance + end + end + + class UDPServer < ObjectBuilder + is_attribute "port" + is_attribute "ip" + is_attribute "poll_every" + + def builder_setup + @result = Mauve::UDPServer.instance + end + end + + class Processor < ObjectBuilder + is_attribute "poll_every" + is_attribute "transmission_cache_expire_time" + + def builder_setup + @result = Mauve::Processor.instance + end + end + + class Timer < ObjectBuilder + is_attribute "poll_every" + + def builder_setup + @result = Mauve::Timer.instance + end + end + + class Notifier < ObjectBuilder + is_attribute "poll_every" + + def builder_setup + @result = Mauve::Notifier.instance + end + end + + class Heartbeat < ObjectBuilder + is_attribute "destination" + is_attribute "detail" + is_attribute "summary" + is_attribute "raise_after" + is_attribute "send_every" + + def builder_setup + @result = Mauve::Heartbeat.instance + end + end + + class Server < ObjectBuilder + # + # Set up second-level builders + # + is_builder "web_interface", HTTPServer + is_builder "listener", UDPServer + is_builder "processor", Processor + is_builder "timer", Timer + is_builder "notifier", Notifier + is_builder "heartbeat", Heartbeat + + is_attribute "hostname" + is_attribute "database" + is_attribute "initial_sleep" + + def builder_setup + @result = Mauve::Server.instance + end + end + end + + # + # Add server to our top-level config builder + # + class ConfigurationBuilder < ObjectBuilder + + is_builder "server", ConfigurationBuilders::Server + + def created_server(server) + raise BuildError.new("Only one 'server' clause can be specified") if @result.server + @result.server = server + end + + end + +end diff --git a/lib/mauve/datamapper.rb b/lib/mauve/datamapper.rb index f46536e..12f95dc 100644 --- a/lib/mauve/datamapper.rb +++ b/lib/mauve/datamapper.rb @@ -4,13 +4,12 @@ # # require 'dm-core' +require 'dm-validations' require 'dm-sqlite-adapter-with-mutex' require 'dm-types' require 'dm-serializer' require 'dm-migrations' -require 'dm-validations' require 'dm-timestamps' - # DataMapper::Model.raise_on_save_failure = true diff --git a/lib/mauve/heartbeat.rb b/lib/mauve/heartbeat.rb index 0f51f80..add0cdf 100644 --- a/lib/mauve/heartbeat.rb +++ b/lib/mauve/heartbeat.rb @@ -12,21 +12,38 @@ module Mauve include Singleton - attr_accessor :destination, :summary, :detail - attr_reader :sleep_interval, :raise_at + attr_reader :raise_after, :destination, :summary, :detail def initialize super @destination = nil - @summary = "Mauve alert server down." + @summary = "Mauve alert server heartbeat failed" @detail = "The Mauve server at #{Server.instance.hostname} has failed to send a heartbeat." - self.raise_at = 600 + @raise_after = 310 + @poll_every = 60 end - def raise_at=(i) - @raise_at = i - @sleep_interval = ((i.to_f)/2.5).round.to_i + def raise_after=(i) + raise ArgumentError "raise_after must be an integer" unless i.is_a?(Integer) + @raise_after = i + end + + alias send_every= poll_every= + + def summary=(s) + raise ArgumentError "summary must be a string" unless s.is_a?(String) + @summary = s + end + + def detail=(d) + raise ArgumentError "detail must be a string" unless d.is_a?(String) + @detail = d + end + + def destination=(d) + raise ArgumentError "destination must be a string" unless d.is_a?(String) + @destination = d end def logger @@ -49,12 +66,13 @@ module Mauve 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.raise_time = (MauveTime.now.to_f+self.raise_after).to_i message.clear_time = MauveTime.now.to_i update.alert << message Mauve::Sender.new(self.destination).send(update) + logger.debug "Sent to #{self.destination}" end end diff --git a/lib/mauve/http_server.rb b/lib/mauve/http_server.rb index 7bd4467..d0ee29f 100644 --- a/lib/mauve/http_server.rb +++ b/lib/mauve/http_server.rb @@ -8,6 +8,7 @@ require 'mauve/mauve_thread' require 'digest/sha1' require 'log4r' require 'thin' +require 'ipaddr' # # Needed for Lenny version of thin converted by apt-ruby, for some reason. # @@ -87,18 +88,53 @@ module Mauve include Singleton - attr_accessor :port, :ip, :document_root, :base_url - attr_accessor :session_secret + attr_reader :port, :ip, :document_root, :base_url + attr_reader :session_secret def initialize super - @port = 1288 - @ip = "127.0.0.1" - @document_root = "/usr/share/mauvealert" - @session_secret = "%x" % rand(2**100) - @server_name = nil + self.port = 1288 + self.ip = "127.0.0.1" + self.document_root = "./" + self.session_secret = "%x" % rand(2**100) end + def port=(pr) + raise ArgumentError, "port must be an integer between 0 and #{2**16-1}" unless pr.is_a?(Integer) and pr < 2**16 and pr > 0 + @port = pr + end + + def ip=(i) + raise ArgumentError, "ip must be a string" unless i.is_a?(String) + # + # Use ipaddr to sanitize our IP. + # + IPAddr.new(i) + @ip = i + end + + def document_root=(d) + raise ArgumentError, "document_root must be a string" unless d.is_a?(String) + raise Errno::ENOENT, d unless File.exists?(d) + raise Errno::ENOTDIR, d unless File.directory?(d) + + @document_root = d + end + + def base_url=(b) + raise ArgumentError, "base_url must be a string" unless b.is_a?(String) + raise ArgumentError, "base_url should start with http:// or https://" unless b =~ /^https?:\/\// + # + # Strip off any trailing slash + # + @base_url = b.chomp("/") + end + + def session_secret=(s) + raise ArgumentError, "session_secret must be a string" unless s.is_a?(String) + @session_secret = s + end + def main_loop # # Sessions are kept for 8 days. diff --git a/lib/mauve/mauve_resolv.rb b/lib/mauve/mauve_resolv.rb new file mode 100644 index 0000000..6c97bef --- /dev/null +++ b/lib/mauve/mauve_resolv.rb @@ -0,0 +1,35 @@ +require 'resolv-replace' + +# +# +# + +module Mauve + class MauveResolv + class << self + def get_ips_for(host) + record_types = %w(A AAAA) + ips = [] + + %w(A AAAA).each do |type| + begin + Resolv::DNS.open do |dns| + dns.getresources(host, Resolv::DNS::Resource::IN.const_get(type)).each do |a| + ips << a.address.to_s + end + end + rescue Resolv::ResolvError, Resolv::ResolvTimeout => e + logger.warn("#{host} could not be resolved because #{e.message}.") + end + end + ips + end + + def logger + @logger ||= Log4r::Logger.new(self.to_s) + end + end + end +end + + diff --git a/lib/mauve/mauve_thread.rb b/lib/mauve/mauve_thread.rb index 6409f98..52c2801 100644 --- a/lib/mauve/mauve_thread.rb +++ b/lib/mauve/mauve_thread.rb @@ -5,7 +5,7 @@ module Mauve class MauveThread - attr_reader :state + attr_reader :state, :poll_every def initialize @thread = nil @@ -16,15 +16,20 @@ module Mauve @logger ||= Log4r::Logger.new(self.class.to_s) end + def poll_every=(i) + raise ArgumentError.new("poll_every must be numeric") unless i.is_a?(Numeric) + @poll_every = i + end + def run_thread(interval = 0.1) # # Good to go. # self.state = :starting - @sleep_interval ||= interval + @poll_every ||= interval - sleep_loops = (@sleep_interval.to_f / 0.1).round.to_i + sleep_loops = (@poll_every.to_f / 0.1).round.to_i sleep_loops = 1 if sleep_loops.nil? or sleep_loops < 1 while self.state != :stopping do @@ -110,9 +115,9 @@ module Mauve @thread.join if @thread.is_a?(Thread) end - def raise(ex) - @thread.raise(ex) - end +# def raise(ex) +# @thread.raise(ex) +# end def backtrace @thread.backtrace if @thread.is_a?(Thread) diff --git a/lib/mauve/notifier.rb b/lib/mauve/notifier.rb index 6099457..1c3bf9b 100644 --- a/lib/mauve/notifier.rb +++ b/lib/mauve/notifier.rb @@ -8,8 +8,6 @@ module Mauve include Singleton - attr_accessor :sleep_interval - def main_loop # # Cycle through the buffer. @@ -48,7 +46,7 @@ module Mauve # Connect to XMPP server # xmpp = Configuration.current.notification_methods['xmpp'] - xmpp.connect + xmpp.connect Configuration.current.people.each do |username, person| # @@ -74,6 +72,12 @@ module Mauve def stop super + + # + # Flush the queue. + # + main_loop + if Configuration.current.notification_methods['xmpp'] Configuration.current.notification_methods['xmpp'].close end diff --git a/lib/mauve/notifiers/email.rb b/lib/mauve/notifiers/email.rb index b6a1e1b..a06d332 100644 --- a/lib/mauve/notifiers/email.rb +++ b/lib/mauve/notifiers/email.rb @@ -27,7 +27,6 @@ module Mauve @hostname = "localhost" @signature = "This is an automatic mailing, please do not reply." @subject_prefix = "" - @suppressed_changed = nil end def logger diff --git a/lib/mauve/notifiers/sms_aql.rb b/lib/mauve/notifiers/sms_aql.rb index 9181be3..54a3104 100644 --- a/lib/mauve/notifiers/sms_aql.rb +++ b/lib/mauve/notifiers/sms_aql.rb @@ -6,13 +6,11 @@ module Mauve module Sms require 'net/https' + class AQL GATEWAY = "https://gw1.aql.com/sms/sms_gw.php" - attr :username, true - attr :password, true - attr :from, true - attr :max_messages_per_alert, true + attr_writer :username, :password, :from attr_reader :name def initialize(name) @@ -64,31 +62,11 @@ module Mauve logger.error("Could not find sms.txt.erb template") alert.to_s end - - others = all_alerts-[alert] - if !others.empty? - txt += (1 == others.length)? - "and a lone other." : - "and #{others.length} others." - #txt += "and #{others.length} others: " - #txt += others.map { |alert| alert.summary_one_line }.join(", ") - end - - # TODO: Fix link to be accurate. - # txt += "link: https://alert.bytemark.co.uk/alerts" - - ## @TODO: Add a link to acknowledge the alert in the text? - #txt += "Acknoweledge alert: "+ - # "https://alert.bytemark.co.uk/alert/acknowledge/"+ - # "#{alert.id}/#{alert.get_default_acknowledge_time} - - txt end def normalize_number(n) n.split("").select { |s| (?0..?9).include?(s[0]) }.join.gsub(/^0/, "44") end - include Debug end end end diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb index 991194d..7cb71c4 100644 --- a/lib/mauve/notifiers/xmpp.rb +++ b/lib/mauve/notifiers/xmpp.rb @@ -169,6 +169,10 @@ module Mauve @client.close! end end + + def ready? + @client.is_a?(Jabber::Client) and @client.is_connected? + end # # Takes an alert and converts it into a message. diff --git a/lib/mauve/people_list.rb b/lib/mauve/people_list.rb index 32e0708..b15db3f 100644 --- a/lib/mauve/people_list.rb +++ b/lib/mauve/people_list.rb @@ -7,23 +7,43 @@ module Mauve # Stores a list of name. # # @author Yann Golanski - class PeopleList < Struct.new(:label, :list) + class PeopleList - # Default contrustor. - def initialize (*args) - super(*args) - end + attr_reader :label, :list - def label - self[:label] + # Default contrustor. + def initialize(label) + raise ArgumentError, "people_list label must be a string" unless label.is_a?(String) + @label = label + @list = [] end alias username label - def list - self[:list] || [] + def +(arr) + case arr + when Array + arr = arr.flatten + when String + arr = [arr] + else + logger.warn "Not sure what to do with #{arr.inspect} -- converting to string, and continuing" + arr = [arr.to_s] + end + + arr.each do |person| + if @list.include?(person) + logger.warn "#{person} is already on the #{self.label} list" + else + @list << person + end + end + + self end + alias add_to_list + + # # Set up the logger def logger diff --git a/lib/mauve/processor.rb b/lib/mauve/processor.rb index e3aac54..f5e8fac 100644 --- a/lib/mauve/processor.rb +++ b/lib/mauve/processor.rb @@ -8,7 +8,7 @@ module Mauve include Singleton - attr_accessor :transmission_cache_expire_time, :sleep_interval + attr_reader :transmission_cache_expire_time def initialize super @@ -24,6 +24,11 @@ module Mauve @logger ||= Log4r::Logger.new(self.class.to_s) end + def transmission_cache_expire_time=(i) + raise ArgumentError, "transmission_cache_expire_time must be an integer" unless i.is_a?(Integer) + @transmission_cache_expire_time = i + end + def main_loop sz = Server.packet_buffer_size diff --git a/lib/mauve/sender.rb b/lib/mauve/sender.rb index ad047fe..8194180 100644 --- a/lib/mauve/sender.rb +++ b/lib/mauve/sender.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 require 'ipaddr' -require 'resolv' require 'socket' +require 'mauve/mauve_resolv' require 'mauve/mauve_time' module Mauve @@ -56,6 +56,7 @@ module Mauve use_srv = false end + list = [] Resolv::DNS.open do |dns| if use_srv # @@ -64,37 +65,29 @@ module Mauve # srv_domain = (domain[0] == ?_ ? domain : "_mauvealert._udp.#{domain}") - list = dns.getresources(srv_domain, SRV).map do |srv| + list += dns.getresources(srv_domain, SRV).map do |srv| [srv.target.to_s, srv.port] end end + end + # + # If nothing found, just use the domain and port + # + list = [[domain, port]] if list.empty? + list.each do |d,p| + r = [] # - # If nothing found, just use the domain and port + # This gets both AAAA and A records # - list = [[domain, port]] if list.empty? - - list.each do |d,p| - r = [] - # - # Try IPv6 first. - # - dns.getresources(d, AAAA).map do |a| - r << [a.address.to_s, p] - end - - # - # Try IPv4 too. - # - dns.getresources(d, A).each do |a| - r << [a.address.to_s, p] - end - - results += r unless r.empty? + Mauve::MauveResolv.get_ips_for(d).each do |a| + r << [a, p] end - end - end - end + + results += r unless r.empty? + end + end ## case + end ## each # # Validate results. diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb index 20f7045..307002f 100644 --- a/lib/mauve/server.rb +++ b/lib/mauve/server.rb @@ -16,34 +16,23 @@ require 'log4r' module Mauve - class Server - - DEFAULT_CONFIGURATION = { } - + class Server < MauveThread # # This is the order in which the threads should be started. # THREAD_CLASSES = [UDPServer, HTTPServer, Processor, Timer, Notifier, Heartbeat] - attr_accessor :hostname, :database, :initial_sleep - attr_reader :stopped_at, :started_at, :packet_buffer, :notification_buffer + attr_reader :hostname, :database, :initial_sleep + attr_reader :packet_buffer, :notification_buffer, :started_at include Singleton def initialize - # Set the logger up - - # Sleep time between pooling the @buffer buffer. - @sleep = 1 - - @frozen = false - @stop = false + super @hostname = "localhost" @database = "sqlite3:///./mauvealert.db" - - @stopped_at = MauveTime.now @started_at = MauveTime.now @initial_sleep = 300 @@ -53,29 +42,28 @@ module Mauve # @packet_buffer = [] @notification_buffer = [] + end + + def hostname=(h) + raise ArgumentError, "hostname must be a string" unless h.is_a?(String) + @hostname = h + end + + def database=(d) + raise ArgumentError, "database must be a string" unless d.is_a?(String) + @database = d + end - @config = DEFAULT_CONFIGURATION + def initial_sleep=(s) + raise ArgumentError, "initial_sleep must be numeric" unless s.is_a?(Numeric) + @initial_sleep = s end def logger @logger ||= Log4r::Logger.new(self.class.to_s) end - def configure(config_spec = nil) - # - # Update the configuration - # - if config_spec.nil? - # Do nothing - elsif config_spec.kind_of?(String) and File.exists?(config_spec) - @config.update(YAML.load_file(config_spec)) - elsif config_spec.kind_of?(Hash) - @config.update(config_spec) - else - raise ArgumentError.new("Unknown configuration spec "+config_spec.inspect) - end - - # + def setup DataMapper.setup(:default, @database) # DataObjects::Sqlite3.logger = Log4r::Logger.new("Mauve::DataMapper") @@ -86,119 +74,93 @@ module Mauve AlertChanged.auto_upgrade! History.auto_upgrade! Mauve::AlertEarliestDate.create_view! - - # - # Work out when the server was last stopped - # - # topped_at = self.last_heartbeat - end - - def last_heartbeat - # - # Work out when the last update was - # - [ Alert.last(:order => :updated_at.asc), - AlertChanged.last(:order => :updated_at.asc) ]. - reject{|a| a.nil? or a.updated_at.nil? }. - collect{|a| a.updated_at.to_time}. - sort. - last end - def freeze - @frozen = true - end + def start + self.state = :starting - def thaw - @thaw = true + self.setup + + self.run_thread { self.main_loop } end - def stop - if @stop - logger.debug("Stop already called!") - return - end - - @stop = true + alias run start + def main_loop thread_list = Thread.list thread_list.delete(Thread.current) THREAD_CLASSES.each do |klass| - thread_list.delete(klass.instance) - klass.instance.stop unless klass.instance.nil? - end - - thread_list.each do |t| - t.exit - end + # + # No need to double check ourselves. + # + thread_list.delete(klass.instance.thread) - logger.info("All threads stopped") - end + # + # Do nothing if we're frozen or supposed to be stopping or still alive! + # + next if self.should_stop? or klass.instance.alive? - def run - @stop = false - - loop do - thread_list = Thread.list - - thread_list.delete(Thread.current) - - THREAD_CLASSES.each do |klass| - # - # No need to double check ourselves. - # - thread_list.delete(klass.instance.thread) - - # - # Do nothing if we're frozen or supposed to be stopping or still alive! - # - next if @frozen or @stop or klass.instance.alive? - - # - # ugh something is beginnging to smell. - # - begin - klass.instance.join - rescue StandardError => ex - logger.error "Caught #{ex.to_s} whilst checking #{klass} thread" - logger.debug ex.backtrace.join("\n") - end - - # - # (re-)start the klass. - # - klass.instance.start unless @stop + # + # ugh something is beginnging to smell. + # + begin + klass.instance.join + rescue StandardError => ex + logger.error "Caught #{ex.to_s} whilst checking #{klass} thread" + logger.debug ex.backtrace.join("\n") end # - # Now do the same with other threads. However if these ones crash, the - # server has to stop, as there is no method to restart them. + # (re-)start the klass. # - thread_list.each do |t| + klass.instance.start unless self.should_stop? + end - next if t.alive? + # + # Now do the same with other threads. However if these ones crash, the + # server has to stop, as there is no method to restart them. + # + thread_list.each do |t| - begin - t.join - rescue StandardError => ex - logger.fatal "Caught #{ex.to_s} whilst checking threads" - logger.debug ex.backtrace.join("\n") - self.stop - break - end + next if self.should_stop? or t.alive? + begin + t.join + rescue StandardError => ex + logger.fatal "Caught #{ex.to_s} whilst checking threads" + logger.debug ex.backtrace.join("\n") + self.stop + break end - break if @stop + end + end + + def stop + if self.state == :stopping + # uh-oh already told to stop. + logger.error "Stop already called. Killing self!" + Kernel.exit 1 + end + + self.state = :stopping - sleep 1 + THREAD_CLASSES.each do |klass| + klass.instance.stop unless klass.instance.nil? end - logger.debug("Thread stopped") + + thread_list = Thread.list + thread_list.delete(Thread.current) + + thread_list.each do |t| + t.exit + end + + self.state = :stopped end - alias start run class << self diff --git a/lib/mauve/source_list.rb b/lib/mauve/source_list.rb index 23f5ae1..c905786 100644 --- a/lib/mauve/source_list.rb +++ b/lib/mauve/source_list.rb @@ -1,6 +1,9 @@ # encoding: UTF-8 require 'log4r' -require 'resolv-replace' +require 'ipaddr' +require 'uri' +require 'mauve/mauve_time' +require 'mauve/mauve_resolv' module Mauve @@ -20,86 +23,130 @@ module Mauve # will occure. # # @author Yann Golanski - class SourceList + class SourceList - # Accessor, read only. Use create_new_list() to create lists. - attr_reader :hash + attr_reader :label, :list ## Default contructor. - def initialize () - @logger = Log4r::Logger.new "Mauve::SourceList" - @hash = Hash.new - @http_head = Regexp.compile(/^http[s]?:\/\//) - @http_tail = Regexp.compile(/\/.*$/) + def initialize (label) + @label = label + @last_resolved_at = nil + @list = [] + @resolved_list = [] end - ## Return whether or not a list contains a source. - # - # @param [String] lst The list name. - # @param [String] src The hostname or IP of the source. - # @return [Boolean] true if there is such a source, false otherwise. - def does_list_contain_source?(lst, src) - raise ArgumentError.new("List name must be a String, not a #{lst.class}") if String != lst.class - raise ArgumentError.new("Source name must be a String, not a #{src.class}") if String != src.class - raise ArgumentError.new("List #{lst} does not exist.") if false == @hash.has_key?(lst) - if src.match(@http_head) - src = src.gsub(@http_head, '').gsub(@http_tail, '') - end - begin - Resolv.getaddresses(src).each do |ip| - return true if @hash[lst].include?(ip) + alias username label + + def +(l) + arr = [l].flatten.collect do |h| + # "*" means [^\.]+ + # "(\d+)\.\.(\d+)" is expanded to every integer between $1 and $2 + # joined by a pipe, e.g. 1..5 means 1|2|3|4|5 + # "." is literal, not a single-character match + if h.is_a?(String) and (h =~ /[\[\]\*]/ or h =~ /(\d+)\.\.(\d+)/) + Regexp.new( + h. + gsub(/(\d+)\.\.(\d+)/) { |a,b| + ($1.to_i..$2.to_i).collect.join("|") + }. + gsub(/\./, "\\."). + gsub(/\*/, "[0-9a-z\\-]+") + + "\\.?$") + elsif h.is_a?(String) and h =~ /^[0-9a-f\.:\/]+$/i + IPAddr.new(h) + else + h + end + end.flatten + + arr.each do |source| + ## + # I would use include? here, but IPAddr tries to convert "foreign" + # classes to intgers, and RegExp doesn't have a to_i method.. + # + if @list.any?{|e| source.is_a?(e.class) and source == e} + logger.warn "#{source} is already on the #{self.label} list" + else + @list << source end - rescue Resolv::ResolvError, Resolv::ResolvMauveTimeout => e - @logger.warn("#{lst} could not be resolved because #{e.message}.") - return false - rescue => e - @logger.error("Unknown exception raised: #{e.class} #{e.message}") - return false end - return false + + @resolved_list = [] + @last_resolved_at = nil + + self end - ## Create a list. - # - # Note that is no elements give IP addresses, we have an empty list. - # This gets logged but otherwise does not stop mauve from working. - # - # @param [String] name The name of the list. - # @param [Array] elem A list of source either hostname or IP. - def create_new_list(name, elem) - raise ArgumentError.new("Name of list is not a String but a #{name.class}") if String != name.class - raise ArgumentError.new("Element list is not an Array but a #{elem.class}") if Array != elem.class - raise ArgumentError.new("A list called #{name} already exists.") if @hash.has_key?(name) - arr = Array.new - elem.each do |host| - begin - Resolv.getaddresses(host).each do |ip| - arr << ip - end - rescue Resolv::ResolvError, Resolv::ResolvMauveTimeout => e - @logger.warn("#{host} could not be resolved because #{e.message}.") - rescue => e - @logger.error("Unknown exception raised: #{e.class} #{e.message}") + alias add_to_list + + + def logger + @logger ||= Log4r::Logger.new self.class.to_s + end + + ## + # Return whether or not a list contains a source. + ## + def includes?(host) + # + # Redo resolution every thirty minutes + # + resolve if @resolved_list.empty? or @last_resolved_at.nil? or (MauveTime.now - 1800) > @last_resolved_at + + # + # Pick out hostnames from URIs. + # + if host =~ /^[a-z][a-z0-9+-]+:\/\// + begin + uri = URI.parse(host) + host = uri.host unless uri.host.nil? + rescue URI::InvalidURIError => ex + # ugh + logger.warn "Did not recognise URI #{host}" end end - @hash[name] = arr.flatten.uniq.compact - if true == @hash[name].empty? - @logger.error("List #{name} is empty! "+ - "Nothing from element list '#{elem}' "+ - "has resolved to anything useable.") + + return true if @resolved_list.any? do |l| + case l + when String + host == l + when Regexp + host =~ l + when IPAddr + begin + l.include?(IPAddr.new(host)) + rescue ArgumentError + # rescue random IPAddr argument errors + false + end + else + false + end end - end - end + return false unless @resolved_list.any?{|l| l.is_a?(IPAddr)} + + ips = MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)} - ## temporary object to convert from configuration file to the SourceList class - class AddSoruceList < Struct.new(:label, :list) + return false if ips.empty? - # Default constructor. - def initialize (*args) - super(*args) + return @resolved_list.select{|i| i.is_a?(IPAddr)}.any? do |list_ip| + ips.any?{|ip| list_ip.include?(ip)} + end + end + def resolve + @last_resolved_at = MauveTime.now + + new_list = @list.collect do |host| + if host.is_a?(String) + ips = [host] + MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)} + else + host + end + end + @resolved_list = new_list.flatten + end end end diff --git a/lib/mauve/timer.rb b/lib/mauve/timer.rb index 91dea18..8533451 100644 --- a/lib/mauve/timer.rb +++ b/lib/mauve/timer.rb @@ -11,8 +11,6 @@ module Mauve include Singleton - attr_accessor :sleep_interval, :last_run_at - def main_loop # # Get the next alert. diff --git a/lib/mauve/udp_server.rb b/lib/mauve/udp_server.rb index 049ea09..4e6296d 100644 --- a/lib/mauve/udp_server.rb +++ b/lib/mauve/udp_server.rb @@ -12,31 +12,30 @@ module Mauve include Singleton - attr_accessor :ip, :port, :sleep_interval + attr_reader :ip, :port def initialize super - # - # Set the logger up - # - @ip = "127.0.0.1" - @port = 32741 + self.ip = "127.0.0.1" + self.port = 32741 @socket = nil - @closing_now = false - @sleep_interval = 0 end - - def open_socket - # - # check the IP address - # - _ip = IPAddr.new(@ip) + + def ip=(i) + raise ArgumentError, "ip must be a string" unless i.is_a?(String) + @ip = IPAddr.new(i) + end + + def port=(pr) + raise ArgumentError, "port must be an integer between 0 and #{2**16-1}" unless pr.is_a?(Integer) and pr < 2**16 and pr > 0 + @port = pr + end + def open_socket # # Specify the family when opening the socket. # - @socket = UDPSocket.new(_ip.family) - @closing_now = false + @socket = UDPSocket.new(@ip.family) logger.debug("Trying to increase Socket::SO_RCVBUF to 10M.") old = @socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF).unpack("i").first @@ -48,9 +47,9 @@ module Mauve logger.debug("Successfully increased Socket::SO_RCVBUF from #{old} to #{new}.") - @socket.bind(@ip, @port) + @socket.bind(@ip.to_s, @port) - logger.info("Opened socket on #{@ip}:#{@port}") + logger.info("Opened socket on #{@ip.to_s}:#{@port}") end def close_socket diff --git a/lib/mauve/version.rb b/lib/mauve/version.rb index a4be324..583741d 100644 --- a/lib/mauve/version.rb +++ b/lib/mauve/version.rb @@ -1,5 +1,5 @@ module Mauve - VERSION="3.1.6" + VERSION="3.2.1" end diff --git a/lib/object_builder.rb b/lib/object_builder.rb index 7cb808c..a455b3e 100644 --- a/lib/object_builder.rb +++ b/lib/object_builder.rb @@ -41,12 +41,14 @@ # TODO: finish this convoluted example, if it kills me # class ObjectBuilder - class BuildException < Exception; end + class BuildException < StandardError; end - attr_reader :result + attr_reader :result + attr_accessor :block_result def initialize(context, *args) @context = context + @result = nil builder_setup(*args) end @@ -93,11 +95,38 @@ class ObjectBuilder end def load(file) + parse(File.read(file), file) + end + + def parse(string, file="string") builder = self.new - builder.instance_eval(File.read(file), file) + begin + builder.instance_eval(string, file) + rescue NameError => ex + # + # Ugh. Catch NameError and re-raise as a BuildException + # + f,l = ex.backtrace.first.split(":").first(2) + if f == file + build_ex = BuildException.new "Unknown word `#{ex.name}' in #{file} at line #{l}" + build_ex.set_backtrace ex.backtrace + raise build_ex + else + raise ex + end + rescue SyntaxError, ArgumentError => ex + if ex.backtrace.find{|l| l =~ /^#{file}:(\d+):/} + build_ex = BuildException.new "#{ex.message} in #{file} at line #{$1}" + build_ex.set_backtrace ex.backtrace + raise build_ex + else + raise ex + end + end + builder.result end - + def inherited(*args) initialize_class end @@ -106,7 +135,7 @@ class ObjectBuilder @words = {} end end - + initialize_class end -- cgit v1.2.1