eye 0.5.2 → 0.6
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.
- checksums.yaml +13 -5
- data/.travis.yml +1 -6
- data/CHANGES.md +12 -0
- data/README.md +5 -0
- data/Rakefile +4 -4
- data/bin/loader_eye +14 -3
- data/bin/runner +16 -0
- data/examples/dependency.eye +17 -0
- data/examples/plugin/README.md +15 -0
- data/examples/plugin/main.eye +15 -0
- data/examples/plugin/plugin.rb +63 -0
- data/examples/unicorn.eye +1 -1
- data/eye.gemspec +1 -2
- data/lib/eye.rb +1 -1
- data/lib/eye/checker.rb +16 -4
- data/lib/eye/checker/children_count.rb +44 -0
- data/lib/eye/checker/children_memory.rb +12 -0
- data/lib/eye/checker/socket.rb +9 -2
- data/lib/eye/child_process.rb +6 -2
- data/lib/eye/cli.rb +13 -2
- data/lib/eye/cli/commands.rb +2 -2
- data/lib/eye/cli/server.rb +11 -3
- data/lib/eye/config.rb +2 -2
- data/lib/eye/controller/commands.rb +29 -2
- data/lib/eye/controller/helpers.rb +31 -6
- data/lib/eye/controller/load.rb +5 -6
- data/lib/eye/controller/options.rb +1 -1
- data/lib/eye/controller/send_command.rb +0 -1
- data/lib/eye/dsl.rb +2 -1
- data/lib/eye/dsl/application_opts.rb +4 -7
- data/lib/eye/dsl/group_opts.rb +2 -1
- data/lib/eye/dsl/helpers.rb +9 -1
- data/lib/eye/dsl/main.rb +11 -5
- data/lib/eye/dsl/opts.rb +5 -22
- data/lib/eye/dsl/process_opts.rb +20 -2
- data/lib/eye/dsl/pure_opts.rb +1 -1
- data/lib/eye/dsl/validation.rb +17 -2
- data/lib/eye/local.rb +79 -50
- data/lib/eye/notify.rb +5 -3
- data/lib/eye/notify/mail.rb +6 -2
- data/lib/eye/process.rb +3 -1
- data/lib/eye/process/children.rb +1 -1
- data/lib/eye/process/commands.rb +17 -6
- data/lib/eye/process/config.rb +6 -1
- data/lib/eye/process/data.rb +20 -0
- data/lib/eye/process/monitor.rb +10 -4
- data/lib/eye/process/states.rb +5 -2
- data/lib/eye/process/states_history.rb +1 -1
- data/lib/eye/process/system.rb +6 -2
- data/lib/eye/process/trigger.rb +0 -1
- data/lib/eye/process/validate.rb +8 -6
- data/lib/eye/process/watchers.rb +1 -7
- data/lib/eye/system.rb +14 -11
- data/lib/eye/system_resources.rb +8 -0
- data/lib/eye/trigger.rb +12 -4
- data/lib/eye/trigger/check_dependency.rb +30 -0
- data/lib/eye/trigger/stop_children.rb +4 -1
- data/lib/eye/trigger/wait_dependency.rb +49 -0
- data/lib/eye/utils.rb +13 -0
- metadata +41 -45
data/lib/eye/dsl/pure_opts.rb
CHANGED
data/lib/eye/dsl/validation.rb
CHANGED
@@ -25,16 +25,31 @@ module Eye::Dsl::Validation
|
|
25
25
|
|
26
26
|
validates[param] = types
|
27
27
|
should_bes << param if should_be
|
28
|
-
|
28
|
+
param_default(param, default)
|
29
29
|
variants[param] = _variants
|
30
30
|
|
31
31
|
return if param == :do
|
32
32
|
|
33
33
|
define_method "#{param}" do
|
34
|
-
@options[param]
|
34
|
+
value = @options[param]
|
35
|
+
value.nil? ? default : value
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
39
|
+
def param_default(param, default)
|
40
|
+
param = param.to_sym
|
41
|
+
defaults[param] = default
|
42
|
+
end
|
43
|
+
|
44
|
+
def del_param(param)
|
45
|
+
param = param.to_sym
|
46
|
+
validates.delete(param)
|
47
|
+
should_bes.delete(param)
|
48
|
+
defaults.delete(param)
|
49
|
+
variants.delete(param)
|
50
|
+
remove_method(param)
|
51
|
+
end
|
52
|
+
|
38
53
|
def validate(options = {})
|
39
54
|
options.each do |param, value|
|
40
55
|
param = param.to_sym
|
data/lib/eye/local.rb
CHANGED
@@ -1,71 +1,100 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
module Eye::Local
|
4
|
-
|
4
|
+
class << self
|
5
|
+
def dir
|
6
|
+
@dir ||= begin
|
7
|
+
if root?
|
8
|
+
'/var/run/eye'
|
9
|
+
else
|
10
|
+
File.expand_path(File.join(home, '.eye'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
5
14
|
|
6
|
-
|
7
|
-
|
8
|
-
'/var/run/eye'
|
9
|
-
else
|
10
|
-
File.expand_path(File.join(home, '.eye'))
|
15
|
+
def dir=(d)
|
16
|
+
@dir = d
|
11
17
|
end
|
12
|
-
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
def eyeconfig
|
20
|
+
if root?
|
21
|
+
'/etc/eye.conf'
|
22
|
+
else
|
23
|
+
File.expand_path(File.join(home, '.eyeconfig'))
|
24
|
+
end
|
19
25
|
end
|
20
|
-
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
def root?
|
28
|
+
Process::UID.eid == 0
|
29
|
+
end
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
def home
|
32
|
+
h = ENV['EYE_HOME'] || ENV['HOME']
|
33
|
+
raise "HOME undefined, should be HOME or EYE_HOME environment" unless h
|
34
|
+
h
|
35
|
+
end
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
def path(path)
|
38
|
+
File.join(dir, path)
|
39
|
+
end
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
|
41
|
+
def ensure_eye_dir
|
42
|
+
FileUtils.mkdir_p( dir )
|
43
|
+
end
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
def socket_path
|
46
|
+
path(ENV['EYE_SOCK'] || "sock#{ENV['EYE_V']}")
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
def pid_path
|
50
|
+
path(ENV['EYE_PID'] || "pid#{ENV['EYE_V']}")
|
51
|
+
end
|
47
52
|
|
48
|
-
|
49
|
-
|
50
|
-
|
53
|
+
def cache_path
|
54
|
+
path("processes#{ENV['EYE_V']}.cache")
|
55
|
+
end
|
51
56
|
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def client_timeout
|
58
|
+
@client_timeout ||= 5
|
59
|
+
end
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
def client_timeout=(cl)
|
62
|
+
@client_timeout = cl
|
63
|
+
end
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
require 'socket'
|
63
|
-
Socket.gethostname
|
65
|
+
def supported_setsid?
|
66
|
+
RUBY_VERSION >= '2.0'
|
64
67
|
end
|
65
|
-
end
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
def host
|
70
|
+
@host ||= begin
|
71
|
+
require 'socket'
|
72
|
+
Socket.gethostname
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def host=(hostname)
|
77
|
+
@host = hostname
|
78
|
+
end
|
79
|
+
|
80
|
+
def eyefile
|
81
|
+
@eyefile ||= find_eyefile('.')
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_eyefile(start_from_dir)
|
85
|
+
fromenv = ENV['EYEFILE']
|
86
|
+
return fromenv if fromenv && !fromenv.empty? && File.exist?(fromenv)
|
70
87
|
|
88
|
+
previous = nil
|
89
|
+
current = File.expand_path(start_from_dir)
|
90
|
+
|
91
|
+
until !File.directory?(current) || current == previous
|
92
|
+
filename = File.join(current, 'Eyefile')
|
93
|
+
return filename if File.file?(filename)
|
94
|
+
current, previous = File.expand_path('..', current), current
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_accessor :local_runner
|
99
|
+
end
|
71
100
|
end
|
data/lib/eye/notify.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
1
3
|
class Eye::Notify
|
2
4
|
include Celluloid
|
3
5
|
include Eye::Dsl::Validation
|
@@ -10,7 +12,7 @@ class Eye::Notify
|
|
10
12
|
def self.get_class(type)
|
11
13
|
klass = eval("Eye::Notify::#{TYPES[type]}") rescue nil
|
12
14
|
raise "unknown notifier :#{type}" unless klass
|
13
|
-
if deps = klass.
|
15
|
+
if deps = klass.requires
|
14
16
|
Array(deps).each { |d| require d }
|
15
17
|
end
|
16
18
|
klass
|
@@ -48,7 +50,7 @@ class Eye::Notify
|
|
48
50
|
log_ex(ex)
|
49
51
|
end
|
50
52
|
|
51
|
-
TIMEOUT = 1
|
53
|
+
TIMEOUT = 1 * 60
|
52
54
|
|
53
55
|
def initialize(options = {}, message_h = {})
|
54
56
|
@message_h = message_h
|
@@ -95,7 +97,7 @@ class Eye::Notify
|
|
95
97
|
Eye::Dsl::ConfigOpts.add_notify(type)
|
96
98
|
end
|
97
99
|
|
98
|
-
def self.
|
100
|
+
def self.requires
|
99
101
|
end
|
100
102
|
|
101
103
|
class Custom < Eye::Notify
|
data/lib/eye/notify/mail.rb
CHANGED
@@ -15,6 +15,8 @@ class Eye::Notify::Mail < Eye::Notify
|
|
15
15
|
param :password, String
|
16
16
|
param :auth, Symbol, nil, nil, [:plain, :login, :cram_md5]
|
17
17
|
|
18
|
+
param :starttls, [TrueClass, FalseClass]
|
19
|
+
|
18
20
|
param :from_mail, String
|
19
21
|
param :from_name, String, nil, 'eye'
|
20
22
|
|
@@ -25,9 +27,11 @@ class Eye::Notify::Mail < Eye::Notify
|
|
25
27
|
def smtp
|
26
28
|
args = [host, port, domain, user, password, auth]
|
27
29
|
debug "called smtp with #{args}"
|
30
|
+
smtp = Net::SMTP.new host, port
|
31
|
+
smtp.enable_starttls if starttls
|
28
32
|
|
29
|
-
|
30
|
-
|
33
|
+
smtp.start(domain, user, password, auth) do |s|
|
34
|
+
s.send_message(message, from_mail || user, contact)
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
data/lib/eye/process.rb
CHANGED
@@ -18,7 +18,8 @@ class Eye::Process
|
|
18
18
|
autoload :Validate, 'eye/process/validate'
|
19
19
|
|
20
20
|
attr_accessor :pid, :watchers, :config, :states_history,
|
21
|
-
:children, :triggers, :name, :state_reason, :flapping_times
|
21
|
+
:children, :triggers, :name, :state_reason, :flapping_times,
|
22
|
+
:parent_pid
|
22
23
|
|
23
24
|
def initialize(config)
|
24
25
|
raise 'you must supply a pid_file location' unless config[:pid_file]
|
@@ -29,6 +30,7 @@ class Eye::Process
|
|
29
30
|
@children = {}
|
30
31
|
@triggers = []
|
31
32
|
@name = @config[:name]
|
33
|
+
|
32
34
|
@flapping_times = 0
|
33
35
|
|
34
36
|
@states_history = Eye::Process::StatesHistory.new(100)
|
data/lib/eye/process/children.rb
CHANGED
@@ -31,7 +31,7 @@ module Eye::Process::Children
|
|
31
31
|
|
32
32
|
if new_children.present?
|
33
33
|
new_children.each do |child_pid|
|
34
|
-
self.children[child_pid] = Eye::ChildProcess.new(child_pid, self[:monitor_children], logger.prefix)
|
34
|
+
self.children[child_pid] = Eye::ChildProcess.new(child_pid, self[:monitor_children], logger.prefix, self.pid)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
data/lib/eye/process/commands.rb
CHANGED
@@ -31,7 +31,7 @@ module Eye::Process::Commands
|
|
31
31
|
|
32
32
|
result
|
33
33
|
|
34
|
-
rescue StateMachine::InvalidTransition => e
|
34
|
+
rescue StateMachine::InvalidTransition, Eye::Process::StateError => e
|
35
35
|
warn "wrong switch '#{e.message}'"
|
36
36
|
|
37
37
|
:state_error
|
@@ -58,7 +58,7 @@ module Eye::Process::Commands
|
|
58
58
|
true
|
59
59
|
end
|
60
60
|
|
61
|
-
rescue StateMachine::InvalidTransition => e
|
61
|
+
rescue StateMachine::InvalidTransition, Eye::Process::StateError => e
|
62
62
|
warn "wrong switch '#{e.message}'"
|
63
63
|
nil
|
64
64
|
end
|
@@ -80,7 +80,7 @@ module Eye::Process::Commands
|
|
80
80
|
|
81
81
|
true
|
82
82
|
|
83
|
-
rescue StateMachine::InvalidTransition => e
|
83
|
+
rescue StateMachine::InvalidTransition, Eye::Process::StateError => e
|
84
84
|
warn "wrong switch '#{e.message}'"
|
85
85
|
nil
|
86
86
|
end
|
@@ -173,7 +173,7 @@ private
|
|
173
173
|
res = Eye::System.daemonize(self[:start_command], config)
|
174
174
|
start_time = Time.now - time_before
|
175
175
|
|
176
|
-
info "daemonizing: `#{self[:start_command]}` with start_grace: #{self[:start_grace].to_f}s, env: #{
|
176
|
+
info "daemonizing: `#{self[:start_command]}` with start_grace: #{self[:start_grace].to_f}s, env: '#{environment_string}', <#{res[:pid]}> (in #{self[:working_dir]})"
|
177
177
|
|
178
178
|
if res[:error]
|
179
179
|
|
@@ -200,7 +200,18 @@ private
|
|
200
200
|
return {:error => :not_really_running}
|
201
201
|
end
|
202
202
|
|
203
|
-
|
203
|
+
# if we using leaf child stratedy, pid should be used as last child process
|
204
|
+
if self[:use_leaf_child]
|
205
|
+
if lpid = Eye::SystemResources.leaf_child(self.pid)
|
206
|
+
info "leaf child for <#{self.pid}> found: <#{lpid}>, accepting it!"
|
207
|
+
self.parent_pid = self.pid
|
208
|
+
self.pid = lpid
|
209
|
+
else
|
210
|
+
warn "leaf child not found for <#{self.pid}>, skipping it"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if control_pid? && !failsafe_save_pid
|
204
215
|
return {:error => :cant_write_pid}
|
205
216
|
end
|
206
217
|
|
@@ -208,7 +219,7 @@ private
|
|
208
219
|
end
|
209
220
|
|
210
221
|
def execute_process
|
211
|
-
info "executing: `#{self[:start_command]}` with start_timeout: #{config[:start_timeout].to_f}s, start_grace: #{self[:start_grace].to_f}s, env: #{
|
222
|
+
info "executing: `#{self[:start_command]}` with start_timeout: #{config[:start_timeout].to_f}s, start_grace: #{self[:start_grace].to_f}s, env: '#{environment_string}' (in #{self[:working_dir]})"
|
212
223
|
time_before = Time.now
|
213
224
|
|
214
225
|
res = execute(self[:start_command], config.merge(:timeout => config[:start_timeout]))
|
data/lib/eye/process/config.rb
CHANGED
@@ -16,7 +16,10 @@ module Eye::Process::Config
|
|
16
16
|
:auto_start => true, # auto start on monitor action
|
17
17
|
|
18
18
|
:children_update_period => 30.seconds,
|
19
|
-
:clear_pid => true # by default clear pid on stop
|
19
|
+
:clear_pid => true, # by default clear pid on stop
|
20
|
+
|
21
|
+
:auto_update_pidfile_grace => 30.seconds,
|
22
|
+
:revert_fuckup_pidfile_grace => 120.seconds,
|
20
23
|
}
|
21
24
|
|
22
25
|
def prepare_config(new_config)
|
@@ -35,6 +38,8 @@ module Eye::Process::Config
|
|
35
38
|
h[:stdout] = Eye::System.normalized_file(h[:stdout], h[:working_dir]) if h[:stdout]
|
36
39
|
h[:stderr] = Eye::System.normalized_file(h[:stderr], h[:working_dir]) if h[:stderr]
|
37
40
|
|
41
|
+
h[:environment] = Eye::System.prepare_env(h)
|
42
|
+
|
38
43
|
h
|
39
44
|
end
|
40
45
|
|
data/lib/eye/process/data.rb
CHANGED
@@ -12,6 +12,10 @@ module Eye::Process::Data
|
|
12
12
|
(self[:group] == '__default__') ? nil : self[:group]
|
13
13
|
end
|
14
14
|
|
15
|
+
def group_name_pure
|
16
|
+
self[:group]
|
17
|
+
end
|
18
|
+
|
15
19
|
def full_name
|
16
20
|
@full_name ||= [app_name, group_name, self[:name]].compact.join(':')
|
17
21
|
end
|
@@ -55,4 +59,20 @@ module Eye::Process::Data
|
|
55
59
|
false
|
56
60
|
end
|
57
61
|
|
62
|
+
def environment_string
|
63
|
+
s = []
|
64
|
+
@config[:environment].each { |k, v| s << "#{k}=#{v}" }
|
65
|
+
s * ' '
|
66
|
+
end
|
67
|
+
|
68
|
+
def shell_string(dir = true)
|
69
|
+
str = ''
|
70
|
+
str += "cd #{self[:working_dir]} && " if dir
|
71
|
+
str += environment_string
|
72
|
+
str += ' '
|
73
|
+
str += self[:start_command]
|
74
|
+
str += ' &' if self[:daemonize]
|
75
|
+
str
|
76
|
+
end
|
77
|
+
|
58
78
|
end
|
data/lib/eye/process/monitor.rb
CHANGED
@@ -31,8 +31,6 @@ private
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
REWRITE_FACKUP_PIDFILE_PERIOD = 2.minutes
|
35
|
-
|
36
34
|
def check_alive
|
37
35
|
if up?
|
38
36
|
|
@@ -55,16 +53,24 @@ private
|
|
55
53
|
msg += ", pid_file write failed! O_o"
|
56
54
|
end
|
57
55
|
else
|
56
|
+
changed_ago_s = Time.now - pid_file_ctime
|
57
|
+
|
58
58
|
if ppid == nil
|
59
59
|
msg += ", reverting to <#{self.pid}> (the pid_file is empty)"
|
60
60
|
unless failsafe_save_pid
|
61
61
|
msg += ", pid_file write failed! O_o"
|
62
62
|
end
|
63
|
-
|
64
|
-
|
63
|
+
|
64
|
+
elsif (changed_ago_s > self[:auto_update_pidfile_grace]) && process_pid_running?(ppid)
|
65
|
+
msg += ", trusting this change, and now monitor <#{ppid}>"
|
66
|
+
self.pid = ppid
|
67
|
+
|
68
|
+
elsif (changed_ago_s > self[:revert_fuckup_pidfile_grace])
|
69
|
+
msg += " over #{self[:revert_fuckup_pidfile_grace]}s ago, reverting to <#{self.pid}>, because <#{ppid}> not alive"
|
65
70
|
unless failsafe_save_pid
|
66
71
|
msg += ", pid_file write failed! O_o"
|
67
72
|
end
|
73
|
+
|
68
74
|
else
|
69
75
|
msg += ', ignoring self-managed pid change'
|
70
76
|
end
|
data/lib/eye/process/states.rb
CHANGED
@@ -2,6 +2,7 @@ require 'state_machine'
|
|
2
2
|
require 'state_machine/version'
|
3
3
|
|
4
4
|
class Eye::Process
|
5
|
+
class StateError < Exception; end
|
5
6
|
|
6
7
|
# do transition
|
7
8
|
def switch(name, reason = nil)
|
@@ -76,8 +77,10 @@ class Eye::Process
|
|
76
77
|
end
|
77
78
|
|
78
79
|
def log_transition(transition)
|
79
|
-
|
80
|
-
|
80
|
+
if transition.to_name != transition.from_name || @state_reason.is_a?(Eye::Reason::User)
|
81
|
+
@states_history.push transition.to_name, @state_reason
|
82
|
+
info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{@state_reason ? "(reason: #{@state_reason})" : nil}"
|
83
|
+
end
|
81
84
|
end
|
82
85
|
|
83
86
|
end
|