smith 0.5.12 → 0.5.13.1

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 (46) hide show
  1. data/bin/smithctl +16 -19
  2. data/lib/smith.rb +25 -41
  3. data/lib/smith/agent.rb +96 -66
  4. data/lib/smith/agent_monitoring.rb +3 -4
  5. data/lib/smith/agent_process.rb +16 -9
  6. data/lib/smith/amqp_errors.rb +53 -0
  7. data/lib/smith/application/agency.rb +23 -20
  8. data/lib/smith/bootstrap.rb +3 -3
  9. data/lib/smith/command.rb +2 -2
  10. data/lib/smith/command_base.rb +4 -0
  11. data/lib/smith/commands/agency/agents.rb +19 -19
  12. data/lib/smith/commands/agency/kill.rb +6 -2
  13. data/lib/smith/commands/agency/list.rb +2 -4
  14. data/lib/smith/commands/agency/logger.rb +27 -28
  15. data/lib/smith/commands/agency/metadata.rb +1 -5
  16. data/lib/smith/commands/agency/object_count.rb +13 -11
  17. data/lib/smith/commands/agency/restart.rb +18 -9
  18. data/lib/smith/commands/agency/start.rb +34 -25
  19. data/lib/smith/commands/agency/stop.rb +58 -41
  20. data/lib/smith/commands/agency/version.rb +10 -10
  21. data/lib/smith/commands/common.rb +7 -4
  22. data/lib/smith/commands/smithctl/acl.rb +46 -37
  23. data/lib/smith/commands/smithctl/commands.rb +1 -1
  24. data/lib/smith/commands/smithctl/firehose.rb +30 -0
  25. data/lib/smith/commands/smithctl/pop.rb +39 -32
  26. data/lib/smith/commands/smithctl/push.rb +70 -51
  27. data/lib/smith/commands/smithctl/rm.rb +32 -9
  28. data/lib/smith/commands/smithctl/subscribe.rb +36 -0
  29. data/lib/smith/commands/smithctl/top.rb +1 -1
  30. data/lib/smith/exceptions.rb +2 -0
  31. data/lib/smith/messaging/acl/agency_command.proto +4 -0
  32. data/lib/smith/messaging/acl/default.rb +8 -1
  33. data/lib/smith/messaging/amqp_options.rb +2 -2
  34. data/lib/smith/messaging/message_counter.rb +21 -0
  35. data/lib/smith/messaging/payload.rb +47 -49
  36. data/lib/smith/messaging/queue.rb +50 -0
  37. data/lib/smith/messaging/queue_definition.rb +18 -0
  38. data/lib/smith/messaging/queue_factory.rb +20 -29
  39. data/lib/smith/messaging/receiver.rb +211 -173
  40. data/lib/smith/messaging/requeue.rb +91 -0
  41. data/lib/smith/messaging/sender.rb +184 -28
  42. data/lib/smith/messaging/util.rb +72 -0
  43. data/lib/smith/queue_definitions.rb +11 -0
  44. data/lib/smith/version.rb +1 -1
  45. metadata +18 -10
  46. data/lib/smith/messaging/endpoint.rb +0 -116
data/bin/smithctl CHANGED
@@ -10,10 +10,11 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
10
 
11
11
  require 'smith'
12
12
 
13
+ include Smith::Logger
14
+
13
15
  module Smith
14
16
  class SmithControl
15
17
 
16
- include Logger
17
18
 
18
19
  def initialize(options={})
19
20
  log_level((options[:log_level_given]) ? options[:log_level].to_sym : :info)
@@ -31,32 +32,28 @@ module Smith
31
32
  private
32
33
 
33
34
  def smithctl_command(command, args, &blk)
34
- responder = Messaging::Responder.new
35
- responder.callback do |v|
36
- blk.call(v)
37
- end
38
-
39
- Command.run(command, args, :responder => responder)
35
+ Command.run(command, args, :responder => EM::Completion.new.tap { |c| c.completion(blk) })
40
36
  end
41
37
 
42
38
  def agency_command(command, args, &blk)
43
- Messaging::Sender.new('agency.control', :auto_delete => false, :durable => false, :persistent => true, :strict => true).ready do |sender|
39
+ Messaging::Sender.new(QueueDefinitions::Agency_control) do |sender|
44
40
 
45
- sender.timeout(@timeout) { blk.call("Timeout. Is the agency still running?") }
41
+ timeout = Messaging::Timeout.new(5) { |message_id| blk.call("Timeout. Is the agency still running?") }
46
42
 
