From 74eaf8902faa7fe798906b08b1fca4cecefb97ee Mon Sep 17 00:00:00 2001 From: Patrick Morgan Date: Mon, 29 Dec 2008 16:16:21 -0600 Subject: [PATCH] initial import --- README | 17 + ServerMonitor-config.xml | 33 ++ ServerMonitor.rb | 398 ++++++++++++++ ServerMonitor.xsl | 125 +++++ doc/classes/ServerMonitor.html | 143 +++++ doc/classes/ServerMonitor/Monitor.html | 293 ++++++++++ .../ServerMonitor/Monitor.src/M000012.html | 24 + .../ServerMonitor/Monitor.src/M000013.html | 18 + .../ServerMonitor/Monitor.src/M000014.html | 32 ++ .../ServerMonitor/Monitor.src/M000015.html | 26 + .../ServerMonitor/Monitor.src/M000016.html | 37 ++ .../ServerMonitor/Monitor.src/M000017.html | 21 + .../ServerMonitor/Monitor.src/M000018.html | 21 + .../ServerMonitor/Monitor.src/M000019.html | 20 + doc/classes/ServerMonitor/Server.html | 299 +++++++++++ .../ServerMonitor/Server.src/M000004.html | 34 ++ .../ServerMonitor/Server.src/M000005.html | 19 + .../ServerMonitor/Server.src/M000006.html | 19 + .../ServerMonitor/Server.src/M000007.html | 18 + .../ServerMonitor/Server.src/M000008.html | 18 + .../ServerMonitor/Server.src/M000009.html | 18 + .../ServerMonitor/Server.src/M000010.html | 18 + .../ServerMonitor/Server.src/M000011.html | 18 + doc/classes/ServerMonitor/TestSuite.html | 182 +++++++ .../ServerMonitor/TestSuite.src/M000001.html | 18 + .../ServerMonitor/TestSuite.src/M000002.html | 24 + .../ServerMonitor/TestSuite.src/M000003.html | 24 + doc/created.rid | 1 + doc/files/ServerMonitor_rb.html | 114 ++++ doc/fr_class_index.html | 30 ++ doc/fr_file_index.html | 27 + doc/fr_method_index.html | 45 ++ doc/index.html | 24 + doc/rdoc-style.css | 208 +++++++ run_monitor.rb | 113 ++++ server_monitor.rb | 506 ++++++++++++++++++ 36 files changed, 2985 insertions(+) create mode 100644 README create mode 100644 ServerMonitor-config.xml create mode 100644 ServerMonitor.rb create mode 100644 ServerMonitor.xsl create mode 100644 doc/classes/ServerMonitor.html create mode 100644 doc/classes/ServerMonitor/Monitor.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000012.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000013.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000014.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000015.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000016.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000017.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000018.html create mode 100644 doc/classes/ServerMonitor/Monitor.src/M000019.html create mode 100644 doc/classes/ServerMonitor/Server.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000004.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000005.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000006.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000007.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000008.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000009.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000010.html create mode 100644 doc/classes/ServerMonitor/Server.src/M000011.html create mode 100644 doc/classes/ServerMonitor/TestSuite.html create mode 100644 doc/classes/ServerMonitor/TestSuite.src/M000001.html create mode 100644 doc/classes/ServerMonitor/TestSuite.src/M000002.html create mode 100644 doc/classes/ServerMonitor/TestSuite.src/M000003.html create mode 100644 doc/created.rid create mode 100644 doc/files/ServerMonitor_rb.html create mode 100644 doc/fr_class_index.html create mode 100644 doc/fr_file_index.html create mode 100644 doc/fr_method_index.html create mode 100644 doc/index.html create mode 100644 doc/rdoc-style.css create mode 100755 run_monitor.rb create mode 100755 server_monitor.rb diff --git a/README b/README new file mode 100644 index 0000000..a14b972 --- /dev/null +++ b/README @@ -0,0 +1,17 @@ +#summary Basic Usage Instructions + += Configuration = + +Program configuration uses a properly formatted XML file. See _ServerMonitor-config.xml_ included in the source archive for an example. Specify server name and domain, and the URLs and ports to test. + += Command Line = + +Run _ruby server_monitor.rb --config path/to/configfile.xml --log path/to/logfile.xml --xsl path/or/url/for/log.xsl_ + += Output = + +Outputs or appends to logfile.xml. To view the xml file in a web browser, the xsl path must be valid, and within the same domain as the xml file (browser security requirement). If the xml file will be viewed from a http url, the path specified for the xsl file when running the program must refer to the same domain (whether internet or file://). Please note that subsequent runs will not modify the the xsl path in the xml logfile. + += Scripting = + +You may specify the _--quiet_ commandline option to disable verbose output. diff --git a/ServerMonitor-config.xml b/ServerMonitor-config.xml new file mode 100644 index 0000000..02038c0 --- /dev/null +++ b/ServerMonitor-config.xml @@ -0,0 +1,33 @@ + + + + + monitor@example.com + + person@example.com + + + + Master Web Design + www.masterwebdesign.net + 80 + 25 + http://www.masterwebdesign.net/ + http://www.masterwebdesign.net/portfolio + + + MerchantHound + www.merchanthound.com + 80 + http://www.merchanthound.com + http://electronics.merchanthound.com/asdghgas + + + MH Skin + compare-prices.tomshardware.com + 80 + http://compare-prices.tomshardware.com/ + http://compare-prices.tomshardware.com/browse/electronics + + + diff --git a/ServerMonitor.rb b/ServerMonitor.rb new file mode 100644 index 0000000..4b73450 --- /dev/null +++ b/ServerMonitor.rb @@ -0,0 +1,398 @@ +module ServerMonitor + + require 'date' + require 'net/http' + require 'net/https' + require 'net/smtp' + require 'open-uri' + require 'rexml/document' + require 'fileutils' + include REXML + + Port = Struct.new("Port",:number,:tested,:success, :time) + Url = Struct.new("Url",:url, :tested, :success, :time) + MonitorReport = Struct.new("MonitorReport", :xml, :text, :delimited) + + class Server + attr_reader :name, :ports, :urls + attr_accessor :domain, :name + + def initialize(opts={}) + options = {:ports => [], :name => "", :urls => [], :domain => ""}.merge(opts) + if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) + raise "Invalid Server Options" + end unless opts == {} or opts.class != Hash + + @name = options[:name] + @domain = options[:domain] + urls = options[:urls] + @urls = [] + urls.each do |url| + add_url(url) + end + ports = options[:ports] + @ports = [] + ports.each do |port| + add_port(port) + end + end + + # Add port to the server's lists of ports to test. Provide an integer or string + def add_port(port) + p = Port.new(port,nil,nil) + @ports << p + end + + # Add url to the server's lists of urls to test. Provide a string. + def add_url(url) + u = Url.new(url,nil,nil) + @urls << u + end + + # Were all tests successful? + def success? + @ports.select{|p| not p.success}.size + @urls.select{|u| not u.success}.size < 1 + end + + # Return an array of port objects that passed + def ports_passed + @ports.select{|p| p.success} + end + + # Return an array of port objects that failed + def ports_failed + @ports.select{|p| not p.success} + end + + # Return an array of url objects that passed + def urls_passed + @urls.select{|u| u.success} + end + + # Return an array of url objects that failed + def urls_failed + @urls.select{|u| not u.success} + end + end + + class TestSuite + + attr_reader :log + + def initialize + @log = ["Starting Test Suite at #{Time.now}\n================================="] + end + + def test_port(domain,port) + msg = " * Testing #{domain}:#{port} => " + now = Time.now + result = get_port(domain,port) + elapsed = Time.now - now + msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s) [#{Time.now}]" + @log << msg + [result,elapsed] + end + + def test_url(url) + msg = " * Testing URL #{url} => " + now = Time.now + result = get_url(url) + elapsed = Time.now - now + msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s) [#{Time.now}]" + @log << msg + [result,elapsed] + end + + + private + + def get_url(uri_str,limit=10) + raise ArgumentError, 'HTTP redirect too deep' if limit == 0 + + https = true if uri_str.match(/https:/i) + uri = URI.parse(uri_str) + http = Net::HTTP.new(uri.host,uri.port) + req = Net::HTTP::Get.new(uri_str) + http.use_ssl = true if https + response = http.request(req) + + #response = Net::HTTP.get_response(URI.parse(uri_str)) + case response + when Net::HTTPSuccess then true + when Net::HTTPRedirection then get_url(response['location'], limit - 1) + else + response.error! + end + rescue + false + end + + def get_port(domain,port) + addr = TCPSocket.gethostbyname(domain)[0] + socket = TCPSocket.new(addr,port.to_i) ? true : false + rescue + false + end + + end + + class Monitor + + attr_reader :servers, :report + + # Specify config, log, and xsl filenames + def initialize(config_filename=nil,log_filename=nil,xslt=nil) + @servers = [] + @logfile = log_filename + @xslt = xslt + @report = MonitorReport.new + @success = nil + @email_addr = [] + load_config(config_filename) if config_filename + end + + # Did all of the servers pass? + def success? + @servers.select{|server| not server.success?}.size == 0 + end + + # Run server tests. + def test_servers + raise "Empty Server List" if @servers.empty? + ts = TestSuite.new + @servers.each do |server| + server.ports.each do |port| + port.tested = true + (port.success, port.time) = ts.test_port(server.domain,port.number) + end + server.urls.each do |url| + url.tested = true + (url.success, url.time) = ts.test_url(url.url) + end + end + @report.text = ts.log.join("\n") + @report.delimited = generate_delimited_report(@servers) + @report.xml = generate_xml_report(@servers) + end + + # Add server to server list (self.servers) + # Accepts hash as argument + # + # Server.new( { :name => String, + # :domain => String, + # :ports => Array of Strings, + # :urls => Array of Strings } ) + def add_server(options) + raise "Invalid Argument (must be a hash)" unless options.class == Hash + if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) + raise "Insufficient Server Options" + end + s = Server.new( :name => options[:name], + :domain => options[:domain], + :ports => options[:ports], + :urls => options[:urls]) + @servers << s + end + + # Creates or appends to existing XML test log + def log_report(filename) + log = File.exist?(filename) ? File.open(filename,"r").read : "" + out = File.open(filename,"w") + log = "" unless log.match(/xml version/i) + doc = Document.new log + if log == "" + doc << XMLDecl.new + docroot = doc.add_element('ServerMonitor') + if @xslt + xslt_text = "type=\"text/xsl\" href=\"#{@xslt}\"" + xslt = Instruction.new("xml-stylesheet", xslt_text) + root = doc.root + root.previous_sibling = xslt + end + else + docroot = doc.elements['ServerMonitor'] + end + docroot = add_report_xml(docroot,servers) + out.puts doc.to_s(0) + ensure + out.close + end + + # Load config from xml file. Provide filename as a String + def load_config(filename) + config = read_config(filename) + @servers = config[:servers] + @email_addr = config[:email][:addr] + @email_sender = config[:email][:sender] + end + + # Save report data text to file + def save_test_report(filename,text) + out = File.open(filename,"w") + out.puts text + ensure + out.close + end + + # Email a report to all of the configuration specified email addresses with an optional custom message in the title + def email_report(message="") + @email_addr.each do |email| + send_email(:to => email, :subject => ("Server Monitor Report" + message), :message => @report.text) + end + end + + private + + # stub + def generate_delimited_report(servers) + # stub + @report.delimited = @report.text.dup + end + + # Generate XML report data from an array of server objects + def generate_xml_report(servers) + xmlstring = "" + doc = Document.new xmlstring + doc << XMLDecl.new + docroot = doc.add_element('ServerMonitor') + xslt_text = 'type="text/xsl" href="servermonitor.xsl"' + xslt = Instruction.new("xml-stylesheet", xslt_text) + root = doc.root + root.previous_sibling = xslt + docroot = add_report_xml(docroot,servers) + doc.to_s + end + + # Add a test run node to the provided REXML XML node. Also specify an array of servers + def add_report_xml(parent,servers) + test_run = parent.add_element('test_run') + tr_date = test_run.add_element('date') + tr_date.add_text Time.now.to_s + test_run = add_server_info_to_xml_node(test_run,servers) + servers.each do |server| + server_node = test_run.add_element('server') + name = server_node.add_element('name') + name.add_text(server.name) + domain = server_node.add_element('domain') + domain.add_text(server.domain) + result = server_node.add_element('result') + result.add_text(server.success? ? "PASSED" : "FAILED") + server.ports.each do |port| + port_node = server_node.add_element('port') + number = port_node.add_element('number') + number.add_text(port.number) + result = port_node.add_element('result') + result.add_text(port.success ? "PASSED" : "FAILED") + time = port_node.add_element('time') + time.add_text(port.time.to_s) + end + server.urls.each do |url| + url_node = server_node.add_element('url') + url_url = url_node.add_element('url') + url_url.add_text url.url + result = url_node.add_element('result') + result.add_text(url.success ? "PASSED" : "FAILED") + time = url_node.add_element('time') + time.add_text(url.time.to_s) + end + end + parent + end + + # Add a server config node to the provided REXML XML node. Also specify an array of servers + def add_server_info_to_xml_node(parent,servers) + config = parent.add_element('config') + servers.each do |server| + server_node = config.add_element('server') + name = server_node.add_element('name') + name.add_text(server.name) + domain = server_node.add_element('domain') + domain.add_text(server.domain) + server.ports.each do |port| + port_node = server_node.add_element('port') + port_node.add_text(port.number) + end + server.urls.each do |url| + url_node = server_node.add_element('url') + url_node.add_text(url.url) + end + end + parent + end + + + # Read config from file + def read_config(filename=nil) + raise "Specify xml formatted configuration filename as String" if filename.nil? + raise "File doesn't exist!" unless File.exist?(filename) + configfile = File.open(filename,"r") + config = Document.new(configfile) + servers = [] + + email_config = config.elements['//config/email'] + if email_config + sender = email_config.elements['sender'] + sender_addr = sender.text + if notify = email_config.elements['notify'] + email_addr = [] + notify.elements.each('addr') do |addr| + email_addr << addr.text + end + end + end + + config.elements.each('//config/server') do |server_node| + server = Server.new + server.name = server_node.elements['name'].text + server.domain = server_node.elements['domain'].text + server_node.elements.each('port') do |port_node| + server.add_port(port_node.text) + end + server_node.elements.each('url') do |url_node| + server.add_url(url_node.text) + end + servers << server + end + { + :servers => servers, + :email => {:addr => email_addr, :sender => sender_addr} + } + ensure + configfile.close + end + + # Send an email. + # Specify a hash { :from => "", + # :from_alias => "", + # :to => "", + # :to_alias => "", + # :subject => "", + # :message => ""} + def send_email(opts) + options = { :from => @email_sender, + :from_alias => "Server Monitor Notifier", + :to => "", + :to_alias => "", + :subject => "", + :message => ""}.merge(opts) + + raise "You forgot the recipient address" unless options[:to] + + msg = < +To: #{options[:to_alias]} <#{options[:to]}> +Subject: #{options[:subject]} + +#{options[:message]} +END_OF_MESSAGE + + Net::SMTP.start('localhost') do |smtp| + smtp.send_message options[:message], options[:from], options[:to] + end +end + true + #rescue + # false + end + +end diff --git a/ServerMonitor.xsl b/ServerMonitor.xsl new file mode 100644 index 0000000..bc2394c --- /dev/null +++ b/ServerMonitor.xsl @@ -0,0 +1,125 @@ + + + + + + + + + + + +

