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
@@ -33,26 +33,35 @@ module Cognizant
33
33
 
34
34
  # The grace time period in seconds for the process to start within.
35
35
  # Covers the time period for the input and start command. After the
36
- # timeout is over, the process the considered not started and it
36
+ # timeout is over, the process is considered as not started and it
37
37
  # re-enters the auto start lifecycle based on conditions.
38
- # @return [String] Defaults to 10
38
+ # @return [String] Defaults to 30
39
39
  attr_accessor :start_timeout
40
40
 
41
41
  # The command to run after the process is started.
42
42
  # @return [String] Defaults to nil
43
43
  attr_accessor :start_after_command
44
44
 
45
+ def reset_attributes!
46
+ self.start_env = {}
47
+ self.start_before_command = nil
48
+ self.start_command = nil
49
+ self.start_with_input = nil
50
+ self.start_with_input_file = nil
51
+ self.start_with_input_command = nil
52
+ self.start_timeout = 30
53
+ self.start_after_command = nil
54
+ super
55
+ end
56
+
45
57
  def start_process
46
- result_handler = Proc.new do |result|
47
- if result.respond_to?(:succeeded?) and result.succeeded?
48
- write_pid(result.pid) if result.pid != 0
49
- end
50
- end
51
- execute_action(
52
- result_handler,
58
+ # We skip so that we're not reinformed about the required transition by the tick.
59
+ skip_ticks_for(self.start_timeout)
60
+
61
+ options = {
53
62
  name: self.name,
54
- daemonize: self.daemonize || true,
55
- env: (self.env || {}).merge(self.start_env || {}),
63
+ daemonize: self.daemonize,
64
+ env: self.env.merge(self.start_env),
56
65
  logfile: self.logfile,
57
66
  errfile: self.errfile,
58
67
  before: self.start_before_command,
@@ -62,7 +71,21 @@ module Cognizant
62
71
  input_command: self.start_with_input_command,
63
72
  after: self.start_after_command,
64
73
  timeout: self.start_timeout
65
- )
74
+ }
75
+ handle_action('_start_result_handler', options)
76
+ end
77
+
78
+ # @private
79
+ def _start_result_handler(result, time_left = 0)
80
+ if result.respond_to?(:succeeded?) and result.succeeded?
81
+ write_pid(result.pid) if self.daemonize and result.pid != 0
82
+ end
83
+
84
+ # Reset cached pid to read from file or command.
85
+ @process_pid = nil
86
+
87
+ # Rollback the pending skips.
88
+ skip_ticks_for(-time_left) if time_left > 0
66
89
  end
67
90
  end
68
91
  end
@@ -18,10 +18,10 @@ module Cognizant
18
18
 
19
19
  # The signals to pass to the process one by one attempting to stop it.
20
20
  # Each signal is passed within the timeout period over equally
21
- # distributed intervals (min. 2 seconds). Override with signals without
21
+ # divided intervals (min. 2 seconds). Override with signals without
22
22
  # "KILL" to never force kill the process.
23
23
  # e.g. ["TERM", "INT"]
24
- # @return [Array] Defaults to ["TERM", "INT", "KILL"]
24
+ # @return [Array] Defaults to ["QUIT", "TERM", "INT"]
25
25
  attr_accessor :stop_signals
26
26
 
27
27
  # The grace time period in seconds for the process to stop within.
@@ -29,29 +29,50 @@ module Cognizant
29
29
  # timeout is over, the process is checked for running status and if
30
30
  # not stopped, it re-enters the auto start lifecycle based on
31
31
  # conditions.
32
- # @return [String] Defaults to 10
32
+ # @return [String] Defaults to 30
33
33
  attr_accessor :stop_timeout
34
34
 
35
35
  # The command to run after the process is stopped.
36
36
  # @return [String] Defaults to nil
37
37
  attr_accessor :stop_after_command
38
38
 
39
+ def reset_attributes!
40
+ self.stop_env = {}
41
+ self.stop_before_command = nil
42
+ self.stop_command = nil
43
+ self.stop_signals = ["QUIT", "TERM", "INT"]
44
+ self.stop_timeout = 30
45
+ self.stop_after_command = nil
46
+ super
47
+ end
48
+
39
49
  def stop_process
