smith 0.5.12 → 0.5.13.1

Sign up to get free protection for your applications and to get access to all the features.
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