penctl-ruby 0.1.0

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/.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