magistrate 0.1.0 → 0.2.0

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/magistrate CHANGED
@@ -8,6 +8,7 @@ require "optparse"
8
8
 
9
9
  action = :start
10
10
  config_file = nil
11
+ verbose = false
11
12
 
12
13
  ARGV.options do |opts|
13
14
  opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} COMMAND [OPTIONS]"
@@ -28,6 +29,10 @@ ARGV.options do |opts|
28
29
  config_file = f
29
30
  end
30
31
 
32
+ opts.on( '-v', '--verbose') do
33
+ verbose = true
34
+ end
35
+
31
36
  begin
32
37
  opts.parse!
33
38
  rescue
@@ -40,4 +45,4 @@ config_file ||= File.join('config', 'magistrate.yaml')
40
45
 
41
46
  ARGV[0] ||= 'run'
42
47
 
43
- Magistrate::Supervisor.new(config_file).send(ARGV[0], ARGV[1])
48
+ Magistrate::Supervisor.new(config_file, :verbose => verbose).send(ARGV[0], ARGV[1])
@@ -1,6 +1,7 @@
1
- class Magistrate::Process
1
+ module Magistrate
2
+ class Process
2
3
 
3
- attr_reader :name, :daemonize, :start_cmd, :stop_cmd, :pid_file, :working_dir, :env
4
+ attr_reader :name, :daemonize, :start_cmd, :stop_cmd, :pid_file, :working_dir, :env, :logs
4
5
  attr_accessor :target_state, :monitored
5
6
 
6
7
  def initialize(name, options = {})
@@ -8,9 +9,10 @@ class Magistrate::Process
8
9
  @daemonize = options[:daemonize]
9
10
  @working_dir = options[:working_dir]
10
11
  @start_cmd = options[:start_cmd]
12
+ @pid_path = options[:pid_path]
11
13
 
12
14
  if @daemonize
13
- @pid_file = File.join('tmp', 'pids', "#{@name}.pid")
15
+ @pid_file = File.join(@pid_path, "#{@name}.pid")
14
16
  @stop_signal = options[:stop_signal] || 'TERM'
15
17
  else
16
18
  @stop_cmd = options[:end_cmd]
@@ -23,6 +25,20 @@ class Magistrate::Process
23
25
  @env = {}
24
26
 
25
27
  @target_state = :unknown
28
+ @logs = []
29
+ end
30
+
31
+ def log(str)
32
+ @logs << str
33
+ end
34
+
35
+ def status
36
+ {
37
+ :state => self.state,
38
+ :target_state => self.target_state,
39
+ :pid => self.pid,
40
+ :logs => @logs
41
+ }
26
42
  end
27
43
 
28
44
  def running?
@@ -44,7 +60,7 @@ class Magistrate::Process
44
60
  # It will check if the pid exists and if so, is the process responding OK?
45
61
  # It will take action based on the target state
46
62
  def supervise!
47
- LOGGER.info("#{@name} supervising. Is: #{state}. Target: #{@target_state}")
63
+ log "Supervising. Is: #{state}. Target: #{@target_state}"
48
64
  if state != @target_state
49
65
  if @target_state == :running
50
66
  start
@@ -55,7 +71,7 @@ class Magistrate::Process
55
71
  end
56
72
 
57
73
  def start
58
- LOGGER.info("#{@name} starting")
74
+ log "#{@name} starting"
59
75
  if @daemonize
60
76
  @pid = double_fork(@start_cmd)
61
77
  # TODO: Should check if the pid really exists as we expect
@@ -76,7 +92,7 @@ class Magistrate::Process
76
92
  ::Process.kill(0, pid)
77
93
  rescue Errno::ESRCH
78
94
  # It died. Good.
79
- LOGGER.info("#{@name} process stopped")
95
+ log "Process stopped"
80
96
  return
81
97
  end
82
98
 
@@ -84,7 +100,7 @@ class Magistrate::Process
84
100
  end
85
101
 
86
102
  signal('KILL', pid)
87
- LOGGER.warn("#{@name} still alive after #{@stop_timeout}s; sent SIGKILL")
103
+ log "Still alive after #{@stop_timeout}s; sent SIGKILL"
88
104
  else
89
105
  single_fork(@stop_cmd)
90
106
  ensure_stop
@@ -99,7 +115,7 @@ class Magistrate::Process
99
115
  exit_code = status[1] >> 8
100
116
 
101
117
  if exit_code != 0
102
- LOGGER.warn("#{@name} command exited with non-zero code = #{exit_code}")
118
+ log "Command exited with non-zero code = #{exit_code}"
103
119
  end
104
120
  pid
