promiscuous 0.53.1 → 0.90.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.
Files changed (106) hide show
  1. data/lib/promiscuous.rb +25 -28
  2. data/lib/promiscuous/amqp.rb +27 -8
  3. data/lib/promiscuous/amqp/bunny.rb +131 -16
  4. data/lib/promiscuous/amqp/fake.rb +52 -0
  5. data/lib/promiscuous/amqp/hot_bunnies.rb +56 -0
  6. data/lib/promiscuous/amqp/null.rb +6 -6
  7. data/lib/promiscuous/cli.rb +108 -24
  8. data/lib/promiscuous/config.rb +73 -12
  9. data/lib/promiscuous/convenience.rb +18 -0
  10. data/lib/promiscuous/dependency.rb +59 -0
  11. data/lib/promiscuous/dsl.rb +36 -0
  12. data/lib/promiscuous/error.rb +3 -1
  13. data/lib/promiscuous/error/already_processed.rb +5 -0
  14. data/lib/promiscuous/error/base.rb +1 -0
  15. data/lib/promiscuous/error/connection.rb +7 -5
  16. data/lib/promiscuous/error/dependency.rb +111 -0
  17. data/lib/promiscuous/error/lock_unavailable.rb +12 -0
  18. data/lib/promiscuous/error/lost_lock.rb +12 -0
  19. data/lib/promiscuous/error/missing_context.rb +29 -0
  20. data/lib/promiscuous/error/publisher.rb +5 -15
  21. data/lib/promiscuous/error/recovery.rb +7 -0
  22. data/lib/promiscuous/error/subscriber.rb +2 -4
  23. data/lib/promiscuous/key.rb +36 -0
  24. data/lib/promiscuous/loader.rb +12 -16
  25. data/lib/promiscuous/middleware.rb +112 -0
  26. data/lib/promiscuous/publisher.rb +7 -4
  27. data/lib/promiscuous/publisher/context.rb +92 -0
  28. data/lib/promiscuous/publisher/mock_generator.rb +72 -0
  29. data/lib/promiscuous/publisher/model.rb +3 -86
  30. data/lib/promiscuous/publisher/model/active_record.rb +8 -15
  31. data/lib/promiscuous/publisher/model/base.rb +136 -0
  32. data/lib/promiscuous/publisher/model/ephemeral.rb +69 -0
  33. data/lib/promiscuous/publisher/model/mock.rb +61 -0
  34. data/lib/promiscuous/publisher/model/mongoid.rb +57 -100
  35. data/lib/promiscuous/{common/lint.rb → publisher/operation.rb} +1 -1
  36. data/lib/promiscuous/publisher/operation/base.rb +707 -0
  37. data/lib/promiscuous/publisher/operation/mongoid.rb +370 -0
  38. data/lib/promiscuous/publisher/worker.rb +22 -0
  39. data/lib/promiscuous/railtie.rb +21 -3
  40. data/lib/promiscuous/redis.rb +132 -40
  41. data/lib/promiscuous/resque.rb +12 -0
  42. data/lib/promiscuous/sidekiq.rb +15 -0
  43. data/lib/promiscuous/subscriber.rb +9 -20
  44. data/lib/promiscuous/subscriber/model.rb +4 -104
  45. data/lib/promiscuous/subscriber/model/active_record.rb +10 -0
  46. data/lib/promiscuous/subscriber/model/base.rb +96 -0
  47. data/lib/promiscuous/subscriber/model/mongoid.rb +86 -0
  48. data/lib/promiscuous/subscriber/model/observer.rb +37 -0
  49. data/lib/promiscuous/subscriber/operation.rb +167 -0
  50. data/lib/promiscuous/subscriber/payload.rb +34 -0
  51. data/lib/promiscuous/subscriber/worker.rb +22 -18
  52. data/lib/promiscuous/subscriber/worker/message.rb +48 -25
  53. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +273 -181
  54. data/lib/promiscuous/subscriber/worker/pump.rb +17 -43
  55. data/lib/promiscuous/subscriber/worker/recorder.rb +24 -0
  56. data/lib/promiscuous/subscriber/worker/runner.rb +24 -3
  57. data/lib/promiscuous/subscriber/worker/stats.rb +62 -0
  58. data/lib/promiscuous/timer.rb +38 -0
  59. data/lib/promiscuous/version.rb +1 -1
  60. metadata +98 -143
  61. data/README.md +0 -33
  62. data/lib/promiscuous/amqp/ruby_amqp.rb +0 -140
  63. data/lib/promiscuous/common.rb +0 -4
  64. data/lib/promiscuous/common/class_helpers.rb +0 -12
  65. data/lib/promiscuous/common/lint/base.rb +0 -24
  66. data/lib/promiscuous/common/options.rb +0 -51
  67. data/lib/promiscuous/ephemeral.rb +0 -14
  68. data/lib/promiscuous/error/recover.rb +0 -1
  69. data/lib/promiscuous/observer.rb +0 -5
  70. data/lib/promiscuous/publisher/active_record.rb +0 -7
  71. data/lib/promiscuous/publisher/amqp.rb +0 -18
  72. data/lib/promiscuous/publisher/attributes.rb +0 -32
  73. data/lib/promiscuous/publisher/base.rb +0 -23
  74. data/lib/promiscuous/publisher/class.rb +0 -36
  75. data/lib/promiscuous/publisher/envelope.rb +0 -7
  76. data/lib/promiscuous/publisher/ephemeral.rb +0 -9
  77. data/lib/promiscuous/publisher/lint.rb +0 -35
  78. data/lib/promiscuous/publisher/lint/amqp.rb +0 -14
  79. data/lib/promiscuous/publisher/lint/attributes.rb +0 -12
  80. data/lib/promiscuous/publisher/lint/base.rb +0 -5
  81. data/lib/promiscuous/publisher/lint/class.rb +0 -15
  82. data/lib/promiscuous/publisher/lint/polymorphic.rb +0 -22
  83. data/lib/promiscuous/publisher/mock.rb +0 -79
  84. data/lib/promiscuous/publisher/mongoid.rb +0 -33
  85. data/lib/promiscuous/publisher/mongoid/embedded.rb +0 -27
  86. data/lib/promiscuous/publisher/mongoid/embedded_many.rb +0 -12
  87. data/lib/promiscuous/publisher/polymorphic.rb +0 -8
  88. data/lib/promiscuous/subscriber/active_record.rb +0 -11
  89. data/lib/promiscuous/subscriber/amqp.rb +0 -25
  90. data/lib/promiscuous/subscriber/attributes.rb +0 -35
  91. data/lib/promiscuous/subscriber/base.rb +0 -29
  92. data/lib/promiscuous/subscriber/class.rb +0 -29
  93. data/lib/promiscuous/subscriber/dummy.rb +0 -19
  94. data/lib/promiscuous/subscriber/envelope.rb +0 -18
  95. data/lib/promiscuous/subscriber/lint.rb +0 -30
  96. data/lib/promiscuous/subscriber/lint/amqp.rb +0 -21
  97. data/lib/promiscuous/subscriber/lint/attributes.rb +0 -21
  98. data/lib/promiscuous/subscriber/lint/base.rb +0 -14
  99. data/lib/promiscuous/subscriber/lint/class.rb +0 -13
  100. data/lib/promiscuous/subscriber/lint/polymorphic.rb +0 -39
  101. data/lib/promiscuous/subscriber/mongoid.rb +0 -27
  102. data/lib/promiscuous/subscriber/mongoid/embedded.rb +0 -17
  103. data/lib/promiscuous/subscriber/mongoid/embedded_many.rb +0 -44
  104. data/lib/promiscuous/subscriber/observer.rb +0 -26
  105. data/lib/promiscuous/subscriber/polymorphic.rb +0 -36
  106. data/lib/promiscuous/subscriber/upsert.rb +0 -12
