eye 0.1.11 → 0.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 (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