+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
+
+
+
+
+ |
+
+
+ Name |
+ Domain |
+ Port / URL |
+ Result |
+ Time |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Included Modules
+
+
+ REXML
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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)
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
\ 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