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.
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