eye 0.2.1 → 0.2.2

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -17
  3. data/bin/eye +5 -0
  4. data/examples/notify.eye +18 -0
  5. data/examples/process_thin.rb +29 -0
  6. data/examples/sidekiq.eye +5 -1
  7. data/examples/test.eye +24 -17
  8. data/examples/thin-farm.eye +29 -0
  9. data/examples/unicorn.eye +2 -0
  10. data/eye.gemspec +1 -1
  11. data/lib/eye.rb +1 -1
  12. data/lib/eye/checker.rb +3 -5
  13. data/lib/eye/checker/http.rb +1 -2
  14. data/lib/eye/checker/socket.rb +2 -2
  15. data/lib/eye/controller/commands.rb +2 -0
  16. data/lib/eye/controller/send_command.rb +8 -2
  17. data/lib/eye/dsl.rb +1 -0
  18. data/lib/eye/dsl/opts.rb +36 -2
  19. data/lib/eye/dsl/pure_opts.rb +17 -39
  20. data/lib/eye/{checker → dsl}/validation.rb +15 -5
  21. data/lib/eye/group.rb +13 -4
  22. data/lib/eye/group/chain.rb +11 -0
  23. data/lib/eye/notify.rb +1 -1
  24. data/lib/eye/notify/mail.rb +1 -1
  25. data/lib/eye/process/child.rb +0 -2
  26. data/lib/eye/process/commands.rb +11 -14
  27. data/lib/eye/process/monitor.rb +10 -16
  28. data/lib/eye/process/scheduler.rb +8 -3
  29. data/lib/eye/process/states.rb +1 -2
  30. data/lib/eye/process/system.rb +38 -7
  31. data/lib/eye/process/watchers.rb +3 -3
  32. data/lib/eye/settings.rb +1 -1
  33. data/lib/eye/system.rb +33 -16
  34. data/lib/eye/trigger.rb +1 -1
  35. data/lib/eye/utils/celluloid_chain.rb +2 -0
  36. data/spec/checker/cpu_spec.rb +1 -1
  37. data/spec/checker/http_spec.rb +2 -2
  38. data/spec/checker/memory_spec.rb +2 -2
  39. data/spec/checker_spec.rb +1 -1
  40. data/spec/controller/controller_spec.rb +6 -0
  41. data/spec/controller/find_objects_spec.rb +6 -0
  42. data/spec/controller/intergration_spec.rb +24 -0
  43. data/spec/dsl/checks_spec.rb +2 -2
  44. data/spec/dsl/notify_spec.rb +12 -3
  45. data/spec/dsl/with_server_spec.rb +26 -2
  46. data/spec/process/checks/child_checks_spec.rb +1 -1
  47. data/spec/process/checks/memory_spec.rb +14 -0
  48. data/spec/process/scheduler_spec.rb +8 -0
  49. data/spec/process/system_spec.rb +27 -4
  50. data/spec/spec_helper.rb +3 -1
  51. data/spec/system_spec.rb +12 -5
  52. data/spec/utils/celluloid_chain_spec.rb +8 -0
  53. metadata +8 -5
@@ -9,7 +9,6 @@ module Eye::Process::Watchers
9
9
  # default watcher :check_alive
10
10
  add_watcher(:check_alive, self[:check_alive_period]) do
11
11
  check_alive
12
- GC.start if rand(1000) == 0
13
12
  end
14
13
 
15
14
  # monitor childs pids
@@ -61,8 +60,9 @@ private
61
60
  unless subject.check
62
61
  return unless up?
63
62
 
64
- notify :crit, "Bounded #{subject.check_name}: #{subject.last_human_values}"
65
- schedule :restart, "bounded #{subject.check_name}"
63
+ action = subject.fire || :restart
64
+ notify :crit, "Bounded #{subject.check_name}: #{subject.last_human_values} send to :#{action}"
65
+ schedule action, "bounded #{subject.check_name}"
66
66
  end
67
67
  end
68
68
 
@@ -8,7 +8,7 @@ module Eye::Settings
8
8
  if Process::UID.eid == 0 # root
9
9
  '/var/run/eye'
10
10
  else
11
- File.expand_path(File.join(ENV['HOME'], '.eye'))
11
+ File.expand_path(File.join(ENV['EYE_HOME'] || ENV['HOME'], '.eye'))
12
12
  end
13
13
  end
14
14
 
@@ -3,13 +3,28 @@ require 'pathname'
3
3
 
4
4
  module Eye::System
5
5
  class << self