data/lib/promiscuous.rb CHANGED
@@ -1,12 +1,28 @@
1
1
  require 'active_support/core_ext'
2
+ require 'active_model/callbacks'
3
+ require 'multi_json'
2
4
 
3
5
  module Promiscuous
6
+ def self.require_for(gem, file)
7
+ require gem
8
+ require file
9
+ rescue LoadError
10
+ end
11
+
4
12
  require 'promiscuous/autoload'
5
- require 'promiscuous/railtie' if defined?(Rails)
13
+ require_for 'rails', 'promiscuous/railtie'
14
+ require_for 'resque', 'promiscuous/resque'
15
+ require_for 'sidekiq', 'promiscuous/sidekiq'
16
+
6
17
 
7
18
  extend Promiscuous::Autoload
8
19
  autoload :Common, :Publisher, :Subscriber, :Observer, :Worker, :Ephemeral,
9
- :CLI, :Error, :Loader, :AMQP, :Redis, :Config
20
+ :CLI, :Error, :Loader, :AMQP, :Redis, :ZK, :Config, :DSL, :Key,
21
+ :Convenience, :Dependency, :Middleware, :Timer
22
+
23
+ extend Promiscuous::DSL
24
+
25
+ Object.__send__(:include, Promiscuous::Convenience)
10
26
 
