eye 0.1.11 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +2 -2
  3. data/README.md +41 -18
  4. data/bin/eye +4 -3
  5. data/examples/processes/em.rb +2 -1
  6. data/examples/processes/thin.ru +12 -0
  7. data/examples/sidekiq.eye +19 -0
  8. data/examples/test.eye +25 -16
  9. data/eye.gemspec +5 -3
  10. data/lib/eye.rb +2 -1
  11. data/lib/eye/checker/validation.rb +1 -1
  12. data/lib/eye/child_process.rb +27 -5
  13. data/lib/eye/controller/load.rb +13 -11
  14. data/lib/eye/controller/send_command.rb +32 -7
  15. data/lib/eye/controller/status.rb +4 -3
  16. data/lib/eye/dsl/config_opts.rb +38 -1
  17. data/lib/eye/dsl/opts.rb +12 -5
  18. data/lib/eye/dsl/pure_opts.rb +2 -2
  19. data/lib/eye/dsl/validate.rb +5 -0
  20. data/lib/eye/group.rb +1 -1
  21. data/lib/eye/group/chain.rb +2 -0
  22. data/lib/eye/notify.rb +86 -0
  23. data/lib/eye/notify/jabber.rb +30 -0
  24. data/lib/eye/notify/mail.rb +44 -0
  25. data/lib/eye/process.rb +5 -1
  26. data/lib/eye/process/child.rb +7 -8
  27. data/lib/eye/process/commands.rb +21 -18
  28. data/lib/eye/process/notify.rb +22 -7
  29. data/lib/eye/process/system.rb +18 -5
  30. data/lib/eye/process/validate.rb +23 -0
  31. data/lib/eye/system.rb +4 -1
  32. data/lib/eye/utils.rb +9 -0
  33. data/spec/checker_spec.rb +0 -1
  34. data/spec/client_server_spec.rb +0 -1
  35. data/spec/controller/controller_spec.rb +1 -1
  36. data/spec/controller/intergration_spec.rb +15 -0
  37. data/spec/controller/load_spec.rb +49 -4
  38. data/spec/dsl/chain_spec.rb +20 -14
  39. data/spec/dsl/checks_spec.rb +17 -0
  40. data/spec/dsl/notify_spec.rb +105 -0
  41. data/spec/dsl/process_spec.rb +50 -0
  42. data/spec/mock_spec.rb +0 -1
  43. data/spec/notify/jabber_spec.rb +25 -0
  44. data/spec/notify/mail_spec.rb +26 -0
  45. data/spec/notify_spec.rb +90 -0
  46. data/spec/process/config_spec.rb +0 -1
  47. data/spec/process/notify_spec.rb +27 -0
  48. data/spec/process/states_history_spec.rb +0 -1
  49. data/spec/process/stop_spec.rb +6 -0
  50. data/spec/process/system_spec.rb +34 -21
  51. data/spec/process/update_config_spec.rb +0 -1
  52. data/spec/spec_helper.rb +9 -2
  53. data/spec/support/spec_support.rb +0 -1
  54. data/spec/system_resources_spec.rb +0 -1
  55. data/spec/system_spec.rb +3 -6
  56. data/spec/utils/alive_array_spec.rb +0 -1
  57. data/spec/utils/celluloid_chain_spec.rb +0 -1
  58. data/spec/utils/tail_spec.rb +0 -1
  59. metadata +71 -7
@@ -72,21 +72,8 @@ module Eye::Process::Commands
72
72
  switch :restarting
73
73
 
74
74
  if self[:restart_command]
