eye 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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