6
+ # Check that pid realy exits
7
+ # very fast
8
+ # return result hash
9
+ def check_pid_alive(pid)
10
+ res = if pid
11
+ ::Process.kill(0, pid)
12
+ true
13
+ else
14
+ false
15
+ end
16
+
17
+ {:result => res}
18
+ rescue => ex
19
+ {:error => ex}
20
+ end
6
21
 
7
22
  # Check that pid realy exits
8
23
  # very fast
24
+ # return true/false
9
25
  def pid_alive?(pid)
10
- pid ? ::Process.kill(0, pid) && true : false
11
- rescue Errno::ESRCH, Errno::EPERM
12
- false
26
+ res = check_pid_alive(pid)
27
+ !!res[:result]
13
28
  end
14
29
 
15
30
  # Send signal to process (uses for kill)
@@ -22,17 +37,15 @@ module Eye::System
22
37
  end
23
38
  code = code.to_s.upcase if code.is_a?(String) || code.is_a?(Symbol)
24
39
 
25
- ::Process.kill(code, pid) if pid
26
- {:status => :ok}
27
-
28
- rescue Errno::ESRCH
29
- {:status => :error, :message => 'process not found'}
30
-
31
- rescue Errno::EPERM
32
- {:status => :error, :message => 'wrong permissions to kill'}
40
+ if pid
41
+ ::Process.kill(code, pid)
42
+ {:result => :ok}
43
+ else
44
+ {:error => Exception.new('no_pid')}
45
+ end
33
46
 
34
- rescue => e
35
- {:status => :error, :message => "failed signal #{code}: #{e.message}"}
47
+ rescue => ex
48
+ {:error => ex}
36
49
  end
37
50
 
38
51
  # Daemonize cmd, and detach
@@ -60,14 +73,18 @@ module Eye::System
60
73
  opts = spawn_options(cfg)
61
74
  pid = Process::spawn(prepare_env(cfg), *Shellwords.shellwords(cmd), opts)
62
75
 
63
- Timeout.timeout(cfg[:timeout] || 1.second) do
76
+ timeout = cfg[:timeout] || 1.second
77
+ Timeout.timeout(timeout) do
64
78
  Process.waitpid(pid)
65
79
  end
66
80
 
67
81
  {:pid => pid}
68
82
 
69
- rescue Timeout::Error => ex
70
- send_signal(pid, 9) if pid
83
+ rescue Timeout::Error => ex
84
+ if pid
85
+ Eye.warn "[#{cfg[:name]}] send signal 9 to #{pid} (because of timeouted<#{timeout}> execution)"
86
+ send_signal(pid, 9)
87
+ end
71
88
  {:error => ex}
72
89
 
73
90
  rescue Errno::ENOENT, Errno::EACCES => ex
@@ -48,6 +48,6 @@ class Eye::Trigger
48
48
  raise 'realize me'
49
49
  end
50
50
 
51
- extend Eye::Checker::Validation
51
+ extend Eye::Dsl::Validation
52
52
 
53
53
  end
@@ -32,6 +32,8 @@ class Eye::Utils::CelluloidChain
32
32
  @calls = []
33
33
  end
34
34
 
35
+ alias :clear_pending_list :clear
36
+
35
37
  # need, because of https://github.com/celluloid/celluloid/issues/22
36
38
  def inspect
37
39
  "Celluloid::Chain(#{@target.class}: #{@calls.inspect})"
@@ -51,7 +51,7 @@ describe "Eye::Checker::Cpu" do
51
51
  end
52
52
 
53
53
  it "bad param below" do
54
- expect{ Eye::Checker.validate!({:type => :cpu, :every => 5.seconds, :times => 1, :below => {1 => 2}}) }.to raise_error(Eye::Checker::Validation::Error)
54
+ expect{ Eye::Checker.validate!({:type => :cpu, :every => 5.seconds, :times => 1, :below => {1 => 2}}) }.to raise_error(Eye::Dsl::Validation::Error)
55
55
  end
56
56
  end
57
57
 
@@ -101,13 +101,13 @@ describe "Eye::Checker::Http" do
101
101
  it "without param url" do
102
102
  expect{ Eye::Checker.validate!({:type => :http, :every => 5.seconds,
103
103
  :times => 1, :kind => :success,
104
- :pattern => /OK/, :timeout => 2}) }.to raise_error(Eye::Checker::Validation::Error)
104
+ :pattern => /OK/, :timeout => 2}) }.to raise_error(Eye::Dsl::Validation::Error)
105
105
  end
106
106
 
107
107
  it "bad param timeout" do
