cognizant 0.0.2 → 0.0.3

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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +17 -0
  4. data/Gemfile +4 -1
  5. data/{LICENSE → License.md} +4 -2
  6. data/Rakefile +5 -0
  7. data/Readme.md +95 -0
  8. data/bin/cognizant +76 -122
  9. data/bin/cognizantd +28 -61
  10. data/cognizant.gemspec +8 -4
  11. data/examples/apps/redis-server.cz +42 -0
  12. data/examples/apps/redis-server.yml +29 -0
  13. data/examples/apps/redis-server_dsl.cz +54 -0
  14. data/examples/apps/resque.cz +17 -0
  15. data/examples/apps/thin.cz +32 -0
  16. data/examples/apps/thin.yml +48 -0
  17. data/examples/cognizantd.yml +18 -47
  18. data/features/child_process.feature +62 -0
  19. data/features/commands.feature +65 -0
  20. data/features/cpu_usage.feature +45 -0
  21. data/features/daemon.feature +12 -0
  22. data/features/flapping.feature +39 -0
  23. data/features/memory_usage.feature +45 -0
  24. data/features/shell.feature +30 -0
  25. data/features/step_definitions/common_steps.rb +14 -0
  26. data/features/step_definitions/daemon_steps.rb +25 -0
  27. data/features/step_definitions/shell_steps.rb +96 -0
  28. data/features/support/env.rb +54 -0
  29. data/lib/cognizant.rb +1 -5
  30. data/lib/cognizant/application.rb +122 -0
  31. data/lib/cognizant/application/dsl_proxy.rb +23 -0
  32. data/lib/cognizant/client.rb +61 -0
  33. data/lib/cognizant/commands.rb +164 -0
  34. data/lib/cognizant/commands/actions.rb +30 -0
  35. data/lib/cognizant/commands/help.rb +10 -0
  36. data/lib/cognizant/commands/load.rb +10 -0
  37. data/lib/cognizant/commands/shutdown.rb +7 -0
  38. data/lib/cognizant/commands/status.rb +11 -0
  39. data/lib/cognizant/commands/use.rb +15 -0
  40. data/lib/cognizant/controller.rb +17 -0
  41. data/lib/cognizant/daemon.rb +279 -0
  42. data/lib/cognizant/interface.rb +17 -0
  43. data/lib/cognizant/log.rb +25 -0
  44. data/lib/cognizant/process.rb +138 -94
  45. data/lib/cognizant/process/actions.rb +30 -41
  46. data/lib/cognizant/process/actions/restart.rb +73 -17
  47. data/lib/cognizant/process/actions/start.rb +35 -12
  48. data/lib/cognizant/process/actions/stop.rb +38 -17
  49. data/lib/cognizant/process/attributes.rb +41 -10
  50. data/lib/cognizant/process/children.rb +36 -0
  51. data/lib/cognizant/process/{condition_check.rb → condition_delegate.rb} +11 -13
  52. data/lib/cognizant/process/conditions.rb +7 -4
  53. data/lib/cognizant/process/conditions/cpu_usage.rb +5 -6
  54. data/lib/cognizant/process/conditions/memory_usage.rb +2 -6
  55. data/lib/cognizant/process/dsl_proxy.rb +23 -0
  56. data/lib/cognizant/process/execution.rb +16 -9
  57. data/lib/cognizant/process/pid.rb +16 -6
  58. data/lib/cognizant/process/status.rb +14 -2
  59. data/lib/cognizant/process/trigger_delegate.rb +57 -0
  60. data/lib/cognizant/process/triggers.rb +19 -0
  61. data/lib/cognizant/process/triggers/flapping.rb +68 -0
  62. data/lib/cognizant/process/triggers/transition.rb +22 -0
  63. data/lib/cognizant/process/triggers/trigger.rb +15 -0
  64. data/lib/cognizant/shell.rb +142 -0
  65. data/lib/cognizant/system.rb +16 -0
  66. data/lib/cognizant/system/ps.rb +1 -1
  67. data/lib/cognizant/system/signal.rb +2 -2
  68. data/lib/cognizant/util/dsl_proxy_methods_handler.rb +25 -0
  69. data/lib/cognizant/util/fixnum_percent.rb +5 -0
  70. data/lib/cognizant/util/transform_hash_keys.rb +33 -0
  71. data/lib/cognizant/validations.rb +142 -142
  72. data/lib/cognizant/version.rb +1 -1
  73. metadata +131 -71
  74. data/README.md +0 -221
  75. data/examples/redis-server.rb +0 -28
  76. data/examples/resque.rb +0 -10
  77. data/images/logo-small.png +0 -0
  78. data/images/logo.png +0 -0
  79. data/images/logo.pxm +0 -0
  80. data/lib/cognizant/logging.rb +0 -33
  81. data/lib/cognizant/process/conditions/flapping.rb +0 -57
  82. data/lib/cognizant/process/conditions/trigger_condition.rb +0 -52
  83. data/lib/cognizant/server.rb +0 -14
  84. data/lib/cognizant/server/commands.rb +0 -80
  85. data/lib/cognizant/server/daemon.rb +0 -277
  86. data/lib/cognizant/server/interface.rb +0 -86
  87. data/lib/cognizant/util/symbolize_hash_keys.rb +0 -19
