phantom-manager 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +3 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.lock +36 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +32 -0
  8. data/Rakefile +1 -0
  9. data/bin/phantom_monitor +55 -0
  10. data/config/config.yml +27 -0
  11. data/lib/.DS_Store +0 -0
  12. data/lib/monitors/base.rb +39 -0
  13. data/lib/monitors/memory.rb +27 -0
  14. data/lib/monitors/processes.rb +33 -0
  15. data/lib/monitors/restart_listener.rb +39 -0
  16. data/lib/monitors/violations_recorders/base.rb +56 -0
  17. data/lib/monitors/violations_recorders/memory.rb +23 -0
  18. data/lib/monitors/violations_recorders/processes.rb +21 -0
  19. data/lib/nginx/manager.rb +59 -0
  20. data/lib/phantom/.DS_Store +0 -0
  21. data/lib/phantom/collector.rb +36 -0
  22. data/lib/phantom/manager/version.rb +5 -0
  23. data/lib/phantom/manager.rb +34 -0
  24. data/lib/phantom/process.rb +54 -0
  25. data/lib/utils/cfg.rb +18 -0
  26. data/lib/utils/limited_array.rb +31 -0
  27. data/lib/utils/lock.rb +29 -0
  28. data/lib/utils/logger.rb +3 -0
  29. data/lib/utils/shell.rb +12 -0
  30. data/phantom-manager.gemspec +24 -0
  31. data/spec/files/config.yml +12 -0
  32. data/spec/files/nginx.conf +26 -0
  33. data/spec/lib/monitors/base_spec.rb +14 -0
  34. data/spec/lib/monitors/memory_spec.rb +33 -0
  35. data/spec/lib/monitors/processes_spec.rb +45 -0
  36. data/spec/lib/monitors/restart_listener_spec.rb +27 -0
  37. data/spec/lib/monitors/violations_recorders/base_spec.rb +126 -0
  38. data/spec/lib/monitors/violations_recorders/memory_spec.rb +73 -0
  39. data/spec/lib/monitors/violations_recorders/processes_spec.rb +51 -0
  40. data/spec/lib/nginx/manager_spec.rb +80 -0
  41. data/spec/lib/phantom/collector_spec.rb +59 -0
  42. data/spec/lib/phantom/manager_spec.rb +25 -0
  43. data/spec/lib/phantom/process_spec.rb +47 -0
  44. data/spec/lib/utils/limited_array_spec.rb +73 -0
  45. data/spec/lib/utils/lock_spec.rb +69 -0
  46. data/spec/lib/utils/shell_spec.rb +18 -0
  47. data/spec/shared_spec.rb +45 -0
  48. data/spec/spec_helper.rb +29 -0
  49. metadata +152 -0
