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
@@ -6,79 +6,98 @@ module Smith
6
6
  module Commands
7
7
  class Push < CommandBase
8
8
  def execute
9
+ push do |ret|
10
+ responder.succeed(ret)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def push(&blk)
9
17
  if target.size == 0
10
- responder.value("No queue specified. Please specify a queue.")
11
- Smith.stop(true)
18
+ blk.call("No queue specified. Please specify a queue.")
12
19
  else
13
20
  begin
14
- messages = case
15
- when options[:message_given]
16
- options[:message]
17
- when options[:file_given]
18
- responder.value("--number option cannot be used with the --file option.") if options[:number_given]
19
-
20
- file = Pathname.new(options[:file])
21
- if file.exist?
22
- file.read
23
- else
24
- responder.value("File does not exist: #{file.display}")
25
- end
26
- else
27
- responder.value("--number option cannot be used when reading messages from standard in.") if options[:number_given]
28
- STDIN.read
29
- end
30
-
31
- if messages.nil? || messages && messages.empty?
32
- responder.value("Message must be empty.")
33
- end
34
-
35
- # This is starting to get a bit messy. The iterator is being used
36
- # for two purposes: the first is to send multiple messages, the
37
- # second is send the same message multiple times.
38
- # TODO Clean this up.
39
-
40
- Messaging::Sender.new(target.first, :auto_delete => options[:dynamic], :persistent => true, :nowait => false, :strict => true).ready do |sender|
41
- work = proc do |message,iter|
42
- m = (options[:number_given]) ? messages : message
43
-
44
- sender.publish(json_to_payload(m, options[:type])) do
45
- iter.next
21
+ Messaging::Sender.new(target.first, :auto_delete => options[:dynamic], :persistent => true, :nowait => false, :strict => true) do |sender|
22
+ if options[:reply]
23
+ timeout = Smith::Messaging::Timeout.new(options[:timeout]) do |message_id|
24
+ blk.call("Timed out after: #{options[:timeout]} seconds for message: #{options[:message]}: message_id: #{message_id}")
46
25
  end
47
- end
48
26
 
49
- done = proc do
50
- responder.value
51
- end
27
+ sender.on_reply(:timeout => timeout) { |payload| blk.call(payload.to_hash) }
28
+ sender.publish(json_to_payload(options[:message], options[:type]))
29
+ else
30
+ on_work = ->(message, iter) do
31
+ sender.publish(json_to_payload(message, options[:type])) do
32
+ iter.next
33
+ end
34
+ end
52
35
 
53
- data = (options[:number_given]) ? 0..options[:number] - 1 : messages.split("\n")
36
+ on_done = -> { blk.call("") }
54
37
 
55
- EM::Iterator.new(data).each(work, done)
38
+ iterator.each(on_work, on_done)
39
+ end
56
40
  end
57
41
  rescue MultiJson::DecodeError => e
58
- responder.value(e)
59
- Smith.stop
42
+ blk.call(e)
60
43
  end
61
44
  end
62
45
  end
63
46
 
64
- private
65
-
66
- def json_to_payload(data, type)
67
- ACL::Payload.new(type.to_sym).content do |m|
68
- MultiJson.load(data, :symbolize_keys => true).each do |k,v|
69
- m.send("#{k}=".to_sym, v)
47
+ # Return a interator that can iterate over whatever the input is.
48
+ def iterator
49
+ case
50
+ when options[:message_given]
51
+ if options[:number_given]
52
+ EM::Iterator.new([options[:message]] * options[:number])
53
+ else
54
+ EM::Iterator.new([options[:message]])
70
55
  end
56
+ when options[:file_given]
57
+ FileReader.new(options[:file])
58
+ else
59
+ raise ArgumentError, "--number option cannot be used when reading messages from standard in." if options[:number_given]
60
+ FileReader.new(STDIN)
71
61
  end
72
62
  end
73
63
 
64
+ def json_to_payload(data, type)
65
+ ACL::Factory.create(type, MultiJson.load(data, :symbolize_keys => true))
66
+ end
67
+
74
68
  def options_spec
75
69
  banner "Send a message to a queue. The ACL can also be specified."
76
70
 
77
71
  opt :type, "message type", :type => :string, :default => 'default', :short => :t
