DanaDanger-equity 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/bin/equity +138 -0
- data/lib/equity/node.rb +54 -0
- data/lib/equity/socket_pairing.rb +15 -0
- metadata +55 -0
data/bin/equity
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
# equity 0.1 - simple queueing software load balancer
|
|
4
|
+
#
|
|
5
|
+
# Usage: equity [-d] <listen-port> [<node-address>:]<node-port> ...
|
|
6
|
+
#
|
|
7
|
+
# The -d option turns on debug mode. Equity will remain in the foreground and
|
|
8
|
+
# print status messages.
|
|
9
|
+
#
|
|
10
|
+
# Node addresses default to localhost.
|
|
11
|
+
#
|
|
12
|
+
|
|
13
|
+
require 'socket'
|
|
14
|
+
require 'equity/node'
|
|
15
|
+
|
|
16
|
+
# Prints a debug message if debugging is enabled.
|
|
17
|
+
def debug(socket, message)
|
|
18
|
+
return unless $debugging
|
|
19
|
+
print "[#{socket.object_id.to_s(16)}] " if socket
|
|
20
|
+
puts message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Process arguments and instantiate nodes.
|
|
25
|
+
$debugging = !!ARGV.delete('-d')
|
|
26
|
+
|
|
27
|
+
if ARGV.length < 2
|
|
28
|
+
STDERR.puts 'equity 0.1'
|
|
29
|
+
STDERR.puts 'Usage: equity [-d] <listen-port> [<node-address>:]<node-port> ...'
|
|
30
|
+
STDERR.print "\n"
|
|
31
|
+
STDERR.puts 'The -d option turns on debug mode. Equity will remain in the foreground and'
|
|
32
|
+
STDERR.puts 'print status messages.'
|
|
33
|
+
STDERR.print "\n"
|
|
34
|
+
STDERR.puts 'Node addresses default to localhost.'
|
|
35
|
+
exit(64) # EX_USAGE
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
$listen_port = ARGV.shift.to_i
|
|
39
|
+
|
|
40
|
+
$nodes = []
|
|
41
|
+
ARGV.each do |node_spec|
|
|
42
|
+
address, port = node_spec.split(':', 2)
|
|
43
|
+
if port.nil?
|
|
44
|
+
port = address
|
|
45
|
+
address = 'localhost'
|
|
46
|
+
end
|
|
47
|
+
$nodes << Equity::Node.new(address, port)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Daemonize.
|
|
51
|
+
unless $debugging
|
|
52
|
+
fork && exit
|
|
53
|
+
Process.setsid
|
|
54
|
+
trap 'SIGHUP', 'IGNORE'
|
|
55
|
+
fork && exit
|
|
56
|
+
Dir.chdir '/tmp'
|
|
57
|
+
File.umask 0000
|
|
58
|
+
ObjectSpace.each_object(IO) do |io|
|
|
59
|
+
unless [STDIN, STDOUT, STDERR].include?(io)
|
|
60
|
+
io.close rescue nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
STDIN.reopen "/dev/null"
|
|
64
|
+
STDOUT.reopen "/dev/null", "a"
|
|
65
|
+
STDERR.reopen STDOUT
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Start up server.
|
|
69
|
+
$client_queue = []
|
|
70
|
+
|
|
71
|
+
$server = TCPServer.new(nil, $listen_port)
|
|
72
|
+
$server.listen(5)
|
|
73
|
+
|
|
74
|
+
# Set up SIGINT handler to print node counters.
|
|
75
|
+
trap 'SIGINT', Proc.new {
|
|
76
|
+
debug(nil, "\nNode Counters")
|
|
77
|
+
$nodes.each do |node|
|
|
78
|
+
debug(nil, "#{node} - #{node.counter} connections")
|
|
79
|
+
end
|
|
80
|
+
exit
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
puts "Ready on port #{$listen_port}" if $debugging
|
|
84
|
+
|
|
85
|
+
# Loop forever.
|
|
86
|
+
while true
|
|
87
|
+
# Wait for one or more sockets to be readable.
|
|
88
|
+
sockets = [$server, $nodes.collect {|n| n.sockets}]
|
|
89
|
+
sockets.flatten!
|
|
90
|
+
selected = select(sockets, nil, nil, 5)
|
|
91
|
+
if selected
|
|
92
|
+
selected[0].each do |socket|
|
|
93
|
+
if socket == $server
|
|
94
|
+
# Incoming connection. Accept it and queue it.
|
|
95
|
+
client = $server.accept
|
|
96
|
+
$client_queue << client
|
|
97
|
+
debug(client, 'connection accepted')
|
|
98
|
+
else
|
|
99
|
+
# Data received. Transfer it to the socket's mate.
|
|
100
|
+
node = $nodes.find {|n| n.owns_socket?(socket)}
|
|
101
|
+
next if node.nil?
|
|
102
|
+
begin
|
|
103
|
+
data = socket.recvfrom(65536)[0]
|
|
104
|
+
raise Errno::EPIPE if data.empty?
|
|
105
|
+
socket.mate.write(data)
|
|
106
|
+
rescue Errno::EPIPE, Errno::ECONNRESET
|
|
107
|
+
# Connection closed. Disconnect this node.
|
|
108
|
+
node.disconnect
|
|
109
|
+
debug(socket, 'disconnected')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Dequeue as many clients as possible, showing favor to nodes that have
|
|
116
|
+
# handled the fewest clients.
|
|
117
|
+
while !$client_queue.empty?
|
|
118
|
+
client = $client_queue.shift
|
|
119
|
+
found_a_node = false
|
|
120
|
+
sorted_notes = $nodes.sort {|a,b| a.counter <=> b.counter}
|
|
121
|
+
sorted_notes.each do |node|
|
|
122
|
+
next if node.connected?
|
|
123
|
+
begin
|
|
124
|
+
node.connect(client)
|
|
125
|
+
rescue
|
|
126
|
+
debug(client, "connection to #{node} failed")
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
debug(client, "assigned to #{node}")
|
|
130
|
+
found_a_node = true
|
|
131
|
+
break
|
|
132
|
+
end
|
|
133
|
+
unless found_a_node
|
|
134
|
+
$client_queue.unshift(client)
|
|
135
|
+
break
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/equity/node.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'equity/socket_pairing'
|
|
2
|
+
|
|
3
|
+
module Equity
|
|
4
|
+
# Represents a cluster node.
|
|
5
|
+
class Node
|
|
6
|
+
attr_reader :counter
|
|
7
|
+
|
|
8
|
+
def initialize(address, port)
|
|
9
|
+
@client = nil
|
|
10
|
+
@counter = 0
|
|
11
|
+
@address = address.dup
|
|
12
|
+
@port = port.to_i
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def connect(client)
|
|
16
|
+
raise(RuntimeError, 'node is already connected') if connected?
|
|
17
|
+
begin
|
|
18
|
+
@client = client
|
|
19
|
+
@server = TCPSocket.new(@address, @port)
|
|
20
|
+
@server.extend SocketPairing
|
|
21
|
+
@server.pair_with(client)
|
|
22
|
+
@counter += 1
|
|
23
|
+
@server
|
|
24
|
+
rescue Exception => e
|
|
25
|
+
@client = nil
|
|
26
|
+
@server = nil
|
|
27
|
+
raise e
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def disconnect
|
|
32
|
+
@client.close
|
|
33
|
+
@server.close
|
|
34
|
+
@client = nil
|
|
35
|
+
@server = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def connected?
|
|
39
|
+
!!@client
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def sockets
|
|
43
|
+
connected? ? [@client, @server] : []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def owns_socket?(socket)
|
|
47
|
+
connected? && ((socket == @client) || (socket == @server))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
"#{@address}:#{@port}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Equity
|
|
2
|
+
# Enables pairing of sockets, so a client socket can be linked to a server
|
|
3
|
+
# socket.
|
|
4
|
+
module SocketPairing
|
|
5
|
+
attr_reader :mate
|
|
6
|
+
|
|
7
|
+
def pair_with(other_socket, one_way = false)
|
|
8
|
+
@mate = other_socket
|
|
9
|
+
unless one_way
|
|
10
|
+
other_socket.extend SocketPairing
|
|
11
|
+
other_socket.pair_with(self, true)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: DanaDanger-equity
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: "0.1"
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- DanaDanger
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-08-13 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description:
|
|
17
|
+
email:
|
|
18
|
+
executables:
|
|
19
|
+
- equity
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
|
|
24
|
+
files:
|
|
25
|
+
- bin/equity
|
|
26
|
+
- lib/equity/node.rb
|
|
27
|
+
- lib/equity/socket_pairing.rb
|
|
28
|
+
has_rdoc: false
|
|
29
|
+
homepage: http://github.com/DanaDanger/equity
|
|
30
|
+
post_install_message:
|
|
31
|
+
rdoc_options: []
|
|
32
|
+
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: "0"
|
|
40
|
+
version:
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: "0"
|
|
46
|
+
version:
|
|
47
|
+
requirements: []
|
|
48
|
+
|
|
49
|
+
rubyforge_project:
|
|
50
|
+
rubygems_version: 1.2.0
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 2
|
|
53
|
+
summary: Queueing software load balancer.
|
|
54
|
+
test_files: []
|
|
55
|
+
|