smith 0.5.7
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/agency +55 -0
- data/bin/smithctl +102 -0
- data/lib/smith.rb +237 -0
- data/lib/smith/acl_compiler.rb +74 -0
- data/lib/smith/agent.rb +207 -0
- data/lib/smith/agent_cache.rb +40 -0
- data/lib/smith/agent_config.rb +22 -0
- data/lib/smith/agent_monitoring.rb +52 -0
- data/lib/smith/agent_process.rb +181 -0
- data/lib/smith/application/agency.rb +126 -0
- data/lib/smith/bootstrap.rb +153 -0
- data/lib/smith/cache.rb +61 -0
- data/lib/smith/command.rb +128 -0
- data/lib/smith/commands/agency/agents.rb +28 -0
- data/lib/smith/commands/agency/common.rb +18 -0
- data/lib/smith/commands/agency/kill.rb +13 -0
- data/lib/smith/commands/agency/list.rb +65 -0
- data/lib/smith/commands/agency/logger.rb +56 -0
- data/lib/smith/commands/agency/metadata.rb +14 -0
- data/lib/smith/commands/agency/restart.rb +39 -0
- data/lib/smith/commands/agency/start.rb +62 -0
- data/lib/smith/commands/agency/state.rb +14 -0
- data/lib/smith/commands/agency/stop.rb +70 -0
- data/lib/smith/commands/agency/version.rb +23 -0
- data/lib/smith/commands/smithctl/cat.rb +70 -0
- data/lib/smith/commands/smithctl/pop.rb +76 -0
- data/lib/smith/commands/smithctl/rm.rb +36 -0
- data/lib/smith/commands/smithctl/smithctl_version.rb +23 -0
- data/lib/smith/commands/smithctl/top.rb +42 -0
- data/lib/smith/commands/template.rb +9 -0
- data/lib/smith/config.rb +32 -0
- data/lib/smith/logger.rb +91 -0
- data/lib/smith/messaging/acl/agency_command.proto +5 -0
- data/lib/smith/messaging/acl/agent_command.proto +5 -0
- data/lib/smith/messaging/acl/agent_config_request.proto +4 -0
- data/lib/smith/messaging/acl/agent_config_update.proto +5 -0
- data/lib/smith/messaging/acl/agent_keepalive.proto +6 -0
- data/lib/smith/messaging/acl/agent_lifecycle.proto +12 -0
- data/lib/smith/messaging/acl/agent_stats.proto +14 -0
- data/lib/smith/messaging/acl/default.rb +51 -0
- data/lib/smith/messaging/acl/search.proto +9 -0
- data/lib/smith/messaging/amqp_options.rb +55 -0
- data/lib/smith/messaging/endpoint.rb +116 -0
- data/lib/smith/messaging/exceptions.rb +7 -0
- data/lib/smith/messaging/payload.rb +102 -0
- data/lib/smith/messaging/queue_factory.rb +67 -0
- data/lib/smith/messaging/receiver.rb +237 -0
- data/lib/smith/messaging/responder.rb +15 -0
- data/lib/smith/messaging/sender.rb +61 -0
- metadata +239 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class Agents < Command
|
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
|
21
|
+
end
|
22
|
+
end.flatten
|
23
|
+
(agent_paths.empty?) ? "" : agent_paths.sort.join(" ")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
module Common
|
5
|
+
def agent_group(group)
|
6
|
+
Smith.agent_paths.map do |path|
|
7
|
+
group_dir = path.join(group)
|
8
|
+
if group_dir.exist? && group_dir.directory?
|
9
|
+
agents = Pathname.glob("#{path.join(group)}/*_agent.rb")
|
10
|
+
return agents.map {|a| Extlib::Inflection.camelize(a.basename(".rb").to_s)}
|
11
|
+
else
|
12
|
+
raise RuntimeError, "Group does not exist: #{group}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class List < Command
|
5
|
+
def execute
|
6
|
+
responder.value do
|
7
|
+
if options[:all]
|
8
|
+
if agents.empty?
|
9
|
+
"No agents running."
|
10
|
+
else
|
11
|
+
if options[:long]
|
12
|
+
tabulate(long_format(agents), :header => "total #{agents.count}")
|
13
|
+
else
|
14
|
+
short_format(agents)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
running_agents = agents.state(:running)
|
19
|
+
if running_agents.empty?
|
20
|
+
"No agents running."
|
21
|
+
else
|
22
|
+
if options[:long]
|
23
|
+
tabulate(long_format(running_agents), :header => "total #{running_agents.count}")
|
24
|
+
else
|
25
|
+
short_format(running_agents)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def options_parser
|
33
|
+
Trollop::Parser.new do
|
34
|
+
banner Command.banner('list')
|
35
|
+
opt :long, "the number of times to send the message", :short => :l
|
36
|
+
opt :all, "show all agents in all states", :short => :a
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def long_format(agents)
|
43
|
+
agents.map do |a|
|
44
|
+
[a.state, a.pid, (a.started_at) ? format_time(a.started_at) : '', (!(a.stopped? || a.null?) && !a.alive?) ? '(agent dead)' : "", a.name]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def short_format(agents)
|
49
|
+
agents.map(&:name).sort.join(" ")
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_time(t)
|
53
|
+
(t) ? Time.at(t).strftime("%Y/%m/%d %H:%M:%S") : ''
|
54
|
+
end
|
55
|
+
|
56
|
+
def tabulate(a, opts={})
|
57
|
+
col_widths = a.transpose.map{|col| col.map{|cell| cell.to_s.length}.max}
|
58
|
+
header = (opts[:header]) ? "#{opts[:header]}\n" : ''
|
59
|
+
a.inject(header) do |acc,e|
|
60
|
+
acc << sprintf("#{col_widths.inject("") { |spec,w| spec << "%-#{w + 2}s"}}\n", *e)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class Logger < Command
|
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"
|
9
|
+
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
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def options_parser
|
40
|
+
Trollop::Parser.new do
|
41
|
+
banner Command.banner('logger')
|
42
|
+
opt :level, "the log level you want to set", :type => :string, :short => :l
|
43
|
+
opt :trace, "turn trace on or off", :type => :boolean, :short => :t
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def send_agent_control_message(agent, message)
|
50
|
+
Messaging::Sender.new(agent.control_queue_name, :durable => false, :auto_delete => true).ready do |sender|
|
51
|
+
sender.publish(ACL::Payload.new(:agent_command).content(message))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class Metadata < Command
|
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
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'common'
|
4
|
+
|
5
|
+
# This is a meta-command. It doesn't implement anything it simply sends
|
6
|
+
# messages to one or many existing commands to do the work.
|
7
|
+
|
8
|
+
module Smith
|
9
|
+
module Commands
|
10
|
+
class Restart < Command
|
11
|
+
|
12
|
+
include Common
|
13
|
+
|
14
|
+
def execute
|
15
|
+
Messaging::Sender.new('agency.control', :auto_delete => true, :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
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def options_parser
|
31
|
+
command = self.class.to_s.split(/::/).last.downcase
|
32
|
+
Trollop::Parser.new do
|
33
|
+
banner Command.banner(command)
|
34
|
+
opt :group, "Start everything in the specified group", :type => :string, :short => :g
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'common'
|
4
|
+
|
5
|
+
module Smith
|
6
|
+
module Commands
|
7
|
+
class Start < Command
|
8
|
+
|
9
|
+
include Common
|
10
|
+
|
11
|
+
def execute
|
12
|
+
# Sort out any groups. If the group option is set it will override
|
13
|
+
# any other specified agents.
|
14
|
+
if options[:group]
|
15
|
+
begin
|
16
|
+
# I don't understand why I need to put self here. target= is a method
|
17
|
+
# on Command so I would have thought that would be used but a local
|
18
|
+
# variable is used instead of the method. TODO work out why and fix.
|
19
|
+
self.target = agent_group(options[:group])
|
20
|
+
if target.empty?
|
21
|
+
responder.value("There are no agents in group: #{options[:group]}")
|
22
|
+
return
|
23
|
+
end
|
24
|
+
rescue RuntimeError => e
|
25
|
+
responder.value(e.message)
|
26
|
+
return
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
responder.value do
|
31
|
+
if target.empty?
|
32
|
+
"Start what? No agent specified."
|
33
|
+
else
|
34
|
+
target.map do |agent|
|
35
|
+
agents[agent].name = agent
|
36
|
+
if agents[agent].path
|
37
|
+
if options[:kill]
|
38
|
+
agents[agent].kill
|
39
|
+
end
|
40
|
+
agents[agent].start
|
41
|
+
nil
|
42
|
+
else
|
43
|
+
"Unknown agent: #{agents[agent].name}".tap do |m|
|
44
|
+
logger.error { m }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end.compact.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def options_parser
|
53
|
+
command = self.class.to_s.split(/::/).last.downcase
|
54
|
+
Trollop::Parser.new do
|
55
|
+
banner Command.banner(command)
|
56
|
+
opt :kill, "Reset the state of the agent before starting", :short => :k
|
57
|
+
opt :group, "Start everything in the specified group", :type => :string, :short => :g
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class State < Command
|
5
|
+
def execute
|
6
|
+
responder.value do
|
7
|
+
target.inject([]) do |acc,agent_name|
|
8
|
+
acc.tap { |a| a << ["#{agent_name}: #{agents[agent_name].state}"] }
|
9
|
+
end.join("\n")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'common'
|
4
|
+
|
5
|
+
module Smith
|
6
|
+
module Commands
|
7
|
+
class Stop < Command
|
8
|
+
|
9
|
+
include Common
|
10
|
+
|
11
|
+
def execute
|
12
|
+
case target.first
|
13
|
+
when 'agency'
|
14
|
+
running_agents = agents.state(:running)
|
15
|
+
if running_agents.empty?
|
16
|
+
logger.info { "Agency shutting down." }
|
17
|
+
Smith.stop
|
18
|
+
responder.value
|
19
|
+
else
|
20
|
+
logger.warn { "Agents are still running: #{running_agents.map(&:name).join(", ")}." }
|
21
|
+
logger.info { "Agency not shutting down. Use force_stop if you really want to shut it down." }
|
22
|
+
responder.value("Not shutting down, agents are still running: #{running_agents.map(&:name).join(", ")}.")
|
23
|
+
end
|
24
|
+
when 'all'
|
25
|
+
agents.state(:running).each do |agent|
|
26
|
+
agent.stop
|
27
|
+
end
|
28
|
+
responder.value
|
29
|
+
else
|
30
|
+
# Sort out any groups. If the group option is set it will override
|
31
|
+
# any other specified agents.
|
32
|
+
if options[:group]
|
33
|
+
begin
|
34
|
+
# I don't understand why I need to put self here. target= is a method
|
35
|
+
# on Command so I would have thought that would be used but a local
|
36
|
+
# variable is used instead of the method. TODO work out why and fix.
|
37
|
+
self.target = agent_group(options[:group])
|
38
|
+
if target.empty?
|
39
|
+
responder.value("There are no agents in group: #{options[:group]}")
|
40
|
+
return
|
41
|
+
end
|
42
|
+
rescue RuntimeError => e
|
43
|
+
responder.value(e)
|
44
|
+
return
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
ret = target.inject([]) do |acc,agent_name|
|
49
|
+
acc << if agents[agent_name].running?
|
50
|
+
agents[agent_name].stop
|
51
|
+
nil
|
52
|
+
else
|
53
|
+
logger.warn { "Agent not running: #{agent_name}" }
|
54
|
+
agent_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
responder.value((ret.compact.empty?) ? nil : "Agent(s) not running: #{ret.compact.join(", ")}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def options_parser
|
62
|
+
command = self.class.to_s.split(/::/).last.downcase
|
63
|
+
Trollop::Parser.new do
|
64
|
+
banner Command.banner(command)
|
65
|
+
opt :group, "Stop everything in the specified group", :type => :string, :short => :g
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Smith
|
3
|
+
module Commands
|
4
|
+
class Version < Command
|
5
|
+
def execute
|
6
|
+
version_file = Smith.root_path.join('VERSION')
|
7
|
+
|
8
|
+
if options[:git] || !version_file.exist?
|
9
|
+
responder.value("#{(`git describe` rescue '').strip}")
|
10
|
+
else
|
11
|
+
responder.value(version_file.read.strip)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def options_parser
|
16
|
+
Trollop::Parser.new do
|
17
|
+
banner Command.banner('version')
|
18
|
+
opt :git, "run git describe, assuming git is installed", :short => :g
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
module Smith
|
6
|
+
module Commands
|
7
|
+
class Cat < Command
|
8
|
+
def execute
|
9
|
+
if target.size == 0
|
10
|
+
responder.value("No queue specified. Please specify a queue.")
|
11
|
+
Smith.stop(true)
|
12
|
+
else
|
13
|
+
begin
|
14
|
+
data = case
|
15
|
+
when options[:json_given]
|
16
|
+
options[:json]
|
17
|
+
when options[:file_given]
|
18
|
+
file = Pathname.new(options[:file])
|
19
|
+
if file.exist?
|
20
|
+
file.read
|
21
|
+
else
|
22
|
+
responder.value("File does not exist: #{file.display}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Smith::Messaging::Sender.new(target.first, :auto_delete => options[:dynamic], :persistent => true, :nowait => false, :strict => true).ready do |sender|
|
27
|
+
|
28
|
+
work = proc do |n,iter|
|
29
|
+
sender.publish(json_to_payload(data, options[:type])) do
|
30
|
+
iter.next
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
done = proc do
|
35
|
+
responder.value
|
36
|
+
end
|
37
|
+
|
38
|
+
EM::Iterator.new(0..options[:number] - 1).each(work, done)
|
39
|
+
|
40
|
+
end
|
41
|
+
rescue MultiJson::DecodeError => e
|
42
|
+
responder.value(e)
|
43
|
+
Smith.stop
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def options_parser
|
49
|
+
Trollop::Parser.new do
|
50
|
+
banner Command.banner('cat')
|
51
|
+
opt :type, "message type", :type => :string, :default => 'default', :short => :t
|
52
|
+
opt :json, "supply the json representation with this flag", :type => :string, :conflicts => :file, :short => :j
|
53
|
+
opt :file, "read the data from the named file", :type => :string, :conflicts => :json, :short => :f
|
54
|
+
opt :number, "the number of times to send the message", :type => :integer, :default => 1, :short => :n
|
55
|
+
opt :dynamic, "send message to a dynamic queue", :type => :boolean, :default => false, :short => :d
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def json_to_payload(data, type)
|
62
|
+
Smith::ACL::Payload.new(type.to_sym).content do |m|
|
63
|
+
MultiJson.decode(data, :symbolize_keys => true).each do |k,v|
|
64
|
+
m.send("#{k}=".to_sym, v)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|