smith 0.5.8 → 0.5.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/bin/agency +49 -21
  2. data/bin/smithctl +51 -48
  3. data/lib/smith.rb +21 -17
  4. data/lib/smith/acl_compiler.rb +6 -3
  5. data/lib/smith/agent.rb +13 -24
  6. data/lib/smith/agent_monitoring.rb +5 -5
  7. data/lib/smith/agent_process.rb +4 -4
  8. data/lib/smith/application/agency.rb +11 -7
  9. data/lib/smith/bootstrap.rb +4 -4
  10. data/lib/smith/command.rb +26 -5
  11. data/lib/smith/command_base.rb +2 -2
  12. data/lib/smith/commands/agency/list.rb +11 -26
  13. data/lib/smith/commands/agency/logger.rb +1 -1
  14. data/lib/smith/commands/agency/restart.rb +1 -1
  15. data/lib/smith/commands/agency/start.rb +20 -7
  16. data/lib/smith/commands/agency/stop.rb +23 -10
  17. data/lib/smith/commands/agency/version.rb +31 -0
  18. data/lib/smith/commands/smithctl/commands.rb +1 -13
  19. data/lib/smith/commands/smithctl/pop.rb +34 -28
  20. data/lib/smith/commands/smithctl/{cat.rb → push.rb} +3 -3
  21. data/lib/smith/commands/smithctl/rm.rb +20 -0
  22. data/lib/smith/commands/smithctl/top.rb +1 -1
  23. data/lib/smith/config.rb +15 -0
  24. data/lib/smith/daemon.rb +68 -0
  25. data/lib/smith/{messaging/exceptions.rb → exceptions.rb} +6 -0
  26. data/lib/smith/logger.rb +2 -3
  27. data/lib/smith/messaging/acl/agent_keepalive.proto +2 -2
  28. data/lib/smith/messaging/acl/agent_lifecycle.proto +2 -2
  29. data/lib/smith/messaging/acl/default.rb +1 -1
  30. data/lib/smith/messaging/endpoint.rb +5 -5
  31. data/lib/smith/messaging/receiver.rb +19 -19
  32. data/lib/smith/messaging/sender.rb +3 -3
  33. data/lib/smith/version.rb +3 -0
  34. metadata +52 -41
  35. data/lib/smith/commands/agency/agency_version.rb +0 -24
  36. data/lib/smith/commands/agency/state.rb +0 -20
  37. data/lib/smith/commands/smithctl/smithctl_version.rb +0 -22
data/bin/agency CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- encoding: utf-8 -*-
3
3
 
4
- require 'pp'
5
- require 'pathname'
6
-
7
- root_path = Pathname.new(__FILE__).dirname.join('..').expand_path
8
- $:.unshift(root_path.join('lib'))
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
9
5
 
6
+ require 'pp'
10
7
  require 'smith'
8
+ require 'smith/daemon'
11
9
  require 'smith/acl_compiler'
12
10
  require 'smith/application/agency'
13
11
 
@@ -16,40 +14,70 @@ module Smith
16
14
 
17
15
  include Logger
18
16
 
19
- def initialize(root)
17
+ AGENCY_NAME = 'agency'
18
+
19
+ def initialize(opts={})
20
20
 
21
- Smith.compile_acls
22
- Smith.load_acls
21
+ @options = opts
22
+ options_check
23
23
 
24
+ @daemon = Daemon.new(AGENCY_NAME, opts[:daemon], opts[:pid_dir])
24
25
  @agency = Agency.new(:paths => Smith.agent_paths)
25
26
 
27
+ Smith.shutdown_hook do
28
+ puts "Shutting down"
29
+ end
30
+
26
31
  # Setup signal handlers to clean up.
27
32
  %w{TERM INT QUIT}.each do |sig|
28
- trap sig, proc { puts "Shutting down"; @agency.stop }
33
+ trap sig, proc { @agency.stop }
29
34
  end
30
35
  end
31
36
 
32
37
  def run
33
- Smith.start do
38
+ if @daemon.running?
39
+ logger.fatal { "The agency is alredy running" }
40
+ else
41
+ @daemon.daemonise
34
42
 
35
- # This block is here so the that the shutdown hook added in
36
- # Smith.start runs last. Yes I know this is leaky but that's how
37
- # it is at the moment.
43
+ Smith.compile_acls
44
+ Smith.load_acls
38
45
 