Monitoring Runs

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDomainPort / URLResultTime
+


+
+ + +
+
diff --git a/doc/classes/ServerMonitor.html b/doc/classes/ServerMonitor.html new file mode 100644 index 0000000..dd1b7c9 --- /dev/null +++ b/doc/classes/ServerMonitor.html @@ -0,0 +1,143 @@ + + + + + + Module: ServerMonitor + + + + + + + + + + +
+ + + + + + + + + + +
ModuleServerMonitor
In: + + ServerMonitor.rb + +
+
+
+ + +
+ + + +
+ + + +
+ + +
+ + + +
+

Included Modules

+ +
+ REXML +
+
+ +
+ +
+

Classes and Modules

+ + Class ServerMonitor::Monitor
+Class ServerMonitor::Server
+Class ServerMonitor::TestSuite
+ +
+ +
+

Constants

+ +
+ + + + + + + + + + + + + + + + +
Port=Struct.new("Port",:number,:tested,:success, :time)
Url=Struct.new("Url",:url, :tested, :success, :time)
MonitorReport=Struct.new("MonitorReport", :xml, :text, :delimited)
+
+
+ + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.html b/doc/classes/ServerMonitor/Monitor.html new file mode 100644 index 0000000..93872d0 --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.html @@ -0,0 +1,293 @@ + + + + + + Class: ServerMonitor::Monitor + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassServerMonitor::Monitor
In: + + ServerMonitor.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ add_server   + email_report   + load_config   + log_report   + new   + save_test_report   + success?   + test_servers   +
+
+ +
+ + + + +
+ + + + + +
+

