banter 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- def initialize(subscribers)
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
- @workers.each do |worker|
26
- puts "Starting worker: #{worker.worker_class.name}"
27
- worker.start
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
- ::Banter::RabbitLogger.log_service("all_services", "Starting shutdown of all services")
42
-
43
- @workers.each do |worker|
44
- ::Banter::RabbitLogger.log_service("all_services", "Tearing down worker: #{worker.worker_class.name}")
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
- ensure
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 warn(message)
59
- old_behavior = ActiveSupport::Deprecation.behavior
60
- ActiveSupport::Deprecation.behavior = [:stderr, :log]
61
- ActiveSupport::Deprecation.warn(message)
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)
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Banter
2
- VERSION = "0.5.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -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,9 @@
1
+ module Banter
2
+ module Web
3
+ module Helpers
4
+ def root_path
5
+ "#{env['SCRIPT_NAME']}/"
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -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(subscriber_server).to receive(:start)
121
- allow(Banter::Server::SubscriberServer).to receive(:new).and_return(subscriber_server)
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(:new).with(all_subscribers)
132
- expect(subscriber_server).to have_received(:start)
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(:new).with([MyTestSubscriber1, MyTestSubscriber2])
140
- expect(subscriber_server).to have_received(:start)
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