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
@@ -65,6 +65,13 @@ module Smith
65
65
  end
66
66
  end
67
67
 
68
+ def add_callback(state, &blk)
69
+ AgentProcess.state_machine do
70
+ puts "changing callback"
71
+ after_transition :on => state, :do => blk
72
+ end
73
+ end
74
+
68
75
  # Check to see if the agent is alive.
69
76
  def alive?
70
77
  if self.pid
@@ -128,18 +135,20 @@ module Smith
128
135
  end
129
136
 
130
137
  def self.stop(agent_process)
131
- Messaging::Sender.new(agent_process.control_queue_name, :durable => false, :auto_delete => true).ready do |sender|
132
- callback = proc {|sender| sender.publish(ACL::Payload.new(:agent_command).content(:command => 'stop')) }
133
- errback = proc do
134
- logger.warn { "Agent is not listening. Setting state to dead." }
135
- agent_process.no_process_running
138
+ Messaging::Sender.new(agent_process.control_queue_name, :durable => false, :auto_delete => true) do |sender|
139
+ sender.consumer_count do |count|
140
+ if count > 0
141
+ sender.publish(ACL::Factory.create(:agent_command, :command => 'stop'))
142
+ else
143
+ logger.warn { "Agent is not listening. Setting state to dead." }
144
+ agent_process.no_process_running
145
+ end
136
146
  end
137
-
138
- sender.consumers?(callback, errback)
139
147
  end
140
148
  end
141
149
 
142
150
  def self.acknowledge_stop(agent_process)
151
+ pp :stopped
143
152
  end
144
153
 
145
154
  def self.kill(agent_process)
@@ -172,10 +181,8 @@ module Smith
172
181
 
173
182
  AgentProcess.state_machine do
174
183
  after_transition :on => :start, :do => AgentProcessObserver.method(:start)
175
- after_transition :on => :acknowledge_start, :do => AgentProcessObserver.method(:acknowledge_start)
176
184
  after_transition :on => :stop, :do => AgentProcessObserver.method(:stop)
177
185
  after_transition :on => :kill, :do => AgentProcessObserver.method(:kill)
178
- after_transition :on => :acknowledge_stop, :do => AgentProcessObserver.method(:acknowledge_stop)
179
186
  after_transition :on => :not_responding, :do => AgentProcessObserver.method(:reap_agent)
180
187
  end