40
- result_handler = Proc.new do |result|
41
- # If it is a boolean and value is true OR if it's an execution result and it succeeded.
42
- if (!!result == result and result) or (result.respond_to?(:succeeded?) and result.succeeded?)
43
- unlink_pid unless pid_running?
44
- end
50
+ # We skip so that we're not reinformed about the required transition by the tick.
51
+ skip_ticks_for(self.stop_timeout)
52
+
53
+ options = {
54
+ env: self.env.merge(self.stop_env),
55
+ before: self.stop_before_command,
56
+ command: self.stop_command,
57
+ signals: self.stop_signals,
58
+ after: self.stop_after_command,
59
+ timeout: self.stop_timeout
60
+ }
61
+ handle_action('_stop_result_handler', options)
62
+ end
63
+
64
+ # @private
65
+ def _stop_result_handler(result, time_left = 0)
66
+ # If it is a boolean and value is true OR if it's an execution result and it succeeded.
67
+ if (!!result == result and result) or (result.respond_to?(:succeeded?) and result.succeeded?)
68
+ unlink_pid if not pid_running? and self.daemonize
45
69
  end
46
- execute_action(
47
- result_handler,
48
- env: (self.env || {}).merge(self.stop_env || {}),
49
- before: self.stop_before_command,
50
- command: self.stop_command,
51
- signals: self.stop_signals,
52
- after: self.stop_after_command,
53
- timeout: self.stop_timeout
54
- )
70
+
71
+ # Reset cached pid to read from file or command.
72
+ @process_pid = nil
73
+
74
+ # Rollback the pending skips.
75
+ skip_ticks_for(-time_left) if time_left > 0
55
76
  end
56
77
  end
57
78
  end
@@ -2,10 +2,12 @@ module Cognizant
2
2
  class Process
3
3
  module Attributes
4
4
  # Unique name for the process.
5
+ # Note: For child processes, this value is set automatically.
5
6
  # @return [String]
6
7
  attr_accessor :name
7
8
 
8
9
  # Group classification for the process.
10
+ # Note: This is not system process group. See `gid` attribute instead.
9
11
  # @return [String] Defaults to nil
10
12
  attr_accessor :group
11
13
 
@@ -16,11 +18,23 @@ module Cognizant
16
18
  # @return [true, false] Defaults to true
17
19
  attr_accessor :daemonize
18
20
 
19
- # Whether or not to auto start the process on initial run. Afterwards,
20
- # this attribute is overwritten by stop or restart request.
21
+ # Whether or not to auto start the process when beginning monitoring.
22
+ # Afterwards, auto start is automatically managed based on user initiated
23
+ # stop or restart requests.
24
+ # Note: For child processes, this value is automatically set to false.
21
25
  # @return [true, false] Defaults to true
22
26
  attr_accessor :autostart
23
27
 
28
+ # The command to check the running status of the process with. The exit
29
+ # status of the command is used to determine the status.
30
+ # e.g. "/usr/bin/redis-cli PING"
31
+ # @return [String] Defaults to nil
32
+ attr_accessor :ping_command
33
+
34
+ # The command that returns the pid of the process.
35
+ # @return [String] Defaults to nil
36
+ attr_accessor :pid_command
37
+
24
38
  # The pid lock file for the process. Required when daemonize is set to
25
39
  # false.
26
40
  # @return [String] Defaults to value of pids_dir/name.pid
@@ -67,15 +81,32 @@ module Cognizant
67
81
  # @return [Array] Defaults to []
68
82
  attr_accessor :groups
69
83
 
70
- # The command to check the running status of the process with. The exit
71
- # status of the command is used to determine the status.
72
- # e.g. "/usr/bin/redis-cli PING"
73
- # @return [String] Defaults to nil
74
- attr_accessor :ping_command
84
+ def daemonize!
85
+ @daemonize = true
86
+ end
75
87
 
76
- # The command that returns the pid of the process.
77
- # @return [String] Defaults to nil
78
- attr_accessor :pid_command
88
+ def autostart!
89
+ @autostart = true
90
+ end
91
+
92
+ # @private
93
+ def reset_attributes!
94
+ self.name = nil
95
+ self.group = nil
96
+ self.daemonize = true
97
+ self.autostart = false
98
+ self.ping_command = nil
99
+ self.pid_command = nil
100
+ self.pidfile = nil
101
+ self.logfile = nil
102
+ self.errfile = nil
103
+ self.env = {}
104
+ self.chroot = nil
105
+ self.chdir = nil
106
+ self.uid = nil
107
+ self.gid = nil
108
+ self.groups = []
109
+ end
79
110
  end
80
111
  end
81
112
  end