39
- Smith.shutdown_hook do
40
- # TODO put this as a command line option
41
- if false
42
- @compiler.clear_cache
46
+ Smith.start do
47
+ # This block is here so the that the shutdown hook added in
48
+ # Smith.start runs last. Yes I know this is leaky but that's how
49
+ # it is at the moment.
50
+
51
+ Smith.shutdown_hook do
52
+ @daemon.unlink_pid_file
43
53
  end
54
+
55
+ logger.info { "Starting #{File.basename($0)}" }
56
+ @agency.setup_queues
57
+ @agency.start_monitoring
44
58
  end
59
+ end
60
+ end
45
61
 
46
- logger.info { "Starting #{File.basename($0)}" }
47
- @agency.setup_queues
48
- @agency.start_monitoring
62
+ private
63
+
64
+ def options_check
65
+ if @options[:daemon] && Regexp.new(/stdout|stderr/i).match(Smith.config.logging.appender[:type])
66
+ puts "Logger set to stdout and daemonise is true. Log messages will be sent to /dev/null."
49
67
  end
50
68
  end
51
69
  end
52
70
  end
53
71
 
54
- agency_runner = Smith::AgencyRunner.new(root_path)
72
+ parser = Trollop::Parser.new do
73
+ opt :daemon, "daemonise", :default => false, :short => :d
74
+ opt :pid, "pid file", :type => :string, :short => :p
75
+ end
76
+
77
+ opts = Trollop::with_standard_exception_handling parser do
78
+ #raise Trollop::HelpNeeded if ARGV.size < 2
79
+ parser.parse ARGV
80
+ end
81
+
82
+ agency_runner = Smith::AgencyRunner.new(opts)
55
83
  agency_runner.run
data/bin/smithctl CHANGED
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- encoding: utf-8 -*-
3
3
 
4
+ %w{TERM INT QUIT}.each do |sig|
5
+ trap sig, proc { exit }
6
+ end
7
+
8
+
4
9
  $:.unshift(File.dirname(__FILE__) + '/../lib')
5
10
 
6
11
  require 'smith'
@@ -10,58 +15,34 @@ module Smith
10
15
 
11
16
  include Logger
12
17
 
13
- AGENCY_TIMEOUT = 60
14
-
15
- def initialize
16
- log_level(:info)
18
+ def initialize(options={})
19
+ log_level((options[:log_level_given]) ? options[:log_level].to_sym : :info)
20
+ @timeout = (options[:timeout_given]) ? options[:timeout] : Smith.config.agency.timeout
17
21
  end
18
22
 
19
23
  def send_command(command, args, &blk)
20
24
  begin
21
25
  send("#{Command.command_type(command)}_command", command, args, &blk)
22
- rescue Smith::Command::UnkownCommandError => e
23
- puts e.message
24
- Smith.stop(true)
26
+ rescue Smith::Command::UnknownCommandError => e
27
+ blk.call(e.message)
25
28
  end
26
29
  end
27
30
 
28
31
  private
29
32
 
30
33
  def smithctl_command(command, args, &blk)
31
- Smith.channel.on_error do |ch,channel_close|
32
- case channel_close.reply_code
33
- when 404
34
- puts "No such queue: #{extract_queue(channel_close.reply_text)}"
35
- when 406
36
- puts "Queue in use: #{extract_queue(channel_close.reply_text)}"
37
- else
38
- puts channel_close.reply_text
39
- puts channel_close.reply_code
40
- end
41
- end
42
-
43
34
  responder = Messaging::Responder.new
44
35
  responder.callback do |v|
45
- puts v if v
46
- Smith.stop(true)
36
+ blk.call(v)
47
37
  end
48
38
 
49
39
  Command.run(command, args, :responder => responder)
50
40
  end
51
41
 
52
- def extract_queue(message)
53
- match = /.*?'(.*?)'.*$/.match(message) #[1]
54
- if match && match[1]
55
- match[1].sub(/smith\./, '')
56
- else
57
- message
58
- end
59
- end
60
-
61
42
  def agency_command(command, args, &blk)
62
- Messaging::Sender.new('agency.control', :auto_delete => true, :durable => false, :persistent => true, :strict => true).ready do |sender|
43
+ Messaging::Sender.new('agency.control', :auto_delete => false, :durable => false, :persistent => true, :strict => true).ready do |sender|
63
44
 
