tochtli 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/PuzzleFlow/tochtli.svg?branch=master)](https://travis-ci.org/PuzzleFlow/tochtli)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/tochtli.svg)](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
|