who_can 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module WhoCan
2
+ VERSION = "0.3.4"
3
+ end
@@ -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
+
@@ -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
@@ -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
+ # module EventedSpec
2
+ # module SpecHelper
3
+ # module AMQP
4
+ # module ExampleMethods
5
+ # def with_open_channel(opts={})
6
+ # AMQP::Channel.new(opts) do |chan,_|
7
+ # yield chan
8
+ # end
9
+ # end
10
+ # end
11
+ # end
12
+ # end
13
+ # end
14
+
@@ -0,0 +1,8 @@
1
+ log_path = File.expand_path('../../../test.log', __FILE__)
2
+
3
+ WhoCan.logger = Logger.new(log_path).tap { |l| l.level = Logger::DEBUG }
4
+
5
+ # def logger
6
+ # WhoCan.logger
7
+ # end
8
+
@@ -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