64
- sender.timeout(AGENCY_TIMEOUT) { puts "Timeout. Is the agency still running"; Smith.stop(true) }
45
+ sender.timeout(@timeout) { blk.call("Timeout. Is the agency still running?") }
65
46
 
66
47
  payload = ACL::Payload.new(:agency_command).content(:command => command, :args => args)
67
48
 
@@ -71,34 +52,56 @@ module Smith
71
52
  end
72
53
  end
73
54
 
74
- errback = proc do puts "Agency not running"
75
- Smith.stop(true)
55
+ errback = proc do
56
+ blk.call("Agency not running.")
76
57
  end
77
58
 
78
59
  sender.consumers?(callback, errback)
79
60
  end
80
61
  end
81
62
  end
82
- end
83
63
 
84
- command = ARGV.shift
85
- args = ARGV
64
+ parser = Trollop::Parser.new do
65
+ version Smith::VERSION
66
+ banner <<-EOS
86
67
 
87
- command || (puts "usage #{File.basename($0)} <command> opts]"; exit 2)
68
+ Command line interface to control Smith.
88
69
 
89
- trap 'INT', proc { (Smith.running?) ? Smith.stop(true) : exit}
70
+ Usage:
71
+ smithctl commands
72
+ smithctl <command> [options]
73
+ EOS
90
74
 
91
- Smith.load_acls
75
+ stop_on Command.commands
76
+ opt :log_level, "Set the log level of smithctl only.", :short => :l, :type => :string
77
+ opt :timeout, "Specify the timeout when communicating with the agency.", :short => :t, :type => :integer, :default => 60
78
+ end
92
79
 
93
- control = Smith::SmithControl.new
80
+ opts = Trollop::with_standard_exception_handling parser do
81
+ raise Trollop::HelpNeeded if ARGV.size < 1
82
+ parser.parse(ARGV).tap do
83
+ unless Command.commands.include?(ARGV.first)
84
+ puts "Unknown command: #{ARGV.first}"
85
+ exit 1
86
+ end
87
+ end
88
+ end
94
89
 
95
- Smith.on_error do
96
- Smith.stop(true)
97
- end
90
+ command = ARGV.shift
91
+ args = ARGV
98
92
 
99
- Smith.start(:quiet => true) do
100
- control.send_command(command, args) do |result|
101
- puts result if result && !result.empty?
102
- Smith.stop(true)
93
+ %w{TERM INT QUIT}.each do |sig|
94
+ trap sig, proc { (Smith.running?) ? Smith.stop(true) : exit}
95
+ end
96
+
97
+ Smith.load_acls
98
+
99
+ control = SmithControl.new(opts)
100
+
101
+ Smith.start do
102
+ control.send_command(command, args) do |result|
103
+ puts result if result && !result.empty?
104
+ Smith.stop(true)
105
+ end
103
106
  end
104
107
  end
data/lib/smith.rb CHANGED
@@ -26,8 +26,8 @@ module Smith
26
26
  @channel
27
27
  end
28
28
 
29
- def on_error=(handler)
30
- @handler = handler
29
+ def environment
30
+ ENV['SMITH_ENV'] || 'development'
31
31
  end
32
32
 
33
33
  def config
@@ -81,8 +81,15 @@ module Smith
81
81
  EM.reactor_running?
82
82
  end
83
83
 
84
- def on_error(&blk)
85
- @on_error = blk
84
+ # Define a channel error handler.
85
+ def on_error(chain=false, &blk)
86
+ # This strikes me as egregiously wrong but I don't know how to
87
+ # overwrite an already existing handler.
88
+ if chain
89
+ Smith.channel.callbacks[:error] << blk
90
+ else
91
+ Smith.channel.callbacks[:error] = [blk]
92
+ end
86
93
  end
87
94
 
88
95
  def start(opts={}, &block)
@@ -134,19 +141,14 @@ module Smith
134
141
  AMQP::Channel.new(connection) do |channel,ok|
135
142
  @channel = channel
136
143
  # Set up QOS. If you do not do this then the subscribe in receive_message
137
- # will get overwelmd and the whole thing will collapse in on itself.
144
+ # will get overwhelmed and the whole thing will collapse in on itself.
138
145
  channel.prefetch(1)