11
27
  class << self
12
28
  def configure(&block)
@@ -19,13 +35,6 @@ module Promiscuous
19
35
  end
20
36
  end
21
37
 
22
- def reload
23
- desc = Promiscuous::Publisher::Base.descendants
24
- desc += Promiscuous::Subscriber::Base.descendants
25
- desc.reject! { |klass| klass.name =~ /^Promiscuous::/ }
26
- desc.each { |klass| klass.setup_class_binding }
27
- end
28
-
29
38
  def connect
30
39
  AMQP.connect
31
40
  Redis.connect
@@ -39,36 +48,24 @@ module Promiscuous
39
48
  def healthy?
40
49
  AMQP.ensure_connected
41
50
  Redis.ensure_connected
42
- rescue
51
+ rescue Exception
43
52
  false
44
53
  else
45
54
  true
46
55
  end
47
- end
48
-
49
- module ConsoleHelpers
50
- # These are just for the subscriber, some helpers to debug in production
51
- def global_key
52
- Promiscuous::Redis.sub_key('global')
53
- end
54
56
 
55
- def global_version
56
- Promiscuous::Redis.get(global_key).to_i
57
+ def disabled
58
+ Thread.current[:promiscuous_disabled] || $promiscuous_disabled
57
59
  end
58
60
 
59
- def global_version=(value)
60
- Promiscuous::Redis.set(global_key, value)
61
- Promiscuous::Redis.publish(global_key, value)
62
- value
61
+ def disabled=(value)
62
+ Thread.current[:promiscuous_disabled] = value
63
63
  end
64
64
 
65
- def global_version!
66
- version = Promiscuous::Redis.incr(global_key)
67
- Promiscuous::Redis.publish(global_key, version)
68
- version
65
+ def context(*args, &block)
66
+ Publisher::Context.open(*args, &block)
69
67
  end
70
68
  end
71
- extend ConsoleHelpers
72
69
 
73
70
  at_exit { self.disconnect rescue nil }
74
71
  end
@@ -1,16 +1,18 @@
1
1
  module Promiscuous::AMQP
2
2
  extend Promiscuous::Autoload
3
- autoload :Bunny, :RubyAMQP, :Null
3
+ autoload :HotBunnies, :Bunny, :Null, :Fake
4
4
 
5
- EXCHANGE = 'promiscuous'.freeze
5
+ PUB_EXCHANGE = ENV['PUB_EXCHANGE'] || 'promiscuous'
6
+ SUB_EXCHANGE = ENV['SUB_EXCHANGE'] || 'promiscuous'
6
7
 
7
8
  class << self
8
9
  attr_accessor :backend
10
+ attr_accessor :backend_class
9
11
 
10
12
  def backend=(value)
