who_can 0.3.4
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 +7 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +8 -0
- data/Rakefile +2 -0
- data/lib/who_can.rb +64 -0
- data/lib/who_can/base.rb +72 -0
- data/lib/who_can/connection_manager.rb +268 -0
- data/lib/who_can/connection_wrapper.rb +210 -0
- data/lib/who_can/heartbeater.rb +7 -0
- data/lib/who_can/heartbeater/beat.rb +72 -0
- data/lib/who_can/heartbeater/ekg.rb +169 -0
- data/lib/who_can/logging.rb +12 -0
- data/lib/who_can/pinger.rb +82 -0
- data/lib/who_can/responder.rb +129 -0
- data/lib/who_can/version.rb +3 -0
- data/poc/heartbeater.rb +37 -0
- data/poc/submitter.rb +180 -0
- data/poc/who_can_with_hearbeat.rb +20 -0
- data/scripts/cross_cluster_ping.rb +107 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/evented_spec_extensions.rb +14 -0
- data/spec/support/logging.rb +8 -0
- data/spec/support/logging_progress_bar_formatter.rb +14 -0
- data/spec/who_can/base_spec.rb +66 -0
- data/spec/who_can/connection_manager_spec.rb +260 -0
- data/spec/who_can/connection_wrapper_spec.rb +91 -0
- data/spec/who_can/heartbeater/beat_spec.rb +101 -0
- data/spec/who_can/heartbeater/ekg_spec.rb +45 -0
- data/spec/who_can/pinger_responder_integration_spec.rb +63 -0
- data/spec/who_can/pinger_spec.rb +82 -0
- data/spec/who_can/responder_spec.rb +48 -0
- data/who_can.gemspec +32 -0
- metadata +290 -0
data/poc/heartbeater.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path('../../lib/who_can', __FILE__)
|
4
|
+
|
5
|
+
WhoCan.logger = logger = Logger.new($stderr).tap { |l| l.level = Logger::DEBUG }
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
AMQP.start(:host => 'localhost') do |connection|
|
10
|
+
# connection.on_closed do
|
11
|
+
# logger.debug { "closed fired" }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# connection.on_tcp_connection_failure do
|
15
|
+
# logger.debug { "tcp connection failure fired" }
|
16
|
+
# end
|
17
|
+
|
18
|
+
connection.on_tcp_connection_loss do
|
19
|
+
logger.debug { "tcp connection loss fired" }
|
20
|
+
end
|
21
|
+
|
22
|
+
hb = WhoCan::Heartbeater::EKG.new(connection, :interval => 1.0)
|
23
|
+
|
24
|
+
hb.on_start do
|
25
|
+
logger.debug { "heartbeater started" }
|
26
|
+
end
|
27
|
+
|
28
|
+
hb.on_heartbeat_failure do
|
29
|
+
logger.debug { "Heartbeat failed! now we WOULD exit!!" }
|
30
|
+
EM.next_tick { EM.stop_event_loop }
|
31
|
+
end
|
32
|
+
|
33
|
+
hb.start!
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
data/poc/submitter.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)).uniq!
|
4
|
+
|
5
|
+
require 'who_can'
|
6
|
+
require 'logger'
|
7
|
+
require 'ruby-debug'
|
8
|
+
|
9
|
+
$log = Logger.new($stderr).tap { |log| log.level = Logger::DEBUG }
|
10
|
+
|
11
|
+
PING_EXCHANGE = 'who_can.worker.ping.fanout'
|
12
|
+
|
13
|
+
class Base
|
14
|
+
attr_reader :cnx
|
15
|
+
|
16
|
+
def initialize(cnx)
|
17
|
+
@cnx = cnx
|
18
|
+
end
|
19
|
+
|
20
|
+
def channel
|
21
|
+
@channel ||= AMQP::Channel.new(cnx, AMQP::Channel.next_channel_id) #, :prefetch => 1)
|
22
|
+
end
|
23
|
+
alias :ch :channel
|
24
|
+
|
25
|
+
def ping_exchange
|
26
|
+
@ping_exchange ||= channel.fanout(PING_EXCHANGE, :durable => true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reply_exchange
|
30
|
+
@reply_exchange ||= channel.default_exchange
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Worker < Base
|
35
|
+
def setup
|
36
|
+
ch.once_opened do
|
37
|
+
$log.debug "channel opened"
|
38
|
+
|
39
|
+
ch.queue('', :exclusive => true) do |q|
|
40
|
+
q.bind(ping_exchange).subscribe do |header,payload|
|
41
|
+
v = rand() * 0.1
|
42
|
+
|
43
|
+
$log.debug "#1 Got #{header.message_id}, replying after #{v} delay"
|
44
|
+
|
45
|
+
EM.add_timer(v) do
|
46
|
+
reply_exchange.publish("worker_1 #{v}", :routing_key => header.reply_to, :message_id => header.message_id)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ch.queue('', :exclusive => true) do |q|
|
52
|
+
q.bind(ping_exchange).subscribe do |header,payload|
|
53
|
+
v = rand() * 0.1
|
54
|
+
|
55
|
+
$log.debug "#2 Got #{header.message_id}, replying after #{v} delay"
|
56
|
+
|
57
|
+
EM.add_timer(v) do
|
58
|
+
reply_exchange.publish("worker_2: #{v}", :routing_key => header.reply_to, :message_id => header.message_id)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
ch.queue('', :exclusive => true) do |q|
|
64
|
+
q.bind(ping_exchange).subscribe do |header,payload|
|
65
|
+
v = rand() * 0.1
|
66
|
+
|
67
|
+
$log.debug "#3 Got #{header.message_id}, replying after #{v} delay"
|
68
|
+
|
69
|
+
EM.add_timer(v) do
|
70
|
+
reply_exchange.publish("worker_3: #{v}", :routing_key => header.reply_to, :message_id => header.message_id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
$log.debug "queues set up"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Submitter < Base
|
81
|
+
attr_reader :requests, :pinger
|
82
|
+
|
83
|
+
def initialize(cnx, opts={})
|
84
|
+
super(cnx)
|
85
|
+
@pinger = WhoCan::Pinger.new(cnx)
|
86
|
+
end
|
87
|
+
|
88
|
+
# def ping!(run_id, &callback)
|
89
|
+
# $log.debug "ping! run_id #{run_id}"
|
90
|
+
|
91
|
+
# got_message = false
|
92
|
+
|
93
|
+
# ch.once_opened do
|
94
|
+
# ch.queue('', :exclusive => true, :auto_delete => true) do |q|
|
95
|
+
|
96
|
+
# # q here is a single-use queue for gathering repsonses to this ping
|
97
|
+
# q.subscribe do |header, payload|
|
98
|
+
# if got_message
|
99
|
+
# $log.debug "discarding response for messgae: #{header.message_id}"
|
100
|
+
# else
|
101
|
+
# $log.debug "unsubscribing queue: #{q.name}"
|
102
|
+
# q.unsubscribe
|
103
|
+
|
104
|
+
# got_message = true
|
105
|
+
|
106
|
+
# msg_id = header.message_id
|
107
|
+
|
108
|
+
# $log.debug "got a reply for #{msg_id}"
|
109
|
+
# callback.call(run_id, payload)
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
|
113
|
+
# msg_id = %Q[__message_#{run_id}_#{UUIDTools::UUID.random_create.to_s.tr('-', '')}__]
|
114
|
+
# requests[msg_id] = []
|
115
|
+
|
116
|
+
# ping_exchange.publish('PING', :reply_to => q.name, :message_id => msg_id) { }
|
117
|
+
# $log.debug "sent PING request on run #{run_id} msg_id: #{msg_id}"
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
|
122
|
+
def ping!(run_id, &callback)
|
123
|
+
@pinger.ping!(PING_EXCHANGE) do |header,payload|
|
124
|
+
$log.debug { "received message for run_id: #{run_id}, payload: #{payload}" }
|
125
|
+
callback.call(run_id, payload)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.main(cnx)
|
130
|
+
sub = new(cnx)
|
131
|
+
|
132
|
+
callback = lambda do |run_id, requests|
|
133
|
+
$log.debug "results of run_id #{run_id}: #{requests.inspect}"
|
134
|
+
|
135
|
+
if run_id >= 100
|
136
|
+
$log.debug "Did 100 rounds, exiting"
|
137
|
+
cnx.close { EM.stop { exit } }
|
138
|
+
else
|
139
|
+
next_id = run_id + 1
|
140
|
+
$log.debug "scheduling run ##{next_id}"
|
141
|
+
EM.next_tick { sub.ping!(next_id, &callback) }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
$stderr.puts "calling ping with run_id 0"
|
146
|
+
sub.ping!(0, &callback)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def main
|
151
|
+
EventMachine.run do
|
152
|
+
AMQP.connect(AMQP::Client.parse_connection_uri("amqp://localhost")) do |cnx|
|
153
|
+
shutdown = lambda do
|
154
|
+
$log.debug "shutting down"
|
155
|
+
cnx.close do
|
156
|
+
EM.stop { exit }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
trap "INT", shutdown
|
161
|
+
|
162
|
+
case ARGV.first
|
163
|
+
when 'submitter'
|
164
|
+
Submitter.main(cnx)
|
165
|
+
when 'worker'
|
166
|
+
Worker.new(cnx).setup
|
167
|
+
else
|
168
|
+
$stderr.puts "what?!"
|
169
|
+
shutdown
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
$stderr.sync = true
|
177
|
+
|
178
|
+
main if __FILE__ == $0
|
179
|
+
|
180
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path('../../lib/who_can', __FILE__)
|
4
|
+
|
5
|
+
WhoCan.logger = logger = Logger.new($stderr).tap { |l| l.level = Logger::DEBUG }
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
AMQP.start(:host => 'localhost') do |connection|
|
10
|
+
|
11
|
+
who_can = WhoCan.new(connection)
|
12
|
+
who_can.on_disconnection do
|
13
|
+
$stderr.puts("got a valid disconnection")
|
14
|
+
end
|
15
|
+
|
16
|
+
who_can.start_monitoring!
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
|
6
|
+
require 'who_can'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
WhoCan.logger = Logger.new($stderr).tap { |n| n.level = Logger::DEBUG }
|
10
|
+
|
11
|
+
class CrossClusterPinger
|
12
|
+
include WhoCan::Logging
|
13
|
+
include Deferred::Accessors
|
14
|
+
|
15
|
+
CLUSTER_NODE_ONE = 'amqp://localhost:15672'
|
16
|
+
CLUSTER_NODE_TWO = 'amqp://localhost:15673'
|
17
|
+
|
18
|
+
EXCHANGE_NAME = 'who_can.cluster.pinger.test'
|
19
|
+
|
20
|
+
deferred_event :pinger_started, :stop
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@trapped_signal = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def ping_on_schedule
|
27
|
+
@ping_timer ||= EM.add_periodic_timer(1.0) do
|
28
|
+
@pinger.ping!(EXCHANGE_NAME) do |header,payload|
|
29
|
+
logger.debug { "got message_id: #{header.message_id}, payload: #{payload}" }
|
30
|
+
end.errback do |e|
|
31
|
+
logger.error { "got exception #{e.to_std_error}" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_pinger(&blk)
|
37
|
+
on_pinger_started(&blk)
|
38
|
+
|
39
|
+
@wc_pinger = WhoCan.new(CLUSTER_NODE_ONE).tap do |wc|
|
40
|
+
wc.on_open do
|
41
|
+
@pinger = WhoCan::Pinger.new(wc.connection)
|
42
|
+
|
43
|
+
@pinger.start! do
|
44
|
+
logger.debug { "pinger started, scheduling perioding ping" }
|
45
|
+
on_pinger_started.succeed
|
46
|
+
ping_on_schedule
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_responder(&blk)
|
53
|
+
@wc_responder = WhoCan.new(CLUSTER_NODE_TWO).tap do |wc|
|
54
|
+
wc.on_open do
|
55
|
+
@responder = WhoCan::Responder.new(wc.connection, EXCHANGE_NAME)
|
56
|
+
|
57
|
+
@responder.on_ping do |rsp|
|
58
|
+
logger.debug { "responder got ping id: #{rsp.header.message_id} payload: #{rsp.payload}" }
|
59
|
+
rsp.delay = 0.1
|
60
|
+
rsp.response = "Time now: #{Time.now.to_s}"
|
61
|
+
end
|
62
|
+
|
63
|
+
@responder.setup!(&blk)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def stop!(&blk)
|
69
|
+
on_stop(&blk)
|
70
|
+
|
71
|
+
@ping_timer.cancel
|
72
|
+
|
73
|
+
@responder.close! do
|
74
|
+
@wc_responder.close! do
|
75
|
+
@wc_pinger.close! do
|
76
|
+
on_stop.succeed
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
on_stop
|
82
|
+
end
|
83
|
+
|
84
|
+
def main
|
85
|
+
EM.run do
|
86
|
+
start_pinger do
|
87
|
+
start_responder do
|
88
|
+
logger.debug { "running" }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
trap('INT') do
|
93
|
+
if @trapped_signal
|
94
|
+
$stderr.puts "EXITING IMMEDIATELY!!"
|
95
|
+
EM.stop_event_loop
|
96
|
+
else
|
97
|
+
@trapped_signal = true
|
98
|
+
stop! do
|
99
|
+
EM.next_tick { EM.stop_event_loop }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
CrossClusterPinger.new.main if __FILE__ == $0
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'who_can'
|
3
|
+
require 'evented-spec'
|
4
|
+
|
5
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
6
|
+
# in spec/support/ and its subdirectories.
|
7
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__), "support/**/*.rb"))].each {|f| require f}
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
# == Mock Framework
|
11
|
+
#
|
12
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
13
|
+
#
|
14
|
+
# config.mock_with :mocha
|
15
|
+
# config.mock_with :flexmock
|
16
|
+
# config.mock_with :rr
|
17
|
+
config.mock_with :flexmock
|
18
|
+
|
19
|
+
config.include(FlexMock::ArgumentTypes)
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rspec/core/formatters/progress_formatter'
|
2
|
+
|
3
|
+
module Motionbox
|
4
|
+
# essentially a monkey-patch to the ProgressBarFormatter, outputs
|
5
|
+
# '== #{example_proxy.description} ==' in the logs before each test. makes it
|
6
|
+
# easier to match up tests with the SQL they produce
|
7
|
+
class LoggingProgressBarFormatter < RSpec::Core::Formatters::ProgressFormatter
|
8
|
+
def example_started(example)
|
9
|
+
WhoCan.logger.info(yellow("\n=====<([ #{example.full_description} ])>=====\n"))
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
module WhoCan
|
5
|
+
|
6
|
+
describe "Base" do
|
7
|
+
describe "using an existing connection" do
|
8
|
+
include EventedSpec::AMQPSpec
|
9
|
+
default_timeout 3.0
|
10
|
+
default_options :host => 'localhost'
|
11
|
+
|
12
|
+
amqp_before do
|
13
|
+
@who_can = WhoCan.new(AMQP.connection)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should fire the on_disconnection when a tcp failure occurs" do
|
17
|
+
@who_can.start_monitoring!
|
18
|
+
@who_can.on_disconnection do
|
19
|
+
true.should be_true #this should be much better
|
20
|
+
done
|
21
|
+
end
|
22
|
+
@who_can.on_open do
|
23
|
+
AMQP.connection.tcp_connection_lost
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should fire disconnection on close!" do
|
28
|
+
@who_can.on_disconnection do
|
29
|
+
true.should be_true #this should be much better
|
30
|
+
done
|
31
|
+
end
|
32
|
+
@who_can.on_open do
|
33
|
+
@who_can.close!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should fire disconnection on heartbeat_failure" do
|
38
|
+
heartbeat_failure_block = nil
|
39
|
+
fake_ekg = flexmock('ekg') do |m|
|
40
|
+
m.should_receive(:on_heartbeat_failure).with(Proc).and_return do |blk|
|
41
|
+
heartbeat_failure_block = blk
|
42
|
+
end
|
43
|
+
m.should_receive(:start!).and_return(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
flexmock(Heartbeater::EKG).should_receive(:new).and_return(fake_ekg)
|
47
|
+
@who_can.start_monitoring!
|
48
|
+
|
49
|
+
@who_can.on_disconnection do
|
50
|
+
true.should be_true #this should be much better
|
51
|
+
done
|
52
|
+
end
|
53
|
+
@who_can.on_open do
|
54
|
+
heartbeat_failure_block.should_not be_nil
|
55
|
+
heartbeat_failure_block.call
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|