@@ -0,0 +1,45 @@
1
+ Feature: CPU Usage Condition
2
+
3
+ Here I want to test proper handling of cpu_usage condition.
4
+
5
+ Background:
6
+ Given a file named "consume_cpu.rb" with:
7
+ """ruby
8
+ require 'timeout'
9
+ $0 = File.basename(__FILE__) # Useful identification when debugging.
10
+ data = ''
11
+ Timeout::timeout(60) do
12
+ loop do
13
+ data += '*' * 100
14
+ end
15
+ end
16
+ data = nil
17
+ """
18
+ Given a file named "monitor.cz" with:
19
+ """ruby
20
+ Cognizant.application 'cpu_usage_app' do
21
+ pids_dir './cognizant/pids/'
22
+ logs_dir './cognizant/logs/'
23
+ monitor 'consume_cpu' do
24
+ autostart false
25
+ daemonize!
26
+ start_command 'ruby ./consume_cpu.rb'
27
+ check :cpu_usage, :every => 2.seconds, :above => 60.percent, :times => [2, 3], :do => :stop
28
+ end
29
+ end
30
+ """
31
+
32
+ @daemon
33
+ @shell
34
+ Scenario: Check CPU usage of a process that consumes a lot of CPU
35
+ Given the daemon is running
36
+ And the shell is running
37
+
38
+ When I run "load monitor.cz" successfully in the shell
39
+ And I run "use cpu_usage_app" successfully in the shell
40
+ Then the status of "consume_cpu" should be "stopped"
41
+
42
+ When I run "start consume_cpu" successfully in the shell
43
+ Then the status of "consume_cpu" should be "running"
44
+
45
+ And the status of "consume_cpu" should be "stopped"
@@ -0,0 +1,12 @@
1
+ Feature: Daemon
2
+
3
+ The monitoring daemon needs to run in the background and handle process
4
+ management and provide a command socket for interaction.
5
+
6
+ @daemon
7
+ Scenario: Start and stop the daemon
8
+ Given the daemon is running
9
+ Then a process named "cognizantd" should be running
10
+
11
+ When the daemon is stopped
12
+ Then a process named "cognizantd" should not be running
@@ -0,0 +1,39 @@
1
+ Feature: Flapping Check
2
+
3
+ Here I want to test proper handling of flapping check.
4
+
5
+ Background:
6
+ Given a file named "monitor.cz" with:
7
+ """ruby
8
+ Cognizant.application 'sleep_app' do
9
+ pids_dir './cognizant/pids/'
10
+ logs_dir './cognizant/logs/'
11
+ monitor 'sleep_process' do
12
+ autostart false
13
+ daemonize!
14
+ start_command 'sleep 8'
15
+ check :flapping, times: 3, within: 1.minute, retry_after: 15.seconds, retries: 0
16
+ end
17
+ end
18
+ """
19
+
20
+ @daemon
21
+ @shell
22
+ Scenario: Handle a process that restarts every 3 seconds
23
+ Given the daemon is running
24
+ And the shell is running
25
+
26
+ When I run "load monitor.cz" successfully in the shell
27
+ And I run "use sleep_app" successfully in the shell
28
+ Then the status of "sleep_process" should be "stopped"
29
+
30
+ When I run "start sleep_process" successfully in the shell
31
+ # Give 3 seconds grace time for process states to be realized by daemon.
32
+ Then the status of "sleep_process" should be "running" for 5 seconds
33
+ # TODO: The process goes through another stopped -> running cycle
34
+ # before being unmonitored. Add step to handle this.
35
+ Then the status of "sleep_process" should be "unmonitored" for 12 seconds
36
+ Then the status of "sleep_process" should be "running" for 5 seconds
37
+
38
+ When I run "stop sleep_process" successfully in the shell
39
+ Then the status of "sleep_process" should be "stopped"
@@ -0,0 +1,45 @@
1
+ Feature: Memory Usage Condition
2
+
3
+ Here I want to test proper handling of memory_usage condition.
4
+
5
+ Background:
6
+ Given a file named "consume_memory.rb" with:
7
+ """ruby
8
+ require 'timeout'
9
+ $0 = File.basename(__FILE__) # Useful identification when debugging.
10
+ data = ''
11
+ Timeout::timeout(60) do
12
+ loop do
13
+ data += '*' * 100
14
+ end
15
+ end
16
+ data = nil
17
+ """
18
+ Given a file named "monitor.cz" with:
19
+ """ruby
20
+ Cognizant.application 'memory_usage_app' do
21
+ pids_dir './cognizant/pids/'
22
+ logs_dir './cognizant/logs/'
23
+ monitor 'consume_memory' do
24
+ autostart false
25
+ daemonize!
26
+ start_command 'ruby ./consume_memory.rb'
27
+ check :memory_usage, :every => 2.seconds, :above => 10.megabytes, :times => [2, 3], :do => :stop
28
+ end
29
+ end
30
+ """
31
+
32
+ @daemon
33
+ @shell
34
+ Scenario: Check memory usage of a process that consumes a lot of memory
35
+ Given the daemon is running
36
+ And the shell is running
37
+
38
+ When I run "load monitor.cz" successfully in the shell
39
+ And I run "use memory_usage_app" successfully in the shell
40
+ Then the status of "consume_memory" should be "stopped"
41
+
42
+ When I run "start consume_memory" successfully in the shell
43
+ Then the status of "consume_memory" should be "running"
44
+
45
+ And the status of "consume_memory" should be "stopped"
@@ -0,0 +1,30 @@
1
+ Feature: Shell
2
+
3
+ Cognizant shell provides an interactive interface to pass commands to
4
+ the monitoring daemon.
5
+
6
+ @shell
7
+ Scenario: Shell should need a deamon to work
8
+ Then the shell is not running
9
+
10
+ @daemon
11
+ @shell
12
+ Scenario: Shell should realize that the daemon has gone away
13
+ Given the daemon is running
14
+ And the shell is running
15
+
16
+ When I run "help" in the shell
17
+ Then I should see "You can run the following commands" in the shell
18
+
19
+ When the daemon is stopped
20
+ And I run "help" in the shell
21
+ Then I should see "Error communicating with cognizantd" in the shell
22
+
23
+ @daemon
24
+ @shell
25
+ Scenario: Shell should prompt about an unrecognized command
26
+ Given the daemon is running
27
+ And the shell is running
28
+
29
+ When I run "test" in the shell
30
+ Then I should see "Unrecognized command" in the shell
@@ -0,0 +1,14 @@
1
+ require "aruba/api"
2
+
3
+ # When /^I run (cognizant) `([^`]*)`$/ do |_, cmd|
4
+ # cmd = "cognizant #{cmd} --sockfile ./example"
5
+ # step "I run `#{cmd}`"
6
+ # end
7
+
8
+ When /^a process named "([^"]*)" (?:should be|is) running$/ do |name|
9
+ `ps -eo command | grep ^#{name}`.should include(name)
10
+ end
11
+
12
+ When /^a process named "([^"]*)" (?:should not be|is not) running$/ do |name|
13
+ `ps -eo command | grep ^#{name}`.should_not include(name)
14
+ end
@@ -0,0 +1,25 @@
1
+ When /^the daemon is running$/ do
2
+ step %Q{I see "Cognizant daemon running successfully." on the daemon terminal}
3
+ end
4
+
5
+ When /^the daemon is stopped$/ do
6
+ Process.kill("TERM", @daemon_pipe.pid)
7
+ sleep 1
8
+ Process.kill("KILL", @daemon_pipe.pid)
9
+ end
10
+
11
+ When /^I (?:should )?see "([^"]*)" on the daemon terminal$/ do |string|
12
+ output = ""
13
+
14
+ begin
15
+ Timeout::timeout(30) do
16
+ while not output =~ /#{string}/
17
+ output += @daemon_pipe.readpartial(1)
18
+ end
19
+ end
20
+ rescue Timeout::Error, EOFError
21
+ nil
22
+ end
23
+
24
+ output.should include(string)
25
+ end
@@ -0,0 +1,96 @@
1
+ When /^the shell is running$/ do
2
+ step %Q{I see "You are speaking to the Cognizant Monitoring Daemon." in the shell}
3
+ end
4
+
5
+ When /^the shell is not running$/ do
6
+ step %Q{I see "Could not connect to Cognizant daemon process" in the shell}
7
+ end
8
+
9
+ When /^I run "([^"]*)" in the shell$/ do |string|
10
+ @shell_pipe.puts(string)
11
+ sleep 0.5
12
+ end
13
+
14
+ When /^I run "([^"]*)" successfully in the shell$/ do |cmd|
15
+ step %Q{I run "#{cmd}" in the shell}
16
+ step %Q{I should see "OK" in the shell}
17
+ end
18
+
19
+ # Once we have the required status, it should stay the same in the given next n seconds.
20
+ When /^the (status) of "([^"]*)" (?:should be|is) "([^"]*)" for (\d+) seconds$/ do |_, name, status, timeframe|
21
+ step %Q{the status of "#{name}" should be "#{status}"}
22
+
23
+ output = ""
24
+
25
+ begin
26
+ Timeout::timeout(timeframe.to_i) do
27
+ catch :stop do
28
+ loop do
29
+ buffer = ""
30
+ @shell_pipe.puts("status #{name}")
31
+ while not buffer =~ /\s#{name}\s.*since/
32
+ begin
33
+ buffer << @shell_pipe.readpartial(1)
34
+ rescue EOFError
35
+ retry
36
+ end
37
+ end
38
+ # We need just the line where it tells name and status.
39
+ output = buffer.split("\n").last
40
+ if buffer =~ /\s#{status}\s/
41
+ # Take rest before next check.
42
+ sleep 0.2
43
+ else
44
+ # Otherwise stop checking and report output in error.
45
+ throw :stop
46
+ end
47
+ end
48
+ end
49
+ end
50
+ rescue Timeout::Error
51
+ # We spent the time gracefully as required.
52
+ nil
53
+ end
54
+
55
+ # Successful if we reach here.
56
+ output.should include(status)
57
+ end
58
+
59
+ When /^the (status) of "([^"]*)" (?:should be|is) "([^"]*)"$/ do |_, name, status|
60
+ output = ""
61
+ time_step = 0.25
62
+ time_spent = 0
63
+ timeout = 30
64
+ begin
65
+ Timeout::timeout(time_step) do
66
+ time_spent += time_step
67
+ @shell_pipe.puts("status #{name}")
68
+ while not output =~ /\s#{status}\s/
69
+ buffer = @shell_pipe.readpartial(1)
70
+ output += buffer if buffer
71
+ end
72
+ end
73
+ rescue Timeout::Error, EOFError
74
+ retry unless time_spent > timeout
75
+ end
76
+
77
+ output.should include(status)
78
+ end
79
+
80
+ When /^I (?:should )?see "([^"]*)" in the shell$/ do |string|
81
+ sleep 0.5
82
+ output = ""
83
+
84
+ begin
85
+ Timeout::timeout(30) do
86
+ while not output =~ /#{string}/
87
+ buffer = @shell_pipe.readpartial(1)
88
+ output += buffer if buffer
89
+ end
90
+ end
91
+ rescue Timeout::Error, EOFError
92
+ nil
93
+ end
94
+
95
+ output.should include(string)
96
+ end
@@ -0,0 +1,54 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require "aruba/cucumber"
5
+ require "aruba/api"
6
+ require "open3"
7
+ require "fileutils"
8
+
9
+ Before "@daemon" do
10
+ in_current_dir do
11
+ FileUtils.mkdir("cognizant")
12
+ File.open("cognizantd.yml", "w") do |f|
13
+ f.write <<-heredoc
14
+ daemonize: false
15
+ sockfile: ./cognizant/cognizantd.sock
16
+ pidfile: ./cognizant/cognizantd.pid
17
+ logfile: ./cognizant/cognizantd.log
18
+ heredoc
19
+ end
20
+
21
+ cmd = "cognizantd cognizantd.yml --trace"
22
+ Aruba.config.hooks.execute(:before_cmd, self, cmd)
23
+ announcer.dir(Dir.pwd)
24
+ announcer.cmd(cmd)
25
+
26
+ @daemon_pipe = IO.popen(cmd, "r")
27
+ sleep 2
28
+ end
29
+ end
30
+
31
+ After "@daemon" do
32
+ ::Process.kill("TERM", @daemon_pipe.pid)
33
+ sleep 0.5
34
+ ::Process.kill("KILL", @daemon_pipe.pid)
35
+ end
36
+
37
+ Before "@shell" do
38
+ in_current_dir do
39
+ cmd = "cognizant shell --sockfile ./cognizant/cognizantd.sock"
40
+
41
+ Aruba.config.hooks.execute(:before_cmd, self, cmd)
42
+ announcer.dir(Dir.pwd)
43
+ announcer.cmd(cmd)
44
+
45
+ @shell_pipe = IO.popen(cmd, "w+")
46
+ sleep 1
47
+ end
48
+ end
49
+
50
+ After "@shell" do
51
+ ::Process.kill("TERM", @shell_pipe.pid)
52
+ sleep 0.5
53
+ ::Process.kill("KILL", @shell_pipe.pid)
54
+ end
@@ -1,10 +1,6 @@
1
1
  require "active_support/core_ext/numeric"