11
- disconnect if @backend
12
- @backend = value.nil? ? nil : "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
13
- connect if @backend
13
+ disconnect
14
+ @backend_class = value.nil? ? nil : "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
15
+ connect if @backend_class
14
16
  end
15
17
 
16
18
  def lost_connection_exception
@@ -23,10 +25,27 @@ module Promiscuous::AMQP
23
25
 
24
26
  def publish(options={})
25
27
  ensure_connected
26
- Promiscuous.debug "[publish] (#{options[:exchange_name]}) #{options[:key]} -> #{options[:payload]}"
27
- backend.publish(options)
28
+ Promiscuous.debug "[publish] #{options[:key]} -> #{options[:payload]}"
29
+ backend.respond_to?(:async) ? backend.async.publish(options) : backend.publish(options)
28
30
  end
29
31
 
30
- delegate :connect, :disconnect, :connected?, :open_queue, :to => :backend
32
+ def connect
33
+ return if @backend
34
+ @backend = backend_class.new
35
+ @backend.connect
36
+ end
37
+
38
+ def disconnect
39
+ return unless @backend
40
+ @backend.disconnect
41
+ @backend.terminate if @backend.respond_to?(:terminate)
42
+ @backend = nil
43
+ end
44
+
45
+ delegate :connected?, :to => :backend
46
+
47
+ def const_missing(sym)
48
+ backend_class.const_get(sym)
49
+ end
31
50
  end
32
51
  end
@@ -1,30 +1,145 @@
1
- module Promiscuous::AMQP:: Bunny
2
- mattr_accessor :connection
1
+ class Promiscuous::AMQP::Bunny
2
+ def self.hijack_bunny
3
+ return if @bunny_hijacked
4
+ ::Bunny::Session.class_eval do
5
+ alias_method :handle_network_failure_without_promiscuous, :handle_network_failure
3
6
 
4
- def self.connect
7
+ def handle_network_failure(e)
8
+ Promiscuous.warn "[amqp] #{e}. Reconnecting..."
9
+ Promiscuous::Config.error_notifier.try(:call, e)
10
+ handle_network_failure_without_promiscuous(e)
11
+ end
12
+ end
13
+ @bunny_hijacked = true
14
+ end
15
+
16
+ attr_accessor :connection, :connection_lock, :callback_mapping
17
+
18
+ def initialize_driver
5
19
  require 'bunny'
6
- self.connection = ::Bunny.new(Promiscuous::Config.amqp_url, { :heartbeat => Promiscuous::Config.heartbeat })
7
- self.connection.start
20
+ self.class.hijack_bunny
21
+ end
22
+
23
+ def initialize
24
+ initialize_driver
25
+ # The bunny socket doesn't like when multiple threads access to it apparently
26
+ @connection_lock = Mutex.new
27
+ @callback_mapping = {}
28
+ end
29
+
30
+ def connect
31
+ @connection, @channel = new_connection
32
+ @exchange = exchange(@channel, :pub)
33
+ confirm_select(@channel, &method(:on_confirm))
34
+ end
35
+
36
+ def new_connection
37
+ connection = ::Bunny.new(Promiscuous::Config.amqp_url,
38
+ :heartbeat_interval => Promiscuous::Config.heartbeat,
39
+ :socket_timeout => Promiscuous::Config.socket_timeout,
40
+ :connect_timeout => Promiscuous::Config.socket_timeout)
41
+ connection.start
42
+
43
+ channel = connection.create_channel
44
+ [connection, channel]
45
+ end
46
+
47
+ def disconnect
48
+ @connection_lock.synchronize do
49
+ return unless connected?
50
+ @connection.stop
51
+ @connection = @channel = nil
52
+ end
8
53
  end
9
54
 
10
- def self.disconnect
11
- self.connection.stop
55
+ def connected?
56
+ !!@connection.try(:connected?)
12
57
  end
13
58
 
14
- def self.connected?
15
- !!self.connection.try(:connected?)
59
+ def raw_publish(options)
60
+ @exchange.publish(options[:payload], :key => options[:key], :persistent => true)
16
61
  end