@@ -0,0 +1,36 @@
1
+ module Cognizant
2
+ class Process
3
+ module Children
4
+ def refresh_children!
5
+ # First prune the list of dead children.
6
+ @children.delete_if do |child|
7
+ !child.process_running?
8
+ end
9
+
10
+ # Add new found children to the list.
11
+ new_children_pids = Cognizant::System.get_children(@process_pid) - @children.map(&:cached_pid)
12
+
13
+ unless new_children_pids.empty?
14
+ Log[self].info "Existing children: #{@children.collect{ |c| c.cached_pid }.join(",")}. Got new children: #{new_children_pids.inspect} for #{@process_pid}."
15
+ end
16
+
17
+ # Construct a new process wrapper for each new found children.
18
+ new_children_pids.each do |child_pid|
19
+ create_child_process(child_pid)
20
+ end
21
+ end
22
+
23
+ def create_child_process(child_pid)
24
+ name = "<child(pid:#{child_pid})>"
25
+ attributes = @child_process_attributes.merge({ name: name, autostart: false }) # We do not have control over child process' lifecycle, so avoid even attempting to maintain its state with autostart.
26
+
27
+ child = Cognizant::Process.new(nil, attributes, &@child_process_block)
28
+ child.instance_variable_set(:@application, @application)
29
+ # TODO: Reset pidfile?
30
+ child.write_pid(child_pid)
31
+ @children << child
32
+ child.monitor
33
+ end
34
+ end
35
+ end
36
+ end
@@ -3,30 +3,28 @@ require "cognizant/util/rotational_array"
3
3
 
4
4
  module Cognizant
5
5
  class Process
6
- class ConditionCheck
6
+ class ConditionDelegate
7
7
  class HistoryValue < Struct.new(:value, :critical); end
8
8
 
9
9
  # No need to recreate one every tick.
10
10
  EMPTY_ARRAY = [].freeze
11
11
 
12
- attr_accessor :condition_name
13
- def initialize(condition_name, options = {}, &block)
14
- @condition_name = condition_name
15
-
16
- if block
17
- @do = Array(block)
18
- else
19
- @do = options.has_key?(:do) ? Array(options.delete(:do)) : [:restart]
20
- end
12
+ attr_accessor :name
21
13
 
14
+ def initialize(name, options = {}, &block)
15
+ @name = name
22
16
  @every = options.delete(:every).to_i
23
- @times = options.delete(:times) || [1, 1]
17
+
18
+ @times = options.delete(:times) || 1
24
19
  @times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
25
20
  @times.map(&:to_i)
26
21
 
22
+ @do = options.has_key?(:do) ? [options.delete(:do)] : [:restart]
23
+ @do = [block] if block
24
+
27
25
  clear_history!
28
26
 
29
- @condition = Cognizant::Process::Conditions[@condition_name].new(options)
27
+ @condition = Cognizant::Process::Conditions[@name].new(options)
30
28
  end
31
29
 
32
30
  def run(pid, tick_number = Time.now.to_i)
@@ -52,7 +50,7 @@ module Cognizant
52
50
 
53
51
  def to_s
54
52
  data = @history.collect { |v| v and "#{v.value}#{'*' unless v.critical}" }.join(", ")
55
- "#{@condition_name}: [#{data}]\n"
53
+ "#{@name}: [#{data}]\n"
56
54
  end
57
55
  end
58
56
  end
@@ -1,15 +1,18 @@
1
1
  require "cognizant/process/conditions/poll_condition"
2
- require "cognizant/process/conditions/trigger_condition"
3
2
 
4
- Dir["#{File.dirname(__FILE__)}/conditions/*.rb"].each do |c|
5
- require c
3
+ Dir["#{File.dirname(__FILE__)}/conditions/*.rb"].each do |condition|
4
+ require condition
6
5
  end
7
6
 
8
7
  module Cognizant
9
8
  class Process
10
9
  module Conditions
11
10
  def self.[](name)
12
- const_get(name.to_s.camelcase)
11
+ begin
12
+ const_get(name.to_s.camelcase)
13
+ rescue NameError
14
+ nil
15
+ end
13
16
  end
14
17
  end
15
18
  end
@@ -4,16 +4,15 @@ module Cognizant
4
4
  class Process
5
5
  module Conditions
6
6
  class CpuUsage < PollCondition
7
- def initialize(options = {})
8
- @above = options[:above].to_f
9
- end
10
-
11
7
  def run(pid)
12
- System.cpu_usage(pid).to_f
8
+ # Check for current value for the given process pid.
9
+ Cognizant::System.cpu_usage(pid).to_f
13
10
  end
14
11
 
15
12
  def check(value)
16
- value > @above
13
+ # Evaluate the value with threshold.
14
+ # The result of this decides condition invoking.
15
+ value > @options[:above].to_f
17
16
  end