2
2
  require "active_support/duration"
3
-
4
- require "cognizant/server"
3
+ require "cognizant/util/fixnum_percent"
5
4
 
6
5
  module Cognizant
7
- def self.monitor(process_name = nil, attributes = {}, &block)
8
- Cognizant::Server.daemon.monitor(process_name, attributes, &block)
9
- end
10
6
  end
@@ -0,0 +1,122 @@
1
+ require "eventmachine"
2
+
3
+ require "cognizant/controller"
4
+ require "cognizant/interface"
5
+ require "cognizant/system"
6
+ require "cognizant/application/dsl_proxy"
7
+
8
+ module Cognizant
9
+ def self.application(application_name = nil, attributes = {}, &block)
10
+ Cognizant::Controller.daemon.create_application(application_name, attributes, &block)
11
+ end
12
+
13
+ class Application
14
+ attr_accessor :name, :pids_dir, :logs_dir
15
+ attr_accessor :processes
16
+
17
+ def initialize(name = nil, options = {}, &block)
18
+ self.setup({ name: name }.merge(options), &block)
19
+ end
20
+
21
+ def setup(options = {}, &block)
22
+ self.reset!
23
+
24
+ set_attributes(options)
25
+
26
+ handle_initialize_block(&block) if block
27
+
28
+ raise "Application name is missing. Aborting." unless self.name
29
+ Log[self].info "Loading application #{self.name}..."
30
+
31
+ raise "Application processes are missing. Aborting." unless self.processes.keys.size > 0
32
+
33
+ self.setup_directories
34
+ end
35
+
36
+ def handle_initialize_block(&block)
37
+ if block.arity == 0
38
+ dsl_proxy = Cognizant::Application::DSLProxy.new(self, &block)
39
+ attributes = dsl_proxy.attributes
40
+ set_attributes(attributes)
41
+ else
42
+ instance_exec(self, &block)
43
+ end
44
+ end
45
+
46
+ def set_attributes(attributes)
47
+ procs = {}
48
+ procs = attributes.delete(:processes) if attributes.has_key?(:processes)
49
+
50
+ mons = {}
51
+ mons = attributes.delete(:monitor) if attributes.has_key?(:monitor)
52
+
53
+ # Attributes.
54
+ attributes.each do |key, value|
55
+ if self.respond_to?("#{key}=")
56
+ value = attributes.delete(key)
57
+ self.send("#{key}=", value)
58
+ end
59
+ end
60
+
61
+ # Processes.
62
+ procs.each do |key, value|
63
+ process(key, value)
64
+ end
65
+
66
+ # Automatically monitor these.
67
+ mons.each do |key, value|
68
+ monitor(key, value)
69
+ end
70
+ end
71
+
72
+ def monitor(process_name = nil, attributes = {}, &block)
73
+ proc = create_process(process_name, attributes, &block)
74
+ proc.monitor
75
+ proc
76
+ end
77
+
78
+ def process(process_name = nil, attributes = {}, &block)
79
+ create_process(process_name, attributes, &block)
80
+ end
81
+
82
+ def reset!
83
+ self.name = nil
84
+ self.pids_dir = nil
85
+ self.logs_dir = nil
86
+ self.processes.values.each(&:reset!) if self.processes.is_a?(Hash)
87
+ self.processes = {}
88
+ end
89
+ alias :shutdown! :reset!
90
+
91
+ def pids_dir
92
+ if @pids_dir and expanded_path = File.expand_path(@pids_dir)
93
+ expanded_path
94
+ else
95
+ "/var/run/cognizant/#{self.name}/pids/"
96
+ end
97
+ end
98
+
99
+ def logs_dir
100
+ if @logs_dir and expanded_path = File.expand_path(@logs_dir)
101
+ expanded_path
102
+ else
103
+ "/var/log/cognizant/#{self.name}/logs/"
104
+ end
105
+ end
106
+
107
+ def create_process(process_name, options = {}, &block)
108
+ p = Cognizant::Process.new(process_name, options, &block)
109
+ p.instance_variable_set(:@application, self)
110
+ self.processes[p.name.to_sym] = p
111
+ p
112
+ end
113
+
114
+ def tick
115
+ self.processes.values.each(&:tick)
116
+ end
117
+
118
+ def setup_directories
119
+ Cognizant::System.mkdir(self.pids_dir, self.logs_dir)
120
+ end
121
+ end
122
+ end