17
62
 
18
- def self.publish(options={})
19
- exchange(options[:exchange_name]).
20
- publish(options[:payload], :key => options[:key], :persistent => true)
63
+ def exchange(channel, which)
64
+ exchange_name = which == :pub ? Promiscuous::AMQP::PUB_EXCHANGE :
65
+ Promiscuous::AMQP::SUB_EXCHANGE
66
+ channel.exchange(exchange_name, :type => :topic, :durable => true)
21
67
  end
22
68
 
23
- def self.open_queue(options={}, &block)
24
- raise "Cannot open queue with bunny"
69
+ def publish(options={})
70
+ @connection_lock.synchronize do
71
+ tag = @channel.next_publish_seq_no if options[:on_confirm]
72
+ raw_publish(options)
73
+ @callback_mapping[tag] = options[:on_confirm] if options[:on_confirm]
74
+ end
75
+ rescue Exception => e
76
+ e = Promiscuous::Error::Publisher.new(e, :payload => options[:payload])
77
+ Promiscuous.warn "[publish] #{e} #{e.backtrace.join("\n")}"
78
+ Promiscuous::Config.error_notifier.try(:call, e)
25
79
  end
26
80
 
27
- def self.exchange(name)
28
- connection.exchange(name, :type => :topic, :durable => true)
81
+ def confirm_select(channel, &callback)
82
+ channel.confirm_select(callback)
83
+ end
84
+
85
+ def on_confirm(tag, multiple, nack=false)
86
+ if multiple
87
+ cbs = @callback_mapping.keys
88
+ .select { |k| k <= tag }
89
+ .map { |k| @callback_mapping.delete(k) }
90
+ cbs.each(&:call) unless nack
91
+ else
92
+ cb = @callback_mapping.delete(tag)
93
+ cb.try(:call) unless nack
94
+ end
95
+ end
96
+
97
+ module Subscriber
98
+ def subscribe(options={}, &block)
99
+ queue_name = options[:queue_name]
100
+ bindings = options[:bindings]
101
+ Promiscuous::AMQP.ensure_connected
102
+
103
+ @lock = Mutex.new
104
+ @connection, @channel = Promiscuous::AMQP.backend.new_connection
105
+ @channel.basic_qos(Promiscuous::Config.prefetch)
106
+ exchange = Promiscuous::AMQP.backend.exchange(@channel, :sub)
107
+ @queue = @channel.queue(queue_name, Promiscuous::Config.queue_options)
108
+ bindings.each do |binding|
109
+ @queue.bind(exchange, :routing_key => binding)
110
+ Promiscuous.debug "[bind] #{queue_name} -> #{binding}"
111
+ end
112
+
113
+ @subscription = subscribe_queue(@queue, &block)
114
+ end
115
+
116
+ def subscribe_queue(queue, &block)
117
+ queue.subscribe(:ack => true) do |delivery_info, metadata, payload|
118
+ block.call(MetaData.new(self, delivery_info), payload)
119
+ end
120
+ end
121
+
122
+ class MetaData
123
+ def initialize(subscriber, delivery_info)
124
+ @subscriber = subscriber
125
+ @delivery_info = delivery_info
126
+ end
127
+
128
+ def ack
129
+ @subscriber.ack_message(@delivery_info.delivery_tag)
130
+ end
131
+ end
132
+
133
+ def ack_message(tag)
134
+ @lock.synchronize { @channel.ack(tag) } if @channel
135
+ end
136
+
137
+ def recover
138
+ @lock.synchronize { @channel.basic_recover(true) } if @channel
139
+ end
140
+
141
+ def disconnect
142
+ @lock.synchronize { @connection.stop; @channel = nil }
143
+ end
29
144
  end
30
145
  end
