phantom-manager 0.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 (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