banter 0.5.0 → 0.7.0
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/.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
|