181
188
  end
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Smith
4
+ module Messaging
5
+ module AmqpErrors
6
+
7
+ private
8
+
9
+ def error_message(code, text, &blk)
10
+ details = error_lookup(code)
11
+ message = "#{details[:error_class]} exception: #{code} (#{details[:name]}). #{details[:description]}"
12
+
13
+ case code
14
+ when 404
15
+ diagnosis = "looks like the queue has been deleted."
16
+ when 406
17
+ case text
18
+ when /.*(unknown delivery tag [0-9]+).*/
19
+ diagnosis = "#{$1} - you've probably already acknowledged the message."
20
+ end
21
+ else
22
+ end
23
+ blk.call(message, diagnosis)
24
+ end
25
+
26
+ def error_lookup(code)
27
+ errors[code]
28
+ end
29
+
30
+ def errors
31
+ @errors ||= {
32
+ 311 => {:name => "content-too-large", :error_class => "Channel", :description => "The client attempted to transfer content larger than the server could accept at the present time. The client may retry at a later time."},
33
+ 313 => {:name => "no-consumers", :error_class => "Channel", :description => "When the exchange cannot deliver to a consumer when the immediate flag is set. As a result of pending data on the queue or the absence of any consumers of the queue."},
34
+ 320 => {:name => "connection-forced", :error_class => "Connection", :description => "An operator intervened to close the Connection for some reason. The client may retry at some later date."},
35
+ 402 => {:name => "invalid-path", :error_class => "Connection", :description => "The client tried to work with an unknown virtual host."},
36
+ 403 => {:name => "access-refused", :error_class => "Channel", :description => "The client attempted to work with a server entity to which it has no access due to security settings."},
37
+ 404 => {:name => "not-found", :error_class => "Channel", :description => "The client attempted to work with a server entity that does not exist."},
38
+ 405 => {:name => "resource-locked", :error_class => "Channel", :description => "The client attempted to work with a server entity to which it has no access because another client is working with it."},
39
+ 406 => {:name => "precondition-failed", :error_class => "Channel", :description => "The client requested a method that was not allowed because some precondition failed."},
40
+ 501 => {:name => "frame-error", :error_class => "Connection", :description => "The sender sent a malformed frame that the recipient could not decode. This strongly implies a programming error in the sending peer."},
41
+ 502 => {:name => "syntax-error", :error_class => "Connection", :description => "The sender sent a frame that contained illegal values for one or more fields. This strongly implies a programming error in the sending peer."},
42
+ 503 => {:name => "command-invalid", :error_class => "Connection", :description => "The client sent an invalid sequence of frames, attempting to perform an operation that was considered invalid by the server. This usually implies a programming error in the client."},
43
+ 504 => {:name => "channel-error", :error_class => "Connection", :description => "The client attempted to work with a Channel that had not been correctly opened. This most likely indicates a fault in the client layer."},
44
+ 505 => {:name => "unexpected-frame", :error_class => "Connection", :description => "The peer sent a frame that was not expected, usually in the context of a content header and body. This strongly indicates a fault in the peer's content processing."},
45
+ 506 => {:name => "resource-error", :error_class => "Connection", :description => "The server could not complete the method because it lacked sufficient resources. This may be due to the client creating too many of some type of entity."},
46
+ 530 => {:name => "not-allowed", :error_class => "Connection", :description => "The client tried to work with some entity in a manner that is prohibited by the server, due to security settings or by some other criteria."},
47
+ 540 => {:name => "not-implemented", :error_class => "Connection", :description => "The client tried to use functionality that is not implemented in the server."},
48
+ 541 => {:name => "internal-error", :error_class => "Connection", :description => "The server could not complete the method because of an internal error. The server may require intervention by an operator in order to resume normal operations."}
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -15,39 +15,42 @@ module Smith
15
15
  end
16
16
 
17
17
  def setup_queues
18
- Messaging::Receiver.new('agency.control', :auto_delete => false, :durable => false, :strict => true).ready do |receiver|
19
- receiver.subscribe do |r|
20
- r.reply do |responder|
21
- # Add a logger proc to the responder chain.
22
- responder.callback { |ret| logger.debug { ret } if ret && !ret.empty? }
23
-
24
- begin
25
- Command.run(r.payload.command, r.payload.args, :agency => self, :agents => @agent_processes, :responder => responder)
26
- rescue Command::UnknownCommandError => e
27
- responder.value("Unknown command: #{r.payload.command}")
18
+ Messaging::Receiver.new(QueueDefinitions::Agency_control) do |receiver|
19
+ receiver.subscribe do |payload, responder|
20
+
21
+ completion = EM::Completion.new.tap do |c|
22
+ c.completion do |value|
23
+ pp value
24
+ responder.reply(Smith::ACL::Factory.create(:agency_command_response, :response => value))
28
25
  end
29
26
  end
27
+
28
+ begin
29
+ Command.run(payload.command, payload.args, :agency => self, :agents => @agent_processes, :responder => completion)
30
+ rescue Command::UnknownCommandError => e
31
+ responder.reply("Unknown command: #{payload.command}")
32
+ end
30
33
  end
31
34
  end
32
35
 
33
- Messaging::Receiver.new('agent.lifecycle', :auto_delete => false, :durable => false).ready do |receiver|
34
- receiver.subscribe do |r|
35
- case r.payload.state
36
+ Messaging::Receiver.new(QueueDefinitions::Agent_lifecycle) do |receiver|
37
+ receiver.subscribe do |payload, r|
38
+ case payload.state
36
39
  when 'dead'
37
- dead(r.payload)
40
+ dead(payload)
38
41
  when 'acknowledge_start'
39
- acknowledge_start(r.payload)
42
+ acknowledge_start(payload)
40
43
  when 'acknowledge_stop'
41
- acknowledge_stop(r.payload)
44
+ acknowledge_stop(payload)
42
45
  else
43
- logger.warn { "Unknown command received on agent.lifecycle queue: #{r.payload.state}" }
46
+ logger.warn { "Unknown command received on #{QueueDefinitions::Agent_lifecycle.name} queue: #{payload.state}" }
44
47
  end
45
48
  end
46
49
  end
47
50
 
48
- Messaging::Receiver.new('agent.keepalive', :auto_delete => false, :durable => false).ready do |receiver|
49
- receiver.subscribe do |r|
50
- keep_alive(r.payload)
51
+ Messaging::Receiver.new(QueueDefinitions::Agent_keepalive) do |receiver|
52
+ receiver.subscribe do |payload, r|
53
+ keep_alive(payload)
51
54
  end
52
55
  end
53
56
  end
@@ -6,6 +6,7 @@
6
6
  $: << File.dirname(__FILE__) + '/..'
7
7
 
8
8
  require 'smith'
9
+ require 'smith/agent'
9
10
 
10
11
  module Smith
11
12
  class AgentBootstrap
@@ -44,7 +45,6 @@ module Smith
44
45
  def start!
45
46
  write_pid_file
46
47
  @agent.run
47
- @agent.started
48
48
  end
49
49
 
50
50
  # Exceptional shutdown of the agent. Note. Whenever this is
@@ -84,8 +84,8 @@ module Smith
84
84
 
85
85
  def send_dead_message
86
86
  logger.debug { "Sending dead message to agency: #{@agent_name}" }
87
- Messaging::Sender.new('agent.lifecycle', :auto_delete => false, :durable => false).ready do |sender|
88
- sender.publish(ACL::Payload.new(:agent_lifecycle).content(:state => 'dead', :name => @agent_name))
87
+ Messaging::Sender.new(QueueDefinitions::Agent_lifecycle) do |sender|
88
+ sender.publish(ACL::Factory.create(:agent_lifecycle, :state => 'dead', :name => @agent_name))
89
89
  end
90
90
  end
91
91
 
data/lib/smith/command.rb CHANGED
@@ -38,9 +38,9 @@ module Smith
38
38
  clazz.execute
39
39
 
40
40
  rescue Trollop::CommandlineError => e
41
- vars[:responder].value(clazz.format_help(:prefix => "Error: #{e.message}.\n"))
41
+ vars[:responder].succeed(clazz.format_help(:prefix => "Error: #{e.message}.\n"))
42
42
  rescue Trollop::HelpNeeded
43
- vars[:responder].value(clazz.format_help)
43
+ vars[:responder].succeed(clazz.format_help)
44
44
  end
45
45
  end
46
46
 
@@ -47,6 +47,10 @@ module Smith
47
47
  @parser.conflicts(*syms)
48
48
  end
49
49
 
50
+ def depends(*syms)
51
+ @parser.depends(*syms)
52
+ end
53
+
50
54
  def options_spec
51
55
  banner "You should really set a proper banner notice for this command."
52
56
  end
@@ -3,26 +3,26 @@ module Smith
3
3
  module Commands
4
4
  class Agents < CommandBase
5
5
  def execute
6
- responder.value do
7
- # FIXME make sure that if the path doesn't exist don't blow up.
8
- agent_paths = Smith.agent_paths.inject([]) do |path_acc,path|
9
- path_acc.tap do |a|
10
- if path.exist?
11
- a << path.each_child.inject([]) do |agent_acc,p|
12
- agent_acc.tap do |b|
13
- b << Extlib::Inflection.camelize(p.basename('.rb')) if p.file? && p.basename('.rb').to_s.end_with?("agent")
14
- end
15
- end.flatten
16
- else
17
- error_message = "Agent path doesn't exist: #{path}"
18
- responder.value(error_message)
19
- []
20
- end
6
+ responder.succeed(_agents)
7
+ end
8
+
9
+ def _agents
10
+ # FIXME make sure that if the path doesn't exist don't blow up.
11
+ separator = (options[:one_column]) ? "\n" : " "
12
+
13
+ Smith.agent_paths.inject([]) do |path_acc,path|
14
+ path_acc.tap do |a|
15
+ if path.exist?
16
+ a << path.each_child.inject([]) do |agent_acc,p|
17
+ agent_acc.tap do |b|
18
+ b << Extlib::Inflection.camelize(p.basename('.rb')) if p.file? && p.basename('.rb').to_s.end_with?("agent")
19
+ end
20
+ end.flatten
21
+ else
22
+ return "Agent path doesn't exist: #{path}"
21
23
  end
22
- end.flatten
23
- separator = (options[:one_column]) ? "\n" : " "
24
- (agent_paths.empty?) ? "" : agent_paths.sort.join(separator)
25
- end
24
+ end
25
+ end.flatten.sort.join(separator)
26
26
  end
27
27
 
28
28
  private
@@ -3,10 +3,14 @@ module Smith
3
3
  module Commands
4
4
  class Kill < CommandBase
5
5
  def execute
6
- target.each do |agent_name|
6
+ work = ->(agent_name, iter) do
7
7
  agents[agent_name].kill
8
+ iter.next
8
9
  end
9
- responder.value
10
+
11
+ done = -> { responder.succeed('') }
12
+
13
+ EM::Iterator.new(target).each(work, done)
10
14
  end
11
15
 
12
16
  private
@@ -3,10 +3,8 @@ module Smith
3
3
  module Commands
4
4
  class List < CommandBase
5
5
  def execute
6
- responder.value do
7
- a = (options[:all]) ? agents : agents.state(:running)
8
- (a.empty?) ? nil : format(a, options[:long])
9
- end
6
+ selected_agents = (options[:all]) ? agents : agents.state(:running)
7
+ responder.succeed((selected_agents.empty?) ? '' : format(selected_agents, options[:long]))
10
8
  end
11
9
 
12
10
  private
@@ -3,44 +3,43 @@ module Smith
3
3
  module Commands
4
4
  class Logger < CommandBase
5
5
  def execute
6
- responder.value do
7
- if options[:level].nil?
8
- "No log level. You must specify a log level and a target"
6
+ responder.succeed(_logger)
7
+ end
8
+
9
+ def _logger(&blk)
10
+ if options[:level].nil?
11
+ "No log level. You must specify a log level and a target"
12
+ else
13
+ case target.first
14
+ when 'all'
15
+ agents.state(:running).each do |agent|
16
+ send_agent_control_message(agent, :command => 'log_level', :options => options[:level])
17
+ end
18
+ when 'agency'
19
+ begin
20
+ logger.info { "Setting agency log level to: #{options[:level]}" }
21
+ log_level(options[:level])
22
+ rescue ArgumentError
23
+ logger.error { "Incorrect log level: #{options[:level]}" }
24
+ end
25
+ when nil
26
+ "No target. You must specify one of the following: 'agency', 'all' or a list of agents"
9
27
  else
10
- case target.first
11
- when 'all'
12
- agents.state(:running).each do |agent|
13
- send_agent_control_message(agent, :command => 'log_level', :options => options[:level])
14
- end
15
- nil
16
- when 'agency'
17
- begin
18
- logger.info { "Setting agency log level to: #{options[:level]}" }
19
- log_level(options[:level])
20
- nil
21
- rescue ArgumentError
22
- logger.error { "Incorrect log level: #{options[:level]}" }
23
- nil
24
- end
25
- when nil
26
- "No target. You must specify one of the following: 'agency', 'all' or a list of agents"
27
- else
28
- target.each do |agent|
29
- if agents[agent].running?
30
- send_agent_control_message(agents[agent], :command => 'log_level', :options => [options[:level]])
31
- end
28
+ target.each do |agent|
29
+ if agents[agent].running?
30
+ send_agent_control_message(agents[agent], :command => 'log_level', :options => [options[:level]])
32
31
  end
33
- nil
34
32
  end
35
33
  end
36
34
  end
35
+ ''
37
36
  end
38
37
 
39
38
  private
40
39
 
41
40
  def send_agent_control_message(agent, message)
42
- Messaging::Sender.new(agent.control_queue_name, :durable => false, :auto_delete => true, :persistent => true, :strict => true).ready do |sender|
43
- sender.publish(ACL::Payload.new(:agent_command).content(message))
41
+ Messaging::Sender.new(agent.control_queue_name, :durable => false, :auto_delete => true, :persistent => true, :strict => true) do |sender|
42
+ sender.publish(ACL::Factory.create(:agent_command, message))
44
43
  end
45
44
  end
46
45
 
@@ -3,11 +3,7 @@ module Smith
3
3
  module Commands
4
4
  class Metadata < CommandBase
5
5
  def execute
6
- responder.value do
7
- target.inject([]) do |acc,agent_name|
8
- acc.tap { |a| a << ["#{agent_name}: #{agents[agent_name].metadata}"] }
9
- end.join("\n")
10
- end
6
+ responder.succeed(target.inject([]) { |acc,agent_name| acc.tap { |a| a << ["#{agent_name}: #{agents[agent_name].metadata}"] } }.join("\n"))
11
7
  end
12
8
 
13
9
  private
@@ -4,24 +4,26 @@ module Smith
4
4
  module Commands
5
5
  class ObjectCount < CommandBase
6
6
  def execute
7
- responder.value do
8
- target.each do |agent|
9
- if agents[agent].running?
10
- send_agent_control_message(agents[agent], :command => 'object_count', :options => [options[:threshold].to_s])
11
- end
7
+ if target.size > 1
8
+ responder.succeed("You can only specify one agent at at time.")
9
+ else
10
+ object_count(target.first) do |objects|
11
+ responder.succeed(objects)
12
12
  end
13
- nil
14
13
  end
15
14
  end
16
15
 
17
- private
18
-
19
- def send_agent_control_message(agent, message)
20
- Messaging::Sender.new(agent.control_queue_name, :durable => false, :auto_delete => true, :persistent => true, :strict => true).ready do |sender|
21
- sender.publish(ACL::Payload.new(:agent_command).content(message))
16
+ def object_count(agent, &blk)
17
+ if agents[agent].running?
18
+ Messaging::Sender.new(agent.control_queue_name, :durable => false, :auto_delete => true, :persistent => true, :strict => true) do |sender|
19
+ sender.on_reply(blk)
20
+ sender.publish(ACL::Payload.new(:agent_command, :command => 'object_count', :options => [options[:threshold].to_s]))
21
+ end
22
22
  end
23
23
  end
24
24
 
25
+ private
26
+
25
27
  def options_spec
26
28
  banner "Dump the ruby ObjectSpace stats. This is purely for debuging purposes only."
27
29
 
@@ -12,17 +12,26 @@ module Smith
12
12
  include Common
13
13
 
14
14
  def execute
15
- Messaging::Sender.new('agency.control', :auto_delete => false, :durable => false, :strict => true).ready do |sender|
16
- payload = ACL::Payload.new(:agency_command).content(:command => 'stop', :args => target)
17
-
18
- sender.publish_and_receive(payload) do |r|
19
- payload = ACL::Payload.new(:agency_command).content(:command => 'start', :args => target)
20
- EM.add_timer(0.5) do
21
- sender.publish_and_receive(payload) do |r|
22
- responder.value
23
- end
15
+ Messaging::Sender.new(QueueDefinitions::Agency_control) do |sender|
16
+ @sender = sender
17
+
18
+ # agent is a method and as such I cannot pass it into the block.
19
+ # This just assigns it to a local method making it all work.
20
+
21
+ lagents = agents
22
+ # lagents.each do |agent_name|
23
+ worker = ->(agent_name, iter) do
24
+ lagents[agent_name].stop
25
+ lagents[agent_name].add_callback(:acknowledge_stop) do
26
+ lagents.invalidate(agent_name)
27
+ lagents[agent_name].start
24
28
  end
29
+ iter.next
25
30
  end
31
+
32
+ done = -> { responder.succeed('') }
33
+
34
+ EM::Iterator.new(target).each(worker, done)
26
35
  end
27
36
  end
28
37