magistrate 0.2.1 → 0.5.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/README.md +1 -1
- data/bin/magistrate +1 -1
- data/lib/magistrate/supervisor.rb +7 -5
- data/lib/magistrate/version.rb +2 -2
- data/lib/magistrate/worker.rb +49 -23
- data/spec/magistrate/worker_spec.rb +71 -10
- metadata +5 -5
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
|
-
|
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
@@ -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
|
data/lib/magistrate/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Magistrate
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.5.0"
|
3
|
+
end
|
data/lib/magistrate/worker.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
8
|
-
|
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(@
|
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
|
-
@
|
25
|
+
@worker.state.should == :unmonitored
|
21
26
|
end
|
22
27
|
|
23
28
|
it 'should be unmonitored when unmonitored is the target state' do
|
24
|
-
@
|
25
|
-
@
|
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
|
-
@
|
30
|
-
@
|
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:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
18
|
+
date: 2011-09-22 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|