magistrate 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -23,7 +23,7 @@ If you're using capistrano, then the tmp/pids directory is persisted across depl
23
23
 
24
24
  Your user-space cron job should look like this:
25
25
 
26
- */5 0 0 0 0 magistrate run --config ~/my_app/current/config/magistrate.yml
26
+ * 0 0 0 0 magistrate run --config ~/my_app/current/config/magistrate.yml
27
27
 
28
28
  ### What if the server is down?
29
29
 
data/bin/magistrate CHANGED
@@ -41,7 +41,7 @@ ARGV.options do |opts|
41
41
  end
42
42
  end
43
43
 
44
- config_file ||= File.join('config', 'magistrate.yaml')
44
+ config_file ||= File.join('config', 'magistrate.yml')
45
45
 
46
46
  ARGV[0] ||= 'run'
47
47
 
@@ -9,7 +9,7 @@ require 'net/http'
9
9
  require 'uri'
10
10
 
11
11
  module Magistrate
12
- class Supervisor
12
+ class Supervisor
13
13
  def initialize(config_file, overrides = {})
14
14
  @workers = {}
15
15
 
@@ -61,10 +61,10 @@ module Magistrate
61
61
  puts worker.logs.join("\n")
62
62
  end
63
63
  end
64
-
65
- send_status
66
-
64
+
67
65
  log "Run Complete at: #{Time.now}" #This is only good in verbose mode, but that's ok
66
+
67
+ send_status
68
68
  end
69
69
 
70
70
  #
@@ -99,6 +99,7 @@ module Magistrate
99
99
  :monitor_url => @config[:monitor_url],
100
100
  :config_file => @config_file,
101
101
  :logs => @logs,
102
+ :env => `env`,
102
103
  :workers => {}
103
104
  }
104
105
 
@@ -139,6 +140,7 @@ module Magistrate
139
140
  response = remote_request Net::HTTP::Get, "api/status/#{self.name}"
140
141
 
141
142
  if response.code == '200'
143
+ log "Retrieved remote target states successfully"
142
144
  @loaded_from = :server
143
145
  @target_states = JSON.parse(response.body)
144
146
  save_target_states! # The double serialization here might not be best for performance, but will guarantee that the locally stored file is internally consistent
@@ -198,4 +200,4 @@ module Magistrate
198
200
  http.request(request)
199
201
  end
200
202
  end
201
- end
203
+ end
@@ -1,3 +1,3 @@
1
1
  module Magistrate
2
- VERSION = "0.2.1"
3
- end
2
+ VERSION = "0.5.0"
3
+ end
@@ -1,7 +1,9 @@
1
+ require 'yaml'
2
+
1
3
  module Magistrate
2
4
  class Worker
3
5
 
4
- attr_reader :name, :daemonize, :start_cmd, :stop_cmd, :pid_file, :working_dir, :env, :logs, :reset_target_state_to
6
+ attr_reader :name, :daemonize, :start_cmd, :stop_cmd, :pid_file, :working_dir, :env, :logs, :reset_target_state_to, :bounces
5
7
  attr_accessor :target_state, :monitored
6
8
 
7
9
  def initialize(name, options = {})
@@ -10,40 +12,58 @@ class Worker
10
12
  @working_dir = options[:working_dir]
11
13
  @start_cmd = options[:start_cmd]
12
14
  @pid_path = options[:pid_path]
13
-
15
+
14
16
  if @daemonize
15
- @pid_file = File.join(@pid_path, "#{@name}.pid")
17
+ @pid_file = File.join(@pid_path, "#{@name}.pid")
18
+ @status_file = File.join(@pid_path, "#{@name}.status")
16
19
  @stop_signal = options[:stop_signal] || 'TERM'
20
+ @previous_status = load_previous_status
21
+ @bounces = @previous_status[:bounces] || 0
17
22
  else
18
23
  @stop_cmd = options[:end_cmd]
19
24
  @pid_file = options[:pid_file]
20
25
  end
21
-
26
+
22
27
  @stop_timeout = 5
23
28
  @start_timeout = 5
24
-
29
+
25
30
  @env = {}
26
31
 
27
32
  @target_state = :unknown