@@ -0,0 +1,31 @@
1
+ class LimitedArray < Array
2
+
3
+ attr_accessor :limit
4
+
5
+ def initialize(limit)
6
+ @limit = limit
7
+ super()
8
+ end
9
+
10
+ def full?
11
+ size == limit
12
+ end
13
+
14
+ def <<(val)
15
+ if full?
16
+ self.rotate!
17
+ self[limit-1] = val
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ def average
24
+ sum.to_f / size
25
+ end
26
+
27
+ def sum
28
+ inject(:+)
29
+ end
30
+
31
+ end
data/lib/utils/lock.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Utils
2
+ class Lock
3
+ def initialize
4
+ @locked=false
5
+ end
6
+
7
+ def acquire
8
+ if !locked?
9
+ lock
10
+ yield
11
+ unlock
12
+ end
13
+ end
14
+
15
+ def lock
16
+ @locked = true
17
+ self
18
+ end
19
+
20
+ def unlock
21
+ @locked = false
22
+ self
23
+ end
24
+
25
+ def locked?
26
+ @locked
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ require 'logger'
2
+ $logger = Logger.new(STDOUT)
3
+ $logger.level = Logger::INFO
@@ -0,0 +1,12 @@
1
+ require 'utils/logger'
2
+ require 'utils/cfg'
3
+
4
+ module Utils
5
+ class Shell
6
+ class << self
7
+ def execute(cmd)
8
+ return system cmd
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'phantom/manager/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "phantom-manager"
8
+ spec.version = Phantom::Manager::VERSION
9
+ spec.authors = ["Erez Rabih"]
10
+ spec.email = ["erez.rabih@gmail.com"]
11
+ spec.description = %q{Uses Nginx as multiple phantomjs workers load balancer}
12
+ spec.summary = %q{Write a gem summary}
13
+ spec.homepage = "https://github.com/FTBpro/phantom-manager"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,12 @@
1
+ test:
2
+ phantom_termination_grace: 10
3
+ phantom_processes_number: 10
4
+ phantom_base_port: 8002
5
+ memory_limit: 150000
6
+ rails_root: ""
7
+ nginx_conf: ""
8
+ phantom_log_path: ""
9
+ memory_retries: 5
10
+ memory_check_interval: 5
11
+ processes_check_retries: 3
12
+ processes_check_interval: 15
@@ -0,0 +1,26 @@
1
+ upstream unicorn {
2
+ server 127.0.0.1:8001;
3
+ }
4
+
5
+ upstream phantomjs {
6
+ server 127.0.0.1:8002;
7
+ server 127.0.0.1:8004;
8
+ server 127.0.0.1:8005;
9
+ server 127.0.0.1:8006;
10
+ server 127.0.0.1:8007;
11
+ server 127.0.0.1:8008;
12
+ server 127.0.0.1:8009;
13
+ server 127.0.0.1:8010;
14
+ server 127.0.0.1:8011;
15
+ }
16
+
17
+ server {
18
+ server_name _
19
+
20
+ location / {
21
+ proxy_pass http://phantomjs;
22
+ }
23
+ blah
24
+ bli
25
+ omo
26
+ }
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require 'monitors/base'
3
+
4
+ module Monitors
5
+ describe Base do
6
+ context "perform_check not implemented" do
7
+ it "should raise NotImplementedError" do
8
+ expect {
9
+ Base.run
10
+ }.to raise_exception(NotImplementedError)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'monitors/memory'
3
+
4
+ module Monitors
5
+
6
+ describe Memory do
7
+
8
+ subject {Memory}
9
+
10
+ before do
11
+ Phantom::Collector.stub(:running_phantoms_shell_output).and_return(phantoms_ps_shell_output)
12
+ $cfg.memory_limit = 110000
13
+ $cfg.memory_retries = 3
14
+ end
15
+
16
+ describe :perform_check do
17
+ context "below memory_retries" do
18
+ it "should not restart processes" do
19
+ Phantom::Manager.should_not_receive(:restart)
20
+ ( $cfg.memory_retries - 1 ).times { subject.perform_check }
21
+ end
22
+ end
23
+
24
+ context "at memory_retries" do
25
+ it "should restart process" do
26
+ Phantom::Manager.should_receive(:restart).once
27
+ $cfg.memory_retries.times { subject.perform_check }
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'monitors/processes'
3
+
4
+ module Monitors
5
+ describe Processes do
6
+
7
+ subject {Processes}
8
+
9
+ describe :perform_check do
10
+
11
+ before do
12
+ ViolationsRecorders::Processes.reset
13
+ end
14
+
15
+ context "no missing ports" do
16
+ before do
17
+ Phantom::Collector.stub(:missing_ports).and_return([])
18
+ end
19
+
20
+ it "should not create instances" do
21
+ Phantom::Manager.should_not_receive(:start)
22
+ subject.perform_check
23
+ end
24
+ end
25
+
26
+ context "two missing ports" do
27
+
28
+ before do
29
+ @p1 = Phantom::Process.new
30
+ @p1.port = 8004
31
+ @p2 = Phantom::Process.new
32
+ @p2.port = 8005
33
+ Phantom::Collector.stub(:missing_ports).and_return([@p1, @p2].map(&:port))
34
+ end
35
+
36
+ it "should create two new instances" do
37
+ Phantom::Manager.should_receive(:start).twice
38
+ $cfg.processes_check_retries.times {subject.perform_check}
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'monitors/restart_listener'
3
+
4
+ module Monitors
5
+ describe RestartListener do
6
+
7
+ subject {RestartListener}
8
+
9
+ describe :respond_to_signal do
10
+ before do
11
+ subject.stub :sleep
12
+ Phantom::Collector.stub(:running_phantoms_shell_output).and_return(phantoms_ps_shell_output)
13
+ end
14
+
15
+ it "should restart each phantom" do
16
+ instances = Phantom::Collector.get_running_instances
17
+ Phantom::Collector.stub get_running_instances: instances
18
+
19
+ instances.each {|i| Phantom::Manager.should_receive(:restart).with(i).once }
20
+
21
+ subject.respond_to_signal
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+ require 'monitors/violations_recorders/base'
3
+
4
+ module Monitors
5
+ module ViolationsRecorders
6
+ describe Base do
7
+
8
+ subject {Base}
9
+
10
+ describe "abstract methods" do
11
+ [:retries_limit, :process_attr].each do |method|
12
+ it "should raise NotImplementedError on #{method}" do
13
+ expect {subject.send(method)}.to raise_exception(NotImplementedError)
14
+ end
15
+ end
16
+
17
+ it "should raise NotImplementedError on process_is_violating?" do
18
+ expect {subject.send(:process_is_violating?,nil)}.to raise_exception(NotImplementedError)
19
+ end
20
+ end
21
+
22
+ context "inheriting recorder" do
23
+
24
+ RETRIES = 3
25
+ PROCESS_ATTR = :attr
26
+ VALUE_ATTR = :val
27
+ VIOLATING_VALUE = 5
28
+
29
+ class DummyRecorder < Base
30
+ class << self
31
+ def retries_limit
32
+ RETRIES
33
+ end
34
+
35
+ def process_attr
36
+ PROCESS_ATTR
37
+ end
38
+
39
+ def process_is_violating?(process)
40
+ process.send(VALUE_ATTR) > VIOLATING_VALUE
41
+ end
42
+ end
43
+ end
44
+
45
+ describe :is_violating? do
46
+
47
+ before do
48
+ DummyRecorder.reset
49
+ @p = Object.new
50
+ @p.stub(PROCESS_ATTR).and_return(1)
51
+ end
52
+
53
+
54
+ context "non violating process" do
55
+
56
+ before do
57
+ @p.stub(VALUE_ATTR).and_return(VIOLATING_VALUE - 1)
58
+ end
59
+
60
+ it "should indicate not violating" do
61
+ RETRIES.times do
62
+ DummyRecorder.is_violating?(@p).should be_false
63
+ end
64
+ end
65
+ end
66
+
67
+ context "violating process" do
68
+
69
+ before do
70
+ @p.stub(VALUE_ATTR).and_return(VIOLATING_VALUE + 1)
71
+ end
72
+
73
+ context "below retry limit" do
74
+
75
+ it "should indicate not violating" do
76
+ (RETRIES-1).times do
77
+ DummyRecorder.is_violating?(@p).should be_false
78
+ end
79
+ end
80
+ end
81
+
82
+ context "equals retry limit" do
83
+
84
+ it "should indicate violating" do
85
+ (RETRIES-1).times do
86
+ DummyRecorder.is_violating?(@p)
87
+ end
88
+ DummyRecorder.is_violating?(@p).should be_true
89
+ end
90
+
91
+ it "should reset count after violation" do
92
+ (RETRIES-1).times do
93
+ DummyRecorder.is_violating?(@p)
94
+ end
95
+ DummyRecorder.is_violating?(@p).should be_true
96
+ (RETRIES-1).times do
97
+ DummyRecorder.is_violating?(@p).should be_false
98
+ end
99
+ DummyRecorder.is_violating?(@p).should be_true
100
+ end
101
+ end
102
+
103
+ describe "count after check passed" do
104
+ it "should be reset" do
105
+ (RETRIES-1).times do
106
+ DummyRecorder.is_violating?(@p)
107
+ end
108
+
109
+ @p.stub(VALUE_ATTR).and_return(VIOLATING_VALUE-1)
110
+ DummyRecorder.is_violating?(@p)
111
+
112
+ @p.stub(VALUE_ATTR).and_return(VIOLATING_VALUE+1)
113
+
114
+ (RETRIES-1).times do
115
+ DummyRecorder.is_violating?(@p).should be_false
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+ require 'monitors/violations_recorders/memory'
3
+
4
+ module Monitors
5
+ module ViolationsRecorders
6
+ describe Memory do
7
+
8
+ subject {Memory}
9
+
10
+ before :each do
11
+ subject.reset
12
+ end
13
+
14
+ describe :is_violating? do
15
+
16
+ let(:violating_memory) { $cfg.memory_limit + 10}
17
+ let(:valid_memory) { $cfg.memory_limit - 10}
18
+
19
+ context "violating process" do
20
+
21
+ before do
22
+ @p = generate_process
23
+ @p.memory_usage = violating_memory
24
+ end
25
+
26
+ it "should return true" do
27
+ ($cfg.memory_retries-1).times do
28
+ subject.is_violating?(@p).should be_false
29
+ end
30
+
31
+ subject.is_violating?(@p).should be_true
32
+ end
33
+ end
34
+
35
+ context "not violating process" do
36
+
37
+ before do
38
+ @p = generate_process
39
+ @p.memory_usage = valid_memory
40
+ end
41
+
42
+ it "should return false" do
43
+ $cfg.memory_retries.times do
44
+ subject.is_violating?(@p).should be_false
45
+ end
46
+ end
47
+
48
+ describe "violations reset" do
49
+
50
+ it "reset violations if valid memory detected one" do
51
+ @p.memory_usage = violating_memory
52
+ ($cfg.memory_retries-1).times do
53
+ subject.is_violating?(@p).should be_false
54
+ end
55
+
56
+ @p.memory_usage = valid_memory
57
+
58
+ subject.is_violating?(@p).should be_false
59
+
60
+ @p.memory_usage = violating_memory
61
+ ($cfg.memory_retries-1).times do
62
+ subject.is_violating?(@p).should be_false
63
+ end
64
+ subject.is_violating?(@p).should be_true
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'monitors/violations_recorders/processes'
3
+
4
+ module Monitors
5
+ module ViolationsRecorders
6
+
7
+ describe Processes do
8
+
9
+ subject {Processes}
10
+
11
+ before :each do
12
+ subject.reset
13
+ end
14
+
15
+ describe :is_violating? do
16
+
17
+ before do
18
+ Phantom::Collector.stub(:missing_ports).and_return([8004])
19
+ @p = Phantom::Process.new
20
+ @p.port = 8004
21
+ $cfg.processes_check_retries = 3
22
+ end
23
+
24
+ context "below retries limit" do
25
+ let(:retries) {$cfg.processes_check_retries - 1}
26
+
27
+ it "should return false" do
28
+ retries.times do
29
+ subject.is_violating?(@p).should be_false
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ context "equals retries limit" do
36
+
37
+ let(:retries) {$cfg.processes_check_retries}
38
+
39
+ it "should return false" do
40
+ (retries-1).times do
41
+ subject.is_violating?(@p).should be_false
42
+ end
43
+ subject.is_violating?(@p).should be_true
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'nginx/manager'
3
+
4
+ INITIAL_CONF=<<-CONF
5
+ upstream unicorn {
6
+ server 127.0.0.1:8001;
7
+ }
8
+
9
+ upstream phantomjs {
10
+ server 127.0.0.1:8002;
11
+ server 127.0.0.1:8003;
12
+ server 127.0.0.1:8004;
13
+ server 127.0.0.1:8005;
14
+ server 127.0.0.1:8006;
15
+ server 127.0.0.1:8007;
16
+ server 127.0.0.1:8008;
17
+ server 127.0.0.1:8009;
18
+ server 127.0.0.1:8010;
19
+ server 127.0.0.1:8011;
20
+ }
21
+
22
+ server {
23
+ server_name _
24
+
25
+ location / {
26
+ proxy_pass http://phantomjs;
27
+ }
28
+ blah
29
+ bli
30
+ omo
31
+ }
32
+ CONF
33
+
34
+ module Nginx
35
+ describe Manager do
36
+ subject {Manager}
37
+
38
+ def port_defined?(port)
39
+ File.readlines($cfg.nginx_conf).grep(/#{port}/).size > 0
40
+ end
41
+
42
+ before do
43
+ File.open($cfg.nginx_conf, "w") do |f|
44
+ f.puts(INITIAL_CONF)
45
+ end
46
+
47
+ subject.stub :reload_nginx
48
+ end
49
+
50
+
51
+
52
+ describe :remove do
53
+ context "existing port" do
54
+ it "should be removed" do
55
+ expect {
56
+ subject.remove(8003)
57
+ }.to change{port_defined?(8003)}.from(true).to(false)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe :add do
63
+ context "not - existing port" do
64
+ it "should be added" do
65
+ expect {
66
+ subject.add(8020)
67
+ }.to change{port_defined?(8020)}.from(false).to(true)
68
+ end
69
+ end
70
+
71
+ context "existing port" do
72
+ it "should not be added" do
73
+ expect {
74
+ subject.add(8003)
75
+ }.not_to change{port_defined?(8003)}
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'phantom/collector'
3
+
4
+ module Phantom
5
+ describe Collector do
6
+ subject {Collector}
7
+
8
+ before do
9
+ $cfg.phantom_processes_number = 5
10
+ $cfg.phantom_base_port = 8002
11
+ subject.stub(:running_phantoms_shell_output).and_return(phantoms_ps_shell_output)
12
+ end
13
+
14
+ describe :get_running_instances do
15
+
16
+ it "should generate correct instances" do
17
+ generated_phantoms = subject.get_running_instances
18
+
19
+ generated_phantoms.size.should eq 3
20
+ generated_phantoms.all? {|p| p.instance_of? Phantom::Process}.should be_true
21
+
22
+ phantoms_data.each_with_index do |data, i|
23
+ data.each do |attr, val|
24
+ generated_phantoms[i].send(attr).should eq val
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ describe :missing_ports do
32
+ it "should return required ports which phantoms do not run at" do
33
+ subject.missing_ports.should =~ [8004, 8005]
34
+ end
35
+ end
36
+
37
+ describe :required_ports do
38
+ it "should return all ports from configuration as array" do
39
+ subject.required_ports.should =~ [8002, 8003, 8004, 8005, 8006]
40
+ end
41
+ end
42
+
43
+ describe :on_port do
44
+ before do
45
+ subject.stub(:running_phantoms_shell_output).and_return(phantoms_ps_shell_output)
46
+ @data = phantoms_data.first
47
+ end
48
+
49
+ it "should return right process" do
50
+ p = subject.on_port(@data[:port])
51
+ @data.each do |attr, val|
52
+ p.send(attr).should eq val
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'phantom/manager'
3
+
4
+ module Phantom
5
+ describe Manager do
6
+
7
+ subject {Manager}
8
+
9
+ describe :start do
10
+
11
+ let(:p) {generate_process}
12
+
13
+ it "should start new phantom process" do
14
+ p.should_receive :start
15
+ subject.start(p)
16
+ end
17
+
18
+ it "should add port to nginx conf" do
19
+ Nginx::Manager.should_receive(:add).with(p.port).once
20
+ subject.start(p)
21
+ end
22
+ end
23
+
24
+ end
25
+ end