aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2011-07-22 16:55:54 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2011-07-22 16:55:54 +0100
commit25b32914b72a5f709eca773f3511cc89c2e710c2 (patch)
tree4dab9e78e4c6b49220a837b38d463328c05e2983
parent3185e5d746abda1b7f42ecdbd74ec14359fda3bc (diff)
parentfd23821950f0562a8995735105cd31fdc6d55933 (diff)
merge
-rwxr-xr-xbin/mauveconsole3
-rwxr-xr-xbin/mauveserver49
-rw-r--r--debian/changelog13
-rw-r--r--debian/control4
-rw-r--r--debian/mauvealert-common.install2
-rw-r--r--debian/mauvealert-server.install4
-rw-r--r--example.conf14
-rw-r--r--heartbeat_hammer.sh6
-rw-r--r--lib/mauve/alert.rb13
-rw-r--r--lib/mauve/alert_group.rb15
-rw-r--r--lib/mauve/configuration.rb454
-rw-r--r--lib/mauve/configuration_builder.rb38
-rw-r--r--lib/mauve/configuration_builders.rb6
-rw-r--r--lib/mauve/configuration_builders/alert_group.rb81
-rw-r--r--lib/mauve/configuration_builders/logger.rb120
-rw-r--r--lib/mauve/configuration_builders/notification_method.rb50
-rw-r--r--lib/mauve/configuration_builders/person.rb60
-rw-r--r--lib/mauve/configuration_builders/server.rb102
-rw-r--r--lib/mauve/datamapper.rb3
-rw-r--r--lib/mauve/heartbeat.rb34
-rw-r--r--lib/mauve/http_server.rb50
-rw-r--r--lib/mauve/mauve_resolv.rb35
-rw-r--r--lib/mauve/mauve_thread.rb17
-rw-r--r--lib/mauve/notifier.rb10
-rw-r--r--lib/mauve/notifiers/email.rb1
-rw-r--r--lib/mauve/notifiers/sms_aql.rb26
-rw-r--r--lib/mauve/notifiers/xmpp.rb4
-rw-r--r--lib/mauve/people_list.rb38
-rw-r--r--lib/mauve/processor.rb7
-rw-r--r--lib/mauve/sender.rb43
-rw-r--r--lib/mauve/server.rb198
-rw-r--r--lib/mauve/source_list.rb175
-rw-r--r--lib/mauve/timer.rb2
-rw-r--r--lib/mauve/udp_server.rb35
-rw-r--r--lib/mauve/version.rb2
-rw-r--r--lib/object_builder.rb39
-rw-r--r--test/tc_mauve_alert.rb47
-rw-r--r--test/tc_mauve_alert_group.rb47
-rw-r--r--test/tc_mauve_configuration_builder.rb72
-rw-r--r--test/tc_mauve_configuration_builders_alert_group.rb13
-rw-r--r--test/tc_mauve_configuration_builders_logger.rb59
-rw-r--r--test/tc_mauve_configuration_builders_notification_method.rb13
-rw-r--r--test/tc_mauve_configuration_builders_person.rb13
-rw-r--r--test/tc_mauve_configuration_builders_server.rb143
-rw-r--r--test/tc_mauve_people_list.rb0
-rw-r--r--test/tc_mauve_source_list.rb78
-rw-r--r--test/th_mauve_resolv.rb30
-rw-r--r--test/ts_mauve.rb20
48 files changed, 1519 insertions, 769 deletions
diff --git a/bin/mauveconsole b/bin/mauveconsole
index d115642..c2d145f 100755
--- a/bin/mauveconsole
+++ b/bin/mauveconsole
@@ -77,6 +77,8 @@ end
require 'irb'
require 'thread'
+require 'mauve/configuration_builder'
+require 'mauve/configuration_builders'
require 'mauve/configuration'
Thread.abort_on_exception = true
@@ -85,6 +87,7 @@ include Mauve
begin
Configuration.current = ConfigurationBuilder.load(configuration_file)
+ Server.instance.setup
rescue StandardError => ex
error ex.message
end
diff --git a/bin/mauveserver b/bin/mauveserver
index 4cb37ed..50e1465 100755
--- a/bin/mauveserver
+++ b/bin/mauveserver
@@ -12,6 +12,10 @@
#
# -m, --manual Show this manual, and exit.
#
+# -v, --verbose Show verbose errors
+#
+# -t, --test Test the configuration
+#
# <configuration file> File from whence to load the configuration. If none is
# specified, then mauvealert.conf in the current
# directory is used, and failing that
@@ -33,12 +37,32 @@
def error(msg)
STDERR.print "*** Error: #{msg}\n"
STDERR.print "*** For help, type: #{$0} -h\n"
+
+ if msg.respond_to?("backtrace")
+ STDERR.print "*** Backtrace:\n"
+ STDERR.print msg.backtrace.join("\n")+"\n"
+ end
+
exit 1
end
-help = ARGV.any?{|a| a =~ /-(h|-help)/}
-version = ARGV.any?{|a| a =~ /-(V|-version)/}
-manual = ARGV.any?{|a| a =~ /-(m|-manual)/}
+help = manual = verbose = version = test = false
+while arg = ARGV.pop
+ case arg
+ when /-(h|-help)/
+ help = true
+ when /-(V|-version)/
+ version = true
+ when /-(m|-manual)/
+ manual = true
+ when /(-(v|-verbose))/
+ verbose = true
+ when /(-(t|-test))/
+ test = true
+ else
+ configuration_file = arg
+ end
+end
#
# CAUTION! Kwality kode.
@@ -77,25 +101,30 @@ rescue SyntaxError => no_blocks_with_procs
error "mauveserver must have Ruby 1.8.7 or later."
end
-configuration_file = ARGV.shift
configuration_file = [".", "/etc/mauvealert/"].collect{|x| File.join(x, "mauveserver.conf") }.find{|d| File.file?(d)} if configuration_file.nil?
configuration_file = File.expand_path(configuration_file) unless configuration_file.nil?
if configuration_file.nil?
- error "No configuration file could be found\n"
+ error "No configuration file could be found"
end
unless File.file?(configuration_file)
- error "Configuration file #{configuration_file} not found\n"
+ error "Configuration file #{configuration_file} not found"
end
require 'mauve/configuration'
+require 'mauve/configuration_builder'
+require 'mauve/configuration_builders'
begin
Mauve::Configuration.current = Mauve::ConfigurationBuilder.load(configuration_file)
rescue StandardError => ex
- STDERR.puts ex.backtrace.join("\n")
- error ex.message
+ error (verbose ? ex : ex.to_s)
+end
+
+if test
+ puts "*** Configuration looks OK!"
+ exit 0
end
%w(HUP).each do |sig|
@@ -154,8 +183,8 @@ end
end
begin
- Mauve::Server.instance.run
+ Mauve::Server.instance.start
rescue StandardError => ex
- error ex.message
+ error (verbose ? ex : ex.to_s)
end
diff --git a/debian/changelog b/debian/changelog
index be8f647..dc1c59d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,16 @@
+mauvealert (3.2.1) stable; urgency=low
+
+ * Fixed up alert_group matching.
+
+ -- Patrick J Cherry <patrick@bytemark.co.uk> Fri, 22 Jul 2011 16:28:52 +0100
+
+mauvealert (3.2.0) stable; urgency=low
+
+ * Re-organisation of config
+ * Added lots of tests.
+
+ -- Patrick J Cherry <patrick@bytemark.co.uk> Fri, 22 Jul 2011 13:30:23 +0100
+
mauvealert (3.1.6) stable; urgency=low
* Added new heartbeat to remote mauve
diff --git a/debian/control b/debian/control
index 2b5020d..fdb7e0a 100644
--- a/debian/control
+++ b/debian/control
@@ -9,7 +9,7 @@ Standards-Version: 3.9.1
Package: mauvealert-client
Architecture: all
Depends: ruby1.8,
- mauvealert-common (>= 3.1.0),
+ mauvealert-common (>= 3.2.0),
${misc:Depends}
Description: Mauve network alert system -- client
Mauve is a network alert system for system and network administrators. You
@@ -22,7 +22,7 @@ Description: Mauve network alert system -- client
Package: mauvealert-server
Architecture: all
Pre-Depends: libjs-jquery
-Depends: mauvealert-common (>= 3.1.0),
+Depends: mauvealert-common (>= 3.2.0),
adduser,
ruby1.8 (>= 1.8.7),
libhaml-ruby1.8 | haml,
diff --git a/debian/mauvealert-common.install b/debian/mauvealert-common.install
index b98b3cf..d928f1b 100644
--- a/debian/mauvealert-common.install
+++ b/debian/mauvealert-common.install
@@ -2,5 +2,5 @@ mauve.proto usr/lib/mauvealert/
lib/mauve/proto.rb usr/lib/ruby/1.8/mauve/
lib/mauve/mauve_time.rb usr/lib/ruby/1.8/mauve/
lib/mauve/version.rb usr/lib/ruby/1.8/mauve/
-
+lib/mauve/mauve_resolv.rb usr/lib/ruby/1.8/mauve/
diff --git a/debian/mauvealert-server.install b/debian/mauvealert-server.install
index f5950da..bc3f0f8 100644
--- a/debian/mauvealert-server.install
+++ b/debian/mauvealert-server.install
@@ -7,7 +7,11 @@ lib/mauve/alert_group.rb usr/lib/ruby/1.8/mauve/
lib/mauve/auth_bytemark.rb usr/lib/ruby/1.8/mauve/
lib/mauve/calendar_interface.rb usr/lib/ruby/1.8/mauve/
lib/mauve/configuration.rb usr/lib/ruby/1.8/mauve/
+lib/mauve/configuration_builder.rb usr/lib/ruby/1.8/mauve/
+lib/mauve/configuration_builders.rb usr/lib/ruby/1.8/mauve/
+lib/mauve/configuration_builders usr/lib/ruby/1.8/mauve/
lib/mauve/datamapper.rb usr/lib/ruby/1.8/mauve/
+lib/mauve/heartbeat.rb usr/lib/ruby/1.8/mauve/
lib/mauve/history.rb usr/lib/ruby/1.8/mauve/
lib/mauve/http_server.rb usr/lib/ruby/1.8/mauve/
lib/mauve/mauve_thread.rb usr/lib/ruby/1.8/mauve/
diff --git a/example.conf b/example.conf
index c7e86e4..1c80744 100644
--- a/example.conf
+++ b/example.conf
@@ -151,13 +151,13 @@ logger {
level Log4r::DEBUG
}
- outputter("email") {
- server "smtp.example.com"
- subject "Mauve logger output"
- from "#{ENV['USER']}@#{Socket.gethostname}"
- to "awooga@example.com"
- level Log4r::WARN
- }
+# outputter("email") {
+# server "smtp.example.com"
+# subject "Mauve logger output"
+# from "#{ENV['USER']}@#{Socket.gethostname}"
+# to "awooga@example.com"
+# level Log4r::WARN
+# }
}
diff --git a/heartbeat_hammer.sh b/heartbeat_hammer.sh
index f2d4671..33cbc29 100644
--- a/heartbeat_hammer.sh
+++ b/heartbeat_hammer.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-PRE="ruby -I lib bin/mauveclient localhost"
+PRE="ruby -I lib bin/mauvesend localhost"
F=60
S=10
n=$*
@@ -28,6 +28,10 @@ _host () {
done
}
+echo -e "This command will go beserk. To kill run\n pkill -t `tty`\n\nGiving you 5 seconds to quit!"
+
+sleep 5
+
for i in `seq 1 100` ; do
_host $i &
sleep 0.2
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
@@ -129,6 +130,18 @@ module Mauve
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
+
+ #
#
#
def level
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 "<name>", 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
diff --git a/test/tc_mauve_alert.rb b/test/tc_mauve_alert.rb
new file mode 100644
index 0000000..ef80424
--- /dev/null
+++ b/test/tc_mauve_alert.rb
@@ -0,0 +1,47 @@
+$:.unshift "../lib"
+
+require 'test/unit'
+require 'mauve/alert'
+require 'mauve/configuration'
+require 'mauve/configuration_builder'
+require 'th_mauve_resolv'
+require 'pp'
+
+class TcMauveAlert < Test::Unit::TestCase
+
+ def test_source_list
+
+ config=<<EOF
+source_list "test", %w(test-1.example.com)
+
+source_list "has_ipv4", "0.0.0.0/0"
+
+source_list "has_ipv6", "2000::/3"
+EOF
+
+ Mauve::Configuration.current = Mauve::ConfigurationBuilder.parse(config)
+
+ a = Mauve::Alert.new
+ a.subject = "www.example.com"
+
+ assert( a.in_source_list?("test") )
+ assert_equal( %w(test has_ipv4).sort, a.source_lists.sort )
+
+ a.subject = "www2.example.com"
+ assert( a.in_source_list?("has_ipv6") )
+ assert_equal( %w(has_ipv6 has_ipv4).sort, a.source_lists.sort )
+ end
+
+
+ def test_summary
+
+ a = Mauve::Alert.new
+ a.summary = "Free swap memory (MB) (memory_swap) is too low"
+
+ assert_match(/memory_swap/, a.summary)
+
+ end
+
+
+end
+
diff --git a/test/tc_mauve_alert_group.rb b/test/tc_mauve_alert_group.rb
new file mode 100644
index 0000000..12f25ef
--- /dev/null
+++ b/test/tc_mauve_alert_group.rb
@@ -0,0 +1,47 @@
+$:.unshift "../lib"
+
+require 'test/unit'
+require 'mauve/alert_group'
+require 'th_mauve_resolv'
+require 'pp'
+
+class TcMauveAlert < Test::Unit::TestCase
+
+ def test_matches_alert
+
+ alert = Mauve::Alert.new
+
+ alert_group = Mauve::AlertGroup.new("test")
+
+ alert_group.includes = Proc.new { true }
+ assert( alert_group.matches_alert?(alert) )
+
+ alert_group.includes = Proc.new { false }
+ assert( !alert_group.matches_alert?(alert) )
+
+ alert_group.includes = Proc.new { summary =~ /Free swap/ }
+ alert.summary = "Free swap memory (mem_swap) too low"
+ assert( alert_group.matches_alert?(alert) )
+ alert.summary = "Free memory (mem_swap) too low"
+ assert( ! alert_group.matches_alert?(alert) )
+
+ alert_group.includes = Proc.new{ source == 'supportbot' }
+ alert.source = "supportbot"
+ assert( alert_group.matches_alert?(alert) )
+ alert.source = "support!"
+ assert( ! alert_group.matches_alert?(alert) )
+
+ alert_group.includes = Proc.new{ /raid/i.match(summary) }
+ alert.summary = "RAID failure"
+ assert( alert_group.matches_alert?(alert) )
+ alert.summary = "Disc failure"
+ assert( ! alert_group.matches_alert?(alert) )
+ end
+
+
+
+end
+
+
+
+
diff --git a/test/tc_mauve_configuration_builder.rb b/test/tc_mauve_configuration_builder.rb
new file mode 100644
index 0000000..03b86ca
--- /dev/null
+++ b/test/tc_mauve_configuration_builder.rb
@@ -0,0 +1,72 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'pp'
+require 'mauve/configuration_builder'
+
+class TcMauveConfigurationBuildersPeopleAndSourceLists < Test::Unit::TestCase
+
+ def setup
+ Log4r::Logger.new("Mauve").outputters = Log4r::Outputter.stdout
+ end
+
+ def test_people_list
+ config =<<EOF
+people_list "team sky", %w(
+ geraint
+ edvald
+ bradley
+ rigoberto
+ ben
+)
+
+people_list "garmin-cervelo", %w(
+ thor
+ ryder
+ tyler
+ julian
+)
+
+EOF
+ x = nil
+ assert_nothing_raised { x = Mauve::ConfigurationBuilder.parse(config) }
+ assert_equal(2, x.people_lists.keys.length)
+ assert_equal(["team sky","garmin-cervelo"].sort,x.people_lists.keys.sort)
+ assert_equal(%w(geraint edvald bradley rigoberto ben), x.people_lists["team sky"].list)
+
+ end
+
+ def test_duplicate_people_list
+
+ config=<<EOF
+
+people_list "htc-highroad",
+ ["mark c", "mark r", "Lars"]
+
+people_list "htc-highroad",
+ %w(Bernie Danny Lars)
+
+EOF
+ x = nil
+ assert_nothing_raised { x = Mauve::ConfigurationBuilder.parse(config) }
+ assert_equal(1, x.people_lists.keys.length)
+ assert_equal(["mark c","mark r","Lars","Bernie","Danny"].sort, x.people_lists["htc-highroad"].list.sort)
+ end
+
+ def test_source_list
+ config =<<EOF
+source_list "sources", %w(
+ test-1.example.com
+ imaginary.host.example.com
+ 192.168.100.1/24
+ *.imaginary.example.com
+)
+EOF
+
+ x = nil
+ assert_nothing_raised { x = Mauve::ConfigurationBuilder.parse(config) }
+
+ x.source_lists["sources"].resolve
+ end
+
+end
diff --git a/test/tc_mauve_configuration_builders_alert_group.rb b/test/tc_mauve_configuration_builders_alert_group.rb
new file mode 100644
index 0000000..a4104bb
--- /dev/null
+++ b/test/tc_mauve_configuration_builders_alert_group.rb
@@ -0,0 +1,13 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'pp'
+require 'mauve/configuration_builders/alert_group'
+
+class TcMauveConfigurationBuildersNotificationMethod < Test::Unit::TestCase
+
+ def test_load
+
+ end
+
+end
diff --git a/test/tc_mauve_configuration_builders_logger.rb b/test/tc_mauve_configuration_builders_logger.rb
new file mode 100644
index 0000000..8a4bc4e
--- /dev/null
+++ b/test/tc_mauve_configuration_builders_logger.rb
@@ -0,0 +1,59 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'pp'
+require 'mauve/configuration_builders/logger'
+
+class TcMauveConfigurationBuildersLogger < Test::Unit::TestCase
+
+ def test_load
+
+ config=<<EOF
+logger {
+ default_format "%d [ %l ] [ %12.12c ] %m"
+ default_level WARN
+
+ outputter "stdout"
+
+ outputter ("file") {
+ trunc false
+ filename "test.conf"
+ level DEBUG
+ }
+
+}
+EOF
+
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+
+ #
+ # Check that we've got the correct things set
+ #
+ logger = nil
+ assert_nothing_raised { logger = Log4r::Logger.get("Mauve") }
+ assert_equal(2, logger.outputters.length)
+
+ outputter = logger.outputters[0]
+
+ assert_kind_of(Log4r::StdoutOutputter, outputter)
+ assert_equal("%d [ %l ] [ %12.12c ] %m", outputter.formatter.pattern )
+ assert_equal(Log4r::WARN, outputter.level )
+
+ outputter = logger.outputters[1]
+ assert_kind_of(Log4r::FileOutputter, outputter)
+ assert_equal("%d [ %l ] [ %12.12c ] %m", outputter.formatter.pattern )
+ assert_equal(Log4r::DEBUG, outputter.level )
+ assert_equal(false, outputter.trunc )
+ assert_equal("test.conf", outputter.filename )
+ end
+
+ def test_levels
+ #
+ # Make sure our levels match those of log4r.
+ #
+ %w(DEBUG WARN FATAL ERROR INFO).each do |l|
+ assert_equal(Log4r.const_get(l), Mauve::LoggerConstants.const_get(l))
+ end
+ end
+
+end
diff --git a/test/tc_mauve_configuration_builders_notification_method.rb b/test/tc_mauve_configuration_builders_notification_method.rb
new file mode 100644
index 0000000..1ea1e08
--- /dev/null
+++ b/test/tc_mauve_configuration_builders_notification_method.rb
@@ -0,0 +1,13 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'pp'
+require 'mauve/configuration_builders/notification_method'
+
+class TcMauveConfigurationBuildersNotificationMethod < Test::Unit::TestCase
+
+ def test_load
+
+ end
+
+end
diff --git a/test/tc_mauve_configuration_builders_person.rb b/test/tc_mauve_configuration_builders_person.rb
new file mode 100644
index 0000000..cb0b100
--- /dev/null
+++ b/test/tc_mauve_configuration_builders_person.rb
@@ -0,0 +1,13 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'pp'
+require 'mauve/configuration_builders/person'
+
+class TcMauveConfigurationBuildersNotificationMethod < Test::Unit::TestCase
+
+ def test_load
+
+ end
+
+end
diff --git a/test/tc_mauve_configuration_builders_server.rb b/test/tc_mauve_configuration_builders_server.rb
new file mode 100644
index 0000000..4330610
--- /dev/null
+++ b/test/tc_mauve_configuration_builders_server.rb
@@ -0,0 +1,143 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'mauve/configuration_builders/server'
+
+class TcMauveConfigurationBuildersServer < Test::Unit::TestCase
+
+ def test_server_params
+ hostname = "test.example.com"
+ database = "sqlite://./test.db"
+ initial_sleep = 314
+
+ config=<<EOF
+server {
+ hostname "#{hostname}"
+ database "#{database}"
+ initial_sleep #{initial_sleep}
+}
+EOF
+
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+
+ #
+ # Check that we've got the correct things set
+ #
+ s = Mauve::Server.instance
+ assert_equal(hostname, s.hostname)
+ assert_equal(database, s.database)
+ assert_equal(initial_sleep, s.initial_sleep)
+ end
+
+ def test_heartbeat_params
+ destination = "test-backup.example.com"
+ summary = "Mauve blurgh!"
+ detail = "<p>A very interesting test.</p>"
+ raise_after = 1000
+ send_every = 60
+
+ config=<<EOF
+server {
+ heartbeat {
+ destination "#{destination}"
+ summary "#{summary}"
+ detail "#{detail}"
+ raise_after #{raise_after}
+ send_every #{send_every}
+ }
+}
+EOF
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+
+ h = Mauve::Heartbeat.instance
+ assert_equal(detail, h.detail)
+ assert_equal(summary, h.summary)
+ assert_equal(destination, h.destination)
+ assert_equal(raise_after, h.raise_at)
+ assert_equal(send_every, h.sleep_interval)
+ end
+
+ def test_web_interface_params
+ ip = "::"
+ port = 12341
+ document_root = "./"
+ base_url = "http://www.example.com"
+ session_secret = "asd2342"
+ sleep_interval = 32
+
+ config=<<EOF
+server {
+ web_interface {
+ ip "#{ip}"
+ port #{port}
+ document_root "#{document_root}"
+ base_url "#{base_url}"
+ session_secret "#{session_secret}"
+ }
+}
+EOF
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+
+ assert_equal(ip, Mauve::HTTPServer.instance.ip)
+ assert_equal(port, Mauve::HTTPServer.instance.port)
+ assert_equal(document_root, Mauve::HTTPServer.instance.document_root)
+ assert_equal(base_url, Mauve::HTTPServer.instance.base_url)
+ assert_equal(session_secret, Mauve::HTTPServer.instance.session_secret)
+ end
+
+ def test_listener_params
+ ip = "::"
+ port = 12341
+ sleep_interval = 5678
+ config=<<EOF
+server {
+ listener {
+ ip "#{ip}"
+ port #{port}
+ sleep_interval #{sleep_interval}
+ }
+}
+EOF
+
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+ u = Mauve::UDPServer.instance
+ assert_equal(sleep_interval, u.sleep_interval)
+ assert_equal(IPAddr.new(ip), u.ip)
+ assert_equal(port, u.port)
+ end
+
+ def test_notifier_params
+ sleep_interval = 23
+ config=<<EOF
+server {
+ notifier {
+ sleep_interval #{sleep_interval}
+ }
+}
+EOF
+
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+ n = Mauve::Notifier.instance
+ assert_equal(sleep_interval, n.sleep_interval)
+ end
+
+ def test_processor_params
+ transmission_cache_expire_time = 3120
+ sleep_interval = 1235
+
+ config=<<EOF
+server {
+ processor {
+ transmission_cache_expire_time #{transmission_cache_expire_time}
+ sleep_interval #{sleep_interval}
+ }
+}
+EOF
+
+ assert_nothing_raised { Mauve::ConfigurationBuilder.parse(config) }
+ pr = Mauve::Processor.instance
+ assert_equal(transmission_cache_expire_time, pr.transmission_cache_expire_time)
+ assert_equal(sleep_interval, pr.sleep_interval)
+ end
+
+end
diff --git a/test/tc_mauve_people_list.rb b/test/tc_mauve_people_list.rb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/tc_mauve_people_list.rb
diff --git a/test/tc_mauve_source_list.rb b/test/tc_mauve_source_list.rb
new file mode 100644
index 0000000..d07791f
--- /dev/null
+++ b/test/tc_mauve_source_list.rb
@@ -0,0 +1,78 @@
+$:.unshift "../lib/"
+
+require 'test/unit'
+require 'mauve/source_list'
+require 'th_mauve_resolv'
+require 'pp'
+
+class TcMauveSourceList < Test::Unit::TestCase
+
+ def test_hostname_match
+ sl = Mauve::SourceList.new("test")
+ assert_equal("test", sl.label)
+
+ list = %w(a.example.com b.example.com c.example.com)
+ assert_nothing_raised{ sl += list }
+
+ assert_equal(list, sl.list)
+
+ assert( sl.includes?("a.example.com") )
+ assert( !sl.includes?("d.example.com") )
+ end
+
+ def test_regex_match
+ sl = Mauve::SourceList.new("test")
+
+ assert_nothing_raised{ sl += %w([a-c].example.com *.[d-f].example.com g.example.com) }
+
+ %w(a.example.com www.f.example.com www.a.example.com g.example.com www.other.a.example.com).each do |h|
+ assert( sl.includes?(h), "#{h} did not match")
+ end
+
+ %w(d.example.com a.example.com.other d.example.com).each do |h|
+ assert( !sl.includes?(h), "#{h} matched when it shouldn't have")
+ end
+ end
+
+ def test_ip_match
+ sl = Mauve::SourceList.new("test")
+
+ assert_nothing_raised{ sl += %w(test-1.example.com 1.2.3.5 2001:1:2:3::5 1.2.4.0/24 2001:1:2:4::/64) }
+
+ %w(1.2.3.4 2001:1:2:3::4 1.2.3.5 2001:1:2:3::5 test-2.example.com 1.2.4.23 2001:1:2:4::23 ).each do |h|
+ assert( sl.includes?(h), "#{h} did not match")
+ end
+
+ %w(1.2.3.6 2001:1:2:3::6 test-3.example.com 1.2.5.23 2001:1:2:5::23 ).each do |h|
+ assert( !sl.includes?(h), "#{h} matched when it shouldn't have")
+ end
+
+ end
+
+ def test_uri_match
+ sl = Mauve::SourceList.new("test")
+
+ assert_nothing_raised { sl += "test-1.example.com" }
+
+ %w(https://www.example.com ftp://test-1.example.com http://1.2.3.4 https://[2001:1:2:3::4]).each do |uri|
+ assert( sl.includes?(uri), "#{uri} did not match")
+ end
+
+ %w(http://www.google.com ftp://www2.example.com).each do |uri|
+ assert( !sl.includes?(uri), "#{uri} matched when it shouldn't have" )
+ end
+ end
+
+ def test_ip_crossmatch
+ sl = Mauve::SourceList.new("test")
+ assert_nothing_raised { sl += "test-1.example.com" }
+ assert( sl.includes?("www.example.com"), "www.example.com not found in #{sl.list}" )
+
+ sl = Mauve::SourceList.new("test")
+ assert_nothing_raised { sl += "2001::/3" }
+ assert( sl.includes?("www2.example.com"), "www2.example.com not found in #{sl.list}" )
+ end
+
+end
+
+
diff --git a/test/th_mauve_resolv.rb b/test/th_mauve_resolv.rb
new file mode 100644
index 0000000..98b597b
--- /dev/null
+++ b/test/th_mauve_resolv.rb
@@ -0,0 +1,30 @@
+
+$:.unshift "../lib"
+
+require 'mauve/mauve_resolv'
+
+#
+# This allows us to specify IPs for test hostnames, and also to fall back on
+# regular DNS if that fails.
+#
+
+module Mauve
+ class MauveResolv
+ class << self
+ alias_method :get_ips_for_without_testing, :get_ips_for
+
+ def get_ips_for_with_testing(host)
+ lookup = {
+ "test-1.example.com" => %w(1.2.3.4 2001:1:2:3::4),
+ "test-2.example.com" => %w(1.2.3.5 2001:1:2:3::5),
+ "www.example.com" => %w(1.2.3.4),
+ "www2.example.com" => %w(1.2.3.5 2001:2::2)
+ }
+ lookup[host] || get_ips_for_without_testing(host)
+ end
+
+ alias_method :get_ips_for, :get_ips_for_with_testing
+ end
+ end
+end
+
diff --git a/test/ts_mauve.rb b/test/ts_mauve.rb
new file mode 100644
index 0000000..27284a6
--- /dev/null
+++ b/test/ts_mauve.rb
@@ -0,0 +1,20 @@
+
+$:.unshift "../lib"
+
+require 'test/unit'
+
+%w(
+tc_mauve_configuration_builder.rb
+tc_mauve_configuration_builders_alert_group.rb
+tc_mauve_configuration_builders_logger.rb
+tc_mauve_configuration_builders_notification_method.rb
+tc_mauve_configuration_builders_person.rb
+tc_mauve_configuration_builders_server.rb
+tc_mauve_source_list.rb
+tc_mauve_people_list.rb
+tc_mauve_alert.rb
+tc_mauve_alert_group.rb
+).each do |s|
+ require s
+end
+