78
- opt :message, "the message, as json", :type => :string, :conflicts => :file, :short => :m
79
- opt :file, "read the data from the named file. One message per line", :type => :string, :conflicts => :message, :short => :f
80
- opt :number, "the number of times to send the message", :type => :integer, :default => 1, :short => :n, :conflicts => :file
72
+ opt :message, "the message, as json", :type => :string, :short => :m
73
+ opt :file, "read messages from the named file", :type => :string, :short => :f
74
+ opt :number, "the number of times to send the message", :type => :integer, :default => 1, :short => :n
75
+ opt :reply, "set a reply listener.", :short => :r
76
+ opt :timeout, "timeout when waiting for a reply", :type => :integer, :depends => :reply, :default => Smith.config.agency.timeout
81
77
  opt :dynamic, "send message to a dynamic queue", :type => :boolean, :default => false, :short => :d
78
+
79
+ conflicts :reply, :number, :file
80
+ conflicts :message, :file
81
+ end
82
+
83
+ class FileReader
84
+ def initialize(file)
85
+ @file = (file.is_a?(IO)) ? file : File.open(file)
86
+ end
87
+
88
+ def each(on_work, on_completed)
89
+ on_done = proc do |message|
90
+ line = @file.readline rescue nil
91
+ if line
92
+ class << on_done; alias :next :call; end
93
+ on_work.call(line, on_done)
94
+ else
95
+ on_completed.call
96
+ end
97
+ end
98
+
99
+ EM.next_tick(&on_done)
100
+ end
82
101
  end
83
102
  end
84
103
  end
@@ -5,24 +5,23 @@ module Smith
5
5
  def execute
6
6
  case target.size
7
7
  when 0
8
- responder.value("No queue specified. Please specify a queue.")
8
+ responder.succeed("No queue specified. Please specify a queue.")
9
9
  else
10
- Smith.on_error do |ch,channel_close|
10
+ @on_error = proc do |ch,channel_close|
11
11
  case channel_close.reply_code
12
12
  when 404
13
- responder.value("No such queue: #{extract_queue(channel_close.reply_text)}")
13
+ responder.succeed("No such queue: [#{channel_close.reply_code}]: #{channel_close.reply_text}")
14
14
  when 406
15
- responder.value("Queue not empty: #{extract_queue(channel_close.reply_text)}. Use -f to force remove")
15
+ responder.succeed("Queue not empty: [#{channel_close.reply_code}]: #{channel_close.reply_text}.")
16
16
  else
17
- responder.value("Unknown error: #{channel_close.reply_text}")
17
+ responder.succeed("Unknown error: [#{channel_close.reply_code}]: #{channel_close.reply_text}")
18
18
  end
19
19
  end
20
20
 
21
21
  target.each do |queue_name|
22
- Smith.channel.queue("smith.#{queue_name}", :passive => true) do |queue|
23
- queue_options = (options[:force]) ? {} : {:if_unused => true, :if_empty => true}
24
- queue.delete(queue_options) do |delete_ok|
25
- responder.value((options[:verbose]) ? delete_ok.message_count.to_s : nil)
22
+ delete_queue(queue_name) do |delete_ok|
23
+ delete_exchange(queue_name) do |delete_ok|
24
+ responder.succeed((options[:verbose]) ? delete_ok.message_count.to_s : nil)
26
25
  end
27
26
  end
28
27
  end
@@ -38,6 +37,30 @@ module Smith
38
37
  opt :verbose, "print the number of messages deleted", :short => :v
39
38
  end
40
39
 
40
+ def delete_exchange(exchange_name, &blk)
41
+ AMQP::Channel.new(Smith.connection) do |channel,ok|
42
+ channel.on_error(&@on_error)
43
+ channel.direct("smith.#{exchange_name}", :passive => true) do |exchange|
44
+ exchange_options = (options[:force]) ? {} : {:if_unused => true}
45
+ exchange.delete(exchange_options) do |delete_ok|
46
+ blk.call(delete_ok)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def delete_queue(queue_name, &blk)
53
+ AMQP::Channel.new(Smith.connection) do |channel,ok|
54
+ channel.on_error(&@on_error)
55
+ channel.queue("smith.#{queue_name}", :passive => true) do |queue|
56
+ queue_options = (options[:force]) ? {} : {:if_unused => true, :if_empty => true}
57
+ queue.delete(queue_options) do |delete_ok|
58
+ blk.call(delete_ok)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
41
64
  def extract_queue(message)
42
65
  match = /.*?'(.*?)'.*$/.match(message) #[1]