@@ -0,0 +1,52 @@
1
+ class Promiscuous::AMQP::Fake
2
+ attr_accessor :messages
3
+
4
+ class << self
5
+ def backend
6
+ Promiscuous::AMQP.backend
7
+ end
8
+ delegate :num_messages, :get_next_message, :get_next_payload, :to => :backend
9
+ end
10
+
11
+ def connect
12
+ @messages = []
13
+ end
14
+
15
+ def disconnect
16
+ end
17
+
18
+ def connected?
19
+ true
20
+ end
21
+
22
+ def publish(options={})
23
+ @messages << options
24
+ options[:on_confirm].try(:call)
25
+ end
26
+
27
+ def num_messages
28
+ @messages.count
29
+ end
30
+
31
+ def get_next_message
32
+ @messages.shift
33
+ end
34
+
35
+ def get_next_payload
36
+ JSON.parse(get_next_message[:payload])
37
+ end
38
+
39
+ def open_queue(options={}, &block)
40
+ end
41
+
42
+ module Subscriber
43
+ def subscribe(options={}, &block)
44
+ end
45
+
46
+ def recover
47
+ end
48
+
49
+ def disconnect
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ class Promiscuous::AMQP::HotBunnies < Promiscuous::AMQP::Bunny
2
+ attr_accessor :connection
3
+
4
+ def initialize_driver
5
+ require 'hot_bunnies'
6
+ end
7
+
8
+ # TODO auto reconnect
9
+
10
+ def new_connection
11
+ connection = ::HotBunnies.connect(:uri => Promiscuous::Config.amqp_url,
12
+ :heartbeat_interval => Promiscuous::Config.heartbeat,
13
+ :connection_timeout => Promiscuous::Config.socket_timeout)
14
+
15
+ channel = connection.create_channel
16
+ [connection, channel]
17
+ end
18
+
19
+ def disconnect
20
+ @connection_lock.synchronize do
21
+ return unless connected?
22
+ @channel.close rescue nil
23
+ @connection.close rescue nil
24
+ @connection = @channel = nil
25
+ end
26
+ end
27
+
28
+ def connected?
29
+ !!@connection.try(:is_open)
30
+ end
31
+
32
+ def raw_publish(options={})
33
+ @exchange.publish(options[:payload], :routing_key => options[:key], :persistent => true)
34
+ end
35
+
36
+ def confirm_select(channel, &callback)
37
+ channel.add_confirm_listener(&callback)
38
+ channel.confirm_select
39
+ end
40
+
41
+ module Subscriber
42
+ include Promiscuous::AMQP::Bunny::Subscriber
43
+
44
+ def subscribe_queue(queue, &block)
45
+ queue.subscribe(:ack => true, :blocking => false, &block)
46
+ end
47
+
48
+ def disconnect
49
+ @lock.synchronize do
50
+ @channel = nil
51
+ @subscription.shutdown! rescue nil
52
+ @connection.close rescue nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,17 +1,17 @@
1
- module Promiscuous::AMQP::Null
2
- def self.connect
1
+ class Promiscuous::AMQP::Null
2
+ def connect
3
3
  end
4
4
 
5
- def self.disconnect
5
+ def disconnect
6
6
  end
7
7
 
8
- def self.connected?
8
+ def connected?
9
9
  true
10
10
  end
11
11
 
12
- def self.publish(options={})
12
+ def publish(options={})
13
13
  end
14
14
 
15
- def self.open_queue(options={}, &block)
15
+ def open_queue(options={}, &block)
16
16
  end
17
17
  end
@@ -1,10 +1,10 @@
1
1
  class Promiscuous::CLI
2
2
  attr_accessor :options
3
3
 
4
- def trap_signals
4
+ def trap_debug_signals
5
5
  Signal.trap 'SIGUSR2' do
6
6
  Thread.list.each do |thread|
7
- print_status '-' * 80
7
+ print_status '----[ Threads ]----' + '-' * (100-19)
8
8
  if thread.backtrace
9
9
  print_status "Thread #{thread} #{thread['label']}"
10
10
  print_status thread.backtrace.join("\n")