47
- payload = ACL::Payload.new(:agency_command).content(:command => command, :args => args)
48
-
49
- callback = proc do |sender|
50
- sender.publish_and_receive(payload) do |r|
51
- blk.call(r.payload)
52
- end
43
+ sender.on_reply(:timeout => timeout) do |reply_payload, r|
44
+ r.ack
45
+ blk.call(reply_payload[:response])
53
46
  end
54
47
 
55
- errback = proc do
56
- blk.call("Agency not running.")
57
- end
48
+ # callback = proc do |sender|
49
+ sender.publish(Smith::ACL::Factory.create(:agency_command, :command => command, :args => args))
50
+ # end
51
+
52
+ # errback = proc do
53
+ # blk.call("Agency not running.")
54
+ # end
58
55
 
59
- sender.consumers?(callback, errback)
56
+ # sender.consumers?(callback, errback)
60
57
  end
61
58
  end
62
59
  end
data/lib/smith.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'amqp'
3
3
  require 'tmpdir'
4
+ require "socket"
4
5
  require 'logging'
5
6
  require 'pathname'
6
7
  require 'fileutils'
@@ -21,9 +22,9 @@ module Smith
21
22
 
22
23
  class << self
23
24
 
24
- def channel
25
- raise RuntimeError, "You must run this in a Smith.start block" if @channel.nil?
26
- @channel
25
+ def connection
26
+ raise RuntimeError, "You must run this in a Smith.start block" if @connection.nil?
27
+ @connection
27
28
  end
28
29
 
29
30
  def environment
@@ -42,6 +43,11 @@ module Smith
42
43
  path_to_pathnames(config.agency.agent_path)
43
44
  end
44
45
 
46
+ # Convenience method to get the hostname
47
+ def hostname
48
+ Socket.gethostname
49
+ end
50
+
45
51
  def acl_path
46
52
  path_to_pathnames(config.agency.acl_path)
47
53
  end
@@ -81,23 +87,12 @@ module Smith
81
87
  EM.reactor_running?
82
88
  end
83
89
 
84
- # Define a channel error handler.
85
- def on_error(chain=false, &blk)
86
- # This strikes me as egregiously wrong but I don't know how to
87
- # overwrite an already existing handler.
88
- if chain
89
- Smith.channel.callbacks[:error] << blk
90
- else
91
- Smith.channel.callbacks[:error] = [blk]
92
- end
93
- end
94
-
95
90
  def start(opts={}, &block)
96
91
  EM.epoll if EM.epoll?
97
92
  EM.kqueue if EM.kqueue?
98
93
  EM.set_descriptor_table_size(opts[:fdsize] || 1024)
99
94
 
