amphibian 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-06-26
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/amphibian.rb
7
+ lib/amphibian/balancerManagerDocument.rb
8
+ lib/amphibian/runner.rb
9
+ script/console
10
+ script/destroy
11
+ script/generate
12
+ test/test_amphibian.rb
13
+ test/test_helper.rb
data/PostInstall.txt ADDED
@@ -0,0 +1,6 @@
1
+
2
+ For more information on amphibian, see http://amphibian.rubyforge.org
3
+
4
+ woot!
5
+
6
+
data/README.rdoc ADDED
@@ -0,0 +1,46 @@
1
+ = amphibian
2
+
3
+ == DESCRIPTION:
4
+
5
+ Amphibian is a ruby library for accessing and interacting with an Apache mod_proxy_balancer via the web GUI created by the balancer_manager directive.
6
+
7
+ == FEATURES/PROBLEMS:
8
+
9
+ * TODO
10
+
11
+ == SYNOPSIS:
12
+
13
+ * TODO
14
+
15
+ == REQUIREMENTS:
16
+
17
+ * TODO
18
+
19
+ == INSTALL:
20
+
21
+ * gem install amphibian
22
+
23
+ == LICENSE:
24
+
25
+ (The MIT License)
26
+
27
+ Copyright (c) 2009 Nick Stielau
28
+
29
+ Permission is hereby granted, free of charge, to any person obtaining
30
+ a copy of this software and associated documentation files (the
31
+ 'Software'), to deal in the Software without restriction, including
32
+ without limitation the rights to use, copy, modify, merge, publish,
33
+ distribute, sublicense, and/or sell copies of the Software, and to
34
+ permit persons to whom the Software is furnished to do so, subject to
35
+ the following conditions:
36
+
37
+ The above copyright notice and this permission notice shall be
38
+ included in all copies or substantial portions of the Software.
39
+
40
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
41
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
42
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
43
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
44
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
45
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
46
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
2
+ %w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
3
+ require File.dirname(__FILE__) + '/lib/amphibian'
4
+
5
+ # Generate all the Rake tasks
6
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
7
+ $hoe = Hoe.new('amphibian', Amphibian::VERSION) do |p|
8
+ p.developer('Nick Stielau', 'nick.stielau@gmail.com')
9
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
+ p.post_install_message = 'PostInstall.txt'
11
+ p.rubyforge_name = p.name
12
+ # p.extra_deps = [
13
+ # ['activesupport','>= 2.0.2'],
14
+ # ]
15
+ p.extra_dev_deps = [
16
+ ['newgem', ">= #{::Newgem::VERSION}"]
17
+ ]
18
+
19
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
20
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
21
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
22
+ p.rsync_args = '-av --delete --ignore-errors'
23
+ end
24
+
25
+ desc "Upload current documentation to Rubyforge"
26
+ task :upload_docs => [:redocs] do
27
+ sh "scp -r doc/* nstielau@rubyforge.org:/var/www/gforge-projects/amphibian/doc/"
28
+ end
29
+
30
+ desc "Upload current documentation to Rubyforge"
31
+ task :upload_site do
32
+ #webgen && scp -r output/* nstielau@rubyforge.org:/var/www/gforge-projects/amphibian/
33
+ sh "scp -r webgen_site/* nstielau@rubyforge.org:/var/www/gforge-projects/amphibian/"
34
+ end
35
+
36
+ require 'newgem/tasks' # load /tasks/*.rake
37
+ Dir['tasks/**/*.rake'].each { |t| load t }
38
+
39
+ # TODO - want other tests/tasks run by default? Add them to the list
40
+ # task :default => [:spec, :features]
@@ -0,0 +1,115 @@
1
+ module Amphibian
2
+ class BalancerManager
3
+
4
+ def initialize(balancer_manager_url, dry_run = false)
5
+ @balancer_manager_url = balancer_manager_url
6
+ @dry_run = dry_run
7
+
8
+ if !balancer_manager_url.match("127.0.0.1") && !dry_run
9
+ @dry_run = true
10
+ log_error
11
+ log_error "Not running on localhost: Performing dry-run"
12
+ log_error
13
+ end
14
+ end
15
+
16
+ # Disables a host from the balancer.
17
+ def disable_host(host)
18
+ # TODO: Check Status
19
+ toggle_host(host, 'Disable')
20
+ end
21
+
22
+ # Enables a host in the balancer
23
+ def enable_host(host)
24
+ # TODO: Check Status
25
+ toggle_host(host, 'Enable')
26
+ end
27
+
28
+ # Returns an array of strings indicated the balancer members parsed out of the BalancerManager page.
29
+ def hosts
30
+ @hosts ||= (get_doc/'a').select{|a_tag| a_tag.inner_text =~ /^http:/}.map{|a_tag| a_tag.inner_text}
31
+ end
32
+
33
+ # TODO: Optionally force refresh
34
+ def enabled_hosts
35
+ hosts_array = []
36
+ hosts_with_status.select{|host,state| state == 'Ok'}.each{|host| hosts_array << host}
37
+ hosts_array
38
+ end
39
+
40
+ # TODO: Optionally force refresh
41
+ def disabled_hosts
42
+ hosts_array = []
43
+ hosts_with_status.select{|host,state| state != 'Ok'}.each{|host| hosts_array << host}
44
+ hosts_array
45
+ end
46
+
47
+ # Returns the name of the balancer on the BalancerManager page.
48
+ def balancer_name
49
+ @balancer_name ||= (get_doc/'a').select{|a_tag| a_tag.inner_text =~ /^balancer:/}.map{|a_tag| a_tag.inner_text}[0].sub('balancer://', '')
50
+ end
51
+
52
+ # Returns the url of the BalancerManager page.
53
+ def balancer_manager_url
54
+ @balancer_manager_url
55
+ end
56
+
57
+ # TODO: Optionally force refresh
58
+ def host_enabled?(host)
59
+ enabled_hosts.include?(host)
60
+ end
61
+
62
+ # TODO: Optionally force refresh
63
+ def host_disabled?(host)
64
+ !enabled_hosts.include?(host)
65
+ end
66
+
67
+ def dry_run?
68
+ @dry_run
69
+ end
70
+
71
+ def hosts_with_status
72
+ host_to_status = {}
73
+ (get_doc/'a').select{|a_tag| a_tag.inner_text =~ /^http:/}.each do |a_tag|
74
+ host_to_status[a_tag.inner_text] = (a_tag.parent.parent.children[6].inner_text.strip)
75
+ end
76
+ host_to_status
77
+ end
78
+
79
+ private
80
+
81
+ # Sets the state of the host to the specified state
82
+ def toggle_host(host, state)
83
+ log_error("#{state} is an invalid state") if state != "Enable" && state != "Disable"
84
+ run "curl -s -o /dev/null #{@balancer_manager_url}\?b=#{balancer_name}\\&w=#{host}\\&dw=#{state}"
85
+ # TODO: Check status
86
+ end
87
+
88
+ # runs a command if not in a dry_run? mode.
89
+ def run(cmd)
90
+ #puts "#{dry_run? ? 'Dry ' : ''}Running: #{cmd}"
91
+ if !dry_run?
92
+ `#{cmd}`
93
+ end
94
+ end
95
+
96
+ def log_error(error)
97
+ puts "ERROR: #{error}"
98
+ end
99
+
100
+ # Returns the Hpricot document of the BalancerManager page.
101
+ def get_doc
102
+ @doc ||= begin
103
+ Hpricot(open(@balancer_manager_url))
104
+ rescue Exception => e
105
+ if e =~ /403/
106
+ log_error "Balancer Manager is getting a 403: Forbidden response. Make sure it is accessable from this location."
107
+ else
108
+ log_error "Error opening the balancer manager: #{e}"
109
+ end
110
+ nil
111
+ end
112
+ end
113
+ end
114
+ end
115
+
@@ -0,0 +1,132 @@
1
+ require 'net/smtp'
2
+
3
+ module Amphibian
4
+ class Runner
5
+ # TODO: log everything, and set log level, so we don't have to print anything
6
+ # TODO: Email at the end
7
+
8
+ def initialize(balancer_manager_url, test_page='/', test_regex=nil, dry_run=false)
9
+ @balancer_manager_url = balancer_manager_url
10
+ @test_page = test_page
11
+ @test_regex = test_regex
12
+ @dry_run = dry_run
13
+ @errors = []
14
+
15
+ if !balancer_manager_url.match("127.0.0.1") && !dry_run
16
+ @dry_run = true
17
+ log_error "Not running on localhost: Performing dry-run"
18
+ end
19
+
20
+ @balancer_manager = BalancerManager.new(@balancer_manager_url, @dry_run)
21
+
22
+ @min_hosts = -1
23
+ end
24
+
25
+ def do_check
26
+ check
27
+ end
28
+
29
+ private
30
+
31
+ # Checks the balancer members, enabling/disabling each depending on the status.
32
+ def check
33
+ puts
34
+ puts "Load Apache Balancer-Manager at '#{@balancer_manager_url}'"
35
+ puts
36
+ puts "Loading Details"
37
+ puts
38
+ puts " Balancer: #{get_balancer_manager.balancer_name}"
39
+ puts
40
+ get_balancer_manager.hosts_with_status.each{|h,s| puts " #{h} => #{s}"}
41
+
42
+ @live_hosts = get_balancer_manager.enabled_hosts
43
+ puts
44
+ puts "Checking #{@live_hosts.size} live hosts"
45
+ puts
46
+
47
+ @live_hosts.each do |host, state|
48
+ status = check_host(host, @test_page)
49
+ puts " #{host} is #{status ? 'OK' : 'not responsive. Disabling via BalancerManager.'}"
50
+ disable_host(host) if !status
51
+ end
52
+ puts
53
+ puts "#{@live_hosts.size}/#{get_balancer_manager.hosts.size} hosts are enabled"
54
+ puts
55
+ end
56
+
57
+ def log_error(error)
58
+ @errors << error
59
+ puts "ERROR: #{error}"
60
+ end
61
+
62
+ # Returns the BalancerManager object.
63
+ def get_balancer_manager
64
+ @balancer_manager
65
+ end
66
+
67
+ # Checks a host with an optional path.
68
+ # Checks for a 200 response, and that the body of the response matches the test regex, if defined.
69
+ def check_host(host, path = '/', timeout = 5)
70
+ begin
71
+ status = Timeout::timeout(timeout) do
72
+ Net::HTTP.start(URI.parse(host).host) do |http|
73
+ response = http.get(path)
74
+
75
+ if not response.code.match(/200/)
76
+ log_error("Web Server down or not responding: #{response.code} #{response.message}")
77
+ return false;
78
+ end
79
+
80
+ if @test_regex && ! response.body.match(@test_regex)
81
+ log_error("The response did not contain the regex '#{@test_regex}'")
82
+ return false
83
+ end
84
+ end
85
+ end
86
+ rescue SocketError => socket_error
87
+ log_error("Error Connecting To Web: #{socket_error}")
88
+ return false;
89
+ rescue TimeoutError => timeout_error
90
+ log_error("Timeout occured checking #{host}#{path} after #{timeout} seconds.")
91
+ return false;
92
+ rescue Exception => e
93
+ log_error("An unknown error occured checking #{host}#{path}: #{e}")
94
+ return false;
95
+ end
96
+
97
+ return true
98
+ end
99
+
100
+ # Disables a host from the balancer.
101
+ def disable_host(host)
102
+ if @live_hosts.size <= @min_hosts
103
+ puts "Will not take #{host} down, alreay at lower limit #{@min_hosts}"
104
+ return
105
+ end
106
+
107
+ #puts "Disabling host '#{host}'"
108
+ get_balancer_manager.disable_host(host)
109
+ send_email("Disabled #{host} from the balancer #{get_balancer_manager.balancer_name} at #{get_balancer_manager.balancer_manager_url}")
110
+ end
111
+
112
+ def send_email(message)
113
+ begin
114
+ Net::SMTP.start('127.0.0.1', 25) do |smtp|
115
+ smtp.open_message_stream('dev@delvenetworks.com', ['nick@delvenetworks.com']) do |f|
116
+ f.puts 'From: dev@delvenetworks.com'
117
+ f.puts 'To: nick@delvenetworks.com'
118
+ f.puts 'Subject: Apache mod_balancer host disabled'
119
+ f.puts
120
+ f.puts message
121
+ f.puts
122
+ f.puts "Errors:"
123
+ f.puts @errors.join(",")
124
+ end
125
+ end
126
+ rescue Exception => e
127
+ log_error("Error sending email: #{e}")
128
+ end
129
+ end
130
+ end
131
+ end
132
+
data/lib/amphibian.rb ADDED
@@ -0,0 +1,20 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ # Core Libraries
5
+ require 'time'
6
+ require 'timeout'
7
+ require 'open-uri'
8
+
9
+ # Gems
10
+ require 'rubygems'
11
+ require 'hpricot'
12
+
13
+ # Files
14
+ require 'amphibian/runner'
15
+ require 'amphibian/balancerManagerDocument'
16
+
17
+
18
+ module Amphibian
19
+ VERSION = '0.0.1'
20
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/amphibian.rb'}"
9
+ puts "Loading amphibian gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestAmphibian < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/amphibian'
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amphibian
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Stielau
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: newgem
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.4.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ version:
35
+ description: Amphibian is a ruby library for accessing and interacting with an Apache mod_proxy_balancer via the web GUI created by the balancer_manager directive.
36
+ email:
37
+ - nick.stielau@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - PostInstall.txt
46
+ - README.rdoc
47
+ files:
48
+ - History.txt
49
+ - Manifest.txt
50
+ - PostInstall.txt
51
+ - README.rdoc
52
+ - Rakefile
53
+ - lib/amphibian.rb
54
+ - lib/amphibian/balancerManagerDocument.rb
55
+ - lib/amphibian/runner.rb
56
+ - script/console
57
+ - script/destroy
58
+ - script/generate
59
+ - test/test_amphibian.rb
60
+ - test/test_helper.rb
61
+ has_rdoc: true
62
+ homepage:
63
+ post_install_message: PostInstall.txt
64
+ rdoc_options:
65
+ - --main
66
+ - README.rdoc
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project: amphibian
84
+ rubygems_version: 1.3.1
85
+ signing_key:
86
+ specification_version: 2
87
+ summary: Amphibian is a ruby library for accessing and interacting with an Apache mod_proxy_balancer via the web GUI created by the balancer_manager directive.
88
+ test_files:
89
+ - test/test_amphibian.rb
90
+ - test/test_helper.rb