75
- cmd = prepare_command(self[:restart_command])
76
- info "executing: `#{cmd}` with restart_timeout: #{self[:restart_timeout].to_f}s and restart_grace: #{self[:restart_grace].to_f}s"
77
-
78
- res = execute(cmd, config.merge(:timeout => self[:restart_timeout]))
79
-
80
- if res[:error]
81
- error "restart raised with #{res[:error].inspect}"
82
-
83
- if res[:error].class == Timeout::Error
84
- error 'you should tune restart_timeout setting'
85
- end
86
- end
87
-
88
- sleep self[:restart_grace].to_f
89
-
75
+ execute_restart_command
76
+ sleep self[:restart_timeout].to_f
90
77
  result = check_alive_with_refresh_pid_if_needed
91
78
  switch(result ? :restarted : :crushed)
92
79
  else
@@ -122,7 +109,7 @@ private
122
109
  sleep self[:stop_grace].to_f
123
110
 
124
111
  elsif self[:stop_signals]
125
- info "executing signals `#{self[:stop_signals].inspect}`"
112
+ info "executing stop_signals #{self[:stop_signals].inspect}"
126
113
  stop_signals = self[:stop_signals].clone
127
114
 
128
115
  signal = stop_signals.shift
@@ -132,8 +119,7 @@ private
132
119
  delay = stop_signals.shift
133
120
  signal = stop_signals.shift
134
121
 
135
- sleep(delay.to_f)
136
- unless process_realy_running?
122
+ if wait_for_condition(delay.to_f, 0.1){ !process_realy_running? }
137
123
  info 'has terminated'
138
124
  break
139
125
  end
@@ -157,6 +143,23 @@ private
157
143
  end
158
144
  end
159
145
  end
146
+
147
+ def execute_restart_command
148
+ cmd = prepare_command(self[:restart_command])
149
+ info "executing: `#{cmd}` with restart_timeout: #{self[:restart_timeout].to_f}s and restart_grace: #{self[:restart_grace].to_f}s"
150
+
151
+ res = execute(cmd, config.merge(:timeout => self[:restart_timeout]))
152
+
153
+ if res[:error]
154
+ error "restart raised with #{res[:error].inspect}"
155
+
156
+ if res[:error].class == Timeout::Error
157
+ error 'you should tune restart_timeout setting'
158
+ end
159
+ end
160
+
161
+ res
162
+ end
160
163
 
161
164
  def daemonize_process
162
165
  time_before = Time.now
@@ -1,17 +1,32 @@
1
1
  module Eye::Process::Notify
2
2
 
3
3
  # notify to user:
4
- # 1) process crushed by itself, and we restart it
5
- # 2) checker fire to restart process +
6
- # 3) flapping + switch to unmonitored
4
+ # 1) process crushed by itself, and we restart it [:warn]
5
+ # 2) checker bounded to restart process [:crit]
6
+ # 3) flapping + switch to unmonitored [:crit]
7
7
 
8
- # level = [:warn, :crit]
8
+ LEVELS = {:warn => 0, :crit => 1}
9
9
 
10
- # TODO: add mail, jabber here
11
10
  def notify(level, msg)
12
- if level != :warn
13
- warn "!!!!!!!! NOTIFY: #{level}, #{msg} !!!!!!!!!!!"
11
+ # logging it
12
+ error "NOTIFY: #{msg}" if ilevel(level) > 0
13
+
14
+ # send notifies
15
+ if self[:notify].present?
16
+ message = {:message => msg, :name => name,
17
+ :full_name => full_name, :pid => pid, :host => Eye::System.host, :level => level,
18
+ :at => Time.now }
19
+
20
+ self[:notify].each do |contact, not_level|
21
+ Eye::Notify.notify(contact, message) if ilevel(level) >= ilevel(not_level)
22
+ end
14
23
  end
15
24
  end
16
25
 
26
+ private
27
+
28
+ def ilevel(level)
29
+ LEVELS[level].to_i
30
+ end
31
+
17
32
  end
@@ -47,14 +47,27 @@ module Eye::Process::System
47
47
  res[:status] == :ok
48
48
  end
49
49
 