108
108
  expect{ Eye::Checker.validate!({:type => :http, :every => 5.seconds,
109
109
  :times => 1, :kind => :success, :url => "http://localhost:3000/",
110
- :pattern => /OK/, :timeout => :fix}) }.to raise_error(Eye::Checker::Validation::Error)
110
+ :pattern => /OK/, :timeout => :fix}) }.to raise_error(Eye::Dsl::Validation::Error)
111
111
  end
112
112
  end
113
113
 
@@ -51,11 +51,11 @@ describe "Eye::Checker::Memory" do
51
51
  end
52
52
 
53
53
  it "bad param below" do
54
- expect{ Eye::Checker.validate!({:type => :memory, :every => 5.seconds, :times => 1, :below => {1 => 2}}) }.to raise_error(Eye::Checker::Validation::Error)
54
+ expect{ Eye::Checker.validate!({:type => :memory, :every => 5.seconds, :times => 1, :below => {1 => 2}}) }.to raise_error(Eye::Dsl::Validation::Error)
55
55
  end
56
56
 
57
57
  it "unknown params" do
58
- expect{ Eye::Checker.validate!({:hello => true, :type => :memory, :every => 5.seconds, :times => 1, :below => 10.0.bytes})}.to raise_error(Eye::Checker::Validation::Error)
58
+ expect{ Eye::Checker.validate!({:hello => true, :type => :memory, :every => 5.seconds, :times => 1, :below => 10.0.bytes})}.to raise_error(Eye::Dsl::Validation::Error)
59
59
  end
60
60
 
61
61
  end
@@ -138,7 +138,7 @@ describe "Eye::Checker" do
138
138
  end
139
139
 
140
140
  it "validate by default" do
141
- expect{ Checker1.validate({:times => "jopa"}) }.to raise_error(Eye::Checker::Validation::Error)
141
+ expect{ Checker1.validate({:times => "jopa"}) }.to raise_error(Eye::Dsl::Validation::Error)
142
142
  end
143
143
  end
144
144
 
@@ -104,6 +104,12 @@ S
104
104
  subject.info_string_short.split("\n").size.should == 2
105
105
  end
106
106
 
107
+ it "should delete all apps" do
108
+ subject.load(fixture("dsl/load.eye")).should include(error: false)
109
+ subject.send_command(:delete, 'all')
110
+ subject.applications.should be_empty
111
+ end
112
+
107
113
  describe "command" do
108
114
  it "should send_command" do
109
115
  mock(Eye::Control).send_command(:restart, 'samples')
@@ -67,6 +67,12 @@ describe "find_objects" do
67
67
  objs.map{|c| c.name}.sort.should == %w{app1 app2}
68
68
  end
69
69
 
70
+ it "'*', find all projects" do
71
+ objs = subject.find_objects("*")
72
+ objs.map{|c| c.class}.should == [Eye::Application, Eye::Application]
73
+ objs.map{|c| c.name}.sort.should == %w{app1 app2}
74
+ end
75
+
70
76
  it "nothing" do
71
77
  subject.find_objects("asdfasdf").should == []
72
78
  end
@@ -166,6 +166,30 @@ describe "Intergration" do
166
166
  # nothing happens
167
167
  @samples.alive?.should == true
168
168
  end
169
+
170
+ it "chain breaker breaks current chain and all pending requests" do
171
+ @samples.config.merge!(:chain => C.restart_async)
172
+
173
+ @c.send_command(:restart, "samples")
174
+ @c.send_command(:stop, "samples")
175
+ sleep 0.5
176
+
177
+ @samples.current_scheduled_command.should == :restart
178
+ @samples.scheduler_actions_list.should == [:stop]
179
+
180
+ @c.send_command(:break_chain, "samples")
181
+ sleep 3
182
+ @samples.current_scheduled_command.should == :restart
183
+ sleep 2
184
+ @samples.current_scheduled_command.should == nil
185
+ @samples.scheduler_actions_list.should == []
186
+
187
+ sleep 1
188
+
189
+ # only first process should be restarted
190
+ @p1.last_scheduled_command.should == :restart
191
+ @p2.last_scheduled_command.should == :monitor
192
+ end
169
193
  end
170
194
 
171
195
  it "stop group" do
@@ -73,7 +73,7 @@ describe "Eye::Dsl checks" do
73
73
  end
74
74
  end
75
75
  E
76
- expect{Eye::Dsl.parse_apps(conf)}.to raise_error(Eye::Checker::Validation::Error)
76
+ expect{Eye::Dsl.parse_apps(conf)}.to raise_error(Eye::Dsl::Validation::Error)
77
77
  end
78
78
 
79
79
  it "ok trigger" do
@@ -98,7 +98,7 @@ describe "Eye::Dsl checks" do
98
98
  end
99
99
  end
100
100
  E
