banter 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/README.md +37 -3
- data/lib/banter.rb +4 -0
- data/lib/banter/cli.rb +13 -3
- data/lib/banter/configuration.rb +10 -0
- data/lib/banter/middleware.rb +9 -1
- data/lib/banter/publisher.rb +45 -4
- data/lib/banter/rabbit_logger.rb +22 -4
- data/lib/banter/railtie.rb +6 -1
- data/lib/banter/server/client_queue_listener.rb +31 -7
- data/lib/banter/server/rabbit_mq_subscriber.rb +2 -1
- data/lib/banter/server/subscriber_server.rb +55 -23
- data/lib/banter/subscriber.rb +0 -1
- data/lib/banter/version.rb +1 -1
- data/lib/banter/web.rb +28 -0
- data/lib/banter/web/application.rb +36 -0
- data/lib/banter/web/helpers.rb +9 -0
- data/lib/banter/web/models/banter_message.rb +82 -0
- data/lib/banter/web/models/banter_worker.rb +68 -0
- data/spec/banter/cli_spec.rb +28 -6
- data/spec/banter/publisher_spec.rb +78 -0
- data/spec/banter/rabbit_logger_spec.rb +27 -2
- data/spec/banter/server/client_queue_listener_spec.rb +86 -22
- data/spec/banter/server/subscriber_server_spec.rb +87 -0
- data/spec/banter/web/application_spec.rb +10 -0
- data/spec/factories/banter_workers.rb +13 -0
- data/spec/mongoid.yml +7 -0
- data/spec/spec_helper.rb +28 -9
- data/web/assets/stylesheets/banter.css +66 -0
- data/web/views/index.haml +32 -0
- data/web/views/layout.haml +45 -0
- data/web/views/message.haml +52 -0
- data/web/views/messages.haml +20 -0
- data/web/views/subscribers.haml +17 -0
- data/web/views/worker.haml +51 -0
- metadata +24 -2
@@ -5,6 +5,7 @@ require 'awesome_print'
|
|
5
5
|
require 'active_support/all'
|
6
6
|
require 'optparse'
|
7
7
|
require 'fileutils'
|
8
|
+
require 'singleton'
|
8
9
|
|
9
10
|
require_relative './client_queue_listener'
|
10
11
|
|
@@ -13,20 +14,57 @@ require_relative './client_queue_listener'
|
|
13
14
|
|
14
15
|
module Banter
|
15
16
|
module Server
|
16
|
-
|
17
17
|
class SubscriberServer
|
18
18
|
include Celluloid
|
19
19
|
|
20
|
-
|
20
|
+
attr_reader :workers, :process_name, :pid, :hostname, :banter_worker_id
|
21
|
+
|
22
|
+
def self.instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_workers(subscribers)
|
21
27
|
@workers = subscribers.map { |subscriber| create_queue_listeners(subscriber) }
|
22
28
|
end
|
23
29
|
|
24
30
|
def start
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
set_information
|
32
|
+
register_workers
|
33
|
+
start_workers
|
34
|
+
handle_interrupts
|
35
|
+
handle_teardown
|
36
|
+
ensure
|
37
|
+
stop_worker
|
38
|
+
end
|
39
|
+
|
40
|
+
def register_workers
|
41
|
+
return unless Banter::Configuration.web_enabled
|
42
|
+
BanterWorker.worker_started
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def handle_teardown
|
48
|
+
::Banter::RabbitLogger.log_service("all_services", "Starting shutdown of all services")
|
49
|
+
unregister_current_worker
|
50
|
+
|
51
|
+
workers.each do |worker|
|
52
|
+
::Banter::RabbitLogger.log_service("all_services", "Tearing down worker: #{worker.worker_class.name}")
|
53
|
+
begin
|
54
|
+
STDOUT.puts "Tearing down subscriber for #{worker.worker_class.name}"
|
55
|
+
worker.shutdown
|
56
|
+
rescue => e
|
57
|
+
::Banter::RabbitLogger.log_service("all_services", "#{worker.worker_class.name} - did not tear down correctly. Error - #{e.message}")
|
58
|
+
end
|
28
59
|
end
|
60
|
+
end
|
29
61
|
|
62
|
+
def unregister_current_worker
|
63
|
+
return unless Banter::Configuration.web_enabled
|
64
|
+
BanterWorker.worker_stopped
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_interrupts
|
30
68
|
thread = Thread.current
|
31
69
|
interrupts = ["HUP", "INT", "QUIT", "ABRT", "TERM"]
|
32
70
|
interrupts.each do |signal_name|
|
@@ -37,30 +75,24 @@ module Banter
|
|
37
75
|
end
|
38
76
|
|
39
77
|
Thread.stop
|
78
|
+
end
|
40
79
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
begin
|
46
|
-
STDOUT.puts "Tearing down subscriber for #{worker.worker_class.name}"
|
47
|
-
worker.shutdown
|
48
|
-
rescue => e
|
49
|
-
::Banter::RabbitLogger.log_service("all_services", "#{worker.worker_class.name} - did not tear down correctly. Error - #{e.message}")
|
50
|
-
end
|
80
|
+
def start_workers
|
81
|
+
workers.each do |worker|
|
82
|
+
puts "starting workers"
|
83
|
+
worker.start
|
51
84
|
end
|
52
|
-
|
85
|
+
end
|
86
|
+
|
87
|
+
def stop_worker
|
53
88
|
Banter::CLI.instance.remove_pid
|
54
89
|
end
|
55
90
|
|
56
|
-
private
|
57
91
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
ensure
|
63
|
-
ActiveSupport::Deprecation.behavior = old_behavior
|
92
|
+
def set_information
|
93
|
+
@process_name = $0
|
94
|
+
@pid = Process.pid
|
95
|
+
@hostname = `hostname -f`.chomp
|
64
96
|
end
|
65
97
|
|
66
98
|
def create_queue_listeners(subscriber)
|
data/lib/banter/subscriber.rb
CHANGED
@@ -60,7 +60,6 @@ module Banter
|
|
60
60
|
# @param [Object] payload Payload of the message
|
61
61
|
def perform!(payload)
|
62
62
|
if !valid_payload?(payload)
|
63
|
-
Banter.logger.error("Payload validation failed for #{self.class.name}")
|
64
63
|
raise ::Banter::PayloadValidationError.new("Invalid Payload for #{self.class.name}")
|
65
64
|
end
|
66
65
|
|
data/lib/banter/version.rb
CHANGED
data/lib/banter/web.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'sinatra/base'
|
3
|
+
rescue LoadError
|
4
|
+
raise "sinatra must be installed for banter/web to work. Add gem 'sinatra' to your Gemfile"
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'haml'
|
9
|
+
rescue LoadError
|
10
|
+
raise "haml must be installed for banter/web to work. Add gem 'haml' to your Gemfile"
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'mongoid'
|
15
|
+
rescue LoadError
|
16
|
+
raise "mongoid must be installed for banter/web to work. Add gem 'mongoid' to your Gemfile"
|
17
|
+
end
|
18
|
+
|
19
|
+
module Banter
|
20
|
+
module Web
|
21
|
+
autoload :Application, 'banter/web/application'
|
22
|
+
autoload :Helpers, 'banter/web/helpers'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'banter'
|
27
|
+
require 'banter/web/models/banter_worker'
|
28
|
+
require 'banter/web/models/banter_message'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Banter
|
2
|
+
module Web
|
3
|
+
class Application < Sinatra::Base
|
4
|
+
set :root, File.expand_path(File.dirname(__FILE__) + "/../../../web")
|
5
|
+
set :public_folder, Proc.new { "#{root}/assets" }
|
6
|
+
set :views, Proc.new { "#{root}/views" }
|
7
|
+
|
8
|
+
helpers Banter::Web::Helpers
|
9
|
+
|
10
|
+
get '/' do
|
11
|
+
@workers = BanterWorker.running.ordered
|
12
|
+
haml :index, locals: { workers: @workers }
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/workers/:id' do
|
16
|
+
worker = BanterWorker.find(params[:id])
|
17
|
+
haml :worker, locals: { worker: worker }
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/messages/:id' do
|
21
|
+
message = BanterMessage.find(params[:id])
|
22
|
+
haml :message, locals: { message: message }
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/workers/:worker_id/messages' do
|
26
|
+
messages = BanterMessage.where(worker_id: Moped::BSON::ObjectId.from_string(params[:worker_id])).ordered
|
27
|
+
worker = BanterWorker.find(params[:worker_id])
|
28
|
+
haml :messages, locals: { messages: messages, worker: worker }
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/subscribers' do
|
32
|
+
haml :subscribers
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class BanterMessage
|
2
|
+
include Mongoid::Document
|
3
|
+
|
4
|
+
field :subscriber_class, type: String
|
5
|
+
field :queue_name, type: String
|
6
|
+
field :subscribed_key, type: String
|
7
|
+
field :payload_key, type: String
|
8
|
+
field :routing_data
|
9
|
+
field :properties
|
10
|
+
field :payload
|
11
|
+
field :context
|
12
|
+
field :started_at, type: Time
|
13
|
+
field :done_at, type: Time
|
14
|
+
field :status, type: String
|
15
|
+
field :error_message, type: String
|
16
|
+
field :worker_id
|
17
|
+
# embeds_one :worker, class_name: 'BanterWorker'
|
18
|
+
|
19
|
+
STATUS_STARTED = 'started'
|
20
|
+
STATUS_SUCCESS = 'success'
|
21
|
+
STATUS_ERROR = 'error'
|
22
|
+
STATUS_VALIDATION_FAILED = 'validation_failed'
|
23
|
+
|
24
|
+
cattr_reader :current
|
25
|
+
|
26
|
+
scope :ordered, ->() { order_by(:started_at.desc) }
|
27
|
+
|
28
|
+
def self.message_received(subscriber_class, worker, payload)
|
29
|
+
return unless BanterWorker.current
|
30
|
+
|
31
|
+
obj = new(
|
32
|
+
subscriber_class: subscriber_class.name,
|
33
|
+
queue_name: subscriber_class.subscribed_queue,
|
34
|
+
subscribed_key: subscriber_class.subscribed_key,
|
35
|
+
payload_key: worker.routing_key,
|
36
|
+
# routing_data: worker.delivery_routing_data.to_hash,
|
37
|
+
# properties: worker.delivery_properties.to_hash,
|
38
|
+
payload: payload,
|
39
|
+
context: worker.context,
|
40
|
+
started_at: Time.now,
|
41
|
+
status: STATUS_STARTED,
|
42
|
+
worker_id: BanterWorker.current.id
|
43
|
+
)
|
44
|
+
|
45
|
+
obj.save!
|
46
|
+
@@current = obj
|
47
|
+
BanterWorker.record_current!(obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.message_successful
|
51
|
+
current.status = STATUS_SUCCESS
|
52
|
+
current.done_at = Time.now
|
53
|
+
current.save!
|
54
|
+
BanterWorker.clear_current_message!(true)
|
55
|
+
@@current = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.message_failed(error_message, status)
|
59
|
+
current.status = status
|
60
|
+
current.done_at = Time.now
|
61
|
+
current.error_message = error_message
|
62
|
+
current.save!
|
63
|
+
BanterWorker.clear_current_message!(false)
|
64
|
+
@@current = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def executing?
|
68
|
+
self.status == STATUS_STARTED
|
69
|
+
end
|
70
|
+
|
71
|
+
def succeeded?
|
72
|
+
self.status == STATUS_SUCCESS
|
73
|
+
end
|
74
|
+
|
75
|
+
def failed?
|
76
|
+
self.status == STATUS_ERROR || self.status == STATUS_VALIDATION_FAILED
|
77
|
+
end
|
78
|
+
|
79
|
+
def worker
|
80
|
+
@worker ||= BanterWorker.find(self['worker_id'].to_s)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class BanterWorker
|
2
|
+
include Mongoid::Document
|
3
|
+
field :process_name, type: String
|
4
|
+
field :pid, type: Integer
|
5
|
+
field :worker_classes
|
6
|
+
field :hostname, type: String
|
7
|
+
field :started_at, type: Time
|
8
|
+
field :stopped_at, type: Time
|
9
|
+
field :current_message
|
10
|
+
field :job_count, type: Integer, default: 0
|
11
|
+
field :success_count, type: Integer, default: 0
|
12
|
+
field :failed_count, type: Integer, default: 0
|
13
|
+
|
14
|
+
cattr_reader :current
|
15
|
+
|
16
|
+
scope :running, ->() { where(:stopped_at => nil) }
|
17
|
+
scope :ordered, ->() { order_by(:started_at.desc) }
|
18
|
+
|
19
|
+
# @param [String] process_name
|
20
|
+
# @param [String] process_pid
|
21
|
+
# @param [String] hostname
|
22
|
+
# @param [Array(Subscriber)] workers The subscriber classes that are being started by this worker
|
23
|
+
def self.worker_started
|
24
|
+
server_instance = Banter::Server::SubscriberServer.instance
|
25
|
+
worker = new(process_name: server_instance.process_name,
|
26
|
+
pid: server_instance.pid,
|
27
|
+
hostname: server_instance.hostname,
|
28
|
+
started_at: Time.now,
|
29
|
+
worker_classes: Array.wrap(server_instance.workers).map(&:worker_class).map(&:name))
|
30
|
+
worker.save!
|
31
|
+
@@current = worker
|
32
|
+
worker
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.worker_stopped
|
36
|
+
current.try(:stop!)
|
37
|
+
@@current = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [BanterMessage] banter_message Message that is currently being executed
|
41
|
+
def self.record_current!(banter_message)
|
42
|
+
current.current_message = banter_message.attributes
|
43
|
+
|
44
|
+
current.save!
|
45
|
+
end
|
46
|
+
|
47
|
+
# Clears the current message key for the worker
|
48
|
+
# @param [Boolean] success Indicate whether the job was successful or not
|
49
|
+
def self.clear_current_message!(success = true)
|
50
|
+
current.current_message = nil
|
51
|
+
current.job_count += 1
|
52
|
+
if success
|
53
|
+
current.success_count += 1
|
54
|
+
else
|
55
|
+
current.failed_count += 1
|
56
|
+
end
|
57
|
+
current.save!
|
58
|
+
end
|
59
|
+
|
60
|
+
def executing?
|
61
|
+
current_message.present?
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop!
|
65
|
+
self.stopped_at = Time.now
|
66
|
+
save!
|
67
|
+
end
|
68
|
+
end
|
data/spec/banter/cli_spec.rb
CHANGED
@@ -53,6 +53,26 @@ describe Banter::CLI do
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
context "process name" do
|
57
|
+
it "works when nothing is passed" do
|
58
|
+
expect(cli.process_name).to be_nil
|
59
|
+
end
|
60
|
+
|
61
|
+
context "passed in as -n" do
|
62
|
+
let(:options) { [ "-n", "dontkillme"]}
|
63
|
+
it "parses" do
|
64
|
+
expect(cli.process_name).to eq('dontkillme')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "passed in as --name" do
|
69
|
+
let(:options) { [ "--name", "dontkillme"]}
|
70
|
+
it "parses" do
|
71
|
+
expect(cli.process_name).to eq('dontkillme')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
56
76
|
context "require" do
|
57
77
|
it "works when nothing is passed" do
|
58
78
|
expect(cli.require_path).to eq(".")
|
@@ -117,27 +137,29 @@ describe Banter::CLI do
|
|
117
137
|
let(:subscriber_server) { double('subscriber_server')}
|
118
138
|
before {
|
119
139
|
cli.subscribers = subscribers
|
120
|
-
allow(
|
121
|
-
allow(Banter::Server::SubscriberServer).to receive(:
|
140
|
+
allow(Banter::Server::SubscriberServer.instance).to receive(:set_workers)
|
141
|
+
allow(Banter::Server::SubscriberServer.instance).to receive(:start)
|
122
142
|
cli.send(:load_subscribers)
|
123
143
|
}
|
124
144
|
|
125
145
|
context "No subscribers passed through CLI" do
|
126
146
|
let(:all_subscribers) { Banter::Subscriber.class_variable_get(:@@registered_subscribers) }
|
147
|
+
|
127
148
|
it "loads all subscribers" do
|
128
149
|
expect(all_subscribers).to include(MyTestSubscriber1)
|
129
150
|
expect(all_subscribers).to include(MyTestSubscriber2)
|
130
151
|
expect(all_subscribers).to include(MyTestSubscriber3)
|
131
|
-
expect(Banter::Server::SubscriberServer).to have_received(:
|
132
|
-
expect(
|
152
|
+
expect(Banter::Server::SubscriberServer.instance).to have_received(:set_workers).with(all_subscribers)
|
153
|
+
expect(Banter::Server::SubscriberServer.instance).to have_received(:start)
|
133
154
|
end
|
155
|
+
|
134
156
|
end
|
135
157
|
|
136
158
|
context "Subscribers passed through CLI" do
|
137
159
|
let(:subscribers) { ['MyTestSubscriber1', 'MyTestSubscriber2'] }
|
138
160
|
it "loads all subscribers" do
|
139
|
-
expect(Banter::Server::SubscriberServer).to have_received(:
|
140
|
-
expect(
|
161
|
+
expect(Banter::Server::SubscriberServer.instance).to have_received(:set_workers).with([MyTestSubscriber1, MyTestSubscriber2])
|
162
|
+
expect(Banter::Server::SubscriberServer.instance).to have_received(:start)
|
141
163
|
end
|
142
164
|
end
|
143
165
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bunny'
|
3
|
+
|
4
|
+
describe Banter::Publisher do
|
5
|
+
let(:routing_key) { "test/logger" }
|
6
|
+
let(:publisher) { Banter::Publisher.new }
|
7
|
+
let(:context) { {unique_id: "1234", orig_ip_address: "127.0.0.1"}}
|
8
|
+
let(:channel_mocker) {
|
9
|
+
double("::Bunny::Channel",
|
10
|
+
:topic=> double("::Bunny::Exchange",
|
11
|
+
:publish=> true,
|
12
|
+
:on_return=> true )
|
13
|
+
)
|
14
|
+
}
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(Banter::RabbitLogger).to receive(:log_publish)
|
18
|
+
allow_any_instance_of(::Bunny::Session).to receive(:start).and_return(true)
|
19
|
+
allow_any_instance_of(::Bunny::Session).to receive(:create_channel) { channel_mocker }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#delay_messages" do
|
23
|
+
|
24
|
+
context "no duplicates", :fixit=>true do
|
25
|
+
let(:result) {
|
26
|
+
publisher.delay_messages { operations } }
|
27
|
+
|
28
|
+
let(:operations) {
|
29
|
+
publisher.publish(context, "first", "one")
|
30
|
+
publisher.publish(context, "second", "two")
|
31
|
+
}
|
32
|
+
|
33
|
+
it "should attempt to publish twice" do
|
34
|
+
result
|
35
|
+
expect(Banter::RabbitLogger).to have_received(:log_publish).exactly(2).times
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context "duplicate entries" do
|
41
|
+
let(:result) { publisher.delay_messages { operations } }
|
42
|
+
|
43
|
+
let(:operations) {
|
44
|
+
publisher.publish(context, "first", "one")
|
45
|
+
publisher.publish(context, "second", "two")
|
46
|
+
publisher.publish(context, "second", "three")
|
47
|
+
}
|
48
|
+
|
49
|
+
before do
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should attempt to publish three times" do
|
54
|
+
expect(Banter::RabbitLogger).to have_received(:log_publish).exactly(3).times
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "adds to the queue when delaying messages" do
|
59
|
+
let(:result) { publisher.delay_messages { operations } }
|
60
|
+
let(:operations) {
|
61
|
+
publisher.publish(context, "first", "one")
|
62
|
+
publisher.publish(context, "second", "two")
|
63
|
+
}
|
64
|
+
|
65
|
+
it "should have added to the message list twice that will be published last" do
|
66
|
+
expect_any_instance_of(Banter::Publisher).to receive(:add_message).exactly(2).times
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should have added to the message list twice that will be published last" do
|
71
|
+
expect_any_instance_of(Banter::Publisher).to receive(:execute_publish).exactly(2).times
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|