aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick J Cherry <patrick@bytemark.co.uk>2012-04-25 17:15:49 +0100
committerPatrick J Cherry <patrick@bytemark.co.uk>2012-04-25 17:15:49 +0100
commite959c0fe4c887154bbe28c31324fef2975cbe467 (patch)
tree3088c7a1f389944d613e57b551b452f7ec83181d
parent5fff12fc11cb8b02a44fd40ed78fa9d196f269d7 (diff)
Big update.
* Max acknowledgement time is now specified in the config * Calendar interface improved. * holiday_url no longer used -- replaced by notify_when_on_holiday! * added notify_when_off_sick! * Added ability for the calendar to be queried for a list of bank holdays. * Added ability for Time to be given a list of bank holidays to check against. * PeopleLists can now be a Proc, allowing downloading of lists * Person is no longer a struct * Moved the method_missing bit into ObjectBuilder from various sub classes. * Added tests for the calendar interface * Updated tests in other bits.
-rw-r--r--bin/mauveserver2
-rw-r--r--example.conf53
-rw-r--r--lib/mauve/alert.rb2
-rw-r--r--lib/mauve/authentication.rb10
-rw-r--r--lib/mauve/calendar_interface.rb100
-rw-r--r--lib/mauve/configuration.rb113
-rw-r--r--lib/mauve/configuration_builder.rb12
-rw-r--r--lib/mauve/configuration_builders/notification_method.rb12
-rw-r--r--lib/mauve/configuration_builders/person.rb6
-rw-r--r--lib/mauve/configuration_builders/server.rb25
-rw-r--r--lib/mauve/mauve_time.rb28
-rw-r--r--lib/mauve/notification.rb3
-rw-r--r--lib/mauve/notifiers/xmpp.rb5
-rw-r--r--lib/mauve/people_list.rb10
-rw-r--r--lib/mauve/person.rb124
-rw-r--r--lib/mauve/processor.rb14
-rw-r--r--lib/mauve/server.rb112
-rw-r--r--lib/mauve/web_interface.rb19
-rw-r--r--lib/object_builder.rb14
-rw-r--r--test/tc_mauve_alert.rb3
-rw-r--r--test/tc_mauve_authentication.rb18
-rw-r--r--test/tc_mauve_calendar_interface.rb155
-rw-r--r--test/tc_mauve_notification.rb6
-rw-r--r--test/tc_mauve_people_list.rb25
-rw-r--r--test/tc_mauve_time.rb7
-rw-r--r--test/th_mauve.rb5
26 files changed, 610 insertions, 273 deletions
diff --git a/bin/mauveserver b/bin/mauveserver
index b0c3573..cc2b124 100644
--- a/bin/mauveserver
+++ b/bin/mauveserver
@@ -34,6 +34,8 @@
# Patrick J Cherry <patrick@bytemark.co.uk>
#
+$:.unshift File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
+
help = manual = verbose = version = test = false
while arg = ARGV.pop
case arg
diff --git a/example.conf b/example.conf
index 4123d8e..347c33d 100644
--- a/example.conf
+++ b/example.conf
@@ -5,13 +5,18 @@ server {
#
# This is where our database lives. SQLite is the default.
#
- database "sqlite3::memory:"
+
+ # database "sqlite3::memory:"
+ database "postgres://localhost/mauvealert"
#
# This is our hostname. It gets used when URLs are generated, and in the heartbeat alert.
#
hostname "mauve.example.com"
+# use_packet_buffer "no"
+# use_notification_buffer "no"
+
#
# This is the UDP listener.
#
@@ -27,7 +32,7 @@ server {
#
# This is how long the UDP server will sleep between looking for packets.
#
- poll_every 1
+ poll_every 0
}
@@ -40,7 +45,7 @@ server {
# This is the length of time the processor will sleep between checking for
# new packets from the UDP listener.
#
- poll_every 1
+ poll_every 0
#
# In order to make sure the same transmission isn't received more then
@@ -78,23 +83,23 @@ server {
# This is where the mauve server sends its own heartbeat. Useful for
# watching the watcher.
#
- heartbeat {
+# heartbeat {
#
# If no destination is specified, then the contents of
# /etc/mauvealert/mauvesend.destination are used.
#
- destination "localhost"
+# destination "localhost"
#
# This is how long to wait before the alert is raised
#
- raise_after 600
+# raise_after 600
#
# These two fields have sensible defaults set, but more informative
# messages can be set here.
#
- summary "Mauve alert server is down"
- detail "The Mauve alert server has failed to send a heartbeat"
- }
+# summary "Mauve alert server is down"
+# detail "The Mauve alert server has failed to send a heartbeat"
+# }
}
@@ -137,7 +142,7 @@ logger {
outputter("file") {
filename "/tmp/mauveserver.log"
trunc true
- level Log4r::DEBUG
+ level Log4r::INFO
}
# outputter("email") {
@@ -154,24 +159,16 @@ logger {
# XMPP instant messaging. This are the credentials to log into the XMPP
# account that mauve will use.
#
-#notification_method ("xmpp") {
-# jid "mauve@chat.example.com/mauve"
-# password "mauvespassword"
-#}
-
-#
-# Email messaging.
-#
-notification_method ("email") {
- server "localhost"
- from "mauve@desk1.tur.bytemark.co.uk"
- subject_prefix "mauve-test"
+# XMPP instant messaging.
+notification_method ("xmpp") {
+ jid "bytemark@chat.bytemark.co.uk/pjc-test"
+ password "OsPzCmqSb"
}
person("office_chat") {
-# xmpp "muc:mauve-test@conference.chat.bytemark.co.uk/MauveAlert"
-# all { xmpp }
-# suppress_notifications_after(310 => 1.minute)
+ xmpp "muc:mauve-test@conference.chat.bytemark.co.uk/MauveAlert"
+ all { xmpp }
+ suppress_notifications_after(310 => 1.minute)
}
person ("pcherry") {
@@ -179,17 +176,13 @@ person ("pcherry") {
all { true }
}
-people_list ("arse") {
- list [ "office_chat" ]
-}
-
#
# Default notification - tell root about all alerts every hour
#
alert_group("default") {
level URGENT
- notify("arse") {
+ notify("office_chat") {
every 2.minutes
}
}
diff --git a/lib/mauve/alert.rb b/lib/mauve/alert.rb
index f249913..6b08f82 100644
--- a/lib/mauve/alert.rb
+++ b/lib/mauve/alert.rb
@@ -420,7 +420,7 @@ module Mauve
#
# Limit acknowledgment time.
#
- limit = Time.now + 15.days
+ limit = Time.now + Configuration.current.max_acknowledgement_time
ack_until = limit if ack_until > limit
self.acknowledged_by = person.username
diff --git a/lib/mauve/authentication.rb b/lib/mauve/authentication.rb
index c467a1d..24c6bd3 100644
--- a/lib/mauve/authentication.rb
+++ b/lib/mauve/authentication.rb
@@ -70,7 +70,7 @@ module Mauve
unless true == result
logger.info "Authentication for #{login} failed"
# Rate limit
- sleep Server.instance.failed_login_delay
+ sleep Configuration.current.failed_login_delay
end
result
@@ -97,15 +97,15 @@ module Mauve
#
# Don't bother checking if no auth_url has been set.
#
- return false unless Server.instance.bytemark_auth_url.is_a?(URI)
+ return false unless Configuration.current.bytemark_auth_url.is_a?(URI)
#
# Don't bother checking if the person doesn't exist.
#
return false unless Mauve::Configuration.current.people.has_key?(login)
- uri = Server.instance.bytemark_auth_url
- timeout = Server.instance.remote_http_timeout
+ uri = Configuration.current.bytemark_auth_url
+ timeout = Configuration.current.remote_http_timeout
# host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil, user=nil, password=nil, use_ssl=nil, timeout=nil)
client = XMLRPC::Client.new(uri.host, uri.path, uri.port, nil, nil, uri.user, uri.password, uri.scheme == "https", timeout)
@@ -114,7 +114,7 @@ module Mauve
#
if client.http.use_ssl?
client.http.ca_path = "/etc/ssl/certs/"
- client.http.verify_mode = Server.instance.remote_https_verify_mode
+ client.http.verify_mode = Configuration.current.remote_https_verify_mode
end
begin
diff --git a/lib/mauve/calendar_interface.rb b/lib/mauve/calendar_interface.rb
index cf515d1..692981d 100644
--- a/lib/mauve/calendar_interface.rb
+++ b/lib/mauve/calendar_interface.rb
@@ -17,52 +17,58 @@ module Mauve
@logger ||= Log4r::Logger.new(self.to_s)
end
- # Gets a list of ssologin on support.
+ def get_attendees(klass, at=Time.now)
+ #
+ # Returns nil if no calendar_url has been set.
+ #
+ return [] unless Configuration.current.bytemark_calendar_url
+
+ url = Configuration.current.bytemark_calendar_url.dup
+
+ url.merge!(File.join(url.path, "/api/attendees/#{klass}/#{at.strftime("%Y-%m-%dT%H:%M:00")}"))
+ ans = do_get(url)
+
+ return [] unless ans.is_a?(Array)
+
+ ans.select{|x| x.is_a?(String)}
+ end
+
#
- # @param [String] url A Calendar API url.
+ # This should return a list of dates of forthcoming bank holidays
#
- # @return [Array] A list of all the usernames on support.
- def get_users_on_support(url)
- result = do_get_with_cache(url)
-
- if result.is_a?(String)
- result = result.split("\n")
- else
- result = []
- end
+ def get_bank_holiday_list(at = Time.now)
+ return [] unless Configuration.current.bytemark_calendar_url
- return result
+ url = Configuration.current.bytemark_calendar_url.dup
+ url.merge!(File.join(url.path, "/api/bank_holidays/#{at.strftime("%Y-%m-%d")}"))
+ ans = do_get(url)
+
+ return [] unless ans.is_a?(Array)
+ ans.select{|x| x.is_a?(Date)}
end
- # Check to see if the user is on support.
+ # Check to see if the user is on holiday.
#
- # @param [String] url A Calendar API url.
- # @param [String] usr User single sign on.
+ # Class method.
#
- # @return [Boolean] True if on support, false otherwise.
- def is_user_on_support?(url, usr)
- return get_users_on_support(url).include?(usr)
+ # @param [String] usr User single sign on.
+ #
+ # @return [Boolean] True if on holiday, false otherwise.
+ def is_user_on_holiday?(usr, at=Time.now)
+ get_attendees("staff_holiday").include?(usr)
end
# Check to see if the user is on holiday.
#
# Class method.
#
- # @param [String] url A Calendar API url.
# @param [String] usr User single sign on.
#
# @return [Boolean] True if on holiday, false otherwise.
- def is_user_on_holiday?(url)
- result = do_get_with_cache(url)
-
- if result.is_a?(String) and result =~ /^\d{4}(-\d\d){2}[ T](\d\d:){2}\d\d/
- return true
- else
- return false
- end
+ def is_user_off_sick?(usr, at=Time.now)
+ get_attendees("sick_period", at).include?(usr)
end
-
private
# Grab a URL from the wide web.
@@ -74,7 +80,7 @@ module Mauve
#
def do_get (uri, limit = 11)
- if 0 == limit
+ if 0 > limit
logger.warn("HTTP redirect too deep for #{uri}.")
return nil
end
@@ -99,8 +105,22 @@ module Mauve
response = http.start { http.get(uri.request_uri()) }
if response.is_a?(Net::HTTPOK)
- return response.body
+ #
+ # Parse the string as YAML.
+ #
+ result = if response.body.is_a?(String)
+ begin
+ YAML.load(response.body)
+ rescue YAML::Error => err
+ logger.error "Caught #{ex.class.to_s} (#{ex.to_s}) whilst querying #{url.to_s}."
+ logger.debug err.backtrace.join("\n")
+ nil
+ end
+ else
+ nil
+ end
+ return result
elsif response.is_a?(Net::HTTPRedirection) and response.key?('Location')
location = response['Location']
@@ -123,6 +143,7 @@ module Mauve
logger.error("Timeout caught during fetch of #{uri.to_s}.")
rescue StandardError => ex
+ pp ex.backtrace
logger.error("#{ex.class} caught during fetch of #{uri.to_s}: #{ex.to_s}.")
logger.debug(ex.backtrace.join("\n"))
@@ -140,10 +161,10 @@ module Mauve
def do_get_with_cache(url, cache_until = Time.now + 5.minutes)
@cache ||= {}
- if @cache.has_key?(url.to_s)
- result, cache_until = @cache[url.to_s]
+ if @cache.has_key?(url)
+ result, cached_until = @cache[url]
- return result if cache_until >= Time.now and not result.nil?
+ return result if cached_until > Time.now and not result.nil?
end
result = do_get(url)
@@ -152,6 +173,19 @@ module Mauve
return result
end
+ #
+ # This should get called periodically.
+ #
+ def clean_cache
+
+ @cache.keys.select do |url|
+ result, cached_until = @cache[url]
+ @cache.delete(url) if !cached_until.is_a?(Time) or cached_until <= Time.now
+ end
+
+ @cache
+ end
+
end
end
diff --git a/lib/mauve/configuration.rb b/lib/mauve/configuration.rb
index 65f5f71..3a8f9cc 100644
--- a/lib/mauve/configuration.rb
+++ b/lib/mauve/configuration.rb
@@ -40,6 +40,12 @@ module Mauve
# @return [Hash]
attr_reader :source_lists
+ # Various further configuration items
+ #
+ attr_reader :bytemark_auth_url, :bytemark_calendar_url, :remote_http_timeout, :remote_https_verify_mode, :failed_login_delay
+ attr_reader :max_acknowledgement_time
+
+
#
# Set up a base config.
#
@@ -50,7 +56,114 @@ module Mauve
@people_lists = {}
@source_lists = Hash.new{|h,k| h[k] = Mauve::SourceList.new(k)}
@alert_groups = []
+
+ #
+ # Set the auth/calendar URLs
+ #
+ @bytemark_auth_url = nil
+ @bytemark_calendar_url = nil
+
+ #
+ # Set a couple of params for remote HTTP requests.
+ #
+ @remote_http_timeout = 5
+ @remote_https_verify_mode = OpenSSL::SSL::VERIFY_PEER
+
+ #
+ # Rate limit login attempts to limit the success of brute-forcing.
+ #
+ @failed_login_delay = 1
+
+ #
+ # Maximum amount of time to acknowledge for
+ #
+ @max_acknowledgement_time = 15.days
+ end
+
+ # Set the calendar URL.
+ #
+ # @param [String] arg
+ # @return [URI]
+ def bytemark_calendar_url=(arg)
+ raise ArgumentError, "bytemark_calendar_url must be a string" unless arg.is_a?(String)
+
+ @bytemark_calendar_url = URI.parse(arg)
+
+ #
+ # Make sure we get an HTTP URL.
+ #
+ raise ArgumentError, "bytemark_calendar_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_calendar_url.scheme)
+
+ #
+ # Set a default request path, if none was given
+ #
+ @bytemark_calendar_url.normalize!
+
+ @bytemark_calendar_url
+ end
+
+ # Set the Bytemark Authentication URL
+ #
+ # @param [String] arg
+ # @return [URI]
+ def bytemark_auth_url=(arg)
+ raise ArgumentError, "bytemark_auth_url must be a string" unless arg.is_a?(String)
+
+ @bytemark_auth_url = URI.parse(arg)
+ #
+ # Make sure we get an HTTP URL.
+ #
+ raise ArgumentError, "bytemark_auth_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_auth_url.scheme)
+
+ #
+ # Set a default request path, if none was given
+ #
+ @bytemark_auth_url.normalize!
+
+ @bytemark_auth_url
+ end
+
+ # Sets the timeout when making remote HTTP requests
+ #
+ # @param [Integer] arg
+ # @return [Integer]
+ def remote_http_timeout=(arg)
+ raise ArgumentError, "remote_http_timeout must be an integer" unless s.is_a?(Integer)
+ @remote_http_timeout = arg
+ end
+
+ # Sets the SSL verification mode when makeing remote HTTPS requests
+ #
+ # @param [String] arg must be one of "none" or "peer"
+ # @return [Constant]
+ def remote_https_verify_mode=(arg)
+ @remote_https_verify_mode = case arg
+ when "peer"
+ OpenSSL::SSL::VERIFY_PEER
+ when "none"
+ OpenSSL::SSL::VERIFY_NONE
+ else
+ raise ArgumentError, "remote_https_verify_mode must be either 'peer' or 'none'"
+ end
+ end
+
+ # Set the delay added following a failed login attempt.
+ #
+ # @param [Numeric] arg Number of seconds to delay following a failed login attempt
+ # @return [Numeric]
+ #
+ def failed_login_delay=(arg)
+ raise ArgumentError, "failed_login_delay must be numeric" unless arg.is_a?(Numeric)
+ @failed_login_delay = arg
end
+ # Set the maximum amount of time alerts can be ack'd for
+ #
+ #
+ def max_acknowledgement_time=(arg)
+ raise ArgumentError, "max_acknowledgement_time must be numeric" unless arg.is_a?(Numeric)
+ @max_acknowledgement_time = arg
+ end
+
end
end
diff --git a/lib/mauve/configuration_builder.rb b/lib/mauve/configuration_builder.rb
index 3f7138e..c689c1b 100644
--- a/lib/mauve/configuration_builder.rb
+++ b/lib/mauve/configuration_builder.rb
@@ -29,18 +29,6 @@ module Mauve
@result.source_lists[label] += list
end
- # Adds a people list
- #
- # @param [String] label
- # @param [Array] list
- #
- # @return [Array] the whole people list for label
- # ef 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.
#
# @return [Log4r::Logger]
diff --git a/lib/mauve/configuration_builders/notification_method.rb b/lib/mauve/configuration_builders/notification_method.rb
index 3f9283e..7951dee 100644
--- a/lib/mauve/configuration_builders/notification_method.rb
+++ b/lib/mauve/configuration_builders/notification_method.rb
@@ -40,18 +40,6 @@ module Mauve
result.extend(Mauve::Notifiers::Debug)
end
- # This catches all methods available for a provider, as needed.
- #
- # Missing methods / bad arguments etc. are caught in the
- # ObjectBuilder#parse method, via NoMethodError.
- #
- def method_missing(name, value=nil)
- if value
- result.send("#{name}=".to_sym, value)
- else
- result.send(name.to_sym)
- end
- end
end
end
diff --git a/lib/mauve/configuration_builders/person.rb b/lib/mauve/configuration_builders/person.rb
index dff6f85..7a20491 100644
--- a/lib/mauve/configuration_builders/person.rb
+++ b/lib/mauve/configuration_builders/person.rb
@@ -18,12 +18,14 @@ module Mauve
is_block_attribute "urgent"
is_block_attribute "normal"
is_block_attribute "low"
+
is_attribute "password"
is_attribute "sms"
- is_attribute "holiday_url"
is_attribute "email"
is_attribute "xmpp"
- is_attribute "sms"
+
+ is_flag_attribute "notify_when_on_holiday!"
+ is_flag_attribute "notify_when_off_sick!"
# Sets the block for all levels of alert
#
diff --git a/lib/mauve/configuration_builders/server.rb b/lib/mauve/configuration_builders/server.rb
index d22ed87..734fb3b 100644
--- a/lib/mauve/configuration_builders/server.rb
+++ b/lib/mauve/configuration_builders/server.rb
@@ -171,31 +171,6 @@ module Mauve
is_attribute "use_packet_buffer"
is_attribute "use_notification_buffer"
- #
- # This is where the calendar is located. The request paths are hard-coded.
- #
- is_attribute "bytemark_calendar_url"
-
- #
- # This is where the Bytemark authentication server is located.
- #
- is_attribute "bytemark_auth_url"
-
- #
- # This is the level of SSL verification used when making external HTTPS connections.
- #
- is_attribute "remote_https_verify_mode"
-
- #
- # This is the default timeout when making remote HTTP requests
- #
- is_attribute "remote_http_timeout"
-
- #
- # This is the default sleep time after an authentication attempt has failed.
- #
- is_attribute "failed_login_delay"
-
def builder_setup
@result = Mauve::Server.instance
end
diff --git a/lib/mauve/mauve_time.rb b/lib/mauve/mauve_time.rb
index f564fdb..dfbf99b 100644
--- a/lib/mauve/mauve_time.rb
+++ b/lib/mauve/mauve_time.rb
@@ -98,12 +98,37 @@ class Time
t
end
+ # Returns the bank_holidays array, or an empty array if bank_holidays hasn't
+ # been set.
+ #
+ #
+ def bank_holidays
+ @bank_holidays ||= []
+ end
+
+ # This sets the bank holiday dates for the bank_holiday? check.
+ #
+ # @param [Array] arg An array of Date of bank holidays
+ # @returns [Array]
+ #
+ def bank_holidays=(arg)
+ raise ArgumentError unless arg.is_a?(Array) and arg.all?{|a| a.is_a?(Date)}
+ @bank_holidays = arg
+ end
+
+ # This relies on bank_holidays being set.
+ #
+ def bank_holiday?
+ today = Date.new(self.year, self.month, self.day)
+ self.bank_holidays.any?{|bh| bh == today}
+ end
+
# Test to see if we're in working hours. The working day is from 8.30am until
# 17:00
#
# @return [Boolean]
def working_hours?
- (1..5).include?(self.wday) and ((9..16).include?(self.hour) or (self.hour == 8 && self.min >= 30))
+ !bank_holiday? and (1..5).include?(self.wday) and ((9..16).include?(self.hour) or (self.hour == 8 && self.min >= 30))
end
# Test to see if it is currently daytime. The daytime day is 14 hours long
@@ -113,7 +138,6 @@ class Time
(8..21).include?(self.hour)
end
-
# We're always in wallclock hours
#
# @return [true]
diff --git a/lib/mauve/notification.rb b/lib/mauve/notification.rb
index d06e03a..5877e0f 100644
--- a/lib/mauve/notification.rb
+++ b/lib/mauve/notification.rb
@@ -163,8 +163,9 @@ module Mauve
# Test to see if we're in working hours. See Time#working_hours?
#
# @return [Boolean]
- def working_hours?
+ def working_hours?
@test_time = @time if @test_time.nil?
+ @test_time.bank_holidays = Server.instance.bank_holidays
@test_time.working_hours?
end
diff --git a/lib/mauve/notifiers/xmpp.rb b/lib/mauve/notifiers/xmpp.rb
index a0ad86e..b291442 100644
--- a/lib/mauve/notifiers/xmpp.rb
+++ b/lib/mauve/notifiers/xmpp.rb
@@ -604,7 +604,10 @@ EOF
end
begin
- ack_until = Time.now.in_x_hours(n_hours, type_hours)
+ now = Time.now
+ now.bank_holidays = Server.instance.bank_holidays
+
+ ack_until = now.in_x_hours(n_hours, type_hours)
rescue RangeError
return "I'm sorry, you tried to acknowedge for far too long, and my buffers overflowed!"
end
diff --git a/lib/mauve/people_list.rb b/lib/mauve/people_list.rb
index eab4ae3..0433034 100644
--- a/lib/mauve/people_list.rb
+++ b/lib/mauve/people_list.rb
@@ -35,6 +35,8 @@ module Mauve
arr = arr.flatten
when String
arr = [arr]
+ when Proc
+ arr = [arr]
else
logger.warn "Not sure what to do with #{arr.inspect} -- converting to string, and continuing"
arr = [arr.to_s]
@@ -62,10 +64,12 @@ module Mauve
#
# @return [Array]
def people
-
l = list.collect do |name|
- Configuration.current.people.has_key?(name) ? Configuration.current.people[name] : nil
- end.reject{|person| person.nil?}
+ name.is_a?(Proc) ? name.call : name
+ end.flatten.compact.uniq.collect do |name|
+ Configuration.current.people[name]
+ end.compact
+
#
# Hmm.. no-one in the list?!
#
diff --git a/lib/mauve/person.rb b/lib/mauve/person.rb
index 1cf4ea0..929f511 100644
--- a/lib/mauve/person.rb
+++ b/lib/mauve/person.rb
@@ -3,23 +3,15 @@ require 'timeout'
require 'log4r'
module Mauve
- class Person < Struct.new(:username, :password, :urgent, :normal, :low, :email, :xmpp, :sms)
+ class Person
+ attr_reader :username, :password, :urgent, :normal, :low, :email, :xmpp, :sms
attr_reader :notification_thresholds, :last_pop3_login, :suppressed, :notifications
-
+ attr_reader :notify_when_off_sick, :notify_when_on_holiday
+
# Set up a new Person
#
- # @param [Hash] args The options for setting up the person
- # @option args [String] :username The person's username
- # @option args [String] :password The SHA1 sum of the person's password
- # @option args [String] :holiday_url The URL that can be checked by Mauve::CalendarInterface#is_user_on_holiday?
- # @option args [Proc] :urgent The block to execute when an urgent-level notification is issued
- # @option args [Proc] :normal The block to execute when an normal-level notification is issued
- # @option args [Proc] :low The block to execute when an low-level notification is issued
- # @option args [String] :email The person's email address
- # @option args [String] :sms The person's mobile number
- #
- def initialize(*args)
+ def initialize(username)
@notification_thresholds = nil
@suppressed = false
#
@@ -28,9 +20,85 @@ module Mauve
@last_pop3_login = {:from => nil, :at => nil}
@notifications = []
- super(*args)
- end
+ @username = username
+ @password = nil
+ @urgent = lambda { false }
+ @normal = lambda { false }
+ @low = lambda { false }
+ @email = @sms = @xmpp = nil
+ @notify_when_on_holiday = @notify_when_off_sick = false
+ end
+
+ # Determines if a user should be notified if they're ill.
+ #
+ # @return [Boolean]
+ #
+ def notify_when_off_sick!
+ @notify_when_off_sick = true
+ end
+
+ # Determines if a user should be notified if they're on their holdiays.
+ #
+ # @return [Boolean]
+ #
+ def notify_when_on_holiday!
+ @notify_when_on_holiday = true
+ end
+
+ # Sets the Proc to call for urgent notifications
+ #
+ def urgent=(block)
+ raise ArgumentError unless block.is_a?(Proc)
+ @urgent = block
+ end
+
+ # Sets the Proc to call for normal notifications
+ #
+ def normal=(block)
+ raise ArgumentError unless block.is_a?(Proc)
+ @normal = block
+ end
+
+ # Sets the Proc to call for low notifications
+ #
+ def low=(block)
+ raise ArgumentError unless block.is_a?(Proc)
+ @low = block
+ end
+
+ # Sets the email parameter
+ #
+ #
+ def email=(arg)
+ raise ArgumentError unless arg.is_a?(String)
+ @email = arg
+ end
+
+ # Sets the sms parameter
+ #
+ #
+ def sms=(arg)
+ raise ArgumentError unless arg.is_a?(String)
+ @sms = arg
+ end
+
+ # Sets the xmpp parameter
+ #
+ #
+ def xmpp=(arg)
+ raise ArgumentError unless arg.is_a?(String)
+ @xmpp = arg
+ end
+
+ # Sets the password parameter
+ #
+ #
+ def password=(arg)
+ raise ArgumentError unless arg.is_a?(String)
+ @password=arg
+ end
+
# @return Log4r::Logger
def logger ; @logger ||= Log4r::Logger.new self.class.to_s ; end
@@ -39,8 +107,6 @@ module Mauve
# @return [Boolean]
def suppressed? ; @suppressed ; end
- def holiday_url ; nil ; end
-
# Works out if a notification should be suppressed. If no parameters are supplied, it will
#
# @param [Time] Theoretical time of notification
@@ -155,8 +221,7 @@ module Mauve
# @param [Mauve::Alert] alert Alert we're notifiying about
#
# @return [Boolean] if the notification was successful
- def send_alert(level, alert)
- now = Time.now
+ def send_alert(level, alert, now=Time.now)
was_suppressed = @suppressed
@suppressed = self.should_suppress?
@@ -168,7 +233,7 @@ module Mauve
# We only suppress notifications if we were suppressed before we started,
# and are still suppressed.
#
- if @suppressed or self.is_on_holiday?
+ if @suppressed or self.is_on_holiday?(now) or self.is_off_sick?(now)
note = "#{alert.update_type.capitalize} notification to #{self.username} suppressed"
logger.info note + " about #{alert}."
History.create(:alerts => [alert], :type => "notification", :event => note)
@@ -217,20 +282,19 @@ module Mauve
end
end
- #
- #
- #
- def alert_during
-
- end
-
# Whether the person is on holiday or not.
#
# @return [Boolean] True if person on holiday, false otherwise.
- def is_on_holiday? ()
- return false if holiday_url.nil? or holiday_url.empty?
+ def is_on_holiday?(at=Time.now)
+ return false if self.notify_when_on_holiday
+
+ return CalendarInterface.is_user_on_holiday?(self.username, at)
+ end
+
+ def is_off_sick?(at=Time.now)
+ return false if self.notify_when_off_sick
- return CalendarInterface.is_user_on_holiday?(holiday_url)
+ return CalendarInterface.is_user_off_sick?(self.username, at)
end
end
diff --git a/lib/mauve/processor.rb b/lib/mauve/processor.rb
index 0ac7b59..acf72b3 100644
--- a/lib/mauve/processor.rb
+++ b/lib/mauve/processor.rb
@@ -57,14 +57,11 @@ module Mauve
to_delete = []
- @transmission_id_cache.each do |tid, received_at|
- to_delete << tid if (now - received_at) > @transmission_cache_expire_time
+ @transmission_id_cache = @transmission_id_cache.delete_if do |cache_data|
+ tid, received_at = cache_data
+ (now - received_at) > @transmission_cache_expire_time
end
- to_delete.each do |tid|
- @transmission_id_cache.delete(tid)
- end
-
@transmission_cache_checked_at = now
end
@@ -197,6 +194,11 @@ module Mauve
sz.times do
process_packet(*Server.packet_pop)
end
+
+ #
+ # Now expire the cache. This will only get processed at most once every minute.
+ #
+ expire_transmission_id_cache
end
def timer_should_stop?
diff --git a/lib/mauve/server.rb b/lib/mauve/server.rb
index 2b0e101..366191c 100644
--- a/lib/mauve/server.rb
+++ b/lib/mauve/server.rb
@@ -50,21 +50,10 @@ module Mauve
@notification_buffer = []
#
- # Set the auth/calendar URLs
+ # Bank Holidays -- this list is kept here, because I can't think of
+ # anywhere else to put it.
#
- @bytemark_auth_url = nil
- @bytemark_calendar_url = nil
-
- #
- # Set a couple of params for remote HTTP requests.
- #
- @remote_http_timeout = 5
- @remote_https_verify_mode = OpenSSL::SSL::VERIFY_PEER
-
- #
- # Rate limit login attempts to limit the success of brute-forcing.
- #
- @failed_login_delay = 1
+ @bank_holidays = nil
#
# Set up a blank config.
@@ -114,83 +103,6 @@ module Mauve
@notification_buffer
end
- # Set the calendar URL.
- #
- # @param [String] arg
- # @return [URI]
- def bytemark_calendar_url=(arg)
- raise ArgumentError, "bytemark_calendar_url must be a string" unless arg.is_a?(String)
-
- @bytemark_calendar_url = URI.parse(arg)
-
- #
- # Make sure we get an HTTP URL.
- #
- raise ArgumentError, "bytemark_calendar_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_calendar_url.scheme)
-
- #
- # Set a default request path, if none was given
- #
- @bytemark_calendar_url.path="/" if @bytemark_calendar_url.path.empty?
-
- @bytemark_calendar_url
- end
-
- # Set the Bytemark Authentication URL
- #
- # @param [String] arg
- # @return [URI]
- def bytemark_auth_url=(arg)
- raise ArgumentError, "bytemark_auth_url must be a string" unless arg.is_a?(String)
-
- @bytemark_auth_url = URI.parse(arg)
- #
- # Make sure we get an HTTP URL.
- #
- raise ArgumentError, "bytemark_auth_url must be an HTTP(S) URL." unless %w(http https).include?(@bytemark_auth_url.scheme)
-
- #
- # Set a default request path, if none was given
- #
- @bytemark_auth_url.path="/" if @bytemark_auth_url.path.empty?
-
- @bytemark_auth_url
- end
-
- # Sets the timeout when making remote HTTP requests
- #
- # @param [Integer] arg
- # @return [Integer]
- def remote_http_timeout=(arg)
- raise ArgumentError, "initial_sleep must be an integer" unless s.is_a?(Integer)
- @remote_http_timeout = arg
- end
-
- # Sets the SSL verification mode when makeing remote HTTPS requests
- #
- # @param [String] arg must be one of "none" or "peer"
- # @return [Constant]
- def remote_https_verify_mode=(arg)
- @remote_https_verify_mode = case arg
- when "peer"
- OpenSSL::SSL::VERIFY_PEER
- when "none"
- OpenSSL::SSL::VERIFY_NONE
- else
- raise ArgumentError, "remote_https_verify_mode must be either 'peer' or 'none'"
- end
- end
-
- # Set the delay added following a failed login attempt.
- #
- # @param [Numeric] arg Number of seconds to delay following a failed login attempt
- # @return [Numeric]
- #
- def failed_login_delay=(arg)
- raise ArgumentError, "initial_sleep must be numeric" unless arg.is_a?(Numeric)
- @failed_login_delay = arg
- end
-
# Set the sleep period during which notifications about old alerts are
# suppressed.
#
@@ -207,6 +119,24 @@ module Mauve
Time.now < self.started_at + self.initial_sleep
end
+ # Check with the calendar for the list of bank holidays
+ #
+ #
+ def bank_holidays
+ #
+ # Update the bank holidays list hourly.
+ #
+ if @bank_holidays.nil? or
+ @bank_holidays_last_checked_at.nil? or
+ @bank_holidays_last_checked_at < (Time.now - 1.hour)
+
+ @bank_holidays = CalendarInterface.get_bank_holiday_list(Time.now)
+ @bank_holidays_last_checked_at = Time.now
+ end
+
+ @bank_holidays
+ end
+
# return [Log4r::Logger]
def logger
@logger ||= Log4r::Logger.new(self.class.to_s)
diff --git a/lib/mauve/web_interface.rb b/lib/mauve/web_interface.rb
index 16e7da2..a8e1ece 100644
--- a/lib/mauve/web_interface.rb
+++ b/lib/mauve/web_interface.rb
@@ -218,9 +218,13 @@ EOF
note = params[:note] || nil
n_hours = (n_hours.to_f > 188 ? 188 : n_hours.to_f)
+ type_hours = "daytime" unless %w(daytime working wallclock).include?(type_hours)
if ack_until.to_s.empty?
- ack_until = Time.now.in_x_hours(n_hours, type_hours.to_s)
+ now = Time.now
+ now.bank_holidays = Server.instance.bank_holidays
+
+ ack_until = now.in_x_hours(n_hours, type_hours.to_s)
else
ack_until = Time.at(ack_until.to_i)
end
@@ -275,12 +279,14 @@ EOF
#
n_hours = ( n_hours > 300 ? 300 : n_hours )
type_hours = "daytime" unless %w(daytime working wallclock).include?(type_hours)
- ack_until = Time.now.in_x_hours(n_hours, type_hours)
+ now = Time.now
+ now.bank_holidays = Server.instance.bank_holidays
+ ack_until = now.in_x_hours(n_hours, type_hours)
#
# Make sure we can't ack longer than a week.
#
- max_ack = (Time.now + 86400*8)
+ max_ack = (Time.now + Configuration.current.max_acknowledgement_time)
ack_until = max_ack if ack_until > max_ack
#
@@ -359,8 +365,13 @@ EOF
type_hours = params[:type_hours].to_s
note = params[:note] || nil
+ type_hours = "daytime" unless %w(daytime working wallclock).include?(type_hours)
+
if ack_until == 0
- ack_until = Time.now.in_x_hours(n_hours, type_hours)
+ now = Time.now
+ now.bank_holidays = Server.instance.bank_holidays
+
+ ack_until = now.in_x_hours(n_hours, type_hours)
else
ack_until = Time.at(ack_until)
end
diff --git a/lib/object_builder.rb b/lib/object_builder.rb
index ebd655b..ccb2c63 100644
--- a/lib/object_builder.rb
+++ b/lib/object_builder.rb
@@ -181,6 +181,20 @@ class ObjectBuilder
end
end
+ # This catches all methods available for a provider, as needed.
+ #
+ # Missing methods / bad arguments etc. are caught in the
+ # ObjectBuilder#parse method, via NoMethodError.
+ #
+ def method_missing(name, value=nil)
+ if value
+ result.send("#{name}=".to_sym, value)
+ else
+ result.send(name.to_sym)
+ end
+ end
+
+
class << self
# Defines a new builder
diff --git a/test/tc_mauve_alert.rb b/test/tc_mauve_alert.rb
index 2b8af6f..3af9075 100644
--- a/test/tc_mauve_alert.rb
+++ b/test/tc_mauve_alert.rb
@@ -95,8 +95,7 @@ EOF
end
def test_acknowledge!
- person = Mauve::Person.new
- person.username = "test-user"
+ person = Mauve::Person.new("test-user")
Server.instance.setup
diff --git a/test/tc_mauve_authentication.rb b/test/tc_mauve_authentication.rb
index d0f2d4f..c364aca 100644
--- a/test/tc_mauve_authentication.rb
+++ b/test/tc_mauve_authentication.rb
@@ -26,9 +26,7 @@ class TcMauveAuthentication < Mauve::UnitTest
def test_default_auth_always_fails
config=<<EOF
-server {
- failed_login_delay 0
-}
+failed_login_delay 0
EOF
Configuration.current = ConfigurationBuilder.parse(config)
@@ -43,9 +41,7 @@ EOF
def test_local_auth
config=<<EOF
-server {
- failed_login_delay 0
-}
+failed_login_delay 0
person ("test") {
password "#{Digest::SHA1.new.hexdigest("password")}"
@@ -70,9 +66,7 @@ EOF
def test_local_auth
config=<<EOF
-server {
- failed_login_delay 0
-}
+failed_login_delay 0
person ("nopass") { }
@@ -97,10 +91,8 @@ EOF
# test2: POKvBqLT7
#
config=<<EOF
-server {
- failed_login_delay 0
- bytemark_auth_url "https://auth.bytemark.co.uk/"
-}
+failed_login_delay 0
+bytemark_auth_url "https://auth.bytemark.co.uk/"
person ("test1") { }
diff --git a/test/tc_mauve_calendar_interface.rb b/test/tc_mauve_calendar_interface.rb
new file mode 100644
index 0000000..ddf4039
--- /dev/null
+++ b/test/tc_mauve_calendar_interface.rb
@@ -0,0 +1,155 @@
+$:.unshift "../lib"
+
+require 'th_mauve'
+require 'mauve/calendar_interface'
+require 'mauve/configuration_builder'
+require 'mauve/configuration'
+require 'webmock'
+#
+# Ugh webmock is annoying.
+WebMock.allow_net_connect!
+
+class TcMauveCalendarInterface < Mauve::UnitTest
+
+ include WebMock::API
+ include Mauve
+
+ def setup
+ WebMock.disable_net_connect!
+ super
+ end
+
+ def teardown
+ WebMock.reset!
+ WebMock.allow_net_connect!
+ super
+ end
+
+ def test_get_attendees
+ attendees = %w(test1 test2)
+ stub_request(:get, "http://localhost/calendar/api/attendees/support_shift/2011-08-01T00:00:00").
+ to_return(:status => 200, :body => YAML.dump(attendees))
+
+ config =<<EOF
+bytemark_calendar_url "http://localhost/calendar"
+EOF
+
+ Configuration.current = ConfigurationBuilder.parse(config)
+
+ assert_equal(attendees, CalendarInterface.get_attendees("support_shift"))
+ end
+
+ def test_is_user_on_holiday?
+ attendees = %w(test1 test2)
+ stub_request(:get, "http://localhost/calendar/api/attendees/staff_holiday/2011-08-01T00:00:00").
+ to_return(:status => 200, :body => YAML.dump(attendees))
+
+
+ config =<<EOF
+bytemark_calendar_url "http://localhost/calendar"
+EOF
+
+ Configuration.current = ConfigurationBuilder.parse(config)
+
+ assert(CalendarInterface.is_user_on_holiday?("test1"))
+ assert(!CalendarInterface.is_user_on_holiday?("test3"))
+ end
+
+ def test_is_user_off_sick?
+ attendees = %w(test1 test2)
+ stub_request(:get, "http://localhost/calendar/api/attendees/sick_period/2011-08-01T00:00:00").
+ to_return(:status => 200, :body => YAML.dump(attendees))
+
+ config =<<EOF
+bytemark_calendar_url "http://localhost/calendar"
+EOF
+
+ Configuration.current = ConfigurationBuilder.parse(config)
+
+ assert(CalendarInterface.is_user_off_sick?("test1"))
+ assert(!CalendarInterface.is_user_off_sick?("test3"))
+ end
+
+ def test_do_get
+ url = "http://localhost/"
+
+ #
+ # This sets up two redirects, followed by the answer (below)
+ #
+ 2.times do |x|
+ next_url = url + "#{x}/"
+ stub_request(:get, url).
+ to_return(:status => 301, :body => nil, :headers => {:location => next_url})
+ url = next_url
+ end
+
+ #
+ # And finally the answer.
+ #
+ stub_request(:get, url).
+ to_return(:status => 200, :body => "OK!", :headers => {})
+
+ #
+ # Now do_get should return "OK!" when the maximum number of redirects is set to two.
+ #
+ result = nil
+ assert_nothing_raised{ result = CalendarInterface.__send__(:do_get, "http://localhost/", 2) }
+ assert_equal("OK!",result)
+
+ #
+ # do_get should return nil when the maximum number of redirects is set to two.
+ #
+ assert_nothing_raised{ result = CalendarInterface.__send__(:do_get, "http://localhost/", 1) }
+ assert_nil(result)
+
+ #
+ # Pop the warning about the redirect off the end of the log.
+ #
+ logger_pop
+ end
+
+ def test_do_get_with_cache
+ url = "http://localhost/"
+
+ #
+ # This stubs the request to give out the time
+ #
+ stub_request(:get, url).
+ to_return( lambda{ {:status => 200, :body => YAML.dump(Time.now), :headers => {}} } )
+
+ #
+ # This reponse should not be cached, the cache-until paramter is "now"
+ #
+ assert_equal(Time.now, CalendarInterface.__send__(:do_get_with_cache, url, Time.now))
+
+ #
+ # Since the last request wasn't cached, the next one should give back
+ # "now", and should be cached for the next 10 seconds.
+ #
+ Timecop.freeze(Time.now + 5)
+ assert_equal(Time.now, CalendarInterface.__send__(:do_get_with_cache, url, Time.now + 10))
+
+ #
+ # This should have been cached from the last query.
+ #
+ Timecop.freeze(Time.now + 5)
+ assert_equal(Time.now - 5, CalendarInterface.__send__(:do_get_with_cache, url, Time.now + 10))
+
+ #
+ # Finally, this should now have expired from the cache.
+ #
+ Timecop.freeze(Time.now + 5)
+ assert_equal(Time.now, CalendarInterface.__send__(:do_get_with_cache, url, Time.now + 10))
+
+ Timecop.freeze(Time.now + 50)
+ cache = CalendarInterface.__send__(:clean_cache)
+ assert(cache.empty?)
+ end
+
+
+end
+
+
+
+
+
diff --git a/test/tc_mauve_notification.rb b/test/tc_mauve_notification.rb
index a447207..d0c2bab 100644
--- a/test/tc_mauve_notification.rb
+++ b/test/tc_mauve_notification.rb
@@ -442,9 +442,15 @@ people_list("testers", %w(test1 test2)) {
alert_group("test") {
level URGENT
+
notify("test1")
notify("test2")
notify("testers")
+
+ notify("testers") {
+ every 60
+ during { hours_in_day (3) }
+ }
}
EOF
diff --git a/test/tc_mauve_people_list.rb b/test/tc_mauve_people_list.rb
index 2d5da6d..fd8bda9 100644
--- a/test/tc_mauve_people_list.rb
+++ b/test/tc_mauve_people_list.rb
@@ -117,4 +117,29 @@ EOF
end
end
+
+ def test_dynamic_people_list
+ #
+ # Allows us to pick up notifications sent.
+ #
+ $sent_notifications = []
+
+ config =<<EOF
+
+person "test1"
+
+person "test2"
+
+#
+# This should oscillate between test1 and test2.
+#
+people_list "testers", lambda { $ans = ($ans == "test1" ? "test2" : "test1") }
+
+EOF
+ Configuration.current = ConfigurationBuilder.parse(config)
+ people_list = Configuration.current.people_lists["testers"]
+ assert_equal([Configuration.current.people["test1"]], people_list.people)
+ assert_equal([Configuration.current.people["test2"]], people_list.people)
+ end
+
end
diff --git a/test/tc_mauve_time.rb b/test/tc_mauve_time.rb
index 7a8fefa..6e5989b 100644
--- a/test/tc_mauve_time.rb
+++ b/test/tc_mauve_time.rb
@@ -35,8 +35,15 @@ class TestMauveTime < Mauve::UnitTest
assert_equal(hour_1, t.in_x_hours(1,"working"))
assert_equal(hour_0, t.in_x_hours(0,"working"))
+ end
+
+ def test_bank_holiday?
+ x = Time.now
+ assert(!x.bank_holiday?)
+ x.bank_holidays << Date.new(x.year, x.month, x.day)
+ assert(x.bank_holiday?)
end
diff --git a/test/th_mauve.rb b/test/th_mauve.rb
index ce29e11..99eaaed 100644
--- a/test/th_mauve.rb
+++ b/test/th_mauve.rb
@@ -55,6 +55,7 @@ module Mauve
def setup
reset_all_singletons
+ reset_mauve_configuration
setup_logger
setup_time
end
@@ -108,6 +109,10 @@ module Mauve
Timecop.return
end
+ def reset_mauve_configuration
+ Mauve::Configuration.current = Mauve::Configuration.new
+ end
+
def reset_all_singletons
Mauve.constants.collect{|const| Mauve.const_get(const)}.each do |klass|
next unless klass.respond_to?(:instance)