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.
@@ -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