101
- expect{Eye::Dsl.parse_apps(conf)}.to raise_error(Eye::Checker::Validation::Error)
101
+ expect{Eye::Dsl.parse_apps(conf)}.to raise_error(Eye::Dsl::Validation::Error)
102
102
  end
103
103
 
104
104
  it "nochecks to remove inherit checks" do
@@ -56,7 +56,7 @@ describe "Eye::Dsl notify" do
56
56
  it "raise on unknown contact type" do
57
57
  conf = <<-E
58
58
  Eye.config do
59
- contact :vasya, :dddd, "vasya@mail.ru"
59
+ contact :vasya, :dddd, "vasya@mail.ru", :host => "localhost", :port => 12
60
60
  end
61
61
  E
62
62
  expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Dsl::Error)
@@ -65,10 +65,19 @@ describe "Eye::Dsl notify" do
65
65
  it "raise on unknown additional_options" do
66
66
  conf = <<-E
67
67
  Eye.config do
68
- contact :vasya, :mail, "vasya@mail.ru", :bla => 1
68
+ contact :vasya, :mail, "vasya@mail.ru", :host => "localhost", :port => 12, :bla => 1
69
69
  end
70
70
  E
71
- expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Checker::Validation::Error)
71
+ expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Dsl::Validation::Error)
72
+ end
73
+
74
+ it "raise on not including on list of values" do
75
+ conf = <<-E
76
+ Eye.config do
77
+ contact :vasya, :mail, "vasya@mail.ru", :host => "localhost", :port => 12, :auth => :ply
78
+ end
79
+ E
80
+ expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Dsl::Validation::Error)
72
81
  end
73
82
 
74
83
  it "set notify inherited" do
@@ -38,6 +38,28 @@ describe "with_server feature" do
38
38
  Eye::Dsl.parse_apps(conf).should == {"bla"=>{:name => "bla", :groups=>{"__default__"=>{:name => "__default__", :application => "bla", :processes=>{"1"=>{:pid_file=>"1.pid", :application=>"bla", :group=>"__default__", :name=>"1"}}}}}}
39
39
  end
40
40
 
41
+ it "should behaves like scoped" do
42
+ stub(Eye::System).host{ "server1" }
43
+
44
+ conf = <<-E
45
+ Eye.application("bla") do
46
+ env "A" => "B"
47
+ with_server /server1/ do
48
+ env "A" => "C"
49
+ group(:a){}
50
+ end
51
+
52
+ group(:b){}
53
+ end
54
+ E
55
+
56
+ Eye::Dsl.parse_apps(conf).should == {
57
+ "bla" => {:name=>"bla", :environment=>{"A"=>"B"},
58
+ :groups=>{
59
+ "a"=>{:name=>"a", :environment=>{"A"=>"C"}, :application=>"bla", :processes=>{}},
60
+ "b"=>{:name=>"b", :environment=>{"A"=>"B"}, :application=>"bla", :processes=>{}}}}}
61
+ end
62
+
41
63
  describe "matches" do
42
64
  subject{ Eye::Dsl::Opts.new }
43
65
 
@@ -80,11 +102,13 @@ describe "with_server feature" do
80
102
  conf = <<-E
81
103
  Eye.application("bla"){
82
104
  with_server('mega_server') do
83
- working_dir "/tmp"
105
+ group :blo do
106
+ working_dir "/tmp"
107
+ end
84
108
  end
85
109
  }
86
110
  E
87
- Eye::Dsl.parse_apps(conf).should == {"bla" => {:working_dir=>"/tmp", :name => "bla"}}
111
+ Eye::Dsl.parse_apps(conf).should == {"bla" => {:name=>"bla", :groups=>{"blo"=>{:name=>"blo", :application=>"bla", :working_dir=>"/tmp", :processes=>{}}}}}
88
112
  end
89
113
 
90
114
  it "hostname work" do
@@ -70,7 +70,7 @@ describe "ChildProcess" do
70
70
 
71
71
 
72
72
  crazy.watchers.keys.should == [:check_memory, :check_cpu]
73
- mock(crazy).notify(:crit, "Bounded cpu(50%): [*55%, *55%]")
73
+ mock(crazy).notify(:crit, "Bounded cpu(50%): [*55%, *55%] send to :restart")
74
74
  mock(crazy).schedule :restart, anything
75
75
 
76
76
  sleep 4
@@ -37,6 +37,20 @@ describe "Process Memory check" do
37
37
  sleep 1
38
38
  end
39
39
 