28
33
  @logs = []
29
34
  end
30
-
35
+
31
36
  def log(str)
32
37
  @logs << str
33
38
  end
34
-
39
+
40
+ # Loads the number of times in a row this worker has been started without successfully staying running
41
+ # This is stored in the @status_file
42
+ def load_previous_status
43
+ File.open(@status_file) { |file| YAML.load(file) } || {}
44
+ rescue
45
+ {}
46
+ end
47
+
48
+ def save_status
49
+ if @status_file
50
+ File.open(@status_file, "w") { |file| YAML.dump(status, file) } rescue nil
51
+ end
52
+ end
53
+
35
54
  def status
36
55
  {
37
56
  :state => self.state,
38
57
  :target_state => self.target_state,
39
58
  :pid => self.pid,
59
+ :bounces => @bounces,
40
60
  :logs => @logs
41
61
  }
42
62
  end
43
-
63
+
44
64
  def running?
45
65
  end
46
-
66
+
47
67
  def state
48
68
  if @target_state == :unmonitored || @target_state == :unknown
49
69
  :unmonitored
@@ -55,7 +75,7 @@ class Worker
55
75
  end
56
76
  end
57
77
  end
58
-
78
+
59
79
  # This is to be called when we first start managing a worker
60
80
  # It will check if the pid exists and if so, is the process responding OK?
61
81
  # It will take action based on the target state
@@ -68,12 +88,18 @@ class Worker
68
88
  log "Restart: Stopping, then Starting, then reporting new target_state of :running"
69
89
  stop
70
90
  start
71
- when :running then start
91
+ when :running then
92
+ start
93
+ @bounces += 1
72
94
  when :stopped then stop
73
95
  end
96
+ else
97
+ @bounces = 0
74
98
  end
99
+
100
+ save_status
75
101
  end
76
-
102
+
77
103
  def start
78
104
  if @daemonize
79
105
  log "Starting as daemon with double_fork"
@@ -86,11 +112,11 @@ class Worker
86
112
  end
87
113
  @pid
88
114
  end
89
-
115
+
90
116
  def stop
91
117
  if @daemonize
92
118
  signal(@stop_signal, pid)
93
-
119
+
94
120
  # Poll to see if it's dead
95
121
  @stop_timeout.times do
96
122
  begin
@@ -100,10 +126,10 @@ class Worker
100
126
  log "Process stopped"
101
127
  return
102
128
  end
103
-
129
+
104
130
  sleep 1
105
131
  end
106
-
132
+
107
133
  signal('KILL', pid)
108
134
  log "Still alive after #{@stop_timeout}s; sent SIGKILL"
109
135
  else
@@ -111,20 +137,20 @@ class Worker
111
137
  ensure_stop
112
138
  end
113
139
  end
114
-
140
+
115
141
  # single fork self-daemonizing processes
116
142
  # we want to wait for them to finish
117
143
  def single_fork(command)
118
144
  pid = self.spawn(command)
119
145
  status = ::Process.waitpid2(pid, 0)
120
146
  exit_code = status[1] >> 8
121
-
147
+
122
148
  if exit_code != 0
123
149
  log "Command exited with non-zero code = #{exit_code}"
124
150
  end
125
151
  pid
126
152
  end
127
-
153
+
128
154
  def double_fork(command)
129
155
  pid = nil
130
156
  # double fork daemonized processes
@@ -137,7 +163,7 @@ class Worker
137
163
  pid = self.spawn(command)
138
164
  puts pid.to_s # send pid back to forker
139
165
  end
140
-
166
+
141
167
  ::Process.waitpid(opid, 0)
142
168
  w.close
143
169
  pid = r.gets.chomp
@@ -146,10 +172,10 @@ class Worker
146
172
  r.close rescue nil
147
173
  w.close rescue nil
148
174
  end
149
-
175
+
150
176
  pid
151
177
  end
152
-
178
+
153
179
  # Fork/exec the given command, returns immediately
154
180
  # +command+ is the String containing the shell command
155
181
  #
@@ -266,4 +292,4 @@ class Worker
266
292
  end
267
293
 
268
294
  end
269
- end
295
+ end
@@ -1,33 +1,94 @@
1
1
  require "spec_helper"