43
66
  if match && match[1]
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Smith
3
+ module Commands
4
+ class Subscribe < CommandBase
5
+ def execute
6
+ Messaging::Receiver.new(target.first, amqp_opts) do |receiver|
7
+ receiver.subscribe do |payload, r|
8
+ pp payload
9
+ end
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def amqp_opts
16
+ {}.tap do |amqp|
17
+ [:durable, :auto_delete, :header].each do |k|
18
+ if k == :header && !options[k].nil?
19
+ amqp[k] = eval(options[k])
20
+ else
21
+ amqp[k] = options[k]
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def options_spec
28
+ banner "Subcribe to the named queue and print and received messages to stdout."
29
+
30
+ opt :durable, "amqp durable option", :default => false
31
+ opt :auto_delete, "amqp auto-delete option", :default => false
32
+ opt :header, "amqp headers as json", :type => :string
33
+ end
34
+ end
35
+ end
36
+ end
@@ -9,7 +9,7 @@ module Smith
9
9
  Curses.init_screen()
10
10
  win = Curses::Window.new(Curses.lines, Curses.cols, 0, 0)
11
11
 
12
- Messaging::Receiver.new('agent.stats', :durable => false, :auto_delete => false).ready do |receiver|
12
+ Messaging::Receiver.new(QueueDefinitions::Agent_stats) do |receiver|
13
13
  receiver.subscribe do |r|
14
14
  payload = r.payload
15
15
  win.setpos(0,0)
@@ -7,6 +7,8 @@ module Smith
7
7
  end
8
8
 
9
9
  module Messaging
10
+ class ACLTimeoutError < RuntimeError; end
11
+
10
12
  class IncompletePayload < RuntimeError; end
11
13
  class IncorrectPayloadType < RuntimeError; end
12
14
  end
@@ -3,3 +3,7 @@ message AgencyCommand {
3
3
  required string command = 1;
4
4
  repeated string args = 2;
5
5
  }
6
+
7
+ message AgencyCommandResponse {
8
+ optional string response = 1;
9
+ }
@@ -1,4 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
3
+ require 'yajl'
4
+
2
5
  module Smith
3
6
  module ACL
4
7
 
@@ -29,11 +32,15 @@ module Smith
29
32
  @message.to_s
30
33
  end
31
34
 
35
+ def to_hash
36
+ @message && @message.to_hash
37
+ end
38
+
32
39
  def inspect
33
40
  "<#{self.class.to_s}> -> #{(self.respond_to?(:to_hash)) ? self.to_hash : self.to_s}"
34
41
  end
35
42
 
36
- def as_json
43
+ def to_json
37
44
  Yajl.dump(@message)
38
45
  end
39
46
 
@@ -4,7 +4,7 @@ module Smith
4
4
  class AmqpOptions
5
5
  include Logger
6
6
 
7
- attr_accessor :queue_name
7
+ attr_accessor :routing_key
8
8
 
9
9
  def initialize(options={})
10
10
  @options = options
@@ -24,7 +24,7 @@ module Smith
24
24
  end
25
25
 
26
26
  def publish(*extra_opts)
27
- merge(Smith.config.amqp.publish.to_hash, {:routing_key => queue_name}, extra_opts)
27
+ merge(Smith.config.amqp.publish.to_hash, {:routing_key => routing_key, :persistent => true}, extra_opts)
28
28
  end
29
29
 
30
30
  def subscribe(*extra_opts)
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Smith
3
+ module Messaging
4
+ class MessageCounter
5
+
6
+ def initialize(queue_name)
7
+ @message_counts = Hash.new(0)
8
+ @queue_name = queue_name
9
+ end
10
+
11
+ # Return the total number of messages sent or received for the named queue.
12
+ def counter
13
+ @message_counts[@queue_name]
14
+ end
15
+
16
+ def increment_counter(value=1)
17
+ @message_counts[@queue_name] += value
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,101 +1,99 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  module Smith
3
- module ACL
4
3
 
4
+ module ACL
5
5
  module ACLInstanceMethods
6
6
  def inspect
7
7
  "<#{self.class.to_s}> -> #{self.to_hash}"
8
8
  end
9
9
 
10
- def as_json
10
+ def to_json
11
11
  Yajl.dump(self.to_hash)
12
12
  end
13
13
  end
14
14
 