105
121
  end
@@ -140,7 +156,8 @@ class Magistrate::Process
140
156
  dir = @working_dir || '/'
141
157
  Dir.chdir dir
142
158
 
143
- $0 = command
159
+ #$0 = command
160
+ $0 = "Magistrate Worker: #{@name}"
144
161
  STDIN.reopen "/dev/null"
145
162
 
146
163
  STDOUT.reopen '/dev/null'
@@ -178,10 +195,10 @@ class Magistrate::Process
178
195
  #
179
196
  # Returns nothing
180
197
  def ensure_stop
181
- LOGGER.warn("#{@name} ensuring stop...")
198
+ log "Ensuring stop..."
182
199
 
183
200
  unless self.pid
184
- LOGGER.warn("#{@name} stop called but pid is uknown")
201
+ log "Stop called but pid is unknown"
185
202
  return
186
203
  end
187
204
 
@@ -199,7 +216,7 @@ class Magistrate::Process
199
216
 
200
217
  # last resort
201
218
  signal('KILL')
202
- LOGGER.warn("#{@name} still alive after #{@stop_timeout}s; sent SIGKILL")
219
+ log "Still alive after #{@stop_timeout}s; sent SIGKILL"
203
220
  end
204
221
 
205
222
  # Send the given signal to this process.
@@ -208,7 +225,7 @@ class Magistrate::Process
208
225
  def signal(sig, target_pid = nil)
209
226
  target_pid ||= self.pid
210
227
  sig = sig.to_i if sig.to_i != 0
211
- LOGGER.info("#{@name} sending signal '#{sig}' to pid #{self.pid}")
228
+ log "Sending signal '#{sig}' to pid #{target_pid}"
212
229
  ::Process.kill(sig, target_pid) rescue nil
213
230
  end
214
231
 
@@ -244,3 +261,4 @@ class Magistrate::Process
244
261
  end
245
262
 
246
263
  end
264
+ end
@@ -10,42 +10,57 @@ require 'uri'
10
10
 
11
11
  module Magistrate
12
12
  class Supervisor
13
- def initialize(config_file)
13
+ def initialize(config_file, overrides = {})
14
14
  @workers = {}
15
15
 
16
16
  #File.expand_path('~')
17
- @pid_path = File.join( 'tmp', 'pids' )
18
-
19
- FileUtils.mkdir_p(@pid_path) unless File.directory? @pid_path
20
-
17
+ @config_file = config_file
21
18
  @config = File.open(config_file) { |file| YAML.load(file) }
22
19
  @config.recursive_symbolize_keys!
23
20
 
24
21
  @uri = URI.parse @config[:monitor_url]
22
+ @pid_path = @config[:pid_path] || File.join( 'tmp', 'pids' )
23
+
24
+ FileUtils.mkdir_p(@pid_path) unless File.directory? @pid_path
25
25
 
26
26
  @config[:workers].each do |k,v|
27
+ v[:pid_path] ||= @pid_path
27
28
  @workers[k] = Process.new(k,v)
28
29
  end
29
30
 
30
31
  @loaded_from = nil
32
+ @logs = []
33
+ @verbose = overrides[:verbose]
34
+
35
+ if @verbose
36
+ require 'pp'
37
+ end
31
38
  end
32
39
 
33
40
  def run(params = nil)
34
- puts "Starting Magistrate [[[#{self.name}]]] talking to [[[#{@config[:monitor_url]}]]]"
41
+ log "Run started at: #{Time.now}"
42
+
43
+ log "Starting Magistrate [[[#{self.name}]]] talking to [[[#{@config[:monitor_url]}]]]"
35
44
  set_target_states!
36
45
 
37
46
  # Pull in all already-running workers and set their target states
38
47
  @workers.each do |k, worker|
39
48
  worker.supervise!
49
+ if @verbose
50
+ puts "==== Worker: #{k}"
51
+ worker.logs.join("\n")
52
+ end
40
53
  end
41
54
 
42
55
  send_status
56
+
57
+ log "Run Complete at: #{Time.now}" #This is only good in verbose mode, but that's ok
43
58
  end
44
59
 
45
60
  #
46
61
  def start(params = nil)
47
62
  worker = params
48
- puts "Starting: #{worker}"
63
+ log "Starting: #{worker}"
49
64
  @workers[worker.to_sym].supervise!
50
65
 
51
66
  # Save that we've requested this to be started
@@ -53,7 +68,7 @@ module Magistrate
53
68
 
54
69
  def stop(params = nil)
55
70
  worker = params
56
- puts "Stopping: #{worker}"
71
+ log "Stopping: #{worker}"
57
72
  @workers[worker.to_sym].stop