139
146
 
140
- if @on_error
141
- channel.on_error(&@on_error)
142
- else
143
- # Log the error and stop the agency when there are channel errors.
144
- # TODO Add recovery instead of stopping the agency.
145
- channel.on_error do |ch,channel_close|
146
- logger.fatal { "Channel level exception: #{channel_close.reply_text}. Class id: #{channel_close.class_id}, Method id: #{channel_close.method_id}, Status code : #{channel_close.reply_code}" }
147
- logger.fatal { "Agency is exiting" }
148
- Smith.stop(true)
149
- end
147
+ # Set up a default handler.
148
+ on_error do |ch,channel_close|
149
+ logger.fatal { "Channel level exception: #{channel_close.reply_code}: #{channel_close.reply_text}" }
150
+ logger.fatal { "Exiting" }
151
+ Smith.stop(true)
150
152
  end
151
153
 
152
154
  # Set up auto-recovery. This will ensure that the AMQP gem reconnects each
@@ -198,7 +200,7 @@ module Smith
198
200
  # Only display the following settings.
199
201
  s = settings.select { |k,v| [:user, :pass, :vhost, :host].include?(k) }
200
202
 
201
- logger.fatal { "Authenticaton failure." }
203
+ logger.fatal { "Authentication failure." }
202
204
  logger.info { "Details:" }
203
205
  s.each do |k,v|
204
206
  logger.info { " Setting: %-7s%s" % [k, v] }
@@ -227,12 +229,14 @@ require_relative 'smith/agent_process'
227
229
  require_relative 'smith/agent_monitoring'
228
230
  require_relative 'smith/command'
229
231
  require_relative 'smith/command_base'
232
+ require_relative 'smith/exceptions'
233
+ require_relative 'smith/version'
234
+
230
235
  require_relative 'smith/messaging/amqp_options'
231
236
  require_relative 'smith/messaging/queue_factory'
232
237
  require_relative 'smith/messaging/payload'
233
238
  require_relative 'smith/messaging/acl/default'
234
239
  require_relative 'smith/messaging/endpoint'
235
- require_relative 'smith/messaging/exceptions'
236
240
  require_relative 'smith/messaging/responder'
237
241
  require_relative 'smith/messaging/receiver'
238
242
  require_relative 'smith/messaging/sender'
@@ -19,10 +19,12 @@ module Smith
19
19
  @cache_path = Smith.acl_cache_path
20
20
  end
21
21
 
22
- # Compile any protocol buffer files. This checks the timestamp
23
- # to see if the file needs compiling.
22
+ # Compile any protocol buffer files. This checks the timestamp to see if
23
+ # the file needs compiling. This is done in a subprocess to stop the agency
24
+ # from dying. I think it's a problem with the class being loaded twice but
25
+ # I don't actually know that so this could be considered a bit brute force.
24
26
  def compile
25
- fork do
27
+ Process.fork do
26
28
  logger.debug { "Protocol buffer cache path: #{@cache_path}" }
27
29
  Smith.acl_path.each do |path|
28
30
  results = {}
@@ -35,6 +37,7 @@ module Smith
35
37
  end
36
38
  end
37
39
  end
40
+ Process.wait
38
41
  @cache_path
39
42
  end
40
43
 
data/lib/smith/agent.rb CHANGED
@@ -5,8 +5,6 @@ module Smith
5
5
 
6
6
  include Logger
7
7
 
8
- @@agent_options = Smith.config.agent
9
-
10
8
  attr_reader :factory, :name, :pid
11
9
 
12
10
  def initialize(options={})
@@ -36,7 +34,7 @@ module Smith
36
34
  start_keep_alive
37
35
  end
38
36
 
39
- # Overide this method to implement your own agent. You can use task but this may
37
+ # Override this method to implement your own agent. You can use task but this may
40
38
  # go away in the future. This method must not block.
41
39
  def run
42
40
  raise ArgumentError, "You need to call Agent.task(&block)" if @@task.nil?
@@ -90,19 +88,10 @@ module Smith
90
88
  # Options supported:
91
89
  # :monitor, the agency will monitor the agent & if dies restart.
92
90
  # :singleton, only every have one agent. If this is set to false
93
- # multiple agents are allow.
91
+ # multiple agents are allowed.
94
92
  def options(opts)