@@ -12,37 +12,98 @@ class Promiscuous::CLI
12
12
  print_status "Thread #{thread} #{thread['label']} -- no backtrace"
13
13
  end
14
14
  end
15
+
16
+ if @worker && @worker.respond_to?(:message_synchronizer)
17
+ if blocked_messages = @worker.message_synchronizer.try(:blocked_messages)
18
+ print_status '----[ Pending Dependencies ]----' + '-' * (100-32)
19
+ blocked_messages.reverse_each { |msg| print_status msg }
20
+ end
21
+ print_status '-' * 80
22
+ end
15
23
  end
24
+ end
16
25
 
26
+ def trap_exit_signals
17
27
  %w(SIGTERM SIGINT).each do |signal|
18
28
  Signal.trap(signal) do
29
+ exit 1 if @stop
19
30
  print_status "Exiting..."
20
- @worker.terminate if @worker.try(:alive?)
31
+ @worker.try(:stop)
32
+ @worker = nil
21
33
  @stop = true
22
34
  end
23
35
  end
24
36
  end
25
37
 
38
+ def trap_signals
39
+ trap_debug_signals
40
+ trap_exit_signals
41
+ end
42
+
26
43
  def publish
27
44
  options[:criterias].map { |criteria| eval(criteria) }.each do |criteria|
28
45
  break if @stop
29
46
  title = criteria.name
30
47
  title = "#{title}#{' ' * [0, 20 - title.size].max}"
31
48
  bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => title, :total => criteria.count)
32
- criteria.each do |doc|
49
+ criteria.unscoped.each do |doc|
33
50
  break if @stop
34
- doc.promiscuous_sync
51
+ Promiscuous.context("cli/sync") { doc.promiscuous.sync }
35
52
  bar.increment
36
53
  end
37
54
  end
38
55
  end
39
56
 
57
+ def record
58
+ @worker = Promiscuous::Subscriber::Worker::Recorder.new(options[:log_file])
59
+ @worker.start
60
+ print_status "Recording..."
61
+ sleep 0.2 until !@worker
62
+ end
63
+
64
+ def replay_payload(payload)
65
+ endpoint = MultiJson.load(payload)['__amqp__']
66
+ if endpoint
67
+ # TODO confirm
68
+ Promiscuous::AMQP.publish(:key => endpoint, :payload => payload)
69
+ @num_msg += 1
70
+ else
71
+ puts "[warn] missing destination in #{payload}"
72
+ end
73
+ end
74
+
75
+ def replay
76
+ require 'json'
77
+ @num_msg = 0
78
+ File.open(options[:log_file], 'r').each do |line|
79
+ break if @stop
80
+ case line
81
+ when /^\[promiscuous\] \[receive\] ({.*})$/ then replay_payload($1)
82
+ when /^\[promiscuous\] \[publish\] .* -> ({.*})$/ then replay_payload($1)
83
+ when /^({.*})$/ then replay_payload($1)
84
+ end
85
+ end
86
+
87
+ puts "Replayed #{@num_msg} messages"
88
+ end
89
+
40
90
  def subscribe
41
- Promiscuous::Loader.load_descriptors if defined?(Rails)
42
- @worker = Promiscuous::Subscriber::Worker.run!
43
- Celluloid::Actor[:pump].subscribe_sync.wait
44
- print_status "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
45
- sleep 1 until !@worker.alive?
91
+ @worker = Promiscuous::Subscriber::Worker.new
92
+ @worker.start
93
+ print_status "Replicating..."
94
+ sleep 0.2 until !@worker
95
+ end
96
+
97
+ def publisher_recovery
98
+ @worker = Promiscuous::Publisher::Worker.new
99
+ @worker.start
100
+ print_status "Waiting for messages to recover..."
101
+ sleep 0.2 until !@worker
102
+ end
103
+
104
+ def generate_mocks
105
+ f = options[:output] ? File.open(options[:output], 'w') : STDOUT
106
+ f.write Promiscuous::Publisher::MockGenerator.generate
46
107
  end
47
108
 