58
73
 
59
74
  # Save that we've requested this to be stopped
@@ -68,18 +83,27 @@ module Magistrate
68
83
 
69
84
  # Returns the actual hash of all workers and their status
70
85
  def status
71
- s = {}
86
+ s = {
87
+ :name => self.name,
88
+ :pid_path => @pid_path,
89
+ :monitor_url => @config[:monitor_url],
90
+ :config_file => @config_file,
91
+ :logs => @logs,
92
+ :workers => {}
93
+ }
72
94
 
73
95
  @workers.each do |k,process|
74
- s[k] = {
75
- :state => process.state,
76
- :target_state => process.target_state
77
- }
96
+ s[:workers][k] = process.status
78
97
  end
79
98
 
80
99
  s
81
100
  end
82
101
 
102
+ def log(str)
103
+ @logs << str
104
+ puts str if @verbose
105
+ end
106
+
83
107
  protected
84
108
 
85
109
  # Loads the @target_states from either the remote server or local cache
@@ -93,7 +117,7 @@ module Magistrate
93
117
  if @workers[name]
94
118
  @workers[name].target_state = target['target_state'].to_sym if target['target_state']
95
119
  else
96
- puts "Worker #{name} not found in local config"
120
+ log "Worker #{name} has an entry in the target_state but it's not listed in the local config file and will be ignored."
97
121
  end
98
122
  end
99
123
  end
@@ -102,25 +126,21 @@ module Magistrate
102
126
  # Gets and sets @target_states from the server
103
127
  # Automatically falls back to the local cache
104
128
  def load_remote_target_states!
105
- http = Net::HTTP.new(@uri.host, @uri.port)
106
- http.read_timeout = 30
107
- request = Net::HTTP::Get.new(@uri.request_uri + "api/status/#{self.name}")
108
-
109
- response = http.request(request)
129
+ response = remote_request(Net::HTTP::Get)
110
130
 
111
131
  if response.code == '200'
112
132
  @loaded_from = :server
113
133
  @target_states = JSON.parse(response.body)
114
134
  save_target_states! # The double serialization here might not be best for performance, but will guarantee that the locally stored file is internally consistent
115
135
  else
116
- puts "Server responded with error #{response.code} : [[[#{response.body}]]]. Using saved target states..."
136
+ log "Server responded with error #{response.code} : [[[#{response.body}]]]. Using saved target states..."
117
137
  load_saved_target_states!
118
138
  end
119
139
 
120
140
  rescue StandardError => e
121
- puts "Connection to server #{@config[:monitor_url]} failed."
122
- puts "Error: #{e}"
123
- puts "Using saved target states..."
141
+ log "Connection to server #{@config[:monitor_url]} failed."
142
+ log "Error: #{e}"
143
+ log "Using saved target states..."
124
144
  load_saved_target_states!
125
145
  end
126
146
 
@@ -142,18 +162,29 @@ module Magistrate
142
162
  # Currently only sends basic worker info, but could start sending lots more:
143
163
  #
144
164
  def send_status
145
- http = Net::HTTP.new(@uri.host, @uri.port)
146
- http.read_timeout = 30
147
- request = Net::HTTP::Post.new(@uri.request_uri + "api/status/#{self.name}")
148
- request.set_form_data({ :status => JSON.generate(status) })
149
- response = http.request(request)
165
+ remote_request Net::HTTP::Post, { :status => JSON.generate(status) }
150
166
  rescue StandardError => e
151
- puts "Sending status to #{@config[:monitor_url]} failed"
167
+ log "Sending status to #{@config[:monitor_url]} failed"
168
+ log "Error: #{e}"
152
169
  end
153
170
 
154
171
  # This is the name that the magistrate_monitor will identify us as
155
172
  def name
156
- @_name ||= (@config[:supervisor_name_override] || "#{`hostname`.chomp}-#{`whoami`.chomp}").gsub(/[^a-zA-Z0-9\-\_]/, ' ').gsub(/\s+/, '-').downcase
173
+ @_name ||= (@config[:supervisor_name_override] || "#{@config[:root_name]}-#{`hostname`.chomp}").gsub(/[^a-zA-Z0-9\-\_]/, ' ').gsub(/\s+/, '-').downcase
174
+ end
175
+
176
+ # Wrapper method for easy remote requests
177
+ def remote_request(klass, form_data = nil)
178
+ http = Net::HTTP.new(@uri.host, @uri.port)
179
+ http.read_timeout = 30
180
+ request = klass.new(@uri.request_uri + "api/status/#{self.name}")
181
+ request.set_form_data(form_data) if form_data
182
+
183
+ if @config[:http_username] && @config[:http_password]
184
+ request.basic_auth @config[:http_username], @config[:http_password]
185
+ end
186
+
187
+ http.request(request)
157
188
  end