95
- opts.each { |k,v| merge_options(k, v) }
96
- end
97
-
98
- def merge_options(option, value)
99
- if @@agent_options[option]
100
- @@agent_options[option] = value
101
- else
102
- raise ArgumentError, "Unknown option: #{option}"
103
- end
93
+ Smith.config.agent._merge!(opts)
104
94
  end
105
- private :merge_options
106
95
  end
107
96
 
108
97
  protected
@@ -143,32 +132,32 @@ module Smith
143
132
  payload = ACL::Payload.new(:agent_stats).content do |p|
144
133
  p.agent_name = self.name
145
134
  p.pid = self.pid
146
- p.rss = (File.read("/proc/#{pid}/statm").split[1].to_i * 4) / 1024 # This assums the page size is 4K & is MB
135
+ p.rss = (File.read("/proc/#{pid}/statm").split[1].to_i * 4) / 1024 # This assumes the page size is 4K & is MB
147
136
  p.up_time = (Time.now - @start_time).to_i
148
137
  factory.each_queue do |q|
149
- p.queues << ACL::AgentStats::QueueStats.new(:name => q.denomalized_queue_name, :type => q.class.to_s, :length => q.counter)
138
+ p.queues << ACL::AgentStats::QueueStats.new(:name => q.denormalized_queue_name, :type => q.class.to_s, :length => q.counter)
150
139
  end
151
140
  end
152
141
 
153
142
  stats_queue.publish(payload)
154
143
  end
155
144
 
156
- # The errback argument is set to nil so as to suppres the default message.
145
+ # The errback argument is set to nil so as to suppress the default message.
157
146
  stats_queue.consumers?(callback, nil)
158
147
  end
159
148
  end
160
149
  end
161
150
 
162
151
  def acknowledge_start
163
- sender('agent.lifecycle', :auto_delete => true, :durable => false, :dont_cache => true) do |ack_start_queue|
164
- message = {:state => 'acknowledge_start', :pid => $$.to_s, :name => self.class.to_s, :metadata => agent_options[:metadata], :started_at => Time.now.utc.to_i.to_s}
152
+ sender('agent.lifecycle', :auto_delete => false, :durable => false, :dont_cache => true) do |ack_start_queue|
153
+ message = {:state => 'acknowledge_start', :pid => $$, :name => self.class.to_s, :metadata => agent_options[:metadata], :started_at => Time.now.to_i}
165
154
  ack_start_queue.publish(ACL::Payload.new(:agent_lifecycle).content(agent_options.merge(message)))
166
155
  end
167
156
  end
168
157
 
169
158
  def acknowledge_stop(&block)
170
- sender('agent.lifecycle', :auto_delete => true, :durable => false, :dont_cache => true) do |ack_stop_queue|
171
- message = {:state => 'acknowledge_stop', :pid => $$.to_s, :name => self.class.to_s}
159
+ sender('agent.lifecycle', :auto_delete => false, :durable => false, :dont_cache => true) do |ack_stop_queue|
160
+ message = {:state => 'acknowledge_stop', :pid => $$, :name => self.class.to_s}
172
161
  ack_stop_queue.publish(ACL::Payload.new(:agent_lifecycle).content(message), &block)
173
162
  end
174
163
  end
@@ -176,8 +165,8 @@ module Smith
176
165
  def start_keep_alive
177
166
  if agent_options[:monitor]
178
167
  EventMachine::add_periodic_timer(1) do
179
- sender('agent.keepalive', :auto_delete => true, :durable => false, :dont_cache => true) do |keep_alive_queue|
180
- message = {:name => self.class.to_s, :pid => $$.to_s, :time => Time.now.utc.to_i.to_s}
168
+ sender('agent.keepalive', :auto_delete => false, :durable => false, :dont_cache => true) do |keep_alive_queue|
169
+ message = {:name => self.class.to_s, :pid => $$, :time => Time.now.to_i}
181
170
  keep_alive_queue.consumers? do |sender|
182
171
  keep_alive_queue.publish(ACL::Payload.new(:agent_keepalive).content(message))
183
172
  end
@@ -193,7 +182,7 @@ module Smith
193
182
  end
194
183
 
195
184
  def agent_options
196
- @@agent_options._child
185
+ Smith.config.agent._child
197
186
  end
198
187
 
199
188
  def control_queue_name