40
+ it "when memory exceed limit process should stop if fire==stop" do
41
+ @check = {:memory => {:every => 2, :below => 40.megabytes, :times => 1, :type => :memory, :fire => :stop}}
42
+ start_ok_process(@c.merge(:checks => @check))
43
+ stub(Eye::SystemResources).memory(@process.pid){ 20_000 }
44
+
45
+ sleep 3
46
+
47
+ stub(Eye::SystemResources).memory(@process.pid){ 50_000 }
48
+ mock(@process).notify(:crit, anything)
49
+ mock(@process).schedule(:stop, anything)
50
+
51
+ sleep 1
52
+ end
53
+
40
54
  it "else should not restart" do
41
55
  start_ok_process(@c.merge(:checks => @check))
42
56
 
@@ -122,6 +122,14 @@ describe "Scheduler" do
122
122
  scheduler.alive?.should == false
123
123
  end
124
124
 
125
+ it "schedule unexisted method should not raise and break anything" do
126
+ scheduler = @process.scheduler
127
+ @process.schedule :hahhaha
128
+ sleep 0.2
129
+ @process.alive?.should == true
130
+ scheduler.alive?.should == true
131
+ end
132
+
125
133
  describe "reasons" do
126
134
  it "1 param without reason" do
127
135
  @process.schedule :scheduler_test3, 1
@@ -4,7 +4,7 @@ describe "Eye::Process::System" do
4
4
  before :each do
5
5
  @process = Eye::Process.new(C.p1)
6
6
  end
7
-
7
+
8
8
  it "load_pid_from_file" do
9
9
  File.open(@process[:pid_file_ex], 'w'){|f| f.write("asdf") }
10
10
  @process.load_pid_from_file.should == nil
@@ -16,6 +16,17 @@ describe "Eye::Process::System" do
16
16
  @process.load_pid_from_file.should == nil
17
17
  end
18
18
 
19
+ it "failsafe_load_pid" do
20
+ File.open(@process[:pid_file_ex], 'w'){|f| f.write("asdf") }
21
+ @process.failsafe_load_pid.should == nil
22
+
23
+ File.open(@process[:pid_file_ex], 'w'){|f| f.write(12345) }
24
+ @process.failsafe_load_pid.should == 12345
25
+
26
+ FileUtils.rm(@process[:pid_file_ex]) rescue nil
27
+ @process.failsafe_load_pid.should == nil
28
+ end
29
+
19
30
  it "set_pid_from_file" do
20
31
  File.open(@process[:pid_file_ex], 'w'){|f| f.write(12345) }
21
32
  @process.set_pid_from_file
@@ -29,6 +40,18 @@ describe "Eye::Process::System" do
29
40
  File.read(@process[:pid_file_ex]).to_i.should == 123456789
30
41
  end
31
42
 
43
+ it "failsafe_save_pid ok case" do
44
+ @process.pid = 123456789
45
+ @process.failsafe_save_pid.should == true
46
+ File.read(@process[:pid_file_ex]).to_i.should == 123456789
47
+ end
48
+
49
+ it "failsafe_save_pid bad case" do
50
+ @process.config[:pid_file_ex] = "/asdf/adf/asd/fs/dfs/das/df.1"
51
+ @process.pid = 123456789
52
+ @process.failsafe_save_pid.should == false
53
+ end
54
+
32
55
  it "clear_pid_file" do
33
56
  @process.pid = 123456789
34
57
  @process.save_pid_to_file
@@ -46,16 +69,16 @@ describe "Eye::Process::System" do
46
69
  @process.process_realy_running?.should == nil
47
70
 
48
71
  @process.pid = -123434
49
- @process.process_realy_running?.should == false
72
+ @process.process_realy_running?.should == nil
50
73
  end
51
74
 
52
75
  it "send_signal ok" do
53
- mock(Eye::System).send_signal(@process.pid, :TERM){ {:status => :ok} }
76
+ mock(Eye::System).send_signal(@process.pid, :TERM){ {:result => :ok} }
54
77
  @process.send_signal(:TERM).should == true
55
78
  end
56
79
 
57
80
  it "send_signal not ok" do
58
- mock(Eye::System).send_signal(@process.pid, :TERM){ {:status => :error} }
81
+ mock(Eye::System).send_signal(@process.pid, :TERM){ {:error => Exception.new('bla')} }
59
82
  @process.send_signal(:TERM).should == false
60
83
  end
61
84
 
@@ -4,7 +4,9 @@ require 'celluloid'
4
4
 
5
5
  if ENV['COV']
6
6
  require 'simplecov'
7
- SimpleCov.start
7
+ SimpleCov.start do
8
+ add_filter "/bundle/"
9
+ end
8
10
  end
9
11
 
10
12
  Bundler.require :default