penctl-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ rdoc
2
+ pkg
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jannis Hermanns
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,197 @@
1
+ == Description
2
+
3
+ penctl-ruby is a ruby implementation of penctl, a tool to control pen load
4
+ balancers (a load balancer for "simple" tcp based protocols such as http or smtp): http://siag.nu/pen/
5
+
6
+ With penctl-ruby you can add and remove servers to a pen server list and to change settings
7
+ without the need to restart the pen balancer. Without the need for a penctl binary.
8
+
9
+ While pen's regular balancing works fine, issuing commands through the control port
10
+ can be a bit flakey at times. That's why penctl-ruby has retries built in - it will
11
+ try to issue your command up to five times. Important commands like add_server() and
12
+ remove_server() make very sure pen has received them correctly and return true or false
13
+ accordingly.
14
+
15
+ keywords: balancer, pen, penctl, rails, gem
16
+
17
+ == Documentation
18
+
19
+ RDocs at http://rdoc.info/projects/jayniz/penctl-ruby
20
+
21
+ == Installation
22
+
23
+ Installing penctl-ruby is quite simple:
24
+
25
+ sudo gem install penctl-ruby -s http://gemcutter.org
26
+
27
+ If you want it as plugin, just do:
28
+
29
+ script/plugin install git://github.com/jayniz/penctl-ruby.git
30
+
31
+ == Basic Usage
32
+
33
+ Let's say you started a pen balancer on some machine with some command
34
+ like `pen -C 192.168.0.1:12000 192.168.0.1:12001`. You can then create a
35
+ PenBalancer object to talk to a pen balancer running on a remote machine:
36
+
37
+ >> pen = PenBalancer.new '192.168.0.1:12000'
38
+ => #<PenBalancer:0x11eb74c @pen="192.168.0.1:12000">
39
+
40
+ If you want to know which servers this pen is balancing requests to, you
41
+ can issue the servers command:
42
+
43
+ >> pen.servers
44
+ => [{:conn=>0, :port=>12501, :hard=>0, :addr=>"127.0.0.1", :sx=>89116821, :slot=>0, :rx=>2891017284, :max=>0},
45
+ {:conn=>0, :port=>12101, :hard=>0, :addr=>"127.0.0.1", :sx=>3178050, :slot=>1, :rx=>46267355, :max=>0},
46
+ {:conn=>0, :port=>0, :hard=>0, :addr=>"0.0.0.0", :sx=>0, :slot=>2, :rx=>0, :max=>0},
47
+ {:conn=>0, :port=>0, :hard=>0, :addr=>"0.0.0.0", :sx=>0, :slot=>3, :rx=>0, :max=>0}]
48
+
49
+ As you can see, there are still two unused slots left. Let's make use of them!
50
+
51
+ == Adding servers to the pool (or removing them)
52
+
53
+ Let's add another server to the pool and check if it's there:
54
+
55
+ >> pen.add_server '127.0.0.1', 12301
56
+ => true
57
+ >> pen.servers
58
+ => [{:conn=>0, :port=>12501, :hard=>0, :addr=>"127.0.0.1", :sx=>89116821, :slot=>0, :rx=>2891017284, :max=>0},
59
+ {:conn=>0, :port=>12101, :hard=>0, :addr=>"127.0.0.1", :sx=>3178050, :slot=>1, :rx=>46267355, :max=>0},
60
+ {:conn=>0, :port=>12301, :hard=>0, :addr=>"127.0.0.1", :sx=>0, :slot=>2, :rx=>0, :max=>0},
61
+ {:conn=>0, :port=>0, :hard=>0, :addr=>"0.0.0.0", :sx=>0, :slot=>3, :rx=>0, :max=>0}]
62
+
63
+ Actually you don't have to check if the new server is really there. After attempting to add the server
64
+ to the pool, it retrieves the server list and makes sure the new server is really there and returns true.
65
+ If you try to add a server to the pool that exists already, penctl-ruby will complain. Let's try
66
+ to add the same server again
67
+
68
+ >> p.add_server '127.0.0.1', 12301
69
+ ArgumentError: Server is in the pool already
70
+ from ./lib/pen_balancer.rb:41:in `add_server'
71
+ from (irb):15
72
+
73
+ To remove a server from the pool, you just call:
74
+
75
+ >> pen.remove_server '127.0.0.1', 12301
76
+ => true
77
+
78
+ == Changing and reading runtime configuration
79
+
80
+ There are a couple of readonly and also writeable runtime variables to pen. Most of them are also documented
81
+ in `man penctl` (or online at http://linux.die.net/man/1/penctl). You can get/set numerical configuration
82
+ parameters of a remote pen balancer quite easily.
83
+
84
+ >> pen.client_acl=3
85
+ => 3
86
+
87
+ Fetching it goes like this:
88
+
89
+ >> pen.client_acl
90
+ => 3
91
+
92
+ Accordingly, you can set booleans (please note that you cannot get the current value):
93
+ >> pen.stubborn=false
94
+ => false
95
+
96
+ There are also values you can only read, but not write:
97
+
98
+ >> pen.conn_max
99
+ => 256
100
+
101
+ Last but not least, you can issue some commands (telling the pen balancer to exit, if you started it
102
+ with the -X flag, for example):
103
+
104
+ >> pen_without_x_flag.exit!
105
+ => ["Exit is not enabled; restart with -X flag"]
106
+ >> pen_with_x_flag.exit!
107
+ => []
108
+
109
+ == Managing access control lists (ACLs)
110
+
111
+ You can define 10 policies to be used for access control. They are organized in slots from
112
+ 0 to 9. To set the ACL in slot 2, you could issue:
113
+
114
+ >> pen.set_acl_entry(2, :policy => 'permit', :source_ip => '192.168.0.1', :netmask => '255.255.255.0')
115
+
116
+ Now we could configure pen to use this policy for client connections:
117
+
118
+ >> p.client_acl=2
119
+ => 2
120
+
121
+ And to configure pen to use this policy for control port connections (don't lock yourself out :):
122
+
123
+ >> p.control_acl=2
124
+ => 2
125
+
126
+ To remove a policy from the ACL, you would do (effectively allowing all connections):
127
+
128
+ >> p.remove_acl_entry 0
129
+ => []
130
+
131
+
132
+ == API
133
+
134
+ The most important functions are adding/removing and blacklisting/whitelisting servers along with
135
+ ACL management (see above). The rest of the API is inspired by penctl's manpage:
136
+
137
+ === Boolean (you can only set, but not get, these)
138
+
139
+ pen.ascii=true # Communication dumps in ascii format (cf option -a).
140
+ pen.ascii=false # Communication dumps in hex format.
141
+ pen.block=true # Do not make sockets nonblocking.
142
+ pen.block=false # Make sockets nonblocking.
143
+ pen.delayed_forward=true # Always wait for the next round of the main loop before
144
+ # forwarding data. Normally pen tries to do that immediately.
145
+ pen.delayed_forward=false # Try to forward data immediately, to avoid the overhead of
146
+ # copying it to a temporary buffer and waiting for the next
147
+ # main loop round.
148
+ pen.hash=true # Use a hash on the client IP address for initial server selection.
149
+ pen.hash=false # Do not use a hash.
150
+ pen.http=true # Add X-Forwarded-For headers to http requests.
151
+ pen.http=false # Do not add X-Forwarded-For headers.
152
+ pen.roundrobin=true # Use round-robin server selection without client tracking
153
+ pen.roundrobin=false # Try to route the same client to the same server over time
154
+ pen.stubborn=true # If the initial server selection is unavailable, close the
155
+ # client connection without trying another
156
+ pen.stubborn=false # If one server does not work, use another
157
+ pen.weight=true # Use weight for server selection.
158
+ pen.weight=false # Do not use weight for server selection.
159
+
160
+ === Numeric (you can get and set these)
161
+
162
+ pen.blacklist=60 # Set blacklsit time to 60 seconds
163
+ pen.client_acl=5 # Check connecting clients against access list N (default 0).
164
+ pen.control_acl=3 # Check accesses to the control port against access list N (default 0).
165
+ pen.debug=3 # Set debug level to 3
166
+ pen.timeout=50 # Return current connect timeout in seconds.
167
+ pen.tracking=5 # Set tracking time, i.e. how long clients will be remembered. The
168
+ # default 0 will never expire clients based on time
169
+
170
+ === Readonly attributes
171
+ pen.clients_max # Return max number of clients
172
+ pen.conn_max # Return max number of simultaneous connections
173
+ pen.control # Return (address and) port where pen listens for control connections
174
+ pen.listen # Return (address and) port pen listens to for incoming client connections
175
+ pen.status # Print status information in html format
176
+ pen.recent(5) # Shows which clients have connected in the last N seconds (default 300)
177
+
178
+ === Commands
179
+ pen.exit! # Exit. Only available if pen was started with the -X option
180
+ pen.include!(filename) # Read commands from filename
181
+ pen.write!(filename) # Write the current configuration into a file which can be used to
182
+ # start pen. If FILE is omitted, the configuration is written into
183
+ # pen's original configuration file
184
+
185
+ == TODO
186
+
187
+ The :log and :web_stat methods are not yet implemented
188
+
189
+ == Bugs and Feedback
190
+
191
+ If you discover any bugs or want to drop a line, feel free to email me: On gmail.com
192
+ my name is jannis.
193
+
194
+ penctl-ruby
195
+ License: MIT
196
+ Version: 0.1
197
+
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = "penctl-ruby"
10
+ gemspec.summary = "Ruby implementation of penctl"
11
+ gemspec.description = "With penctl-ruby you can add and remove servers to a pen server list and to change settings without the need to restart the pen balancer."
12
+ gemspec.email = "jannis@gmail.com"
13
+ gemspec.homepage = "http://github.com/jayniz/penctl-ruby"
14
+ gemspec.authors = ["Jannis Hermanns"]
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ desc 'Generate documentation for InheritedResources.'
21
+ Rake::RDocTask.new(:rdoc) do |rdoc|
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.title = 'InheritedResources'
24
+ rdoc.options << '--line-numbers' << '--inline-source'
25
+ rdoc.rdoc_files.include('README.rdoc')
26
+ rdoc.rdoc_files.include('MIT-LICENSE')
27
+ rdoc.rdoc_files.include('lib/**/*.rb')
28
+ end
29
+
30
+ desc "Run all specs for penctl-ruby"
31
+ Spec::Rake::SpecTask.new do |t|
32
+ t.spec_files = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ desc "Default runs specs"
36
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,130 @@
1
+ class PenBalancer
2
+
3
+ # TODO: :log has log, log=false and log=file
4
+ # :web_stats
5
+
6
+ BOOLEAN_ATTRIBS = [:ascii=, :block=, :control=, :delayed_forward=, :hash=, :http=, :roundrobin=, :stubborn=, :weight=]
7
+ GETTERS_SETTERS = [:blacklist, :client_acl, :control_acl, :debug, :tracking, :timeout]
8
+ GETTERS = [:clients_max, :conn_max, :control, :listen, :status, :recent]
9
+ COMMANDS = [:exit!, :include!, :write!]
10
+
11
+ #
12
+ # Creates a new PenBalancer instance: If you have launched a local pen
13
+ # instance with 8080 as the control port, you should use PenBalancer
14
+ # like this: PenBalancer.new 'localhost:8080'.
15
+ #
16
+ # Throws an exception when pen can't be reached.
17
+ #
18
+ def initialize( address )
19
+ @pen = address
20
+ raise "Can't find pen balancer at #{address}" unless Penctl.execute( @pen, "control")[0].match /#{address.split(':')[1]}/
21
+ end
22
+
23
+ #
24
+ # Returns an array of servers pen currently knows
25
+ #
26
+ def servers
27
+ list = []
28
+ Penctl.execute(@pen, "servers", 5).each do |l|
29
+ server = Penctl.parse_server_line(l)
30
+ list[server[:slot]] = server
31
+ end
32
+ list.compact
33
+ end
34
+
35
+ #
36
+ # Adds a server to the pool or throws an exception when there is no free
37
+ # slot left
38
+ #
39
+ def add_server( host, port )
40
+ raise ArgumentError.new("Server is in the pool already") if server_in_pool?( host, port)
41
+ free_slot = get_server( '0.0.0.0', 0 )
42
+ Penctl.update_server( @pen, free_slot[:slot], :address => host, :port => port)
43
+ server_in_pool? host, port
44
+ end
45
+
46
+ #
47
+ # Removes a server from the pool or throws an exception when the server
48
+ # cannot be found in the pool
49
+ #
50
+ def remove_server( host, port )
51
+ server = get_server( host, port )
52
+ Penctl.update_server( @pen, server[:slot], :address => '0.0.0.0', :port => 0 )
53
+ !server_in_pool? host, port
54
+ end
55
+
56
+ #
57
+ # Blacklists a server for the given amount of seconds or throws an
58
+ # exception when the server cannot be found in the pool
59
+ #
60
+ def blacklist_server( host, port, blacklist_seconds = 3600)
61
+ server = get_server( host, port )
62
+ Penctl.update_server( @pen, server[:slot], :blacklist => blacklist_seconds)
63
+ end
64
+
65
+
66
+ #
67
+ # A shortcut for blacklisting a server for 0 seconds.
68
+ #
69
+ def whitelist_server( host, port)
70
+ server = get_server( host, port )
71
+ Penctl.update_server( @pen, server[:slot], :blacklist => 0)
72
+ end
73
+
74
+ #
75
+ # Updates an entry of the access control list. Params is a hash with
76
+ # mandatory :policy => permit/deny and :source_ip. You can optionally
77
+ # pass a :netmask.
78
+ #
79
+ def set_acl_entry( slot, params )
80
+ raise RangeError.new("slot #{slot} outside range 0-9") unless (0..9).include?(slot)
81
+ raise ArgumentError.new("policy must be either permit or deny") unless ['permit', 'deny'].include? params[:policy]
82
+ Penctl.execute(@pen, "acl #{slot} #{params[:policy]} #{params[:source_ip]} #{params[:netmask]}".strip)
83
+ end
84
+
85
+ #
86
+ # Flushes the rules of an acl entry (==permit from all)
87
+ #
88
+ def remove_acl_entry( slot )
89
+ raise RangeError.new("slot #{slot} outside range 0-9") unless (0..9).include?(slot)
90
+ Penctl.execute(@pen, "no acl #{slot}")
91
+ end
92
+
93
+ #
94
+ # penctl has the following three patterns:
95
+ # 1) booleans (no getters): penctl localhost:8080 no ascii
96
+ # penctl localhost:8080 ascii
97
+ # 2) getters/setters: penctl localhost:8080 debug 3
98
+ # penctl localhost:8080 debug
99
+ # 3) commands: penctl localhost:8080 exit
100
+ # The first two are turned into regular getters and setters and
101
+ # the last into methods.
102
+ #
103
+ def method_missing(method, *args)
104
+ return Penctl.set_boolean_attribute(@pen, method, args[0]) if BOOLEAN_ATTRIBS.include? method
105
+ return Penctl.get_set_attribute(@pen, method, args[0]) if GETTERS_SETTERS.include? method.to_s.chomp('=').to_sym
106
+ return Penctl.get_set_attribute(@pen, method, args[0]) if GETTERS.include? method
107
+ return Penctl.execute(@pen, "#{method.to_s.chomp('!')} #{args[0]}".strip) if COMMANDS.include? method
108
+ raise "Missing method #{method}"
109
+ end
110
+
111
+ protected
112
+
113
+ #
114
+ # Fetches the list of servers currently known to pen and returns a hash with
115
+ # :slot, :addr, :port, :conn, :max, :hard, :sx, :rx
116
+ # Raises an exception when no server with the given address and port is in that list.
117
+ #
118
+ def get_server( host, port )
119
+ server_list = servers
120
+ server_list.select{ |s| s[:addr] == host and s[:port] == port.to_i}[0] or raise ArgumentError.new("Could not find #{host}:#{port} in #{server_list.inspect}")
121
+ end
122
+
123
+ #
124
+ # Checks if a given server is already in the pool
125
+ #
126
+ def server_in_pool?( host, port )
127
+ server_list = servers
128
+ !server_list.select{ |s| s[:addr] == host and s[:port] == port.to_i}.empty?
129
+ end
130
+ end
data/lib/penctl.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'socket'
2
+
3
+ class Penctl
4
+
5
+ #
6
+ # Connects to pen's control port and issues a command. It will wait a moment and
7
+ # try to contact the pen server again, when it could not be reached (raising an
8
+ # exception when it gives up).
9
+ #
10
+ def self.execute( server, cmd, tries_left = 5 )
11
+ raise StandardError.new("Error talking to pen, giving up.") unless tries_left > 0
12
+ shell_cmd = "penctl #{server} #{cmd}"
13
+ host, port = server.split ':'
14
+ begin
15
+ send_command( host, port, cmd )
16
+ rescue Errno::ECONNRESET, Errno::EPIPE
17
+ sleep 0.5
18
+ return Penctl.execute( server, cmd, tries_left.pred)
19
+ end
20
+ end
21
+
22
+ #
23
+ # Opens a socket, sends a command to pen and returns the result
24
+ #
25
+ def self.send_command( host, port, cmd )
26
+ socket = TCPSocket.open( host, port.to_i )
27
+ socket.puts cmd
28
+ result = []
29
+ while line = socket.gets
30
+ result << line.chomp
31
+ end
32
+ socket.close
33
+ result
34
+ end
35
+
36
+ #
37
+ # Sends a penctl server command to update a server's settings.
38
+ #
39
+ def self.update_server( server, slot, settings )
40
+ cmd = settings.to_a.sort_by{ |k,v| k.to_s }.flatten.join(' ')
41
+ Penctl.execute( server, "server #{slot} #{cmd}" )
42
+ end
43
+
44
+
45
+ #
46
+ # Takes a line of the output of penctl's 'servers' command and turns it into
47
+ # a hash with the keys :slot, :addr:, :port, :conn, :max, :hard, :sx, :rx
48
+ #
49
+ def self.parse_server_line( line )
50
+ keys = %w{ slot addr port conn max hard sx rx}
51
+ hash = Hash[*("slot #{line}".split)]
52
+ server = {}
53
+ keys.each { |k| server[k.to_sym] = (k == "addr") ? hash[k] : hash[k].to_i }
54
+ return server
55
+ end
56
+
57
+ #
58
+ # Takes an attribute name along with a boolean value and turns it into
59
+ # a penctl command. Returns true on success, false on failure.
60
+ #
61
+ def self.set_boolean_attribute(pen, attribute, value)
62
+ cmd = attribute.to_s.chomp '='
63
+ cmd = value ? cmd : "no " + cmd
64
+ Penctl.execute(pen, cmd) == ["0"]
65
+ end
66
+
67
+ #
68
+ # Takes an attribute name along with a value and returns the value.
69
+ #
70
+ def self.get_set_attribute(pen, attribute, value = nil)
71
+ cmd = attribute.to_s.chomp '='
72
+ value = value.to_s.empty? ? '' : " #{value}"
73
+ tidy_output(Penctl.execute(pen, "#{cmd}#{value}".chomp))
74
+ end
75
+
76
+ protected
77
+
78
+ def self.tidy_output( values )
79
+ if values.size == 1
80
+ values[0].match(/^[\d]+$/) ? values[0].to_i : values[0]
81
+ else
82
+ values
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,52 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{penctl-ruby}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jannis Hermanns"]
12
+ s.date = %q{2009-09-28}
13
+ s.description = %q{With penctl-ruby you can add and remove servers to a pen server list and to change settings without the need to restart the pen balancer.}
14
+ s.email = %q{jannis@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "lib/pen_balancer.rb",
25
+ "lib/penctl.rb",
26
+ "penctl-ruby.gemspec",
27
+ "rails/init.rb",
28
+ "spec/lib/pen_balancer_spec.rb",
29
+ "spec/lib/penctl_spec.rb",
30
+ "spec/spec_helper.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/jayniz/penctl-ruby}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.4}
36
+ s.summary = %q{Ruby implementation of penctl}
37
+ s.test_files = [
38
+ "spec/lib/pen_balancer_spec.rb",
39
+ "spec/lib/penctl_spec.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ else
49
+ end
50
+ else
51
+ end
52
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'lib/pen_balancer'
2
+ require 'lib/penctl'
@@ -0,0 +1,152 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'lib/pen_balancer'
3
+ require 'lib/penctl'
4
+
5
+ describe PenBalancer do
6
+
7
+ describe "methods implementing the commands from `man penctl` (except for acl and server)" do
8
+
9
+ before(:each) do
10
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "control").and_return ["127.0.0.1:12000"]
11
+ @pen = PenBalancer.new '127.0.0.1:12000'
12
+ end
13
+
14
+ it ":servers should return an array of hashes with the servers pen currently knows" do
15
+ servers_reply = ["0 addr 127.0.0.1 port 12101 conn 0 max 0 hard 0 sx 1054463671 rx 2586728338", "1 addr 127.0.0.1 port 12501 conn 1 max 0 hard 0 sx 1103014051 rx 2688785671"]
16
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "servers", 5).and_return servers_reply
17
+
18
+ result = @pen.servers
19
+ result.should have(2).items
20
+ result[0].should be_a(Hash)
21
+ result[1].should be_a(Hash)
22
+ end
23
+
24
+ it "should set boolean variables (e.g. pen.http=true)" do
25
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'http')
26
+ @pen.http = true
27
+ end
28
+
29
+ it "should set boolean variables (e.g. pen.http=false)" do
30
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'no http')
31
+ @pen.http = false
32
+ end
33
+
34
+ it "should set other variables (e.g. pen.debug=5)" do
35
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'debug 5').and_return ["5"]
36
+ @pen.debug = 5
37
+ end
38
+
39
+ it "should get other variables (e.g. pen.debug)" do
40
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'debug').and_return ["5"]
41
+ @pen.debug.should == 5
42
+ end
43
+
44
+ it "should get readonly variables (e.g. pen.status)" do
45
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'status').and_return ["lots", "of", "lines"]
46
+ @pen.status.should == ["lots", "of", "lines"]
47
+ end
48
+
49
+ it "should get readonly variables with a parameter (e.g. pen.recent(5))" do
50
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'recent 5').and_return ["lots", "of", "lines"]
51
+ @pen.recent(5).should == ["lots", "of", "lines"]
52
+ end
53
+
54
+ it "should issue commands (e.g. pen.exit!)" do
55
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'exit').and_return []
56
+ @pen.exit!
57
+ end
58
+
59
+ it "should issue commands with parameters (e.g. pen.write! /etc/pen.conf)" do
60
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'write /etc/pen.conf').and_return []
61
+ @pen.write!('/etc/pen.conf').should be_true
62
+ end
63
+
64
+ it "should return false when a command could not be issued" do
65
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", 'exit').and_return ["Exit is not enabled; restart with -X flag"]
66
+ @pen.exit!
67
+ end
68
+
69
+ end
70
+
71
+ describe "methods making the penctl commands [no] acl and server more convenient" do
72
+
73
+ describe "adding or removing servers from the pool" do
74
+
75
+ before(:each) do
76
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "control").and_return ["127.0.0.1:12000"]
77
+ @pen = PenBalancer.new '127.0.0.1:12000'
78
+ servers_reply = ["0 addr 127.0.0.1 port 100 conn 0 max 0 hard 0 sx 1054463671 rx 2586728338",
79
+ "1 addr 127.0.0.1 port 101 conn 1 max 0 hard 0 sx 1103014051 rx 2688785671",
80
+ "2 addr 0.0.0.0 port 0 conn 0 max 0 hard 0 sx 0 rx 0",
81
+ "3 addr 0.0.0.0 port 0 conn 0 max 0 hard 0 sx 0 rx 0"]
82
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "servers", 5).at_least(1).and_return servers_reply
83
+ end
84
+
85
+ it "should add a server into an empty slot" do
86
+ Penctl.should_receive(:update_server).with('127.0.0.1:12000', 2, :address => '127.0.0.1', :port => 102)
87
+ @pen.should_receive(:server_in_pool?).with('127.0.0.1', 102).and_return false
88
+ @pen.should_receive(:server_in_pool?).with('127.0.0.1', 102).and_return true
89
+ @pen.add_server('127.0.0.1', 102).should be_true
90
+ end
91
+
92
+ it "should remove a server freeing a slot" do
93
+ Penctl.should_receive(:update_server).with('127.0.0.1:12000', 1, :address => '0.0.0.0', :port => 0)
94
+ @pen.should_receive(:server_in_pool?).with('127.0.0.1', 101).and_return false
95
+ @pen.remove_server('127.0.0.1', 101).should be_true
96
+ end
97
+
98
+ it "should raise an exception when given server could not be found in the list" do
99
+ lambda {
100
+ @pen.remove_server('127.0.0.2', 100)
101
+ }.should raise_error(ArgumentError)
102
+ end
103
+
104
+ it "should raise an exception when adding a server that is already in the list" do
105
+ lambda {
106
+ @pen.add_server('127.0.0.1', 100)
107
+ }.should raise_error(ArgumentError)
108
+ end
109
+
110
+ it "should blacklist a server" do
111
+ Penctl.should_receive(:update_server).with('127.0.0.1:12000', 0, :blacklist => 999)
112
+ @pen.blacklist_server('127.0.0.1', 100, 999)
113
+ end
114
+
115
+ it "should whitelist a server" do
116
+ Penctl.should_receive(:update_server).with('127.0.0.1:12000', 0, :blacklist => 0)
117
+ @pen.whitelist_server('127.0.0.1', 100)
118
+ end
119
+
120
+ end
121
+
122
+
123
+ describe "managing access control lists" do
124
+
125
+ before(:each) do
126
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "control").and_return ["127.0.0.1:12000"]
127
+ @pen = PenBalancer.new '127.0.0.1:12000'
128
+ end
129
+
130
+ it ":set_acl_entry should set access control list with netmask" do
131
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "acl 2 permit 192.168.0.1 255.255.255.0")
132
+ @pen.set_acl_entry(2, :policy => 'permit', :source_ip => '192.168.0.1', :netmask => '255.255.255.0')
133
+ end
134
+
135
+ it ":set_acl_entry should set access control list without netmask" do
136
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "acl 2 permit 192.168.0.1")
137
+ @pen.set_acl_entry(2, :policy => 'permit', :source_ip => '192.168.0.1')
138
+ end
139
+
140
+ it ":set_acl_entry should remove access control (allow from all)" do
141
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "no acl 2")
142
+ @pen.remove_acl_entry(2)
143
+ end
144
+
145
+ it ":set_acl_entry should raise an exception when given ACL list is out of range [0..9]" do
146
+ lambda {
147
+ @pen.set_acl_entry(10, :policy => 'deny', :source_ip => '127.0.0.1')
148
+ }.should raise_error(RangeError)
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'lib/penctl'
3
+
4
+ describe Penctl do
5
+
6
+ it ":execute should call the penctl binary and contact the right pen" do
7
+ socket = mock("socket")
8
+ socket.should_receive(:puts).with "foo"
9
+ socket.should_receive(:gets).and_return "something"
10
+ socket.should_receive(:gets).and_return false
11
+ socket.should_receive(:close)
12
+ TCPSocket.should_receive(:open).with("127.0.0.1", 12000).and_return socket
13
+ Penctl.execute( "127.0.0.1:12000", "foo").should == ["something"]
14
+ end
15
+
16
+ it ":parse_server_line should turn penctl servers output into a hash" do
17
+ line = "1 addr 127.0.0.1 port 12501 conn 2709 max 2212 hard 2 sx 1092895943 rx 2664422154"
18
+ expected = { :slot => 1,
19
+ :addr => '127.0.0.1',
20
+ :port => 12501,
21
+ :conn => 2709,
22
+ :max => 2212,
23
+ :hard => 2,
24
+ :sx => 1092895943,
25
+ :rx => 2664422154 }
26
+ Penctl.parse_server_line(line).should == expected
27
+ end
28
+
29
+ it ":update_server should change server settings" do
30
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "server 1 address 127.0.0.1 port 88")
31
+ Penctl.update_server '127.0.0.1:12000', 1, :address => '127.0.0.1', :port => 88
32
+ end
33
+
34
+ it ":set_boolean_attribute should turn true into the right penctl command" do
35
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "foo")
36
+ Penctl.set_boolean_attribute('127.0.0.1:12000', 'foo', true)
37
+ end
38
+
39
+ it ":set_boolean_attribute should turn true into the right penctl command" do
40
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "no foo")
41
+ Penctl.set_boolean_attribute('127.0.0.1:12000', 'foo', false)
42
+ end
43
+
44
+ it ":get_set_attribute should set some attribute's value" do
45
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "foo bar").and_return ["0"]
46
+ Penctl.get_set_attribute('127.0.0.1:12000', 'foo=', 'bar')
47
+ end
48
+
49
+ it ":get_set_attribute should get some attribute's value" do
50
+ Penctl.should_receive(:execute).with("127.0.0.1:12000", "foo").and_return ["0"]
51
+ Penctl.get_set_attribute('127.0.0.1:12000', 'foo')
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ RAILS_ENV="test"
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: penctl-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jannis Hermanns
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-28 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: With penctl-ruby you can add and remove servers to a pen server list and to change settings without the need to restart the pen balancer.
17
+ email: jannis@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - MIT-LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION
30
+ - lib/pen_balancer.rb
31
+ - lib/penctl.rb
32
+ - penctl-ruby.gemspec
33
+ - rails/init.rb
34
+ - spec/lib/pen_balancer_spec.rb
35
+ - spec/lib/penctl_spec.rb
36
+ - spec/spec_helper.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/jayniz/penctl-ruby
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.4
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Ruby implementation of penctl
65
+ test_files:
66
+ - spec/lib/pen_balancer_spec.rb
67
+ - spec/lib/penctl_spec.rb
68
+ - spec/spec_helper.rb