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.
- data/bin/smithctl +16 -19
- data/lib/smith.rb +25 -41
- data/lib/smith/agent.rb +96 -66
- data/lib/smith/agent_monitoring.rb +3 -4
- data/lib/smith/agent_process.rb +16 -9
- data/lib/smith/amqp_errors.rb +53 -0
- data/lib/smith/application/agency.rb +23 -20
- data/lib/smith/bootstrap.rb +3 -3
- data/lib/smith/command.rb +2 -2
- data/lib/smith/command_base.rb +4 -0
- data/lib/smith/commands/agency/agents.rb +19 -19
- data/lib/smith/commands/agency/kill.rb +6 -2
- data/lib/smith/commands/agency/list.rb +2 -4
- data/lib/smith/commands/agency/logger.rb +27 -28
- data/lib/smith/commands/agency/metadata.rb +1 -5
- data/lib/smith/commands/agency/object_count.rb +13 -11
- data/lib/smith/commands/agency/restart.rb +18 -9
- data/lib/smith/commands/agency/start.rb +34 -25
- data/lib/smith/commands/agency/stop.rb +58 -41
- data/lib/smith/commands/agency/version.rb +10 -10
- data/lib/smith/commands/common.rb +7 -4
- data/lib/smith/commands/smithctl/acl.rb +46 -37
- data/lib/smith/commands/smithctl/commands.rb +1 -1
- data/lib/smith/commands/smithctl/firehose.rb +30 -0
- data/lib/smith/commands/smithctl/pop.rb +39 -32
- data/lib/smith/commands/smithctl/push.rb +70 -51
- data/lib/smith/commands/smithctl/rm.rb +32 -9
- data/lib/smith/commands/smithctl/subscribe.rb +36 -0
- data/lib/smith/commands/smithctl/top.rb +1 -1
- data/lib/smith/exceptions.rb +2 -0
- data/lib/smith/messaging/acl/agency_command.proto +4 -0
- data/lib/smith/messaging/acl/default.rb +8 -1
- data/lib/smith/messaging/amqp_options.rb +2 -2
- data/lib/smith/messaging/message_counter.rb +21 -0
- data/lib/smith/messaging/payload.rb +47 -49
- data/lib/smith/messaging/queue.rb +50 -0
- data/lib/smith/messaging/queue_definition.rb +18 -0
- data/lib/smith/messaging/queue_factory.rb +20 -29
- data/lib/smith/messaging/receiver.rb +211 -173
- data/lib/smith/messaging/requeue.rb +91 -0
- data/lib/smith/messaging/sender.rb +184 -28
- data/lib/smith/messaging/util.rb +72 -0
- data/lib/smith/queue_definitions.rb +11 -0
- data/lib/smith/version.rb +1 -1
- metadata +18 -10
- 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
|
-
|
11
|
-
Smith.stop(true)
|
18
|
+
blk.call("No queue specified. Please specify a queue.")
|
12
19
|
else
|
13
20
|
begin
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
36
|
+
on_done = -> { blk.call("") }
|
54
37
|
|
55
|
-
|
38
|
+
iterator.each(on_work, on_done)
|
39
|
+
end
|
56
40
|
end
|
57
41
|
rescue MultiJson::DecodeError => e
|
58
|
-
|
59
|
-
Smith.stop
|
42
|
+
blk.call(e)
|
60
43
|
end
|
61
44
|
end
|
62
45
|
end
|
63
46
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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, :
|
79
|
-
opt :file, "read
|
80
|
-
opt :number, "the number of times to send the message", :type => :integer, :default => 1, :short => :n
|
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.
|
8
|
+
responder.succeed("No queue specified. Please specify a queue.")
|
9
9
|
else
|
10
|
-
|
10
|
+
@on_error = proc do |ch,channel_close|
|
11
11
|
case channel_close.reply_code
|
12
12
|
when 404
|
13
|
-
responder.
|
13
|
+
responder.succeed("No such queue: [#{channel_close.reply_code}]: #{channel_close.reply_text}")
|
14
14
|
when 406
|
15
|
-
responder.
|
15
|
+
responder.succeed("Queue not empty: [#{channel_close.reply_code}]: #{channel_close.reply_text}.")
|
16
16
|
else
|
17
|
-
responder.
|
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
|
-
|
23
|
-
|
24
|
-
|
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(
|
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)
|
data/lib/smith/exceptions.rb
CHANGED
@@ -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
|
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 :
|
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 =>
|
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
|
10
|
+
def to_json
|
11
11
|
Yajl.dump(self.to_hash)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
@@acl_classes ||= {:default => Default}
|
15
|
+
class Factory
|
16
|
+
include Logger
|
18
17
|
|
19
|
-
|
18
|
+
@@acl_classes = {:default => Default}
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
logger.
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
@@acl_classes[
|
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(
|
46
|
-
if
|
47
|
-
@
|
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
|
-
|
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
|
72
|
-
@
|
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
|
-
@
|
75
|
+
@acl.to_hash
|
78
76
|
end
|
79
77
|
|
80
78
|
# Encode the content, returning the encoded data.
|
81
79
|
def encode
|
82
|
-
@
|
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 @
|
88
|
-
@
|
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
|
-
@
|
91
|
+
@acl.inspect
|
94
92
|
end
|
95
93
|
|
96
94
|
# Decode the content using the specified decoder.
|
97
|
-
def self.decode(payload,
|
98
|
-
|
95
|
+
def self.decode(payload, type=:default)
|
96
|
+
Factory.create(type).parse_from_string(payload)
|
99
97
|
end
|
100
98
|
end
|
101
99
|
end
|