158
189
  end
159
190
  end
@@ -1,3 +1,3 @@
1
1
  module Magistrate
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/magistrate.rb CHANGED
@@ -5,10 +5,6 @@ require 'magistrate/version'
5
5
  require 'magistrate/supervisor'
6
6
  require 'magistrate/process'
7
7
 
8
- require 'logger'
9
- # App wide logging system
10
- LOGGER = Logger.new(STDOUT)
11
-
12
8
  module Magistrate
13
9
 
14
10
  end
@@ -30,10 +30,6 @@ describe "Magistrate::Process" do
30
30
  @process.state.should == :stopped
31
31
  end
32
32
  end
33
-
34
- it "should " do
35
- true
36
- end
37
33
  end
38
34
 
39
35
  describe 'Self-Daemonizing Worker' do
@@ -11,9 +11,27 @@ describe "Magistrate::Supervisor" do
11
11
  end
12
12
 
13
13
  it "should show basic status for its workers" do
14
- @supervisor.status.should == { :daemon_worker=>{:state=>:unmonitored, :target_state=>:unknown},
15
- :rake_like_worker=>{:state=>:unmonitored, :target_state=>:unknown} }
14
+ s = @supervisor.status
15
+
16
+ s[:name].should == 'test_name'
17
+ s[:pid_path].should == File.join('tmp','pids')
18
+
16
19
  end
17
20
 
18
- # it 'should'
21
+ it 'should run successfully' do
22
+ body = {}
23
+ body = JSON.generate(body)
24
+ stub_request(:get, "http://localhost:3000/magistrate/api/status/test_name").
25
+ to_return(:status => 200, :body => body, :headers => {})
26
+
27
+ stub_request(:post, "http://localhost:3000/magistrate/api/status/test_name").
28
+ to_return(:status => 200, :body => 'OK', :headers => {})
29
+
30
+
31
+
32
+ @supervisor.run
33
+
34
+ a_request(:get, "http://localhost:3000/magistrate/api/status/test_name").should have_been_made
35
+ a_request(:post, "http://localhost:3000/magistrate/api/status/test_name").should have_been_made
36
+ end
19
37
  end
@@ -1,21 +1,28 @@
1
- monitor_url: http://localhost
1
+ # Should have a trailing slash
2
+ monitor_url: http://localhost:3000/magistrate/
2
3
 
3
- #Normal magistrate reports itself as: `hostname`-`user`
4
- #supervisor_name_override: some_other_name
4
+ # By default, if this isn't set, it'll use tmp/pids
5
+ # pid_path: /var/www/app/current/tmp/pids
6
+
7
+ #Normal magistrate reports itself as: root_name-`hostname`
8
+ root_name: super1
9
+
10
+ #Use this to avoid using the hostname at all. The supervisor name will be this instead of root_name-`hostname`
11
+ supervisor_name_override: test_name
5
12
 
6
13
  workers:
7
14
  # If daemonize is true, then Magistrate will daemonize this process (it doesn't daemonize itself)
8
15
  # Magistrate will track the pid of the underlying process
9
16
  # And will stop it by killing the pid
10
17
  # It will ping the status by sending USR1 signal to the process
11
- rake_like_worker:
18
+ rake_like_worker1:
12
19
  daemonize: true
13
20
  working_dir: /data/app/
14
21
  start_cmd: rake my:task RAILS_ENV=production
15
22
 
16
23
  # If daemonize is false, then Magistrate will use a separate start and stop command
17
24
  # You must also manually specify the pid that this daemonized process creates
18
- daemon_worker:
25
+ daemon_worker1:
19
26
  daemonize: false
20
27
  working_dir: /data/app/
21
28
  start_cmd: mongrel_rails start -d
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "rubygems"
2
+ require 'webmock/rspec'
2
3
  require "rspec"
3
4
  #require "fakefs/safe"
4
5
  #require "fakefs/spec_helpers"
@@ -40,4 +41,9 @@ RSpec.configure do |config|
40
41
  config.color_enabled = true
41
42
  #config.include FakeFS::SpecHelpers
42
43
  config.mock_with :rr
44
+
45
+ config.before(:suite) do
46
+ f = 'tmp/pids/target_states.yml'
47
+ File.delete(f) if File.exist?(f)
48
+ end
43
49
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magistrate
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Drew Blas
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-16 00:00:00 -05:00
18
+ date: 2011-08-22 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency