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