DanaDanger-equity 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/equity +132 -20
  2. data/bin/equityctl +4 -4
  3. data/lib/equity/node.rb +27 -4
  4. metadata +2 -2
data/bin/equity CHANGED
@@ -1,41 +1,145 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # equity 0.3 - simple queueing software load balancer
3
+ # equity 0.4 - queueing software load balancer with transmission inspection
4
+ # and simple HTTP header rewriting
4
5
  #
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.
6
+ # Usage: equity [-dp] [-h "Header: Value"] <listen-port> [<node-address>:]<node-port> ...
7
+ # Run equity with no arguments for detailed usage information.
11
8
  #
12
9
 
13
10
  require 'socket'
14
11
  require 'equity/node'
15
12
  require 'equity/manager'
13
+ require 'getoptlong'
14
+
15
+ # Returns a string that can be used to identify a particular node in debugging
16
+ # messages. At the moment this returns the address and port of the client being
17
+ # served by the node.
18
+ def client_id(node)
19
+ if node.connected?
20
+ "#{node.client_address}:#{node.client_port}"
21
+ else
22
+ "not connected"
23
+ end
24
+ end
16
25
 
17
26
  # Prints a debug message if debugging is enabled.
18
27
  def debug(socket, message)
19
- return unless $debugging
20
- print "[#{socket.object_id.to_s(16)}] " if socket
28
+ return unless $debugging
29
+ if socket
30
+ if node = Equity::Node.with_socket(socket)
31
+ client_id = client_id(node)
32
+ else
33
+ client_id = nil
34
+ begin
35
+ nameinfo = Socket.getnameinfo(socket.getpeername,
36
+ Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
37
+ client_id = "#{nameinfo[0]}:#{nameinfo[1]}"
38
+ rescue
39
+ client_id = "???:???"
40
+ end
41
+ end
42
+ print "[#{client_id}] "
43
+ end
21
44
  puts message
22
45
  end
23
46
 
47
+ # Prints data in hex and ASCII if packet dumping is enabled.
48
+ BYTES_PER_PACKET_LINE = 16
49
+ SAFE_PACKET_RX = (32..126).to_a.collect! {|n| n.chr}.join("").sub!(/[\[\]]/, '\\\1')
50
+ def dump_packet(data, src, dst)
51
+ return unless $dump_packets
52
+
53
+ node = Equity::Node.with_socket(src) || return
54
+ to_server = (src == node.sockets.first)
55
+ arrow = (to_server ? "--->" : "<---")
56
+ print "[#{client_id(node)}] #{arrow} #{node.address}:#{node.port}\n\n"
57
+
58
+ data = data.dup
59
+ until data.empty?
60
+ bytes = data.slice!(0, BYTES_PER_PACKET_LINE)
61
+ hex = bytes.unpack("H*").first + (" " * (BYTES_PER_PACKET_LINE - bytes.length))
62
+ hex1 = hex.slice!(0, BYTES_PER_PACKET_LINE)
63
+ hex2 = hex
64
+ hex1.gsub!(/(....)/, '\1 ')
65
+ hex2.gsub!(/(....)/, '\1 ')
66
+ bytes.gsub!(/[^#{SAFE_PACKET_RX}]/, ".")
67
+ puts " #{hex1} #{hex2} #{bytes}"
68
+ end
69
+ print "\n"
70
+ end
71
+
72
+ # Handles rewriting of HTTP request headers. This method isn't very smart about
73
+ # how it detects HTTP requests, but should work for most situations.
74
+ def rewrite_headers!(data, src)
75
+ return if $header_rewrites.empty?
76
+ return unless http_slash_idx = data.index(" HTTP/")
77
+ node = Equity::Node.with_socket(src) || return
78
+ return unless (src == node.sockets.first)
79
+
80
+ debug(src, "rewriting HTTP request headers")
81
+
82
+ before_headers = data.slice!(0, data.index("\r\n", http_slash_idx))
83
+ headers = data.slice!(0, data.index("\r\n\r\n"))
84
+ after_headers = data
85
+
86
+ $header_rewrites.each do |header, value|
87
+ if headers.sub!(/\r\n#{header}: (.+?)\r\n/m, "\r\n#{header}: #{value}\r\n")
88
+ debug(src, " #{header}: #{$1} => #{value}")
89
+ end
90
+ end
91
+ data.replace(before_headers + headers + after_headers)
92
+ rescue
93
+ # If an exception occurs, ignore it. Rewriting the headers is far less
94
+ # important than keeping the program alive.
95
+ end
96
+
24
97
 
25
- # Process arguments and instantiate nodes.
26
- $debugging = !!ARGV.delete('-d')
98
+ # Process arguments.
99
+ $debugging = !!ARGV.delete('-d')
100
+ $dump_packets = !!ARGV.delete('-p')
101
+ $header_rewrites = {}
102
+
103
+ options = GetoptLong.new(
104
+ ["-d", "--debug", GetoptLong::NO_ARGUMENT],
105
+ ["-p", "--packets", GetoptLong::NO_ARGUMENT],
106
+ ["-h", "--header", GetoptLong::REQUIRED_ARGUMENT]
107
+ )
108
+ options.each do |option, argument|
109
+ case option
110
+ when "-d"
111
+ $debugging = true
112
+ when "-p"
113
+ $dump_packets = true
114
+ when "-h"
115
+ header = argument.split(/: +/)
116
+ unless header.length == 2
117
+ STDERR.puts "I don't understand this header rewrite:"
118
+ STDERR.puts " #{argument}"
119
+ STDERR.puts "Run equity with no arguments for help."
120
+ exit(64) # EX_USAGE
121
+ end
122
+ $header_rewrites[header.first] = header.last
123
+ end
124
+ end
27
125
 
28
126
  if ARGV.length < 2
29
- STDERR.puts 'equity 0.3'
30
- STDERR.puts 'Usage: equity [-d] <listen-port> [<node-address>:]<node-port> ...'
31
- STDERR.print "\n"
32
- STDERR.puts 'The -d option turns on debug mode. Equity will remain in the foreground and'
33
- STDERR.puts 'print status messages.'
127
+ STDERR.puts 'equity 0.4'
128
+ STDERR.puts 'Usage: equity [<options>] <listen-port> [<node-address>:]<node-port> ...'
34
129
  STDERR.print "\n"
35
130
  STDERR.puts 'Node addresses default to localhost.'
131
+ STDERR.print "\n"
132
+ STDERR.puts 'The following options can be used:'
133
+ STDERR.puts '-d Debug mode. Stays in the foreground and prints status messages.'
134
+ STDERR.puts '-p Packet dumper. Stays in the foreground and displays the raw data being'
135
+ STDERR.puts ' transferred.'
136
+ STDERR.puts '-h "<header>: <value>" Rewrite an HTTP request header before retransmitting.'
137
+ STDERR.puts ' Can be used more than once to rewrite several headers.'
138
+ STDERR.print "\n"
36
139
  exit(64) # EX_USAGE
37
140
  end
38
141
 
142
+ # Instantiate nodes.
39
143
  $listen_port = ARGV.shift.to_i
40
144
 
41
145
  $nodes = []
@@ -49,7 +153,7 @@ ARGV.each do |node_spec|
49
153
  end
50
154
 
51
155
  # Daemonize.
52
- unless $debugging
156
+ unless $debugging || $dump_packets
53
157
  fork && exit
54
158
  Process.setsid
55
159
  trap 'SIGHUP', 'IGNORE'
@@ -66,6 +170,11 @@ unless $debugging
66
170
  STDERR.reopen STDOUT
67
171
  end
68
172
 
173
+ # Print debugging info for header rewrites.
174
+ $header_rewrites.each do |header, value|
175
+ debug(nil, "Will rewrite value of HTTP request header `#{header}' to `#{value}'")
176
+ end
177
+
69
178
  # Set up SIGINT handler to print node counters.
70
179
  trap 'SIGINT', Proc.new {
71
180
  debug(nil, "\nNode Counters")
@@ -106,7 +215,10 @@ while true
106
215
  begin
107
216
  data = socket.recvfrom(65536)[0]
108
217
  raise Errno::EPIPE if data.empty?
109
- socket.mate.write(data)
218
+ mate = socket.mate
219
+ rewrite_headers!(data, socket)
220
+ dump_packet(data, socket, mate)
221
+ mate.write(data)
110
222
  rescue Errno::EPIPE, Errno::ECONNRESET
111
223
  # Connection closed. Disconnect this node.
112
224
  node.disconnect
@@ -127,8 +239,8 @@ while true
127
239
  next if node.connected?
128
240
  begin
129
241
  node.connect(client)
130
- rescue
131
- debug(client, "connection to #{node} failed")
242
+ rescue Exception => e
243
+ debug(client, "connection to #{node} failed: #{e.message}")
132
244
  failure_count += 1
133
245
  next
134
246
  end
data/bin/equityctl CHANGED
@@ -23,9 +23,9 @@ if ARGV.length != 2
23
23
  STDERR.puts 'The host defaults to localhost.'
24
24
  STDERR.print "\n"
25
25
  STDERR.puts 'Command Description'
26
- STDERR.puts 'status Displays each node\'s address, port, connection status, and'
27
- STDERR.puts ' connection counter.'
28
- exit(64)
26
+ STDERR.puts 'status Displays each node\'s address, port, connection status,'
27
+ STDERR.puts ' connection counter, and failure counter.'
28
+ exit(64) # EX_USAGE
29
29
  end
30
30
 
31
31
  address, port = ARGV.shift.split(':', 2)
@@ -45,7 +45,7 @@ begin
45
45
  when 'status'
46
46
  nodes = controller.node_status || raise(NoResponseError)
47
47
  nodes.each do |node|
48
- puts "#{node} #{node.connected? ? 'C' : '-'} #{node.counter}"
48
+ puts "#{node} #{node.connected? ? 'C' : '-'} #{node.counter} #{node.failure_counter}"
49
49
  end
50
50
  else
51
51
  STDERR.puts "Unrecognized command: #{command}"
data/lib/equity/node.rb CHANGED
@@ -4,14 +4,18 @@ module Equity
4
4
  # Represents a cluster node.
5
5
  class Node
6
6
  attr_reader :counter
7
+ attr_reader :failure_counter
7
8
  attr_reader :address
8
9
  attr_reader :port
10
+ attr_reader :client_address
11
+ attr_reader :client_port
9
12
 
10
13
  def initialize(address, port)
11
- @client = nil
12
- @counter = 0
13
- @address = address.dup
14
- @port = port.to_i
14
+ @client = nil
15
+ @counter = 0
16
+ @failure_counter = 0
17
+ @address = address.dup
18
+ @port = port.to_i
15
19
  end
16
20
 
17
21
  def connect(client)
@@ -22,10 +26,20 @@ module Equity
22
26
  @server.extend SocketPairing
23
27
  @server.pair_with(client)
24
28
  @counter += 1
29
+
30
+ begin
31
+ nameinfo = Socket.getnameinfo(@client.getpeername,
32
+ Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV)
33
+ @client_address, @client_port = nameinfo
34
+ rescue
35
+ @client_address, @client_port = "???", "???"
36
+ end
37
+
25
38
  @server
26
39
  rescue Exception => e
27
40
  @client = nil
28
41
  @server = nil
42
+ @failure_counter += 1
29
43
  raise e
30
44
  end
31
45
  end
@@ -35,6 +49,8 @@ module Equity
35
49
  @server.close
36
50
  @client = nil
37
51
  @server = nil
52
+ @client_address = nil
53
+ @client_port = nil
38
54
  end
39
55
 
40
56
  def connected?
@@ -53,6 +69,13 @@ module Equity
53
69
  "#{@address}:#{@port}"
54
70
  end
55
71
 
72
+ def self.with_socket(socket)
73
+ ObjectSpace.each_object(self) do |node|
74
+ return node if node.owns_socket?(socket)
75
+ end
76
+ nil
77
+ end
78
+
56
79
  class Static < Node
57
80
  attr_writer :counter
58
81
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: DanaDanger-equity
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.3"
4
+ version: "0.4"
5
5
  platform: ruby
6
6
  authors:
7
7
  - DanaDanger
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-13 00:00:00 -07:00
12
+ date: 2009-04-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15