48
109
  def parse_args(args)
@@ -54,8 +115,12 @@ class Promiscuous::CLI
54
115
 
55
116
  opts.separator ""
56
117
  opts.separator "Actions:"
57
- opts.separator " promiscuous publish \"Member.where(:updated_at.gt => 1.day.ago)\" BrandAction"
118
+ opts.separator " promiscuous publish \"Model1.where(:updated_at.gt => 1.day.ago)\" Model2 Model3..."
58
119
  opts.separator " promiscuous subscribe"
120
+ opts.separator " promiscuous mocks"
121
+ opts.separator " promiscuous record logfile"
122
+ opts.separator " promiscuous replay logfile"
123
+ opts.separator " promiscuous publisher_recovery"
59
124
  opts.separator ""
60
125
  opts.separator "Options:"
61
126
 
@@ -76,6 +141,14 @@ class Promiscuous::CLI
76
141
  Promiscuous::Config.prefetch = prefetch.to_i
77
142
  end
78
143
 
144
+ opts.on "-o", "--output FILE", "Output file for mocks. Defaults to stdout" do |file|
145
+ options[:output] = file
146
+ end
147
+
148
+ opts.on "-s", "--stat-interval [DURATION]", "Stats refresh rate (0 to disable)" do |duration|
149
+ Promiscuous::Config.stats_interval = duration.to_f
150
+ end
151
+
79
152
  opts.on("-h", "--help", "Show this message") do
80
153
  puts opts
81
154
  exit
@@ -93,19 +166,21 @@ class Promiscuous::CLI
93
166
 
94
167
  options[:action] = args.shift.try(:to_sym)
95
168
  options[:criterias] = args
169
+ options[:log_file] = args.first
96
170
 
97
- unless options[:action].in? [:publish, :subscribe]
98
- puts parser
99
- exit
100
- end
101
-
102
- if options[:action] == :publish
103
- raise "Please specify one or more criterias" unless options[:criterias].present?
104
- else
105
- raise "Why are you specifying a criteria?" if options[:criterias].present?
171
+ case options[:action]
172
+ when :publish then raise "Please specify one or more criterias" unless options[:criterias].present?
173
+ when :subscribe then raise "Why are you specifying a criteria?" if options[:criterias].present?
174
+ when :record then raise "Please specify a log file to record" unless options[:log_file].present?
175
+ when :replay then raise "Please specify a log file to replay" unless options[:log_file].present?
176
+ when :publisher_recovery
177
+ when :mocks
178
+ else puts parser; exit 1
106
179
  end
107
180
 
108
181
  options
182
+ rescue SystemExit
183
+ exit
109
184
  rescue Exception => e
110
185
  puts e
111
186
  exit
@@ -113,10 +188,15 @@ class Promiscuous::CLI
113
188
 
114
189
  def load_app
115
190
  if options[:require]
116
- require options[:require]
191
+ begin
192
+ require options[:require]
193
+ rescue LoadError
194
+ require "./#{options[:require]}"
195
+ end
117
196
  else
118
197
  require 'rails'
119
- require File.expand_path("./config/environment.rb")
198
+ require 'promiscuous/railtie'
199
+ require File.expand_path("./config/environment")
120
200
  ::Rails.application.eager_load!
121
201
  end
122
202
  end
@@ -131,8 +211,12 @@ class Promiscuous::CLI
131
211
  def run
132
212
  trap_signals
133
213
  case options[:action]
134
- when :publish then publish
214
+ when :publish then publish
135
215
  when :subscribe then subscribe
216
+ when :record then record
217
+ when :replay then replay
218
+ when :mocks then generate_mocks
219
+ when :publisher_recovery then publisher_recovery
136
220
  end
137
221
  end
138
222
 
@@ -146,6 +230,6 @@ class Promiscuous::CLI
146
230
 
147
231
  def print_status(msg)
148
232
  Promiscuous.info msg
149
- $stderr.puts msg
233
+ STDERR.puts msg
150
234
  end
151
235
  end