2
2
  require "magistrate/worker"
3
+ require 'fakefs'
3
4
 
4
5
  describe "Magistrate::Worker" do
5
6
  describe 'Rake-Like Worker' do
6
7
  before(:each) do
7
- @process = Magistrate::Worker.new(
8
- :name => 'rake_like_worker',
8
+ # Dir.glob('spec/tmp/pids/*').each do |f|
9
+ # File.delete(f)
10
+ # end
11
+ @worker = Magistrate::Worker.new(
12
+ 'rake_like_worker',
9
13
  :daemonize => true,
10
- :start_cmd => 'ruby spec/resources/rake_like_worker.rb'
14
+ :start_cmd => 'ruby spec/resources/rake_like_worker.rb',
15
+ :pid_path => 'spec/tmp/pids'
11
16
  )
12
17
 
13
- stub(@process).spawn do
18
+ stub(@worker).spawn do
14
19
  raise "Unexpected spawn call made...you don't want your specs actually spawning stuff, right?"
15
20
  end
16
21
  end
17
22
 
18
23
  describe 'state' do
19
24
  it 'should be unmonitored by default' do
20
- @process.state.should == :unmonitored
25
+ @worker.state.should == :unmonitored
21
26
  end
22
27
 
23
28
  it 'should be unmonitored when unmonitored is the target state' do
24
- @process.target_state = :unmonitored
25
- @process.state.should == :unmonitored
29
+ @worker.target_state = :unmonitored
30
+ @worker.state.should == :unmonitored
26
31
  end
27
32
 
28
33
  it 'should be stopped when target state other that unmonitored' do
29
- @process.target_state = :foo
30
- @process.state.should == :stopped
34
+ @worker.target_state = :foo
35
+ @worker.state.should == :stopped
36
+ end
37
+ end
38
+
39
+ describe 'bounces' do
40
+ it 'should start with 0 bounces' do
41
+ @worker.bounces.should == 0
42
+ end
43
+
44
+ it 'should show a bounce' do
45
+ stub(@worker).alive? { false }
46
+ mock(@worker).start { true }
47
+ @worker.target_state = :running
48
+ @worker.supervise!
49
+ @worker.bounces.should == 1
50
+ end
51
+
52
+ it 'should show no bounce' do
53
+ stub(@worker).alive? { true }
54
+ @worker.target_state = :running
55
+ @worker.supervise!
56
+ @worker.bounces.should == 0
57
+ end
58
+
59
+ it 'should show a bounce, then reload it' do
60
+ stub(@worker).alive? { false }
61
+ mock(@worker).start { true }
62
+ @worker.target_state = :running
63
+ @worker.supervise!
64
+ @worker.bounces.should == 1
65
+
66
+ @worker2 = Magistrate::Worker.new(
67
+ 'rake_like_worker',
68
+ :daemonize => true,
69
+ :start_cmd => 'ruby spec/resources/rake_like_worker.rb',
70
+ :pid_path => 'spec/tmp/pids'
71
+ )
72
+
73
+ # The second worker should load the config of the first
74
+ @worker2.bounces.should == 1
75
+
76
+
77
+ stub(@worker2).alive? { true }
78
+ @worker2.target_state = :running
79
+ @worker2.supervise!
80
+
81
+ @worker2.bounces.should == 0
82
+
83
+ @worker3 = Magistrate::Worker.new(
84
+ 'rake_like_worker',
85
+ :daemonize => true,
86
+ :start_cmd => 'ruby spec/resources/rake_like_worker.rb',
87
+ :pid_path => 'spec/tmp/pids'
88
+ )
89
+
90
+ # Third worker should load the config of the second
91
+ @worker3.bounces.should == 0
31
92
  end
32
93
  end
33
94
  end
@@ -35,4 +96,4 @@ describe "Magistrate::Worker" do
35
96
  describe 'Self-Daemonizing Worker' do
36
97
 
37
98
  end
38
- end
99
+ 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: 21
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 1
10
- version: 0.2.1
8
+ - 5
9
+ - 0
10
+ version: 0.5.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-24 00:00:00 -05:00
18
+ date: 2011-09-22 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency