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.
Files changed (50) hide show
  1. data/bin/agency +55 -0
  2. data/bin/smithctl +102 -0
  3. data/lib/smith.rb +237 -0
  4. data/lib/smith/acl_compiler.rb +74 -0
  5. data/lib/smith/agent.rb +207 -0
  6. data/lib/smith/agent_cache.rb +40 -0
  7. data/lib/smith/agent_config.rb +22 -0
  8. data/lib/smith/agent_monitoring.rb +52 -0
  9. data/lib/smith/agent_process.rb +181 -0
  10. data/lib/smith/application/agency.rb +126 -0
  11. data/lib/smith/bootstrap.rb +153 -0
  12. data/lib/smith/cache.rb +61 -0
  13. data/lib/smith/command.rb +128 -0
  14. data/lib/smith/commands/agency/agents.rb +28 -0
  15. data/lib/smith/commands/agency/common.rb +18 -0
  16. data/lib/smith/commands/agency/kill.rb +13 -0
  17. data/lib/smith/commands/agency/list.rb +65 -0
  18. data/lib/smith/commands/agency/logger.rb +56 -0
  19. data/lib/smith/commands/agency/metadata.rb +14 -0
  20. data/lib/smith/commands/agency/restart.rb +39 -0
  21. data/lib/smith/commands/agency/start.rb +62 -0
  22. data/lib/smith/commands/agency/state.rb +14 -0
  23. data/lib/smith/commands/agency/stop.rb +70 -0
  24. data/lib/smith/commands/agency/version.rb +23 -0
  25. data/lib/smith/commands/smithctl/cat.rb +70 -0
  26. data/lib/smith/commands/smithctl/pop.rb +76 -0
  27. data/lib/smith/commands/smithctl/rm.rb +36 -0
  28. data/lib/smith/commands/smithctl/smithctl_version.rb +23 -0
  29. data/lib/smith/commands/smithctl/top.rb +42 -0
  30. data/lib/smith/commands/template.rb +9 -0
  31. data/lib/smith/config.rb +32 -0
  32. data/lib/smith/logger.rb +91 -0
  33. data/lib/smith/messaging/acl/agency_command.proto +5 -0
  34. data/lib/smith/messaging/acl/agent_command.proto +5 -0
  35. data/lib/smith/messaging/acl/agent_config_request.proto +4 -0
  36. data/lib/smith/messaging/acl/agent_config_update.proto +5 -0
  37. data/lib/smith/messaging/acl/agent_keepalive.proto +6 -0
  38. data/lib/smith/messaging/acl/agent_lifecycle.proto +12 -0
  39. data/lib/smith/messaging/acl/agent_stats.proto +14 -0
  40. data/lib/smith/messaging/acl/default.rb +51 -0
  41. data/lib/smith/messaging/acl/search.proto +9 -0
  42. data/lib/smith/messaging/amqp_options.rb +55 -0
  43. data/lib/smith/messaging/endpoint.rb +116 -0
  44. data/lib/smith/messaging/exceptions.rb +7 -0
  45. data/lib/smith/messaging/payload.rb +102 -0
  46. data/lib/smith/messaging/queue_factory.rb +67 -0
  47. data/lib/smith/messaging/receiver.rb +237 -0
  48. data/lib/smith/messaging/responder.rb +15 -0
  49. data/lib/smith/messaging/sender.rb +61 -0
  50. 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,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Smith
3
+ module Commands
4
+ class Kill < Command
5
+ def execute
6
+ target.each do |agent_name|
7
+ agents[agent_name].kill
8
+ end
9
+ responder.value
10
+ end
11
+ end
12
+ end
13
+ 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