tochtli 0.5.0 → 0.5.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.
- checksums.yaml +4 -4
- data/History.md +4 -0
- data/README.md +2 -1
- data/VERSION +1 -1
- data/examples/01-screencap-service/client.rb +7 -7
- data/examples/01-screencap-service/common.rb +4 -4
- data/examples/01-screencap-service/server.rb +9 -9
- data/examples/02-log-analyzer/client.rb +34 -34
- data/examples/02-log-analyzer/server.rb +12 -12
- data/lib/tochtli.rb +3 -3
- data/lib/tochtli/base_controller.rb +66 -60
- data/lib/tochtli/controller_manager.rb +22 -23
- data/lib/tochtli/message.rb +4 -4
- data/lib/tochtli/test/integration.rb +1 -0
- data/log_generator.rb +2 -2
- data/test/controller_functional_test.rb +2 -2
- data/test/controller_manager_test.rb +13 -13
- data/test/key_matcher_test.rb +71 -71
- data/test/rabbit_connection_test.rb +151 -151
- data/test/version_test.rb +4 -4
- data/tochtli.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7a4aeafb78f7399f7f0f7444bcae42b8a025fa6
|
4
|
+
data.tar.gz: 5d387cebbd67b3323ee3ef60e7f2eac1d6cc7087
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 556377482683580c625cbf00a34f100bbf03fd8a0c3338b7f6a10efba216283d27be4389dca208168a47923ea08dd72f68710bddafd2271c60c32a3f83100bd0
|
7
|
+
data.tar.gz: c7b5e7dee6939180abe8981e110c87d02e62fefa223971f32aa46bd27ce72ddb7fa605bf944ca7107f1f3cf52ba9f8638195ec687327461243768a0302c4f572
|
data/History.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
[](https://travis-ci.org/PuzzleFlow/tochtli)
|
2
|
+
[](http://badge.fury.io/rb/tochtli)
|
2
3
|
|
3
4
|
# What's Tochtli?
|
4
5
|
|
@@ -43,4 +44,4 @@ Read more about Tochtli and go through the tutorial on [Tochtli homepage](http:/
|
|
43
44
|
|
44
45
|
# License
|
45
46
|
|
46
|
-
Released under the MIT license.
|
47
|
+
Released under the MIT license.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.1
|
@@ -3,13 +3,13 @@ require_relative 'common'
|
|
3
3
|
Tochtli.logger.progname = 'CLIENT'
|
4
4
|
|
5
5
|
class ScreenerClient < Tochtli::BaseClient
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
def create_screen(url, file_name)
|
7
|
+
handler = SyncMessageHandler.new
|
8
|
+
message = CreateScreenMessage.new(url: url, file: file_name)
|
9
|
+
rabbit_client.publish message, handler: handler
|
10
|
+
handler.wait!(20)
|
11
|
+
puts "Done in #{handler.reply.time} seconds"
|
12
|
+
end
|
13
13
|
end
|
14
14
|
|
15
15
|
ScreenerClient.new.create_screen(ARGV[0], ARGV[1])
|
@@ -4,12 +4,12 @@ Bundler.require
|
|
4
4
|
Tochtli.logger = Logger.new('tochtli.log')
|
5
5
|
|
6
6
|
class CreateScreenMessage < Tochtli::Message
|
7
|
-
|
7
|
+
route_to 'screener.create'
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
attribute :url, String
|
10
|
+
attribute :file, String
|
11
11
|
end
|
12
12
|
|
13
13
|
class CreateScreenReplyMessage < Tochtli::Message
|
14
|
-
|
14
|
+
attribute :time, Float
|
15
15
|
end
|
@@ -3,17 +3,17 @@ require_relative 'common'
|
|
3
3
|
Tochtli.logger.progname = 'SERVER'
|
4
4
|
|
5
5
|
class ScreenerController < Tochtli::BaseController
|
6
|
-
|
6
|
+
bind 'screener.*'
|
7
7
|
|
8
|
-
|
8
|
+
on CreateScreenMessage, :create
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def create
|
11
|
+
start_time = Time.now
|
12
|
+
f = Screencap::Fetcher.new(message.url)
|
13
|
+
f.fetch output: File.join(__dir__, 'images', message.file)
|
14
|
+
total_time = Time.now - start_time
|
15
|
+
reply CreateScreenReplyMessage.new(time: total_time)
|
16
|
+
end
|
17
17
|
end
|
18
18
|
|
19
19
|
Tochtli::ControllerManager.setup
|
@@ -9,9 +9,9 @@ module LogAnalyzer
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def react_on_events(client_id, severities, handler=nil, &block)
|
12
|
-
|
12
|
+
handler = block unless handler
|
13
13
|
severities = Array(severities)
|
14
|
-
|
14
|
+
routing_keys = severities.map {|severity| "log.events.#{severity}" }
|
15
15
|
Tochtli::ControllerManager.start EventsController,
|
16
16
|
queue_name: "log_analyzer/events/#{client_id}", # custom queue name
|
17
17
|
routing_keys: routing_keys, # make custom binding (only selected severities)
|
@@ -19,7 +19,7 @@ module LogAnalyzer
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def monitor_status(monitor=nil, &block)
|
22
|
-
|
22
|
+
monitor = block unless monitor
|
23
23
|
Tochtli::ControllerManager.start MonitorController, env: { monitor: monitor }
|
24
24
|
end
|
25
25
|
end
|
@@ -27,18 +27,18 @@ module LogAnalyzer
|
|
27
27
|
protected
|
28
28
|
|
29
29
|
class EventsController < Tochtli::BaseController
|
30
|
-
|
30
|
+
on EventOccurred, :handle, routing_key: 'log.events.*'
|
31
31
|
|
32
32
|
def handle
|
33
|
-
|
33
|
+
handler.call(message.severity, message.timestamp, message.message)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
protected
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
def handler
|
39
|
+
raise "Internal Error: handler not set for EventsController" unless env.has_key?(:handler)
|
40
|
+
env[:handler]
|
41
|
+
end
|
42
42
|
end
|
43
43
|
|
44
44
|
class MonitorController < Tochtli::BaseController
|
@@ -50,14 +50,14 @@ module LogAnalyzer
|
|
50
50
|
self.queue_auto_delete = true
|
51
51
|
|
52
52
|
on CurrentStatus do
|
53
|
-
|
53
|
+
monitor.call(message.to_hash)
|
54
54
|
end
|
55
55
|
|
56
56
|
protected
|
57
57
|
|
58
58
|
def monitor
|
59
|
-
|
60
|
-
|
59
|
+
raise "Internal Error: monitor not set for MonitorController" unless env.has_key?(:monitor)
|
60
|
+
env[:monitor]
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
@@ -66,30 +66,30 @@ client = LogAnalyzer::Client.new
|
|
66
66
|
command = ARGV[0]
|
67
67
|
|
68
68
|
def hold
|
69
|
-
|
70
|
-
|
69
|
+
puts 'Press Ctrl-C to stop...'
|
70
|
+
sleep
|
71
71
|
end
|
72
72
|
|
73
73
|
|
74
74
|
case command
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
75
|
+
when 's'
|
76
|
+
client.send_new_log ARGV[1]
|
77
|
+
when 'm'
|
78
|
+
client.monitor_status {|status| p status }
|
79
|
+
hold
|
80
|
+
when 'c'
|
81
|
+
client.react_on_events ARGV[1], [:fatal, :error], lambda {|severity, timestamp, message|
|
82
|
+
puts "[#{timestamp}] Got #{severity}: #{message}"
|
83
|
+
}
|
84
|
+
hold
|
85
|
+
else
|
86
|
+
puts "Unknown command: #{command.inspect}"
|
87
|
+
puts
|
88
|
+
puts "Usage: bundle exec ruby client [command] [params]"
|
89
|
+
puts
|
90
|
+
puts "Commands:"
|
91
|
+
puts " s [path] - send log from file to server"
|
92
|
+
puts " m - start status monitor"
|
93
|
+
puts " c [client ID] - catch fatal and error events"
|
94
94
|
end
|
95
95
|
|
@@ -14,8 +14,8 @@ module LogAnalyzer
|
|
14
14
|
cattr_accessor :monitor
|
15
15
|
|
16
16
|
after_setup do |rabbit_connection|
|
17
|
-
|
18
|
-
|
17
|
+
self.monitor = StatusMonitor.new(rabbit_connection)
|
18
|
+
self.monitor.start
|
19
19
|
end
|
20
20
|
|
21
21
|
def create
|
@@ -68,11 +68,11 @@ module LogAnalyzer
|
|
68
68
|
end
|
69
69
|
|
70
70
|
class EventNotifier < Tochtli::BaseClient
|
71
|
-
|
71
|
+
SIGNIFICANT_SEVERITIES = [:fatal, :error, :warn]
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
def self.significant?(severity)
|
74
|
+
SIGNIFICANT_SEVERITIES.include?(severity)
|
75
|
+
end
|
76
76
|
|
77
77
|
def notify(event)
|
78
78
|
publish EventOccurred.new(event), mandatory: false
|
@@ -105,18 +105,18 @@ module LogAnalyzer
|
|
105
105
|
protected
|
106
106
|
|
107
107
|
def reset_status
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
108
|
+
synchronize do
|
109
|
+
status = @status
|
110
|
+
@status = Hash.new(0)
|
111
|
+
status
|
112
|
+
end
|
113
113
|
end
|
114
114
|
|
115
115
|
def monitor
|
116
116
|
loop do
|
117
117
|
current_status = reset_status
|
118
118
|
current_status[:timestamp] = Time.now
|
119
|
-
|
119
|
+
@notifier.update_status current_status
|
120
120
|
sleep 10
|
121
121
|
end
|
122
122
|
end
|
data/lib/tochtli.rb
CHANGED
@@ -69,9 +69,9 @@ module Tochtli
|
|
69
69
|
@logger = Logger.new(File.join(Rails.root, 'log/service.log'))
|
70
70
|
@logger.level = Rails.env.production? ? Logger::WARN : Logger::DEBUG
|
71
71
|
else
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
@logger = Logger.new(STDERR)
|
73
|
+
@logger.level = Logger::WARN
|
74
|
+
end
|
75
75
|
end
|
76
76
|
@logger
|
77
77
|
end
|
@@ -83,9 +83,9 @@ module Tochtli
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def start(queue_name=nil, routing_keys=nil, initial_env={})
|
86
|
-
|
86
|
+
run_hook :before_start, queue_name, initial_env
|
87
87
|
self.dispatcher.start(queue_name || self.queue_name, routing_keys || self.routing_keys, initial_env)
|
88
|
-
|
88
|
+
run_hook :after_start, queue_name, initial_env
|
89
89
|
end
|
90
90
|
|
91
91
|
def set_up?
|
@@ -97,24 +97,30 @@ module Tochtli
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def stop(options={})
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
if started?
|
101
|
+
queues = self.dispatcher.queues
|
102
|
+
run_hook :before_stop, queues
|
103
|
+
end
|
104
|
+
|
105
|
+
if self.dispatcher
|
106
|
+
self.dispatcher.shutdown(options)
|
107
|
+
self.dispatcher = nil
|
103
108
|
|
104
|
-
|
105
|
-
self.dispatcher = nil
|
109
|
+
run_hook :after_stop, queues
|
106
110
|
|
107
|
-
|
108
|
-
|
111
|
+
true
|
112
|
+
else
|
113
|
+
false
|
114
|
+
end
|
109
115
|
end
|
110
116
|
|
111
117
|
def restart(options={})
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
+
if started?
|
119
|
+
queues = self.dispatcher.queues
|
120
|
+
run_hook :before_restart, queues
|
121
|
+
self.dispatcher.restart options
|
122
|
+
run_hook :after_restart, queues
|
123
|
+
end
|
118
124
|
end
|
119
125
|
|
120
126
|
def find_message_route(routing_key)
|
@@ -181,13 +187,13 @@ module Tochtli
|
|
181
187
|
end
|
182
188
|
|
183
189
|
def rabbit_connection
|
184
|
-
|
190
|
+
self.class.dispatcher.rabbit_connection if self.class.set_up?
|
185
191
|
end
|
186
192
|
|
187
193
|
class MessageRoute < Struct.new(:message_class, :action, :routing_key, :pattern)
|
188
|
-
|
189
|
-
|
190
|
-
|
194
|
+
def initialize(message_class, action, routing_key)
|
195
|
+
super message_class, action, routing_key, KeyPattern.new(routing_key)
|
196
|
+
end
|
191
197
|
end
|
192
198
|
|
193
199
|
class Dispatcher
|
@@ -209,13 +215,13 @@ module Tochtli
|
|
209
215
|
end
|
210
216
|
|
211
217
|
def restart(options={})
|
212
|
-
|
218
|
+
queues = @queues.dup
|
213
219
|
|
214
|
-
|
220
|
+
shutdown options
|
215
221
|
|
216
|
-
|
217
|
-
|
218
|
-
|
222
|
+
queues.each do |queue_name, queue_opts|
|
223
|
+
start queue_name, queue_opts[:initial_env]
|
224
|
+
end
|
219
225
|
end
|
220
226
|
|
221
227
|
def process_message(delivery_info, properties, payload, initial_env)
|
@@ -266,15 +272,15 @@ module Tochtli
|
|
266
272
|
rescue Bunny::ConnectionClosedError
|
267
273
|
# ignore closed connection error
|
268
274
|
ensure
|
269
|
-
|
275
|
+
@queues = {}
|
270
276
|
end
|
271
277
|
|
272
278
|
def started?(queue_name=nil)
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
279
|
+
if queue_name
|
280
|
+
@queues.has_key?(queue_name)
|
281
|
+
else
|
282
|
+
!@queues.empty?
|
283
|
+
end
|
278
284
|
end
|
279
285
|
|
280
286
|
def queues
|
@@ -326,35 +332,35 @@ module Tochtli
|
|
326
332
|
end
|
327
333
|
|
328
334
|
class KeyPattern
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
335
|
+
ASTERIX_EXP = '[a-zA-Z0-9]+'
|
336
|
+
HASH_EXP = '[a-zA-Z0-9\.]*'
|
337
|
+
|
338
|
+
def initialize(pattern)
|
339
|
+
@str = pattern
|
340
|
+
@simple = !pattern.include?('*') && !pattern.include?('#')
|
341
|
+
if @simple
|
342
|
+
@pattern = pattern
|
343
|
+
else
|
344
|
+
@pattern = Regexp.new('^' + pattern.gsub('.', '\\.').
|
345
|
+
gsub('*', ASTERIX_EXP).gsub(/(\\\.)?#(\\\.)?/, HASH_EXP) + '$')
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def =~(key)
|
350
|
+
if @simple
|
351
|
+
@pattern == key
|
352
|
+
else
|
353
|
+
@pattern =~ key
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def !~(key)
|
358
|
+
!(self =~ key)
|
359
|
+
end
|
360
|
+
|
361
|
+
def to_s
|
362
|
+
@str
|
363
|
+
end
|
364
|
+
end
|
359
365
|
end
|
360
366
|
end
|
@@ -26,13 +26,13 @@ module Tochtli
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def start(*controllers)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
options = controllers.extract_options!
|
30
|
+
setup_options = options.except!(:logger, :cache, :connection)
|
31
|
+
queue_name = options.delete(:queue_name)
|
32
|
+
routing_keys = options.delete(:routing_keys)
|
33
|
+
initial_env = options.delete(:env) || {}
|
34
34
|
|
35
|
-
|
35
|
+
setup(setup_options) unless set_up?
|
36
36
|
|
37
37
|
if controllers.empty? || controllers.include?(:all)
|
38
38
|
controllers = @controller_classes
|
@@ -41,34 +41,33 @@ module Tochtli
|
|
41
41
|
controllers.each do |controller_class|
|
42
42
|
raise ArgumentError, "Controller expected, got: #{controller_class.inspect}" unless controller_class.is_a?(Class) && controller_class < Tochtli::BaseController
|
43
43
|
unless controller_class.started?(queue_name)
|
44
|
-
@logger.info "Starting #{controller_class}..." if @logger
|
45
44
|
controller_class.setup(@rabbit_connection, @cache, @logger) unless controller_class.set_up?
|
46
45
|
controller_class.start queue_name, routing_keys, initial_env
|
46
|
+
@logger.info "[#{Time.now} AMQP] Started #{controller_class}" if @logger
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
def stop
|
52
52
|
@controller_classes.each do |controller_class|
|
53
|
-
if controller_class.
|
54
|
-
@logger.info "
|
55
|
-
controller_class.stop
|
53
|
+
if controller_class.stop
|
54
|
+
@logger.info "[#{Time.now} AMQP] Stopped #{controller_class}" if @logger
|
56
55
|
end
|
57
56
|
end
|
58
57
|
@rabbit_connection = nil
|
59
58
|
end
|
60
59
|
|
61
60
|
def restart(options={})
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
options[:rabbit_connection] ||= @rabbit_connection
|
62
|
+
options[:logger] ||= @logger
|
63
|
+
options[:cache] ||= @cache
|
65
64
|
|
66
|
-
|
67
|
-
|
65
|
+
setup options
|
66
|
+
restart_active_controllers
|
68
67
|
end
|
69
68
|
|
70
69
|
def set_up?
|
71
|
-
|
70
|
+
!@rabbit_connection.nil?
|
72
71
|
end
|
73
72
|
|
74
73
|
def running?
|
@@ -78,17 +77,17 @@ module Tochtli
|
|
78
77
|
protected
|
79
78
|
|
80
79
|
def restart_active_controllers
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
80
|
+
@controller_classes.each do |controller_class|
|
81
|
+
if controller_class.started?
|
82
|
+
@logger.info "Restarting #{controller_class}..." if @logger
|
83
|
+
controller_class.restart
|
84
|
+
end
|
85
|
+
end
|
87
86
|
end
|
88
87
|
|
89
88
|
class << self
|
90
89
|
def method_missing(method, *args)
|
91
|
-
|
90
|
+
if instance.respond_to?(method)
|
92
91
|
instance.send(method, *args)
|
93
92
|
else
|
94
93
|
super
|
data/lib/tochtli/message.rb
CHANGED
@@ -88,11 +88,11 @@ module Tochtli
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def routing_key
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
if self.class.routing_key.is_a?(Proc)
|
92
|
+
self.instance_eval(&self.class.routing_key)
|
93
|
+
else
|
94
94
|
self.class.routing_key
|
95
|
-
|
95
|
+
end
|
96
96
|
end
|
97
97
|
|
98
98
|
def to_hash
|
@@ -16,6 +16,7 @@ module Tochtli
|
|
16
16
|
@client = Tochtli::RabbitClient.new(nil, @logger)
|
17
17
|
@connection = @client.rabbit_connection
|
18
18
|
@controller_manager = Tochtli::ControllerManager.instance
|
19
|
+
@controller_manager.stop # restart all controllers - some can be started with functional tests on test connections
|
19
20
|
@controller_manager.setup(connection: @connection, logger: @logger)
|
20
21
|
@controller_manager.start(:all)
|
21
22
|
|
data/log_generator.rb
CHANGED
@@ -5,7 +5,7 @@ SEVERITIES = [:fatal] + [:error]*3 + [:warn]*5 + [:info]*12 + [:debug]*20
|
|
5
5
|
log = Logger.new('sample.log')
|
6
6
|
|
7
7
|
10000.times do
|
8
|
-
|
9
|
-
|
8
|
+
severity = SEVERITIES.sample
|
9
|
+
log.send severity, "Sample #{severity}"
|
10
10
|
end
|
11
11
|
|
@@ -11,7 +11,7 @@ class ControllerFunctionalTest < Minitest::Test
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class CustomTopicMessage < Tochtli::Message
|
14
|
-
|
14
|
+
route_to { "fn.test.#{key}.#{action}" }
|
15
15
|
|
16
16
|
attribute :resource, String
|
17
17
|
|
@@ -40,7 +40,7 @@ class ControllerFunctionalTest < Minitest::Test
|
|
40
40
|
off 'fn.test.off.accept'
|
41
41
|
|
42
42
|
on CustomTopicMessage, routing_key: 'fn.test.*.reject' do
|
43
|
-
|
43
|
+
reply TestCustomReply.new(:message => "#{message.resource} rejected")
|
44
44
|
end
|
45
45
|
|
46
46
|
def echo
|
@@ -56,20 +56,20 @@ class ControllerManagerTest < Minitest::Test
|
|
56
56
|
Tochtli::ControllerManager.start(FirstController, connection: @connection, logger: @logger)
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
def test_multiple_queues
|
60
|
+
Tochtli::ControllerManager.setup(connection: @connection, logger: @logger)
|
61
|
+
Tochtli::ControllerManager.start(FirstController, queue_name: 'first_queue')
|
62
|
+
Tochtli::ControllerManager.start(FirstController, queue_name: 'second_queue')
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
assert_equal ["first_queue", "second_queue"], FirstController.dispatcher.queues.map(&:name).sort
|
65
|
+
end
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
def test_restart_multiple_queues
|
68
|
+
Tochtli::ControllerManager.setup(connection: @connection, logger: @logger)
|
69
|
+
Tochtli::ControllerManager.start(FirstController, queue_name: 'first_queue')
|
70
|
+
Tochtli::ControllerManager.start(FirstController, queue_name: 'second_queue')
|
71
|
+
Tochtli::ControllerManager.restart
|
72
72
|
|
73
|
-
|
74
|
-
|
73
|
+
assert_equal ["first_queue", "second_queue"], FirstController.dispatcher.queues.map(&:name).sort
|
74
|
+
end
|
75
75
|
end
|
data/test/key_matcher_test.rb
CHANGED
@@ -2,99 +2,99 @@ require_relative 'test_helper'
|
|
2
2
|
require 'benchmark'
|
3
3
|
|
4
4
|
class MessageTest < Minitest::Test
|
5
|
-
|
5
|
+
KeyPattern = Tochtli::BaseController::KeyPattern
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
def test_simple_pattern
|
8
|
+
pattern = KeyPattern.new('a.b.c')
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
assert_matches pattern, 'a.b.c'
|
11
|
+
refute_matches pattern, 'a.b.c.d'
|
12
|
+
refute_matches pattern, 'b.c.d'
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
def test_asterix_at_start
|
16
|
+
pattern = KeyPattern.new('*.b.c')
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
assert_matches pattern, 'a.b.c'
|
19
|
+
assert_matches pattern, 'b.b.c'
|
20
|
+
refute_matches pattern, 'a.b.c.d'
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
def test_asterix_in_the_middle
|
24
|
+
pattern = KeyPattern.new('a.*.b.c')
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
assert_matches pattern, 'a.a.b.c'
|
27
|
+
assert_matches pattern, 'a.d.b.c'
|
28
|
+
refute_matches pattern, 'a.b.c.d'
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
def test_asterix_at_the_end
|
32
|
+
pattern = KeyPattern.new('a.b.c.*')
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
assert_matches pattern, 'a.b.c.d'
|
35
|
+
assert_matches pattern, 'a.b.c.a'
|
36
|
+
refute_matches pattern, 'a.b.c'
|
37
|
+
refute_matches pattern, 'a.b.c.d.e'
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
def test_hash
|
41
|
+
pattern = KeyPattern.new('#')
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
assert_matches pattern, ''
|
44
|
+
assert_matches pattern, 'a.b.c'
|
45
|
+
assert_matches pattern, 'a.b.b.c'
|
46
|
+
end
|
47
47
|
|
48
|
-
|
49
|
-
|
48
|
+
def test_hash_at_start
|
49
|
+
pattern = KeyPattern.new('#.b.c')
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
assert_matches pattern, 'b.c'
|
52
|
+
assert_matches pattern, 'a.b.c'
|
53
|
+
assert_matches pattern, 'a.b.b.c'
|
54
|
+
refute_matches pattern, 'a.b.c.d'
|
55
|
+
end
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
def test_hash_in_the_middle
|
58
|
+
pattern = KeyPattern.new('a.#.c')
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
assert_matches pattern, 'a.a.b.c'
|
61
|
+
assert_matches pattern, 'a.d.b.c'
|
62
|
+
refute_matches pattern, 'a.b.c.d'
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
65
|
+
def test_hash_at_the_end
|
66
|
+
pattern = KeyPattern.new('a.b.#')
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
assert_matches pattern, 'a.b.c.d'
|
69
|
+
assert_matches pattern, 'a.b.c'
|
70
|
+
assert_matches pattern, 'a.b.c.d.e'
|
71
|
+
end
|
72
72
|
|
73
|
-
|
74
|
-
|
73
|
+
def test_complex
|
74
|
+
pattern = KeyPattern.new('*.*.a.b.#.c.#')
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
assert_matches pattern, '1.2.a.b.c.d'
|
77
|
+
assert_matches pattern, '1.2.a.b.3.4.c.d'
|
78
|
+
assert_matches pattern, '1.2.a.b.c'
|
79
|
+
refute_matches pattern, 'a.b.c.d.e'
|
80
|
+
end
|
81
81
|
|
82
|
-
|
83
|
-
|
82
|
+
def test_performance
|
83
|
+
pattern = KeyPattern.new('*.*.a.b.#.c.#')
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
n = 10_000
|
86
|
+
time = Benchmark.realtime { n.times { pattern =~ '1.2.a.b.3.4.c.d' } }
|
87
87
|
|
88
|
-
|
89
|
-
|
88
|
+
assert time < 0.1
|
89
|
+
end
|
90
90
|
|
91
|
-
|
91
|
+
protected
|
92
92
|
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
def assert_matches(pattern, key)
|
94
|
+
assert pattern =~ key, "#{key} SHOULD match #{pattern}"
|
95
|
+
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
def refute_matches(pattern, key)
|
98
|
+
assert pattern !~ key, "#{key} MUST NOT match #{pattern}"
|
99
|
+
end
|
100
100
|
end
|
@@ -1,151 +1,151 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
require 'tochtli/test/test_case'
|
3
|
-
|
4
|
-
class RabbitConnectionTest < Minitest::Test
|
5
|
-
def setup
|
6
|
-
Tochtli::RabbitConnection.close('test')
|
7
|
-
end
|
8
|
-
|
9
|
-
def teardown
|
10
|
-
Tochtli::RabbitConnection.close('test')
|
11
|
-
end
|
12
|
-
|
13
|
-
class TestMessage < Tochtli::Message
|
14
|
-
attributes :text
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_connection_with_default_options
|
18
|
-
Tochtli::RabbitConnection.open('test') do |connection|
|
19
|
-
assert_equal "puzzleflow.services", connection.exchange.name
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_connection_with_custom_options
|
24
|
-
Tochtli::RabbitConnection.open('test', exchange_name: "puzzleflow.tests") do |connection|
|
25
|
-
assert_equal "puzzleflow.tests", connection.exchange.name
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_multiple_channels_and_exchanges
|
30
|
-
Tochtli::RabbitConnection.open('test', exchange_name: "puzzleflow.tests") do |connection|
|
31
|
-
another_thread = Thread.new {}
|
32
|
-
|
33
|
-
current_channel = connection.channel
|
34
|
-
another_channel = connection.channel(another_thread)
|
35
|
-
|
36
|
-
current_exchange = connection.exchange
|
37
|
-
another_exchange = connection.exchange(another_thread)
|
38
|
-
|
39
|
-
refute_equal current_channel, another_channel
|
40
|
-
refute_equal current_exchange, another_exchange
|
41
|
-
assert_equal "puzzleflow.tests", current_exchange.name
|
42
|
-
assert_equal "puzzleflow.tests", another_exchange.name
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_queue_creation_and_existance
|
47
|
-
Tochtli::RabbitConnection.open('test') do |connection|
|
48
|
-
queue = connection.queue('test-queue', [], auto_delete: true)
|
49
|
-
refute_nil queue
|
50
|
-
assert_equal 'test-queue', queue.name
|
51
|
-
assert connection.queue_exists?('test-queue')
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_reply_queue_recovery
|
56
|
-
Tochtli::RabbitConnection.open('test',
|
57
|
-
network_recovery_interval: 0.1,
|
58
|
-
recover_from_connection_close: true) do |rabbit_connection|
|
59
|
-
reply_queue = rabbit_connection.reply_queue
|
60
|
-
original_name = reply_queue.name
|
61
|
-
timeout = 0.3
|
62
|
-
|
63
|
-
message = TestMessage.new(text: "Response")
|
64
|
-
reply = TestMessage.new(text: "Reply")
|
65
|
-
handler = Tochtli::Test::TestMessageHandler.new
|
66
|
-
reply_queue.register_message_handler message, handler, timeout
|
67
|
-
|
68
|
-
rabbit_connection.publish reply_queue.name, reply, correlation_id: message.id, timeout: timeout
|
69
|
-
sleep timeout
|
70
|
-
|
71
|
-
refute_nil handler.reply
|
72
|
-
|
73
|
-
# simulate network failure
|
74
|
-
rabbit_connection.connection.handle_network_failure(RuntimeError.new('fake connection error'))
|
75
|
-
sleep 0.1 until rabbit_connection.open? # wait for recovery
|
76
|
-
refute_equal original_name, reply_queue.name, "Recovered queue should have re-generated name"
|
77
|
-
|
78
|
-
message = TestMessage.new(text: "Response")
|
79
|
-
reply = TestMessage.new(text: "Reply")
|
80
|
-
handler = Tochtli::Test::TestMessageHandler.new
|
81
|
-
reply_queue.register_message_handler message, handler, timeout
|
82
|
-
|
83
|
-
rabbit_connection.publish reply_queue.name, reply, correlation_id: message.id, timeout: timeout
|
84
|
-
sleep timeout
|
85
|
-
|
86
|
-
refute_nil handler.reply
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def test_multithreaded_consumer_performance
|
91
|
-
work_pool_size = 10
|
92
|
-
Tochtli::RabbitConnection.open('test',
|
93
|
-
exchange_name: "puzzleflow.tests",
|
94
|
-
work_pool_size: work_pool_size) do |connection|
|
95
|
-
mutex = Mutex.new
|
96
|
-
cv = ConditionVariable.new
|
97
|
-
thread_count = 5
|
98
|
-
message_count = 100
|
99
|
-
expected_message_count = message_count*thread_count
|
100
|
-
|
101
|
-
consumed = 0
|
102
|
-
consumed_mutex = Mutex.new
|
103
|
-
consumer_threads = Set.new
|
104
|
-
consumer = Proc.new do |delivery_info, metadata, payload|
|
105
|
-
consumed_mutex.synchronize { consumed += 1 }
|
106
|
-
consumer_threads << Thread.current
|
107
|
-
connection.publish metadata.reply_to, TestMessage.new(text: "Response to #{payload}")
|
108
|
-
end
|
109
|
-
|
110
|
-
queue = connection.channel.queue('', auto_delete: true)
|
111
|
-
queue.bind(connection.exchange, routing_key: queue.name)
|
112
|
-
queue.subscribe(block: false, &consumer)
|
113
|
-
|
114
|
-
replies = 0
|
115
|
-
reply_consumer = Proc.new do |delivery_info, metadata, payload|
|
116
|
-
replies += 1
|
117
|
-
mutex.synchronize { cv.signal } if replies == expected_message_count
|
118
|
-
end
|
119
|
-
|
120
|
-
reply_queue = connection.channel.queue('', auto_delete: true)
|
121
|
-
reply_queue.bind(connection.exchange, routing_key: reply_queue.name)
|
122
|
-
reply_queue.subscribe(block: false, &reply_consumer)
|
123
|
-
|
124
|
-
start_t = Time.now
|
125
|
-
|
126
|
-
threads = (1..thread_count).collect do
|
127
|
-
t = Thread.new do
|
128
|
-
message_count.times do |i|
|
129
|
-
connection.publish queue.name, TestMessage.new(text: "Message #{i}"),
|
130
|
-
reply_to: reply_queue.name
|
131
|
-
end
|
132
|
-
end
|
133
|
-
t.abort_on_exception = true
|
134
|
-
t
|
135
|
-
end
|
136
|
-
|
137
|
-
threads.each(&:join)
|
138
|
-
|
139
|
-
mutex.synchronize { cv.wait(mutex, 5.0) }
|
140
|
-
|
141
|
-
end_t = Time.now
|
142
|
-
time = end_t - start_t
|
143
|
-
|
144
|
-
assert_equal expected_message_count, consumed
|
145
|
-
assert_equal expected_message_count, replies
|
146
|
-
assert_equal work_pool_size, consumer_threads.size
|
147
|
-
|
148
|
-
puts "Published: #{expected_message_count} in #{time} (#{expected_message_count/time}req/s)"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'tochtli/test/test_case'
|
3
|
+
|
4
|
+
class RabbitConnectionTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
Tochtli::RabbitConnection.close('test')
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
Tochtli::RabbitConnection.close('test')
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestMessage < Tochtli::Message
|
14
|
+
attributes :text
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_connection_with_default_options
|
18
|
+
Tochtli::RabbitConnection.open('test') do |connection|
|
19
|
+
assert_equal "puzzleflow.services", connection.exchange.name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_connection_with_custom_options
|
24
|
+
Tochtli::RabbitConnection.open('test', exchange_name: "puzzleflow.tests") do |connection|
|
25
|
+
assert_equal "puzzleflow.tests", connection.exchange.name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_multiple_channels_and_exchanges
|
30
|
+
Tochtli::RabbitConnection.open('test', exchange_name: "puzzleflow.tests") do |connection|
|
31
|
+
another_thread = Thread.new {}
|
32
|
+
|
33
|
+
current_channel = connection.channel
|
34
|
+
another_channel = connection.channel(another_thread)
|
35
|
+
|
36
|
+
current_exchange = connection.exchange
|
37
|
+
another_exchange = connection.exchange(another_thread)
|
38
|
+
|
39
|
+
refute_equal current_channel, another_channel
|
40
|
+
refute_equal current_exchange, another_exchange
|
41
|
+
assert_equal "puzzleflow.tests", current_exchange.name
|
42
|
+
assert_equal "puzzleflow.tests", another_exchange.name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_queue_creation_and_existance
|
47
|
+
Tochtli::RabbitConnection.open('test') do |connection|
|
48
|
+
queue = connection.queue('test-queue', [], auto_delete: true)
|
49
|
+
refute_nil queue
|
50
|
+
assert_equal 'test-queue', queue.name
|
51
|
+
assert connection.queue_exists?('test-queue')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_reply_queue_recovery
|
56
|
+
Tochtli::RabbitConnection.open('test',
|
57
|
+
network_recovery_interval: 0.1,
|
58
|
+
recover_from_connection_close: true) do |rabbit_connection|
|
59
|
+
reply_queue = rabbit_connection.reply_queue
|
60
|
+
original_name = reply_queue.name
|
61
|
+
timeout = 0.3
|
62
|
+
|
63
|
+
message = TestMessage.new(text: "Response")
|
64
|
+
reply = TestMessage.new(text: "Reply")
|
65
|
+
handler = Tochtli::Test::TestMessageHandler.new
|
66
|
+
reply_queue.register_message_handler message, handler, timeout
|
67
|
+
|
68
|
+
rabbit_connection.publish reply_queue.name, reply, correlation_id: message.id, timeout: timeout
|
69
|
+
sleep timeout
|
70
|
+
|
71
|
+
refute_nil handler.reply
|
72
|
+
|
73
|
+
# simulate network failure
|
74
|
+
rabbit_connection.connection.handle_network_failure(RuntimeError.new('fake connection error'))
|
75
|
+
sleep 0.1 until rabbit_connection.open? # wait for recovery
|
76
|
+
refute_equal original_name, reply_queue.name, "Recovered queue should have re-generated name"
|
77
|
+
|
78
|
+
message = TestMessage.new(text: "Response")
|
79
|
+
reply = TestMessage.new(text: "Reply")
|
80
|
+
handler = Tochtli::Test::TestMessageHandler.new
|
81
|
+
reply_queue.register_message_handler message, handler, timeout
|
82
|
+
|
83
|
+
rabbit_connection.publish reply_queue.name, reply, correlation_id: message.id, timeout: timeout
|
84
|
+
sleep timeout
|
85
|
+
|
86
|
+
refute_nil handler.reply
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_multithreaded_consumer_performance
|
91
|
+
work_pool_size = 10
|
92
|
+
Tochtli::RabbitConnection.open('test',
|
93
|
+
exchange_name: "puzzleflow.tests",
|
94
|
+
work_pool_size: work_pool_size) do |connection|
|
95
|
+
mutex = Mutex.new
|
96
|
+
cv = ConditionVariable.new
|
97
|
+
thread_count = 5
|
98
|
+
message_count = 100
|
99
|
+
expected_message_count = message_count*thread_count
|
100
|
+
|
101
|
+
consumed = 0
|
102
|
+
consumed_mutex = Mutex.new
|
103
|
+
consumer_threads = Set.new
|
104
|
+
consumer = Proc.new do |delivery_info, metadata, payload|
|
105
|
+
consumed_mutex.synchronize { consumed += 1 }
|
106
|
+
consumer_threads << Thread.current
|
107
|
+
connection.publish metadata.reply_to, TestMessage.new(text: "Response to #{payload}")
|
108
|
+
end
|
109
|
+
|
110
|
+
queue = connection.channel.queue('', auto_delete: true)
|
111
|
+
queue.bind(connection.exchange, routing_key: queue.name)
|
112
|
+
queue.subscribe(block: false, &consumer)
|
113
|
+
|
114
|
+
replies = 0
|
115
|
+
reply_consumer = Proc.new do |delivery_info, metadata, payload|
|
116
|
+
replies += 1
|
117
|
+
mutex.synchronize { cv.signal } if replies == expected_message_count
|
118
|
+
end
|
119
|
+
|
120
|
+
reply_queue = connection.channel.queue('', auto_delete: true)
|
121
|
+
reply_queue.bind(connection.exchange, routing_key: reply_queue.name)
|
122
|
+
reply_queue.subscribe(block: false, &reply_consumer)
|
123
|
+
|
124
|
+
start_t = Time.now
|
125
|
+
|
126
|
+
threads = (1..thread_count).collect do
|
127
|
+
t = Thread.new do
|
128
|
+
message_count.times do |i|
|
129
|
+
connection.publish queue.name, TestMessage.new(text: "Message #{i}"),
|
130
|
+
reply_to: reply_queue.name
|
131
|
+
end
|
132
|
+
end
|
133
|
+
t.abort_on_exception = true
|
134
|
+
t
|
135
|
+
end
|
136
|
+
|
137
|
+
threads.each(&:join)
|
138
|
+
|
139
|
+
mutex.synchronize { cv.wait(mutex, 5.0) }
|
140
|
+
|
141
|
+
end_t = Time.now
|
142
|
+
time = end_t - start_t
|
143
|
+
|
144
|
+
assert_equal expected_message_count, consumed
|
145
|
+
assert_equal expected_message_count, replies
|
146
|
+
assert_equal work_pool_size, consumer_threads.size
|
147
|
+
|
148
|
+
puts "Published: #{expected_message_count} in #{time} (#{expected_message_count/time}req/s)"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/test/version_test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
3
|
class VersionTest < Minitest::Test
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def test_version_match
|
5
|
+
spec = Gem::Specification::load(File.expand_path('../../tochtli.gemspec', __FILE__))
|
6
|
+
assert_equal Tochtli::VERSION, spec.version.to_s, "Gem version mismatch. Run: 'rake gemspec'"
|
7
|
+
end
|
8
8
|
end
|
data/tochtli.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: tochtli 0.5.
|
5
|
+
# stub: tochtli 0.5.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "tochtli"
|
9
|
-
s.version = "0.5.
|
9
|
+
s.version = "0.5.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Rafal Bigaj"]
|
14
|
-
s.date = "2015-08-
|
14
|
+
s.date = "2015-08-20"
|
15
15
|
s.description = "Lightweight framework for service oriented applications based on bunny (RabbitMQ)"
|
16
16
|
s.email = "rafal.bigaj@puzzleflow.com"
|
17
17
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tochtli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rafal Bigaj
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|