eye 0.5.2 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +13 -5
  2. data/.travis.yml +1 -6
  3. data/CHANGES.md +12 -0
  4. data/README.md +5 -0
  5. data/Rakefile +4 -4
  6. data/bin/loader_eye +14 -3
  7. data/bin/runner +16 -0
  8. data/examples/dependency.eye +17 -0
  9. data/examples/plugin/README.md +15 -0
  10. data/examples/plugin/main.eye +15 -0
  11. data/examples/plugin/plugin.rb +63 -0
  12. data/examples/unicorn.eye +1 -1
  13. data/eye.gemspec +1 -2
  14. data/lib/eye.rb +1 -1
  15. data/lib/eye/checker.rb +16 -4
  16. data/lib/eye/checker/children_count.rb +44 -0
  17. data/lib/eye/checker/children_memory.rb +12 -0
  18. data/lib/eye/checker/socket.rb +9 -2
  19. data/lib/eye/child_process.rb +6 -2
  20. data/lib/eye/cli.rb +13 -2
  21. data/lib/eye/cli/commands.rb +2 -2
  22. data/lib/eye/cli/server.rb +11 -3
  23. data/lib/eye/config.rb +2 -2
  24. data/lib/eye/controller/commands.rb +29 -2
  25. data/lib/eye/controller/helpers.rb +31 -6
  26. data/lib/eye/controller/load.rb +5 -6
  27. data/lib/eye/controller/options.rb +1 -1
  28. data/lib/eye/controller/send_command.rb +0 -1
  29. data/lib/eye/dsl.rb +2 -1
  30. data/lib/eye/dsl/application_opts.rb +4 -7
  31. data/lib/eye/dsl/group_opts.rb +2 -1
  32. data/lib/eye/dsl/helpers.rb +9 -1
  33. data/lib/eye/dsl/main.rb +11 -5
  34. data/lib/eye/dsl/opts.rb +5 -22
  35. data/lib/eye/dsl/process_opts.rb +20 -2
  36. data/lib/eye/dsl/pure_opts.rb +1 -1
  37. data/lib/eye/dsl/validation.rb +17 -2
  38. data/lib/eye/local.rb +79 -50
  39. data/lib/eye/notify.rb +5 -3
  40. data/lib/eye/notify/mail.rb +6 -2
  41. data/lib/eye/process.rb +3 -1
  42. data/lib/eye/process/children.rb +1 -1
  43. data/lib/eye/process/commands.rb +17 -6
  44. data/lib/eye/process/config.rb +6 -1
  45. data/lib/eye/process/data.rb +20 -0
  46. data/lib/eye/process/monitor.rb +10 -4
  47. data/lib/eye/process/states.rb +5 -2
  48. data/lib/eye/process/states_history.rb +1 -1
  49. data/lib/eye/process/system.rb +6 -2
  50. data/lib/eye/process/trigger.rb +0 -1
  51. data/lib/eye/process/validate.rb +8 -6
  52. data/lib/eye/process/watchers.rb +1 -7
  53. data/lib/eye/system.rb +14 -11
  54. data/lib/eye/system_resources.rb +8 -0
  55. data/lib/eye/trigger.rb +12 -4
  56. data/lib/eye/trigger/check_dependency.rb +30 -0
  57. data/lib/eye/trigger/stop_children.rb +4 -1
  58. data/lib/eye/trigger/wait_dependency.rb +49 -0
  59. data/lib/eye/utils.rb +13 -0
  60. metadata +41 -45
@@ -79,7 +79,7 @@ class Eye::Dsl::PureOpts
79
79
  self.instance_eval(&block) if cond && block
80
80
  end
81
81
 
82
- def include(proc, *args)
82
+ def use(proc, *args)
83
83
  if proc.is_a?(String)
84
84
  self.class.with_parsed_file(proc) do |path|
85
85
  if File.exists?(path)
@@ -25,16 +25,31 @@ module Eye::Dsl::Validation
25
25
 
26
26
  validates[param] = types
27
27
  should_bes << param if should_be
28
- defaults[param] = default
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] || default
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
- module_function
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
- def dir
7
- if root?
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
- def eyeconfig
15
- if root?
16
- '/etc/eye.conf'
17
- else
18
- File.expand_path(File.join(home, '.eyeconfig'))
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
- def root?
23
- Process::UID.eid == 0
24
- end
27
+ def root?
28
+ Process::UID.eid == 0
29
+ end
25
30
 
26
- def home
27
- h = ENV['EYE_HOME'] || ENV['HOME']
28
- raise "HOME undefined, should be HOME or EYE_HOME environment" unless h
29
- h
30
- end
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
- def path(path)
33
- File.join(dir, path)
34
- end
37
+ def path(path)
38
+ File.join(dir, path)
39
+ end
35
40
 
36
- def ensure_eye_dir
37
- FileUtils.mkdir_p( dir )
38
- end
41
+ def ensure_eye_dir
42
+ FileUtils.mkdir_p( dir )
43
+ end
39
44
 
40
- def socket_path
41
- path(ENV['EYE_SOCK'] || "sock#{ENV['EYE_V']}")
42
- end
45
+ def socket_path
46
+ path(ENV['EYE_SOCK'] || "sock#{ENV['EYE_V']}")
47
+ end
43
48
 
44
- def pid_path
45
- path(ENV['EYE_PID'] || "pid#{ENV['EYE_V']}")
46
- end
49
+ def pid_path
50
+ path(ENV['EYE_PID'] || "pid#{ENV['EYE_V']}")
51
+ end
47
52
 
48
- def cache_path
49
- path("processes#{ENV['EYE_V']}.cache")
50
- end
53
+ def cache_path
54
+ path("processes#{ENV['EYE_V']}.cache")
55
+ end
51
56
 
52
- def client_timeout
53
- 5
54
- end
57
+ def client_timeout
58
+ @client_timeout ||= 5
59
+ end
55
60
 
56
- def supported_setsid?
57
- RUBY_VERSION >= '2.0'
58
- end
61
+ def client_timeout=(cl)
62
+ @client_timeout = cl
63
+ end
59
64
 
60
- def host
61
- @host ||= begin
62
- require 'socket'
63
- Socket.gethostname
65
+ def supported_setsid?
66
+ RUBY_VERSION >= '2.0'
64
67
  end
65
- end
66
68
 
67
- def host=(hostname)
68
- @host = hostname
69
- end
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.depends_on
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.minute
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.depends_on
100
+ def self.requires
99
101
  end
100
102
 
101
103
  class Custom < Eye::Notify
@@ -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
- Net::SMTP.start(*args) do |smtp|
30
- smtp.send_message(message, from_mail || user, contact)
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)
@@ -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
 
@@ -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: #{self[:environment].inspect}, working_dir: #{self[:working_dir]}, <#{res[:pid]}>"
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
- unless failsafe_save_pid
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: #{self[:environment].inspect}, working_dir: #{self[:working_dir]}"
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]))
@@ -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
 
@@ -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
@@ -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
- elsif (Time.now - pid_file_ctime > REWRITE_FACKUP_PIDFILE_PERIOD)
64
- msg += " over #{REWRITE_FACKUP_PIDFILE_PERIOD}s ago, reverting to <#{self.pid}>"
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
@@ -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
- @states_history.push transition.to_name, @state_reason
80
- info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{@state_reason ? "(reason: #{@state_reason})" : nil}"
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