18
17
  end
19
18
  end
@@ -7,16 +7,12 @@ module Cognizant
7
7
  MB_LABEL = "MB"
8
8
  KB_LABEL = "KB"
9
9
 
10
- def initialize(options = {})
11
- @above = options[:above].to_f
12
- end
13
-
14
10
  def run(pid)
15
- System.memory_usage(pid).to_f
11
+ Cognizant::System.memory_usage(pid).to_f
16
12
  end
17
13
 
18
14
  def check(value)
19
- value.kilobytes > @above
15
+ value.kilobytes > @options[:above].to_f
20
16
  end
21
17
 
22
18
  def format_value(value)
@@ -0,0 +1,23 @@
1
+ require 'cognizant/util/dsl_proxy_methods_handler'
2
+
3
+ module Cognizant
4
+ class Process
5
+ class DSLProxy
6
+ include Cognizant::Util::DSLProxyMethodsHandler
7
+
8
+ def initialize(process, &dsl_block)
9
+ super
10
+ @process = process
11
+ instance_eval(&dsl_block)
12
+ end
13
+
14
+ def check(condition_name, options, &block)
15
+ @process.check(condition_name, options, &block)
16
+ end
17
+
18
+ def monitor_children(&child_process_block)
19
+ @process.monitor_children(&child_process_block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,5 @@
1
+ require 'cognizant/util/transform_hash_keys'
2
+
1
3
  module Cognizant
2
4
  class Process
3
5
  module Execution
@@ -29,15 +31,13 @@ module Cognizant
29
31
  if options[:daemonize]
30
32
  # Create a new session to detach from the controlling terminal.
31
33
  unless ::Process.setsid
32
- # raise Koth::RuntimeException.new('cannot detach from controlling terminal')
34
+ Log[self].error "Cannot detach #{options[:name]} from controlling terminal"
33
35
  end
34
36
 
35
37
  # TODO: Set pgroup: true so that the spawned process is the group leader, and it's death would kill all children as well.
36
38
 
37
39
  # Prevent inheriting signals from parent process.
38
- Signal.trap('TERM', 'SIG_DFL')
39
- Signal.trap('INT', 'SIG_DFL')
40
- Signal.trap('HUP', 'SIG_DFL')
40
+ setup_execution_traps
41
41
 
42
42
  # Give the process a name.
43
43
  $0 = options[:name] if options[:name]
@@ -52,7 +52,7 @@ module Cognizant
52
52
 
53
53
  # TODO: Run popen as spawned process before privileges are dropped for increased abilities?
54
54
  stdin_data = options[:input] if options[:input]
55
- stdin_data = IO.popen(options[:input_command]).gets if options[:input_command]
55
+ stdin_data = IO.popen(options[:input_command]).read if options[:input_command]
56
56
 
57
57
  if stdin_data
58
58
  stdin, stdin_w = IO.pipe
@@ -72,8 +72,8 @@ module Cognizant
72
72
  })
73
73
 
74
74
  # Spawn a process to execute the command.
75
- process_pid = ::Process.spawn(options[:env], command, spawn_options)
76
- # puts "process_pid: #{process_pid} (#{command})"
75
+ process_pid = ::Process.spawn(options[:env].deep_stringify_keys!, command, spawn_options)
76
+ # Log[self].debug "process_pid: #{process_pid} (#{command})"
77
77
  pid_w.write(process_pid)
78
78
 
79
79
  if options[:daemonize]
@@ -116,11 +116,11 @@ module Cognizant
116
116
 
117
117
  # Collect and return stdout, stderr and exitcode.
118
118
  return ExecutionResult.new(
119
- pid.read.to_i,
119
+ nil,
120
120
  out_r.read,
121
121
  err_r.read,
122
122
  status.exitstatus,
123
- status.exitstatus.zero?
123
+ status.exitstatus ? status.exitstatus.zero? : false # TODO: What rare case would not have status.existatus?
124
124
  )
125
125
  end
126
126
  end
@@ -164,6 +164,13 @@ module Cognizant
164
164
 
165
165
  private
166
166
 
167
+ def setup_execution_traps
168
+ Signal.trap('TERM', 'SIG_DFL')
169
+ Signal.trap('INT', 'SIG_DFL')
170
+ Signal.trap('QUIT', 'SIG_DFL')
171
+ Signal.trap('HUP', 'SIG_DFL')
172
+ end
173
+
167
174
  def construct_spawn_options(options, overrides = {})
168
175
  spawn_options = {}
169
176
  [:chdir, :umask].each do |o|