50
- def with_timeout(time, &block)
51
- Timeout.timeout(time.to_f, &block)
52
- rescue Timeout::Error
53
- :timeout
50
+ # non blocking actor timeout
51
+ def wait_for_condition(timeout, step = 0.1, &block)
52
+ defer{ wait_for_condition_sync(timeout, step, &block) }
54
53
  end
55
54
 
56
55
  def execute(cmd, cfg = {})
57
56
  defer{ Eye::System::execute cmd, cfg }
58
57
  end
59
-
58
+
59
+ private
60
+
61
+ def wait_for_condition_sync(timeout, step, &block)
62
+ res = nil
63
+
64
+ Timeout::timeout(timeout.to_f) do
65
+ sleep step.to_f until res = yield
66
+ end
67
+
68
+ res
69
+ rescue Timeout::Error
70
+ false
71
+ end
72
+
60
73
  end
@@ -0,0 +1,23 @@
1
+ require 'shellwords'
2
+
3
+ module Eye::Process::Validate
4
+
5
+ class Error < Exception; end
6
+
7
+ def validate(config)
8
+ if (str = config[:start_command])
9
+ # it should parse with Shellwords and not raise
10
+ spl = Shellwords.shellwords(str) * '#'
11
+
12
+ if config[:daemonize]
13
+ if spl =~ %r[sh#\-c|#&&#|;#]
14
+ raise Error, "#{config[:name]}, start_command in daemonize not supported shell concats like '&&'"
15
+ end
16
+ end
17
+ end
18
+
19
+ Shellwords.shellwords(config[:stop_command]) if config[:stop_command]
20
+ Shellwords.shellwords(config[:restart_command]) if config[:restart_command]
21
+ end
22
+
23
+ end
@@ -67,11 +67,14 @@ module Eye::System
67
67
  {:pid => pid}
68
68
 
69
69
  rescue Timeout::Error => ex
70
- send_signal(pid, 9)
70
+ send_signal(pid, 9) if pid
71
71
  {:error => ex}
72
72
 
73
73
  rescue Errno::ENOENT, Errno::EACCES => ex
74
74
  {:error => ex}
75
+
76
+ ensure
77
+ Process.detach(pid) if pid
75
78
  end
76
79
 
77
80
  # get table
@@ -2,4 +2,13 @@ module Eye::Utils
2
2
  autoload :Tail, 'eye/utils/tail'
3
3
  autoload :AliveArray, 'eye/utils/alive_array'
4
4
  autoload :CelluloidChain, 'eye/utils/celluloid_chain'
5
+
6
+ def self.deep_clone(value)
7
+ case
8
+ when value.is_a?(Array) then value.map{|v| deep_clone(v) }
9
+ when value.is_a?(Hash) then value.inject({}){|r, (k, v)| r[ deep_clone(k) ] = deep_clone(v); r }
10
+ else value
11
+ end
12
+ end
13
+
5
14
  end
@@ -1,4 +1,3 @@
1
- # -*- encoding : utf-8 -*-
2
1
  require File.dirname(__FILE__) + '/spec_helper'
3
2
 
4
3
  class Checker1 < Eye::Checker
@@ -1,4 +1,3 @@
1
- # -*- encoding : utf-8 -*-
2
1
  require File.dirname(__FILE__) + '/spec_helper'
3
2
 
4
3
  describe "Eye::Client, Eye::Server" do
@@ -94,7 +94,7 @@ S
94
94
 
95
95
  it "info_string_debug should be" do
96
96
  subject.load(fixture("dsl/load.eye"))
97
- subject.info_string_debug.split("\n").size.should > 10
97
+ subject.info_string_debug.split("\n").size.should > 5
98
98
  end
99
99
 
100
100
  it "info_string_short should be" do
@@ -78,6 +78,21 @@ describe "Intergration" do
78
78
  @p3.last_scheduled_reason.should == 'restart by user'
79
79
  end
80
80
 
81
+ it "restart forking named child" do
82
+ @p3.childs.size.should == 3
83
+ dead_pid = @p3.childs.keys.sample
84
+
85
+ @c.send_command(:restart, "child-#{dead_pid}").should == ["int:forking:child-#{dead_pid}"]
86
+ sleep 11 # while it
87
+
88
+ new_childs = @p3.childs.keys
89
+ new_childs.size.should == 3
90
+ new_childs.should_not include(dead_pid)
91
+ (@childs - [dead_pid]).each do |pid|
92
+ new_childs.should include(pid)
93
+ end
94
+ end
95
+
81
96
  it "restart missing" do
82
97
  @old_pid1 = @p1.pid
83
98
  @old_pid2 = @p2.pid
@@ -308,19 +308,64 @@ describe "Eye::Controller::Load" do
308
308
 
309
309
  describe "load is exclusive" do
310
310
  it "run double in time" do
311
- t = Time.now
312
311
  Eye::Control.async.command(:load, fixture("dsl/long_load.eye"))
313
312
  Eye::Control.async.command(:load, fixture("dsl/long_load.eye"))
314
- sleep 2.5
315
- Eye::Control.command(:info).should be_a(String) # actor should free here
313
+ sleep 2.5
314
+ should_spend(0, 0.2) do
315
+ Eye::Control.command(:info).should be_a(String)
316
+ end
316
317
  end
317
318
 
318
319
  it "load with subloads" do
319
320
  silence_warnings{
320
321
  Eye::Control.command(:load, fixture("dsl/subfolder2.eye"))
321
322
  }
322
- Eye::Control.command(:info).should be_a(String) # actor should free here
323
+ should_spend(0, 0.2) do
324
+ Eye::Control.command(:info).should be_a(String)
325
+ end
323
326
  end
324
327
  end
325
328
 
329
+ describe "cleanup configs on delete" do
330
+ it "load config, delete 1 process, load another config" do
331
+ subject.load(fixture('dsl/load.eye'))
332
+ subject.process_by_name('p1').should be
333
+
334
+ subject.command(:delete, "p1"); sleep 0.1
335
+ subject.process_by_name('p1').should be_nil
336
+
337
+ subject.load(fixture('dsl/load2.eye'))
338
+ subject.process_by_name('p1').should be_nil
339
+ end
340
+
341
+ it "load config, delete 1 group, load another config" do
342
+ subject.load(fixture('dsl/load.eye'))
343
+ subject.group_by_name('gr1').should be
344
+
345
+ subject.command(:delete, "gr1"); sleep 0.1
346
+ subject.group_by_name('p1').should be_nil
347
+
348
+ subject.load(fixture('dsl/load2.eye'))
349
+ subject.group_by_name('gr1').should be_nil
350
+ end
351
+
352
+ it "load config, then delete app, and load it with changed app-name" do
353
+ subject.load(fixture('dsl/load3.eye'))
354
+ subject.command(:delete, "app3"); sleep 0.1
355
+ subject.load(fixture('dsl/load4.eye')).should include(error: false)
356
+ end
357
+ end
358
+
359
+ it "should update only changed apps" do
360
+ mock(subject).update_or_create_application('app1', is_a(Hash))
361
+ mock(subject).update_or_create_application('app2', is_a(Hash))
362
+ subject.load(fixture('dsl/load.eye'))
363
+
364
+ mock(subject).update_or_create_application('app3', is_a(Hash))
365
+ subject.load(fixture('dsl/load2.eye'))
366
+
367
+ mock(subject).update_or_create_application('app3', is_a(Hash))
368
+ subject.load(fixture('dsl/load3.eye'))
369
+ end
370
+
326
371
  end
@@ -10,28 +10,34 @@ describe "Eye::Dsl::Chain" do
10
10
  process("3") do
11
11
  pid_file "3"
12
12
  end
13
+
14
+ group :yy do
15
+ end
13
16
  end
14
17
  E
15
-
18
+
16
19
  h = {
17
- "bla" => {:name => "bla",
18
- :chain=>{
19
- :start=>{:grace=>5, :action=>:start},
20
- :restart=>{:grace=>5, :action=>:restart}},
20
+ "bla" => {
21
+ :name=>"bla",
22
+ :chain=>{:start=>{:grace=>5, :action=>:start}, :restart=>{:grace=>5, :action=>:restart}},
21
23
  :groups=>{
22
- "__default__"=>{:name => "__default__", :application => "bla",
23
- :chain=>{
24
- :start=>{:grace=>5, :action=>:start},
25
- :restart=>{:grace=>5, :action=>:restart}},
24
+ "__default__"=>{
25
+ :name=>"__default__",
26
+ :chain=>{:start=>{:grace=>5, :action=>:start}, :restart=>{:grace=>5, :action=>:restart}},
27
+ :application=>"bla",
26
28
  :processes=>{
27
29
  "3"=>{
28
- :chain=>{:start=>{:grace=>5, :action=>:start},
29
- :restart=>{:grace=>5, :action=>:restart}},
30
- :pid_file=>"3",
30
+ :name=>"3",
31
+ :chain=>{:start=>{:grace=>5, :action=>:start}, :restart=>{:grace=>5, :action=>:restart}},
31
32
  :application=>"bla",
32
33
  :group=>"__default__",
33
- :name=>"3"}}}}}}
34
-
34
+ :pid_file=>"3"}}},
35
+ "yy"=>{
36
+ :name=>"yy",
37
+ :chain=>{:start=>{:grace=>5, :action=>:start}, :restart=>{:grace=>5, :action=>:restart}},
38
+ :application=>"bla", :processes=>{}}}}
39
+ }
40
+
35
41
  Eye::Dsl.parse_apps(conf).should == h