100
- connection_settings = config.amqp.broker._merge(
95
+ connection_settings = config.amqp.broker.to_hash.merge(
101
96
  :on_tcp_connection_failure => method(:tcp_connection_failure_handler),
102
97
  :on_possible_authentication_failure => method(:authentication_failure_handler))
103
98
 
@@ -121,15 +116,16 @@ module Smith
121
116
  logger.info { "Connection to AMQP server restored" }
122
117
  end
123
118
 
124
- connection.on_error do |connection, reason|
125
- case reason.reply_code
119
+ connection.on_error do |connection, connection_close|
120
+ case connection_close.reply_code
126
121
  when 320
127
122
  logger.warn { "AMQP server shutdown. Waiting." }
128
123
  else
129
124
  if @handler
130
125
  @handler.call(connection, reason)
131
126
  else
132
- logger.error { "AMQP Server error: #{reason.reply_code}: #{reason.reply_text}" }
127
+ logger.error { "AMQP Server error: #{connection_close.reply_code}: #{connection_close.reply_text}" }
128
+ EM.stop_event_loop
133
129
  end
134
130
  end
135
131
  end
@@ -137,25 +133,7 @@ module Smith
137
133
  # This will be the last thing run by the reactor.
138
134
  shutdown_hook { logger.debug { "Reactor Stopped" } }
139
135
 
140
- AMQP::Channel.new(connection) do |channel,ok|
141
- @channel = channel
142
- # Set up QOS. If you do not do this then the subscribe in receive_message
143
- # will get overwhelmed and the whole thing will collapse in on itself.
144
- channel.prefetch(1)
145
-
146
- # Set up a default handler.
147
- on_error do |ch,channel_close|
148
- logger.fatal { "Channel level exception: #{channel_close.reply_code}: #{channel_close.reply_text}" }
149
- logger.fatal { "Exiting" }
150
- Smith.stop(true)
151
- end
152
-
153
- # Set up auto-recovery. This will ensure that the AMQP gem reconnects each
154
- # channel and sets up the various exchanges & queues.
155
- channel.auto_recovery = true
156
-
157
- block.call
158
- end
136
+ block.call
159
137
  end
160
138
  end
161
139
 
@@ -168,7 +146,9 @@ module Smith
168
146
 
169
147
  if running?
170
148
  if immediately
171
- @connection.close { EM.stop_event_loop }
149
+ EM.next_tick do
150
+ @connection.close { EM.stop_event_loop }
151
+ end
172
152
  else
173
153
  EM.add_timer(1) do
174
154
  @connection.close { EM.stop_event_loop }
@@ -221,22 +201,26 @@ module Smith
221
201
  end
222
202
  end
223
203
 
204
+ require_relative 'smith/amqp_errors'
224
205
  require_relative 'smith/object_count'
225
206
  require_relative 'smith/cache'
226
- require_relative 'smith/agent'
227
207
  require_relative 'smith/agent_cache'
228
208
  require_relative 'smith/agent_process'
229
209
  require_relative 'smith/agent_monitoring'
230
210
  require_relative 'smith/command'
231
211
  require_relative 'smith/command_base'
232
212
  require_relative 'smith/exceptions'
213
+ require_relative 'smith/object_count'
233
214
  require_relative 'smith/version'
234
215
 
216
+ require_relative 'smith/messaging/queue_definition'
235
217
  require_relative 'smith/messaging/amqp_options'
236
218
  require_relative 'smith/messaging/queue_factory'
237
- require_relative 'smith/messaging/payload'
238
219
  require_relative 'smith/messaging/acl/default'
239
- require_relative 'smith/messaging/endpoint'
220
+ require_relative 'smith/messaging/payload'
221
+ require_relative 'smith/messaging/util'
240
222
  require_relative 'smith/messaging/responder'
241
223
  require_relative 'smith/messaging/receiver'
242
224
  require_relative 'smith/messaging/sender'
225
+
226
+ require_relative 'smith/queue_definitions'
data/lib/smith/agent.rb CHANGED
@@ -17,22 +17,54 @@ module Smith
17
17
  @signal_handlers = Hash.new { |h,k| h[k] = Array.new }
18
18
 
19
19
  setup_control_queue
20
- setup_stats_queue
21
20
 
22
21
  @start_time = Time.now
23
22
 
24
- on_started do
25
- logger.info { "#{name}:[#{pid}] started." }
23
+ @state = :starting
24
+
25
+ @on_stopping = proc {|completion| completion.succeed }
26
+ @on_starting = proc {|completion| completion.succeed }
27
+ @on_running = proc {|completion| completion.succeed }
28
+
29
+ @on_starting_completion = EM::Completion.new.tap do |c|
30
+ c.completion do |completion|
31
+ acknowledge_start do
32
+ @on_running.call(@on_running_completion)
33
+ logger.info { "Agent started: #{name}:[#{pid}]." }
34
+ end
35
+ end
36
+ end
37
+
38
+ @on_running_completion = EM::Completion.new.tap do |c|
39
+ c.completion do |completion|
40
+ start_keep_alive
41
+ setup_stats_queue
42
+ @state = :running
43
+ end
26
44
  end
27
45
 
28
- on_stopped do
29
- logger.info { "#{name}:[#{pid}] stopped." }
46
+ @on_stopping_completion = EM::Completion.new.tap do |c|
47
+ c.completion do |completion|
48
+ acknowledge_stop do
49
+ @state = :stopping
50
+ Smith.stop do
51
+ logger.info { "Agent stopped: #{name}:[#{pid}]." }
52
+ end
53
+ end
54
+ end
30
55
  end
31
56
 
32
57
  EM.threadpool_size = 1
33
58
 
34
- acknowledge_start
35
- start_keep_alive
59
+ @on_starting.call(@on_starting_completion)
60
+ end
61
+
62
+ def on_stopping(&blk)
63
+ @on_stopping = blk
64
+ end
65
+
66
+ def on_running(&blk)
67
+ @on_running = blk
36
68
  end
37
69
 
38
70
  # Override this method to implement your own agent. You can use task but this may
@@ -47,14 +79,6 @@ module Smith
47
79
  end
48
80
  end
49
81
 
50
- def on_started(&blk)
51
- @on_started = blk
52
- end
53
-
54
- def on_stopped(&blk)
55
- Smith.shutdown_hook(&blk)
56
- end
57
-
58
82
  def install_signal_handler(signal, position=:end, &blk)
59
83
  raise ArgumentError, "Unknown position: #{position}" if ![:beginning, :end].include?(position)
60
84
 
@@ -65,20 +89,21 @@ module Smith
65
89
  end
66
90
  end
67
91
 
68
- def started
69
- @on_started.call
92
+ def state
93
+ @state
70
94
  end
71
95
 
72
- def receiver(queue_name, opts={})
73
- queues.receiver(queue_name, opts) do |receiver|
74
- receiver.subscribe do |r|
75
- yield r
76
- end
77
- end
96
+ # Set convenience state methods.
97
+ [:starting, :stopping, :running].each do |method|
98
+ define_method("#{method}?", proc { state == method })
99
+ end
100
+
101
+ def receiver(queue_name, opts={}, &blk)
102
+ queues.receiver(queue_name, opts, &blk)
78
103
  end
79
104
 
80
- def sender(queue_name, opts={})
81
- queues.sender(queue_name, opts) { |sender| yield sender }
105
+ def sender(queue_name, opts={}, &blk)
106
+ queues.sender(queue_name, opts, &blk)
82
107
  end
83
108
 
84
109
  class << self
@@ -104,56 +129,59 @@ module Smith
104
129
 
105
130
  def setup_control_queue
106
131
  logger.debug { "Setting up control queue: #{control_queue_name}" }
107
- receiver(control_queue_name, :auto_delete => true, :durable => false) do |r|
108
- logger.debug { "Command received on agent control queue: #{r.payload.command} #{r.payload.options}" }
109
-
110
- case r.payload.command
111
- when 'object_count'
112
- object_count(r.payload.options.first.to_i).each{|o| logger.info{o}}
113
- when 'stop'
114
- acknowledge_stop { Smith.stop }
115
- when 'log_level'
116
- begin
117
- level = r.payload.options.first
118
- logger.info { "Setting log level to #{level} for: #{name}" }
119
- log_level(level)
120
- rescue ArgumentError => e
121
- logger.error { "Incorrect log level: #{level}" }
132
+
133
+ Messaging::Receiver.new(control_queue_name, :durable => false, :auto_delete => true) do |receiver|
134
+ receiver.subscribe do |payload|
135
+ logger.debug { "Command received on agent control queue: #{payload.command} #{payload.options}" }
136
+
137
+ case payload.command
138
+ when 'object_count'
139
+ object_count(payload.options.first.to_i).each{|o| logger.info{o}}
140
+ when 'stop'
141
+ @on_stopping.call(@on_stopping_completion)
142
+ when 'log_level'
143
+ begin
144
+ level = payload.options.first
145
+ logger.info { "Setting log level to #{level} for: #{name}" }
146
+ log_level(level)
147
+ rescue ArgumentError => e
148
+ logger.error { "Incorrect log level: #{level}" }
149
+ end
150
+ else
151
+ logger.warn { "Unknown command: #{level} -> #{level.inspect}" }
122
152
  end
123
- else
124
- logger.warn { "Unknown command: #{level} -> #{level.inspect}" }
125
153
  end
126
154
  end
127
155
  end
128
156
 
129
157
  def setup_stats_queue
158
+ puts "setting stats"
130
159
  # instantiate this queue without using the factory so it doesn't show
131
160
  # up in the stats.
132
- sender('agent.stats', :dont_cache => true, :durable => false, :auto_delete => false) do |stats_queue|
161
+ Messaging::Sender.new(QueueDefinitions::Agent_stats) do |stats_queue|
133
162
  EventMachine.add_periodic_timer(2) do
134
- callback = proc do |consumers|
135
- payload = ACL::Payload.new(:agent_stats).content do |p|
136
- p.agent_name = self.name
137
- p.pid = self.pid
138
- p.rss = (File.read("/proc/#{pid}/statm").split[1].to_i * 4) / 1024 # This assumes the page size is 4K & is MB
139
- p.up_time = (Time.now - @start_time).to_i
140
- factory.each_queue do |q|
141
- p.queues << ACL::AgentStats::QueueStats.new(:name => q.denormalized_queue_name, :type => q.class.to_s, :length => q.counter)
163
+ stats_queue.number_of_consumers do |consumers|
164
+ if consumers > 0
165
+ payload = ACL::Factory.create(:agent_stats) do |p|
166
+ p.agent_name = self.name
167
+ p.pid = self.pid
168
+ p.rss = (File.read("/proc/#{pid}/statm").split[1].to_i * 4) / 1024 # This assumes the page size is 4K & is MB
169
+ p.up_time = (Time.now - @start_time).to_i
170
+ factory.each_queue do |q|
171
+ p.queues << ACL::Factory.create('agent_stats::queue_stats', :name => q.denormalised_queue_name, :type => q.class.to_s, :length => q.counter)
172
+ end
142
173
  end
143
- end
144
174
 
145
- stats_queue.publish(payload)
175
+ stats_queue.publish(payload)
176
+ end
146
177
  end
147
-
148
- # The errback argument is set to nil so as to suppress the default message.
149
- stats_queue.consumers?(callback, nil)
150
178
  end
151
179
  end
152
180
  end
153
181
 
154
- def acknowledge_start
155
- sender('agent.lifecycle', :auto_delete => false, :durable => false, :dont_cache => true) do |ack_start_queue|
156
- payload = ACL::Payload.new(:agent_lifecycle).content do |p|
182
+ def acknowledge_start(&blk)
183
+ Messaging::Sender.new(QueueDefinitions::Agent_lifecycle) do |ack_start_queue|
184
+ payload = ACL::Factory.create(:agent_lifecycle) do |p|
157
185
  p.state = 'acknowledge_start'
158
186
  p.pid = $$
159
187
  p.name = self.class.to_s
@@ -162,24 +190,26 @@ module Smith
162
190
  p.singleton = Smith.config.agent.singleton
163
191
  p.started_at = Time.now.to_i
164
192
  end
165
- ack_start_queue.publish(payload)
193
+ ack_start_queue.publish(payload, &blk)
166
194
  end
167
195
  end
168
196
 
169
- def acknowledge_stop(&block)
170
- sender('agent.lifecycle', :auto_delete => false, :durable => false, :dont_cache => true) do |ack_stop_queue|
197
+ def acknowledge_stop(&blk)
198
+ Messaging::Sender.new(QueueDefinitions::Agent_lifecycle) do |ack_stop_queue|
171
199
  message = {:state => 'acknowledge_stop', :pid => $$, :name => self.class.to_s}
172
- ack_stop_queue.publish(ACL::Payload.new(:agent_lifecycle).content(message), &block)
200
+ ack_stop_queue.publish(ACL::Factory.create(:agent_lifecycle, message), &blk)
173
201
  end
174
202
  end
175
203
 
176
204
  def start_keep_alive
177
205
  if Smith.config.agent.monitor
178
206
  EventMachine::add_periodic_timer(1) do
179
- sender('agent.keepalive', :auto_delete => false, :durable => false, :dont_cache => true) do |keep_alive_queue|
207
+ Messaging::Sender.new(QueueDefinitions::Agent_keepalive) do |queue|
180
208
  message = {:name => self.class.to_s, :pid => $$, :time => Time.now.to_i}
181
- keep_alive_queue.consumers? do |sender|
182
- keep_alive_queue.publish(ACL::Payload.new(:agent_keepalive).content(message))
209
+ keep_alive_queue.consumers do |consumers|
210
+ if consumers > 0
211
+ keep_alive_queue.publish(ACL::Factory.create(:agent_keepalive, message))
212
+ end
183
213
  end
184
214
  end
185
215
  end
@@ -36,10 +36,9 @@ module Smith
36
36
  logger.info { "Agent is shutting down: #{agent_process.name}" }
37
37
  when 'dead'
38
38
  logger.info { "Restarting dead agent: #{agent_process.name}" }
39
- Messaging::Sender.new('agency.control', :auto_delete => false, :durable => false, :strict => true).ready do |sender|
40
- sender.publish_and_receive(ACL::Payload.new(:agency_command).content(:command => 'start', :args => [agent_process.name])) do |r|
41
- logger.debug { "Agent restart message acknowledged: #{agent_process.name}" }
42
- end
39
+ Messaging::Sender.new(QueueDefinitions::Agency_control) do |sender|
40
+ sender.on_reply { |p, r| logger.debug { "Agent restart message acknowledged: #{agent_process.name}" } }
41
+ sender.publish(ACL::Factory.create(:agency_command).content(:command => 'start', :args => [agent_process.name]))
43
42
  end
44
43
  when 'unknown'
45
44
  logger.info { "Agent is in an unknown state: #{agent_process.name}" }