15
- module ClassMethods
16
- def content_class(e)
17
- @@acl_classes ||= {:default => Default}
15
+ class Factory
16
+ include Logger
18
17
 
19
- e = e.to_sym
18
+ @@acl_classes = {:default => Default}
20
19
 
21
- if @@acl_classes.include?(e)
22
- @@acl_classes[e]
23
- else
24
- class_name = Extlib::Inflection.camelize(e)
25
- if ACL.constants.include?(class_name)
26
- logger.error { "Shouldn't get here." }
20
+ class << self
21
+ def create(type, content=nil, &blk)
22
+ type = type.to_s
23
+
24
+ unless @@acl_classes.include?(type)
25
+ logger.debug { "Loading ACL: #{type}" }
26
+ # decorate the ACL class
27
+ @@acl_classes[type] = clazz(type).send(:include, ACLInstanceMethods)
28
+ @@acl_classes[type].send(:define_method, :_type) { type }
29
+ end
30
+
31
+ if blk
32
+ if content.nil?
33
+ @@acl_classes[type].new.tap { |m| blk.call(m) }
34
+ else
35
+ raise ArgumentError, "You cannot give a content hash and a block."
36
+ end
27
37
  else
28
- logger.debug { "#{class_name} Loaded from #{e}.pb.rb" }
29
- ACL.const_get(class_name).tap do |clazz|
30
- # Override the inspect method
31
- @@acl_classes[e] = clazz.send(:include, ACLInstanceMethods)
38
+ if content.respond_to?(:serialize_to_string)
39
+ content
40
+ elsif content.nil?
41
+ @@acl_classes[type].new
42
+ else
43
+ @@acl_classes[type].new(content)
32
44
  end
33
45
  end
34
46
  end
47
+
48
+ def clazz(type)
49
+ type.split(/::/).inject(ACL) do |a,m|
50
+ a.const_get(Extlib::Inflection.camelize(m))
51
+ end
52
+ end
35
53
  end
36
54
  end
37
55
 
38
56
  class Payload
39
57
  include Logger
40
58
 
41
- include ClassMethods
42
- extend ClassMethods
43
-
44
59
  # content can be an existing ACL class.
45
- def initialize(type=:default, opts={})
46
- if opts[:from]
47
- @type = opts[:from].class.to_s.split(/::/).last.snake_case
48
- @content = opts[:from]
49
- else
50
- @type = type
51
- @clazz = content_class(type)
52
- end
53
- end
54
-
55
- # Add content to the content or get the content from a payload
56
- def content(*content, &block)
57
- if content.empty?
58
- if block.nil?
59
- return @content
60
- else
61
- @content = @clazz.new
62
- block.call(@content)
63
- end
60
+ def initialize(acl, opts={})
61
+ if acl.respond_to?(:serialize_to_string)
62
+ @acl = acl
64
63
  else
65
- @content = @clazz.new(content.first)
64
+ raise ArgumentError, "ACL does not have a serialize_to_string method."
66
65
  end
67
- self
68
66
  end
69
67
 
70
68
  # The type of content.
71
- def type
72
- @type.to_s
69
+ def _type
70
+ @acl._type
73
71
  end
74
72
 
75
73
  # Returns a hash of the payload.
76
74
  def to_hash
77
- @content.to_hash
75
+ @acl.to_hash
78
76
  end
79
77
 
80
78
  # Encode the content, returning the encoded data.
81
79
  def encode
82
- @content.serialize_to_string
80
+ @acl.serialize_to_string
83
81
  end
84
82
 
85
83
  # Returns true if the payload has all its required fields set.
86
84
  def initialized?
87
- raise RuntimeError, "You probably forgot to call #content or give the :from option when instantiating the object." if @content.nil?
88
- @content.initialized?
85
+ raise RuntimeError, "You probably forgot to call #content or give the :from option when instantiating the object." if @acl.nil?
86
+ @acl.initialized?
89
87
  end
90
88
 
91
89
  # Convert the payload to a pretty string.
92
90
  def to_s
93
- @content.inspect
91
+ @acl.inspect
94
92
  end
95
93
 
96
94
  # Decode the content using the specified decoder.
97
- def self.decode(payload, decoder=:default)
98
- content_class(decoder).new.parse_from_string(payload)
95
+ def self.decode(payload, type=:default)
96
+ Factory.create(type).parse_from_string(payload)
99
97
  end
100
98
  end
101
99
  end