36
42
  end
37
43
 
@@ -191,12 +191,29 @@ describe "Eye::Dsl checks" do
191
191
 
192
192
  checks :socket, :addr => "unix:/tmp/1", :expect_data => Proc.new{|data| data == 1}
193
193
  end
194
+
195
+ process("3") do
196
+ pid_file "3.pid"
197
+
198
+ checks :socket, :addr => "unix:/tmp/3", :expect_data => /regexp/
199
+ end
200
+
201
+ process("2") do
202
+ pid_file "2.pid"
203
+
204
+ checks :socket, :addr => "unix:/tmp/2", :expect_data => Proc.new{|data| data == 1}
205
+ end
206
+
194
207
  end
195
208
  E
196
209
  res = Eye::Dsl.parse_apps(conf)
197
210
  proc = res['bla'][:groups]['__default__'][:processes]['1'][:checks][:socket][:expect_data]
198
211
  proc[0].should == false
199
212
  proc[1].should == true
213
+
214
+ proc = res['bla'][:groups]['__default__'][:processes]['2'][:checks][:socket][:expect_data]
215
+ proc[0].should == false
216
+ proc[1].should == true
200
217
  end
201
218
 
202
219
  end
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Eye::Dsl notify" do
4
+ it "integration" do
5
+ conf = <<-E
6
+ Eye.config do
7
+ mail :host => "mx.some.host.ru", :port => 25
8
+
9
+ contact :vasya, :mail, "vasya@mail.ru"
10
+ contact :petya, :mail, "petya@mail.ru", :port => 1111
11
+
12
+ contact_group :idiots do
13
+ contact :idiot1, :mail, "idiot1@mail.ru"
14
+ contact :idiot2, :mail, "idiot1@mail.ru", :port => 1111
15
+ end
16
+ end
17
+
18
+ Eye.application :bla do
19
+ notify :vasya
20
+ notify :idiots, :crit
21
+
22
+ group :gr1 do
23
+ notify :petya
24
+ notify :idiot1, :warn
25
+ end
26
+ end
27
+ E
28
+ res = Eye::Dsl.parse(conf)
29
+
30
+ res.should == {
31
+ :applications => {
32
+ "bla"=>{:name=>"bla",
33
+ :notify=>{"vasya"=>:crit, "idiots"=>:crit},
34
+ :groups=>{"gr1"=>{:name=>"gr1",
35
+ :notify=>{"vasya"=>:crit, "idiots"=>:crit, "petya"=>:crit, "idiot1"=>:warn}, :application=>"bla", :processes=>{}}}}},
36
+ :config => {
37
+ :mail=>{:host=>"mx.some.host.ru", :port => 25, :type => :mail},
38
+ :contacts=>{
39
+ "vasya"=>{:name=>"vasya", :type=>:mail, :contact=>"vasya@mail.ru", :opts=>{}},
40
+ "petya"=>{:name=>"petya", :type=>:mail, :contact=>"petya@mail.ru", :opts=>{:port=>1111}},
41
+ 'idiots'=>[{:name=>"idiot1", :type=>:mail, :contact=>"idiot1@mail.ru", :opts=>{}}, {:name=>"idiot2", :type=>:mail, :contact=>"idiot1@mail.ru", :opts=>{:port=>1111}}],
42
+ "idiot1"=>{:name=>"idiot1", :type=>:mail, :contact=>"idiot1@mail.ru", :opts=>{}},
43
+ "idiot2"=>{:name=>"idiot2", :type=>:mail, :contact=>"idiot1@mail.ru", :opts=>{:port=>1111}}}}}
44
+ end
45
+
46
+ it "valid contact type" do
47
+ conf = <<-E
48
+ Eye.config do
49
+ contact :vasya, :mail, "vasya@mail.ru", :port => 25, :host => "localhost"
50
+ end
51
+ E
52
+ Eye::Dsl.parse(conf)[:config].should == {:contacts=>{
53
+ "vasya"=>{:name=>"vasya", :type=>:mail, :contact=>"vasya@mail.ru", :opts=>{:port => 25, :host => "localhost"}}}}
54
+ end
55
+
56
+ it "raise on unknown contact type" do
57
+ conf = <<-E
58
+ Eye.config do
59
+ contact :vasya, :dddd, "vasya@mail.ru"
60
+ end
61
+ E
62
+ expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Dsl::Error)
63
+ end
64
+
65
+ it "raise on unknown additional_options" do
66
+ conf = <<-E
67
+ Eye.config do
68
+ contact :vasya, :mail, "vasya@mail.ru", :bla => 1
69
+ end
70
+ E
71
+ expect{ Eye::Dsl.parse(conf) }.to raise_error(Eye::Checker::Validation::Error)
72
+ end
73
+
74
+ it "set notify inherited" do
75
+ conf = <<-E
76
+ Eye.app :bla do
77
+ notify :vasya
78
+
79
+ group :bla do
80
+ end
81
+ end
82
+ E
83
+ Eye::Dsl.parse_apps(conf).should == {
84
+ "bla" => {:name=>"bla",
85
+ :notify=>{"vasya"=>:crit},
86
+ :groups=>{"bla"=>{:name=>"bla",
87
+ :notify=>{"vasya"=>:crit}, :application=>"bla", :processes=>{}}}}}
88
+ end
89
+
90
+ it "clear notify with nonotify" do
91
+ conf = <<-E
92
+ Eye.app :bla do
93
+ notify :vasya
94
+
95
+ group :bla do
96
+ nonotify :vasya
97
+ end
98
+ end
99
+ E
100
+ Eye::Dsl.parse_apps(conf).should == {
101
+ "bla" => {:name=>"bla",
102
+ :notify=>{"vasya"=>:crit},
103
+ :groups=>{"bla"=>{:name=>"bla", :notify=>{}, :application=>"bla", :processes=>{}}}}}
104
+ end
105
+ end