Attributes

+ +
+ + + + + + + + + + + +
report [R] 
servers [R] 
+
+
+ + + + +
+

Public Class methods

+ +
+ + + + +
+

+Specify config, log, and xsl filenames +

+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+Add server to server list (self.servers) Accepts hash as argument +

+
+                    Server.new( { :name => String,
+                      :domain => String,
+                      :ports => Array of Strings,
+                      :urls => Array of Strings } )
+
+
+
+ +
+ + + + +
+

+Email a report to all of the configuration specified email addresses with +an optional custom message in the title +

+
+
+ +
+ + + + +
+

+Load config from xml file. Provide filename as a String +

+
+
+ +
+ + + + +
+

+Creates or appends to existing XML test log +

+
+
+ +
+ + + + +
+

+Save report data text to file +

+
+
+ +
+ + + + +
+

+Did all of the servers pass? +

+
+
+ +
+ + + + +
+

+Run server tests. +

+
+
+ + +
+ + +
+ + + + + + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000012.html b/doc/classes/ServerMonitor/Monitor.src/M000012.html new file mode 100644 index 0000000..1631897 --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000012.html @@ -0,0 +1,24 @@ + + + + + + new (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 144
+                def initialize(config_filename=nil,log_filename=nil,xslt=nil)
+                        @servers = []
+                        @logfile = log_filename
+                        @xslt = xslt
+                        @report = MonitorReport.new
+                        @success = nil
+                        @email_addr = []
+                        load_config(config_filename) if config_filename
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000013.html b/doc/classes/ServerMonitor/Monitor.src/M000013.html new file mode 100644 index 0000000..ba9bc31 --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000013.html @@ -0,0 +1,18 @@ + + + + + + success? (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 155
+                def success?
+                        @servers.select{|server| not server.success?}.size == 0
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000014.html b/doc/classes/ServerMonitor/Monitor.src/M000014.html new file mode 100644 index 0000000..aa9778c --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000014.html @@ -0,0 +1,32 @@ + + + + + + test_servers (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 160
+                def test_servers
+                        raise "Empty Server List" if @servers.empty?
+                        ts = TestSuite.new
+                        @servers.each do |server|
+                                server.ports.each do |port|
+                                        port.tested = true
+                                        (port.success, port.time) = ts.test_port(server.domain,port.number)
+                                end
+                                server.urls.each do |url|
+                                        url.tested = true
+                                        (url.success, url.time) = ts.test_url(url.url)
+                                end
+                        end
+                        @report.text = ts.log.join("\n")
+                        @report.delimited = generate_delimited_report(@servers)
+                        @report.xml = generate_xml_report(@servers)
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000015.html b/doc/classes/ServerMonitor/Monitor.src/M000015.html new file mode 100644 index 0000000..3fd351d --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000015.html @@ -0,0 +1,26 @@ + + + + + + add_server (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 185
+                def add_server(options)
+                        raise "Invalid Argument (must be a hash)" unless options.class == Hash
+                        if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) 
+                                raise "Insufficient Server Options" 
+                        end
+                        s = Server.new( :name => options[:name],
+                                                         :domain => options[:domain],
+                                                     :ports => options[:ports], 
+                                                         :urls => options[:urls])
+                        @servers << s
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000016.html b/doc/classes/ServerMonitor/Monitor.src/M000016.html new file mode 100644 index 0000000..5ee535a --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000016.html @@ -0,0 +1,37 @@ + + + + + + log_report (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 198
+                def log_report(filename)
+                        log = File.exist?(filename) ? File.open(filename,"r").read : ""
+                        out = File.open(filename,"w")
+                        log = "" unless log.match(/xml version/i)
+                        doc = Document.new log
+                        if log == ""
+                                doc << XMLDecl.new
+                                docroot = doc.add_element('ServerMonitor')
+                                if @xslt
+                                        xslt_text = "type=\"text/xsl\" href=\"#{@xslt}\""
+                                        xslt = Instruction.new("xml-stylesheet", xslt_text)
+                                        root = doc.root
+                                        root.previous_sibling = xslt
+                                end
+                        else
+                                docroot = doc.elements['ServerMonitor']
+                        end
+                        docroot = add_report_xml(docroot,servers)
+                        out.puts doc.to_s(0)
+                        ensure
+                                out.close
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000017.html b/doc/classes/ServerMonitor/Monitor.src/M000017.html new file mode 100644 index 0000000..9902b66 --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000017.html @@ -0,0 +1,21 @@ + + + + + + load_config (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 222
+                def load_config(filename)
+                        config = read_config(filename)
+                        @servers = config[:servers]
+                        @email_addr = config[:email][:addr]
+                        @email_sender = config[:email][:sender]
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000018.html b/doc/classes/ServerMonitor/Monitor.src/M000018.html new file mode 100644 index 0000000..b496189 --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000018.html @@ -0,0 +1,21 @@ + + + + + + save_test_report (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 230
+                def save_test_report(filename,text)
+                        out = File.open(filename,"w")
+                        out.puts text
+                        ensure
+                                out.close
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Monitor.src/M000019.html b/doc/classes/ServerMonitor/Monitor.src/M000019.html new file mode 100644 index 0000000..c63899a --- /dev/null +++ b/doc/classes/ServerMonitor/Monitor.src/M000019.html @@ -0,0 +1,20 @@ + + + + + + email_report (ServerMonitor::Monitor) + + + + +
# File ServerMonitor.rb, line 238
+                def email_report(message="")
+                        @email_addr.each do |email|
+                                send_email(:to => email, :subject => ("Server Monitor Report" + message), :message => @report.text)
+                        end
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.html b/doc/classes/ServerMonitor/Server.html new file mode 100644 index 0000000..64a2c8a --- /dev/null +++ b/doc/classes/ServerMonitor/Server.html @@ -0,0 +1,299 @@ + + + + + + Class: ServerMonitor::Server + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassServerMonitor::Server
In: + + ServerMonitor.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ add_port   + add_url   + new   + ports_failed   + ports_passed   + success?   + urls_failed   + urls_passed   +
+
+ +
+ + + + +
+ + + + + +
+

Attributes

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
domain [RW] 
name [R] 
name [RW] 
ports [R] 
urls [R] 
+
+
+ + + + +
+

Public Class methods

+ +
+ + + + +
+
+
+ +

Public Instance methods

+ +
+ + + + +
+

+Add port to the server’s lists of ports to test. Provide an integer +or string +

+
+
+ +
+ + + + +
+

+Add url to the server’s lists of urls to test. Provide a string. +

+
+
+ +
+ + + + +
+

+Return an array of port objects that failed +

+
+
+ +
+ + + + +
+

+Return an array of port objects that passed +

+
+
+ +
+ + + + +
+

+Were all tests successful? +

+
+
+ +
+ + + + +
+

+Return an array of url objects that failed +

+
+
+ +
+ + + + +
+

+Return an array of url objects that passed +

+
+
+ + +
+ + +
+ + + + + + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000004.html b/doc/classes/ServerMonitor/Server.src/M000004.html new file mode 100644 index 0000000..3223217 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000004.html @@ -0,0 +1,34 @@ + + + + + + new (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 20
+                def initialize(opts={})
+                        options = {:ports => [], :name => "", :urls => [], :domain => ""}.merge(opts)
+                        if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) 
+                                raise "Invalid Server Options" 
+                        end unless opts == {} or opts.class != Hash
+
+                        @name = options[:name]
+                        @domain = options[:domain]
+                        urls = options[:urls]
+                        @urls = []
+                        urls.each do |url|
+                                add_url(url)
+                        end
+                        ports = options[:ports]
+                        @ports = []
+                        ports.each do |port|
+                                add_port(port)
+                        end
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000005.html b/doc/classes/ServerMonitor/Server.src/M000005.html new file mode 100644 index 0000000..2d8c7e8 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000005.html @@ -0,0 +1,19 @@ + + + + + + add_port (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 41
+                def add_port(port)
+                        p = Port.new(port,nil,nil)
+                        @ports << p
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000006.html b/doc/classes/ServerMonitor/Server.src/M000006.html new file mode 100644 index 0000000..0756dd4 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000006.html @@ -0,0 +1,19 @@ + + + + + + add_url (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 47
+                def add_url(url)
+                        u = Url.new(url,nil,nil)
+                        @urls << u
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000007.html b/doc/classes/ServerMonitor/Server.src/M000007.html new file mode 100644 index 0000000..392bd9f --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000007.html @@ -0,0 +1,18 @@ + + + + + + success? (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 53
+                def success?
+                        @ports.select{|p| not p.success}.size + @urls.select{|u| not u.success}.size < 1
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000008.html b/doc/classes/ServerMonitor/Server.src/M000008.html new file mode 100644 index 0000000..e6aca0b --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000008.html @@ -0,0 +1,18 @@ + + + + + + ports_passed (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 58
+                def ports_passed
+                        @ports.select{|p| p.success}
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000009.html b/doc/classes/ServerMonitor/Server.src/M000009.html new file mode 100644 index 0000000..68f33f0 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000009.html @@ -0,0 +1,18 @@ + + + + + + ports_failed (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 63
+                def ports_failed
+                        @ports.select{|p| not p.success}
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000010.html b/doc/classes/ServerMonitor/Server.src/M000010.html new file mode 100644 index 0000000..3533656 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000010.html @@ -0,0 +1,18 @@ + + + + + + urls_passed (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 68
+                def urls_passed
+                        @urls.select{|u| u.success}
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/Server.src/M000011.html b/doc/classes/ServerMonitor/Server.src/M000011.html new file mode 100644 index 0000000..344fae6 --- /dev/null +++ b/doc/classes/ServerMonitor/Server.src/M000011.html @@ -0,0 +1,18 @@ + + + + + + urls_failed (ServerMonitor::Server) + + + + +
# File ServerMonitor.rb, line 73
+                def urls_failed
+                        @urls.select{|u| not u.success}
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/TestSuite.html b/doc/classes/ServerMonitor/TestSuite.html new file mode 100644 index 0000000..c5ec8c7 --- /dev/null +++ b/doc/classes/ServerMonitor/TestSuite.html @@ -0,0 +1,182 @@ + + + + + + Class: ServerMonitor::TestSuite + + + + + + + + + + +
+ + + + + + + + + + + + + + +
ClassServerMonitor::TestSuite
In: + + ServerMonitor.rb + +
+
Parent: + Object +
+
+ + +
+ + + +
+ + + +
+ +
+

Methods

+ +
+ new   + test_port   + test_url   +
+
+ +
+ + + + +
+ + + + + +
+

Attributes

+ +
+ + + + + + +
log [R] 
+
+
+ + + + +
+

Public Class methods

+ +
+ + + + +
+
+
+ +

Public Instance methods

+ + + +
+ + + + +
+
+
+ + +
+ + +
+ + + + + + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/TestSuite.src/M000001.html b/doc/classes/ServerMonitor/TestSuite.src/M000001.html new file mode 100644 index 0000000..c9b96cf --- /dev/null +++ b/doc/classes/ServerMonitor/TestSuite.src/M000001.html @@ -0,0 +1,18 @@ + + + + + + new (ServerMonitor::TestSuite) + + + + +
# File ServerMonitor.rb, line 82
+                def initialize
+                        @log = ["Starting Test Suite at #{Time.now}\n================================="]
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/TestSuite.src/M000002.html b/doc/classes/ServerMonitor/TestSuite.src/M000002.html new file mode 100644 index 0000000..e63f07d --- /dev/null +++ b/doc/classes/ServerMonitor/TestSuite.src/M000002.html @@ -0,0 +1,24 @@ + + + + + + test_port (ServerMonitor::TestSuite) + + + + +
# File ServerMonitor.rb, line 86
+                def test_port(domain,port)
+                        msg = " * Testing #{domain}:#{port} => "
+                        now = Time.now
+                        result = get_port(domain,port)
+                        elapsed = Time.now - now
+                        msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s)  [#{Time.now}]"
+                        @log << msg
+                        [result,elapsed]
+                end
+ + \ No newline at end of file diff --git a/doc/classes/ServerMonitor/TestSuite.src/M000003.html b/doc/classes/ServerMonitor/TestSuite.src/M000003.html new file mode 100644 index 0000000..bf2c169 --- /dev/null +++ b/doc/classes/ServerMonitor/TestSuite.src/M000003.html @@ -0,0 +1,24 @@ + + + + + + test_url (ServerMonitor::TestSuite) + + + + +
# File ServerMonitor.rb, line 96
+                def test_url(url)
+                        msg =  " * Testing URL #{url} => "
+                        now = Time.now
+                        result = get_url(url)
+                        elapsed = Time.now - now
+                        msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s)  [#{Time.now}]"
+                        @log << msg
+                        [result,elapsed]
+                end
+ + \ No newline at end of file diff --git a/doc/created.rid b/doc/created.rid new file mode 100644 index 0000000..70b65b3 --- /dev/null +++ b/doc/created.rid @@ -0,0 +1 @@ +Mon Mar 19 12:54:39 CDT 2007 diff --git a/doc/files/ServerMonitor_rb.html b/doc/files/ServerMonitor_rb.html new file mode 100644 index 0000000..8892cb6 --- /dev/null +++ b/doc/files/ServerMonitor_rb.html @@ -0,0 +1,114 @@ + + + + + + File: ServerMonitor.rb + + + + + + + + + + +
+

ServerMonitor.rb

+ + + + + + + + + +
Path:ServerMonitor.rb +
Last Update:Mon Mar 19 12:50:59 CDT 2007
+
+ + +
+ + + +
+ + +
+

Required files

+ +
+ date   + net/http   + net/https   + net/smtp   + open-uri   + rexml/document   + fileutils   +
+
+ +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/doc/fr_class_index.html b/doc/fr_class_index.html new file mode 100644 index 0000000..f9e2479 --- /dev/null +++ b/doc/fr_class_index.html @@ -0,0 +1,30 @@ + + + + + + + + Classes + + + + + + + + \ No newline at end of file diff --git a/doc/fr_file_index.html b/doc/fr_file_index.html new file mode 100644 index 0000000..08fdec6 --- /dev/null +++ b/doc/fr_file_index.html @@ -0,0 +1,27 @@ + + + + + + + + Files + + + + + +
+

Files

+ +
+ + \ No newline at end of file diff --git a/doc/fr_method_index.html b/doc/fr_method_index.html new file mode 100644 index 0000000..e1d36fd --- /dev/null +++ b/doc/fr_method_index.html @@ -0,0 +1,45 @@ + + + + + + + + Methods + + + + + + + + \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..a04ef2c --- /dev/null +++ b/doc/index.html @@ -0,0 +1,24 @@ + + + + + + + RDoc Documentation + + + + + + + + + + + \ No newline at end of file diff --git a/doc/rdoc-style.css b/doc/rdoc-style.css new file mode 100644 index 0000000..44c7b3d --- /dev/null +++ b/doc/rdoc-style.css @@ -0,0 +1,208 @@ + +body { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 90%; + margin: 0; + margin-left: 40px; + padding: 0; + background: white; +} + +h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } +h1 { font-size: 150%; } +h2,h3,h4 { margin-top: 1em; } + +a { background: #eef; color: #039; text-decoration: none; } +a:hover { background: #039; color: #eef; } + +/* Override the base stylesheet's Anchor inside a table cell */ +td > a { + background: transparent; + color: #039; + text-decoration: none; +} + +/* and inside a section title */ +.section-title > a { + background: transparent; + color: #eee; + text-decoration: none; +} + +/* === Structural elements =================================== */ + +div#index { + margin: 0; + margin-left: -40px; + padding: 0; + font-size: 90%; +} + + +div#index a { + margin-left: 0.7em; +} + +div#index .section-bar { + margin-left: 0px; + padding-left: 0.7em; + background: #ccc; + font-size: small; +} + + +div#classHeader, div#fileHeader { + width: auto; + color: white; + padding: 0.5em 1.5em 0.5em 1.5em; + margin: 0; + margin-left: -40px; + border-bottom: 3px solid #006; +} + +div#classHeader a, div#fileHeader a { + background: inherit; + color: white; +} + +div#classHeader td, div#fileHeader td { + background: inherit; + color: white; +} + + +div#fileHeader { + background: #057; +} + +div#classHeader { + background: #048; +} + + +.class-name-in-header { + font-size: 180%; + font-weight: bold; +} + + +div#bodyContent { + padding: 0 1.5em 0 1.5em; +} + +div#description { + padding: 0.5em 1.5em; + background: #efefef; + border: 1px dotted #999; +} + +div#description h1,h2,h3,h4,h5,h6 { + color: #125;; + background: transparent; +} + +div#validator-badges { + text-align: center; +} +div#validator-badges img { border: 0; } + +div#copyright { + color: #333; + background: #efefef; + font: 0.75em sans-serif; + margin-top: 5em; + margin-bottom: 0; + padding: 0.5em 2em; +} + + +/* === Classes =================================== */ + +table.header-table { + color: white; + font-size: small; +} + +.type-note { + font-size: small; + color: #DEDEDE; +} + +.xxsection-bar { + background: #eee; + color: #333; + padding: 3px; +} + +.section-bar { + color: #333; + border-bottom: 1px solid #999; + margin-left: -20px; +} + + +.section-title { + background: #79a; + color: #eee; + padding: 3px; + margin-top: 2em; + margin-left: -30px; + border: 1px solid #999; +} + +.top-aligned-row { vertical-align: top } +.bottom-aligned-row { vertical-align: bottom } + +/* --- Context section classes ----------------------- */ + +.context-row { } +.context-item-name { font-family: monospace; font-weight: bold; color: black; } +.context-item-value { font-size: small; color: #448; } +.context-item-desc { color: #333; padding-left: 2em; } + +/* --- Method classes -------------------------- */ +.method-detail { + background: #efefef; + padding: 0; + margin-top: 0.5em; + margin-bottom: 1em; + border: 1px dotted #ccc; +} +.method-heading { + color: black; + background: #ccc; + border-bottom: 1px solid #666; + padding: 0.2em 0.5em 0 0.5em; +} +.method-signature { color: black; background: inherit; } +.method-name { font-weight: bold; } +.method-args { font-style: italic; } +.method-description { padding: 0 0.5em 0 0.5em; } + +/* --- Source code sections -------------------- */ + +a.source-toggle { font-size: 90%; } +div.method-source-code { + background: #262626; + color: #ffdead; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; +} + +div.method-source-code pre { color: #ffdead; overflow: hidden; } + +/* --- Ruby keyword styles --------------------- */ + +.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } \ No newline at end of file diff --git a/run_monitor.rb b/run_monitor.rb new file mode 100755 index 0000000..ae90a0f --- /dev/null +++ b/run_monitor.rb @@ -0,0 +1,113 @@ +#!/usr/bin/ruby + +require 'ServerMonitor' +require 'getoptlong' +require 'fileutils' +include ServerMonitor + + +class MonitorRunner + attr_reader :ready, :errors + + def initialize + @errors = nil + @ready = true + options = options_from_opts(get_opts) + return unless @ready + + @defaults = {:configfile => "monitor.xml", :log => "monitor-log.xml", :quiet => false, :xslt => nil } + @config = @defaults.merge(options) + @config[:configfile] = File.expand_path(@config[:configfile]) + @config[:log] = File.expand_path(@config[:log]) + @monitor = Monitor.new(@config[:configfile],@config[:log],@config[:xslt]) + #load_config(@config[:configfile]) + #rescue Exception + # @errors = "Invalid Options" + # @ready = false + end + + def run + puts "Running Monitor.\n" unless @quiet + @monitor.test_servers + @monitor.log_report(@config[:log]) + print_results unless @quiet + unless @monitor.success? + @monitor.email_report(": one or more servers failed!") + puts "Sending notification emails" unless @quiet + end + end + + def print_results + puts @monitor.report.text + puts "\n\n -- Results ---------------" + @monitor.servers.each do |server| + success = server.success? + puts "Server: #{server.name} => #{success ? 'PASSED!' : 'FAILED!'}" + unless success + ports_failed = server.ports_failed + urls_failed = server.urls_failed + puts " Ports Failed (#{ports_failed.size}): #{ports_failed.collect{|p| p.number}.join(', ')}" if ports_failed + puts " URL's Failed (#{urls_failed.size}): #{urls_failed.collect{|p| p.url}.join(', ')}" if urls_failed + end + end + end + + def help + @ready = false + puts "Usage: ServerMonitor.rb -c CONFIGFILE -l LOGFILE [--quiet]\n\n" + end + + private + + def get_opts + GetoptLong.new( + ['--config', '-c', GetoptLong::OPTIONAL_ARGUMENT], + ['--log', '-l', GetoptLong::OPTIONAL_ARGUMENT], + ['--quiet', '-q', GetoptLong::NO_ARGUMENT], + ['--help', '-h', GetoptLong::NO_ARGUMENT], + ['--xslt','-x',GetoptLong::OPTIONAL_ARGUMENT]) + end + + def options_from_opts(opts) + options = {} + opts.each do |opt,arg| + case opt + when '--config' + options[:configfile] = arg + when '-c' + options[:configfile] = arg + when '--log' + options[:log] = arg + when '-l' + options[:log] = arg + when '--quiet' + @quiet = true + when '-q' + @quiet = true + when '-h' + help + when '--help' + help + when '--xslt' + options[:xslt] = arg + when '-x' + options[:xslt] = arg + end + end + options + end + + def load_config(filename) + @monitor.load_config(filename) + end + +end + + + +m = MonitorRunner.new +if m.ready + m.run +else + puts m.errors if m.errors +end diff --git a/server_monitor.rb b/server_monitor.rb new file mode 100755 index 0000000..f6584eb --- /dev/null +++ b/server_monitor.rb @@ -0,0 +1,506 @@ +#!/usr/bin/ruby + +module ServerMonitor + + require 'date' + require 'net/http' + require 'net/https' + require 'net/smtp' + require 'open-uri' + require 'rexml/document' + require 'fileutils' + include REXML + + Port = Struct.new("Port",:number,:tested,:success, :time) + Url = Struct.new("Url",:url, :tested, :success, :time) + MonitorReport = Struct.new("MonitorReport", :xml, :text, :delimited) + + class Server + attr_reader :name, :ports, :urls + attr_accessor :domain, :name + + def initialize(opts={}) + options = {:ports => [], :name => "", :urls => [], :domain => ""}.merge(opts) + if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) + raise "Invalid Server Options" + end unless opts == {} or opts.class != Hash + + @name = options[:name] + @domain = options[:domain] + urls = options[:urls] + @urls = [] + urls.each do |url| + add_url(url) + end + ports = options[:ports] + @ports = [] + ports.each do |port| + add_port(port) + end + end + + def add_port(port) + p = Port.new(port,nil,nil) + @ports << p + end + + def add_url(url) + u = Url.new(url,nil,nil) + @urls << u + end + + def success? + @ports.select{|p| not p.success}.size + @urls.select{|u| not u.success}.size < 1 + end + + def ports_passed + @ports.select{|p| p.success} + end + + def ports_failed + @ports.select{|p| not p.success} + end + + def urls_passed + @urls.select{|u| u.success} + end + + def urls_failed + @urls.select{|u| not u.success} + end + end + + class TestSuite + + attr_reader :log + + def initialize + @log = ["Starting Test Suite at #{Time.now}\n================================="] + end + + def test_port(domain,port) + msg = " * Testing #{domain}:#{port} => " + now = Time.now + result = get_port(domain,port) + elapsed = Time.now - now + msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s) [#{Time.now}]" + @log << msg + [result,elapsed] + end + + def test_url(url) + msg = " * Testing URL #{url} => " + now = Time.now + result = get_url(url) + elapsed = Time.now - now + msg += "#{result ? 'OK' : 'Failed!'} (#{elapsed}s) [#{Time.now}]" + @log << msg + [result,elapsed] + end + + + private + + def get_url(uri_str,limit=10) + raise ArgumentError, 'HTTP redirect too deep' if limit == 0 + + https = true if uri_str.match(/https:/i) + uri = URI.parse(uri_str) + http = Net::HTTP.new(uri.host,uri.port) + req = Net::HTTP::Get.new(uri_str) + http.use_ssl = true if https + response = http.request(req) + + #response = Net::HTTP.get_response(URI.parse(uri_str)) + case response + when Net::HTTPSuccess then true + when Net::HTTPRedirection then get_url(response['location'], limit - 1) + else + response.error! + end + rescue + false + end + + def get_port(domain,port) + addr = TCPSocket.gethostbyname(domain)[0] + socket = TCPSocket.new(addr,port) ? true : false + rescue + false + end + + end + + class Monitor + + attr_reader :servers, :report + + # Specify config, log, and xsl filenames + def initialize(config_filename=nil,log_filename=nil,xslt=nil) + @servers = [] + @logfile = log_filename + @xslt = xslt + @report = MonitorReport.new + @success = nil + @email_addr = [] + load_config(config_filename) if config_filename + end + + # Did all of the servers pass? + def success? + @servers.select{|server| not server.success?}.size == 0 + end + + # Run server tests. + def test_servers + raise "Empty Server List" if @servers.empty? + ts = TestSuite.new + @servers.each do |server| + server.ports.each do |port| + port.tested = true + (port.success, port.time) = ts.test_port(server.domain,port.number) + end + server.urls.each do |url| + url.tested = true + (url.success, url.time) = ts.test_url(url.url) + end + end + @report.text = ts.log.join("\n") + @report.delimited = generate_delimited_report(@servers) + @report.xml = generate_xml_report(@servers) + end + + # Add server to server list (self.servers) + # Accepts hash as argument + # Server.new( { :name => String, + # :domain => String, + # :ports => Array of Strings, + # :urls => Array of Strings } ) + def add_server(options) + raise "Invalid Argument (must be a hash)" unless options.class == Hash + if (options[:ports].empty? or options[:name].empty? or options[:urls].empty? or options[:domain].empty?) + raise "Insufficient Server Options" + end + s = Server.new( :name => options[:name], + :domain => options[:domain], + :ports => options[:ports], + :urls => options[:urls]) + @servers << s + end + + # Creates or appends to existing XML test log + def log_report(filename) + log = File.exist?(filename) ? File.open(filename,"r").read : "" + out = File.open(filename,"w") + log = "" unless log.match(/xml version/i) + doc = Document.new log + if log == "" + doc << XMLDecl.new + docroot = doc.add_element('ServerMonitor') + if @xslt + xslt_text = "type=\"text/xsl\" href=\"#{@xslt}\"" + xslt = Instruction.new("xml-stylesheet", xslt_text) + root = doc.root + root.previous_sibling = xslt + end + else + docroot = doc.elements['ServerMonitor'] + end + docroot = add_report_xml(docroot,servers) + out.puts doc.to_s(0) + ensure + out.close + end + + # Load config from xml file. Provide filename as a String + def load_config(filename) + config = read_config(filename) + @servers = config[:servers] + @email_addr = config[:email][:addr] + @email_sender = config[:email][:sender] + end + + # Save report data text to file + def save_test_report(filename,text) + out = File.open(filename,"w") + out.puts text + ensure + out.close + end + + # Email a report to all of the specified email addresses with an optional custom message in the title + def email_report(message="") + @email_addr.each do |email| + send_email(:to => email, :subject => ("Server Monitor Report" + message), :message => @report.text) + end + end + + private + + # stub + def generate_delimited_report(servers) + # stub + @report.delimited = @report.text.dup + end + + # Generate XML report data from an array of server objects + def generate_xml_report(servers) + xmlstring = "" + doc = Document.new xmlstring + doc << XMLDecl.new + docroot = doc.add_element('ServerMonitor') + xslt_text = 'type="text/xsl" href="servermonitor.xsl"' + xslt = Instruction.new("xml-stylesheet", xslt_text) + root = doc.root + root.previous_sibling = xslt + docroot = add_report_xml(docroot,servers) + doc.to_s + end + + # Add a test run node to the provided REXML XML node. Also specify an array of servers + def add_report_xml(parent,servers) + test_run = parent.add_element('test_run') + tr_date = test_run.add_element('date') + tr_date.add_text Time.now.to_s + test_run = add_server_info_to_xml_node(test_run,servers) + servers.each do |server| + server_node = test_run.add_element('server') + name = server_node.add_element('name') + name.add_text(server.name) + domain = server_node.add_element('domain') + domain.add_text(server.domain) + result = server_node.add_element('result') + result.add_text(server.success? ? "PASSED" : "FAILED") + server.ports.each do |port| + port_node = server_node.add_element('port') + number = port_node.add_element('number') + number.add_text(port.number) + result = port_node.add_element('result') + result.add_text(port.success ? "PASSED" : "FAILED") + time = port_node.add_element('time') + time.add_text(port.time.to_s) + end + server.urls.each do |url| + url_node = server_node.add_element('url') + url_url = url_node.add_element('url') + url_url.add_text url.url + result = url_node.add_element('result') + result.add_text(url.success ? "PASSED" : "FAILED") + time = url_node.add_element('time') + time.add_text(url.time.to_s) + end + end + parent + end + + # Add a server config node to the provided REXML XML node. Also specify an array of servers + def add_server_info_to_xml_node(parent,servers) + config = parent.add_element('config') + servers.each do |server| + server_node = config.add_element('server') + name = server_node.add_element('name') + name.add_text(server.name) + domain = server_node.add_element('domain') + domain.add_text(server.domain) + server.ports.each do |port| + port_node = server_node.add_element('port') + port_node.add_text(port.number) + end + server.urls.each do |url| + url_node = server_node.add_element('url') + url_node.add_text(url.url) + end + end + parent + end + + + # Read config from file + def read_config(filename=nil) + raise "Specify xml formatted configuration filename as String" if filename.nil? + raise "File doesn't exist!" unless File.exist?(filename) + configfile = File.open(filename,"r") + config = Document.new(configfile) + servers = [] + + email_config = config.elements['//config/email'] + if email_config + sender = email_config.elements['sender'] + sender_addr = sender.text + if notify = email_config.elements['notify'] + email_addr = [] + notify.elements.each('addr') do |addr| + email_addr << addr.text + end + end + end + + config.elements.each('//config/server') do |server_node| + server = Server.new + server.name = server_node.elements['name'].text + server.domain = server_node.elements['domain'].text + server_node.elements.each('port') do |port_node| + server.add_port(port_node.text) + end + server_node.elements.each('url') do |url_node| + server.add_url(url_node.text) + end + servers << server + end + { + :servers => servers, + :email => {:addr => email_addr, :sender => sender_addr} + } + ensure + configfile.close + end + + # Send an email. + # Specify a hash { :from => "", + # :from_alias => "", + # :to => "", + # :to_alias => "", + # :subject => "", + # :message => ""} + def send_email(opts) + options = { :from => @email_sender, + :from_alias => "Server Monitor Notifier", + :to => "", + :to_alias => "", + :subject => "", + :message => ""}.merge(opts) + + raise "You forgot the recipient address" unless options[:to] + + msg = < +To: #{options[:to_alias]} <#{options[:to]}> +Subject: #{options[:subject]} + +#{options[:message]} +END_OF_MESSAGE + + Net::SMTP.start('localhost') do |smtp| + smtp.send_message options[:message], options[:from], options[:to] + end +end + true + #rescue + # false + end + +end + + + + +require 'getoptlong' +require 'fileutils' +include ServerMonitor + + +class MonitorRunner + attr_reader :ready, :errors + + def initialize + @errors = nil + @ready = true + options = options_from_opts(get_opts) + return unless @ready + + @defaults = {:configfile => "monitor.xml", :log => "monitor-log.xml", :quiet => false, :xslt => nil } + @config = @defaults.merge(options) + @config[:configfile] = File.expand_path(@config[:configfile]) + @config[:log] = File.expand_path(@config[:log]) + @monitor = Monitor.new(@config[:configfile],@config[:log],@config[:xslt]) + #load_config(@config[:configfile]) + #rescue Exception + # @errors = "Invalid Options" + # @ready = false + end + + def run + puts "Running Monitor.\n" unless @quiet + @monitor.test_servers + @monitor.log_report(@config[:log]) + print_results unless @quiet + unless @monitor.success? + @monitor.email_report(": one or more servers failed!") + puts "Sending notification emails" unless @quiet + end + end + + def print_results + puts @monitor.report.text + puts "\n\n -- Results ---------------" + @monitor.servers.each do |server| + success = server.success? + puts "Server: #{server.name} => #{success ? 'PASSED!' : 'FAILED!'}" + unless success + ports_failed = server.ports_failed + urls_failed = server.urls_failed + puts " Ports Failed (#{ports_failed.size}): #{ports_failed.collect{|p| p.number}.join(', ')}" if ports_failed + puts " URL's Failed (#{urls_failed.size}): #{urls_failed.collect{|p| p.url}.join(', ')}" if urls_failed + end + end + end + + def help + @ready = false + puts "Usage: ServerMonitor.rb -c CONFIGFILE -l LOGFILE [--quiet]\n\n" + end + + private + + def get_opts + GetoptLong.new( + ['--config', '-c', GetoptLong::OPTIONAL_ARGUMENT], + ['--log', '-l', GetoptLong::OPTIONAL_ARGUMENT], + ['--quiet', '-q', GetoptLong::NO_ARGUMENT], + ['--help', '-h', GetoptLong::NO_ARGUMENT], + ['--xslt','-x',GetoptLong::OPTIONAL_ARGUMENT]) + end + + def options_from_opts(opts) + options = {} + opts.each do |opt,arg| + case opt + when '--config' + options[:configfile] = arg + when '-c' + options[:configfile] = arg + when '--log' + options[:log] = arg + when '-l' + options[:log] = arg + when '--quiet' + @quiet = true + when '-q' + @quiet = true + when '-h' + help + when '--help' + help + when '--xslt' + options[:xslt] = arg + when '-x' + options[:xslt] = arg + end + end + options + end + + def load_config(filename) + @monitor.load_config(filename) + end + +end + + + +m = MonitorRunner.new +if m.ready + m.run +else + puts m.errors if m.errors +end