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,23 @@
1
+ require 'cognizant/util/dsl_proxy_methods_handler'
2
+
3
+ module Cognizant
4
+ class Application
5
+ class DSLProxy
6
+ include Cognizant::Util::DSLProxyMethodsHandler
7
+
8
+ def initialize(application, &dsl_block)
9
+ super
10
+ @application = application
11
+ instance_eval(&dsl_block)
12
+ end
13
+
14
+ def monitor(process_name = nil, attributes = {}, &block)
15
+ @application.monitor(process_name, attributes, &block)
16
+ end
17
+
18
+ def process(process_name = nil, attributes = {}, &block)
19
+ @application.process(process_name, attributes, &block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ require 'uri'
2
+ require 'yaml'
3
+ require 'socket'
4
+
5
+ module Cognizant
6
+ class Client
7
+ # Keep this in this file so client can be loaded entirely
8
+ # standalone by user code.
9
+ module Transport
10
+ def self.send_message(socket, message)
11
+ line = serialize_message(message)
12
+ socket.write(line)
13
+ end
14
+
15
+ def self.receive_message(socket)
16
+ line = socket.readline
17
+ deserialize_message(line)
18
+ end
19
+
20
+ def self.serialize_message(message)
21
+ serialized = YAML.dump(message)
22
+ escaped = URI.escape(serialized, "%\n")
23
+ escaped + "\n"
24
+ end
25
+
26
+ def self.deserialize_message(line)
27
+ serialized = URI.unescape(line)
28
+ YAML.load(serialized)
29
+ end
30
+ end
31
+
32
+ @@responseless_commands = ["shutdown"]
33
+
34
+ def self.for_port(hostname = "127.0.0.1", port)
35
+ socket = TCPSocket.open(hostname, port)
36
+ self.new(socket)
37
+ end
38
+
39
+ def self.for_path(path_to_socket)
40
+ socket = UNIXSocket.open(path_to_socket)
41
+ self.new(socket)
42
+ end
43
+
44
+ def initialize(socket)
45
+ @socket = socket
46
+ end
47
+
48
+ def command(command_hash)
49
+ Transport.send_message(@socket, command_hash)
50
+ Transport.receive_message(@socket) if expect_response?(command_hash)
51
+ end
52
+
53
+ def expect_response?(command_hash)
54
+ !@@responseless_commands.include?(command_hash['command'])
55
+ end
56
+
57
+ def close
58
+ @socket.close
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,164 @@
1
+ require "json"
2
+ require "cognizant/client"
3
+ require "cognizant/system"
4
+
5
+ module Cognizant
6
+ module Commands
7
+ @@commands = {}
8
+
9
+ def self.command(name, description = nil, &block)
10
+ @@commands[name] = { :description => description, :block => block }
11
+ end
12
+
13
+ def self.process_command(connection, command)
14
+ response = generate_response(connection, command)
15
+ if !response.nil?
16
+ send_message(connection, response)
17
+ else
18
+ Log[self].debug("Got back nil response, so not responding to command.")
19
+ end
20
+ # connection.close_connection_after_writing
21
+ end
22
+
23
+ def self.send_message(connection, response)
24
+ if response.kind_of?(String)
25
+ response = { 'message' => response }
26
+ end
27
+ serialized_message = Cognizant::Client::Transport.serialize_message(response)
28
+ connection.send_data(serialized_message)
29
+ end
30
+
31
+ def self.generate_response(connection, command)
32
+ begin
33
+ request = Cognizant::Client::Transport.deserialize_message(command)
34
+ rescue ArgumentError => e
35
+ return { 'message' => "Could not parse command: #{e}" }
36
+ end
37
+
38
+ unless command_name = request['command']
39
+ return { 'message' => 'No "command" parameter provided; not sure what you want me to do.' }
40
+ end
41
+
42
+ run_command(connection, request, command, command_name)
43
+ end
44
+
45
+ def self.run_command(connection, request, command, command_name)
46
+ if command_spec = @@commands[command_name]
47
+ # Log[self].debug("Received command: #{command.inspect}")
48
+ begin
49
+ return command_spec[:block].call(connection, request)
50
+ rescue StandardError => e
51
+ msg = "Error while processing command #{command_name.inspect}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}"
52
+ Log[self].error(msg)
53
+ return msg
54
+ end
55
+ else
56
+ Log[self].debug("Received unrecognized command: #{command.inspect}")
57
+ return unrecognized_command(connection, request)
58
+ end
59
+ end
60
+
61
+ def self.command_descriptions
62
+ command_specs = @@commands.select do |_, spec|
63
+ spec[:description]
64
+ end.sort_by {|name, _| name}
65
+
66
+ command_specs.map do |name, spec|
67
+ "#{name}: #{spec[:description]}"
68
+ end.join("\n")
69
+ end
70
+
71
+ def self.unrecognized_command(connection, request)
72
+ <<EOF
73
+ Unrecognized command: #{request['command'].inspect}
74
+
75
+ #{command_descriptions}
76
+ EOF
77
+ end
78
+
79
+ # Used by cognizant shell.
80
+ command '_ehlo' do |conn, request|
81
+ if request["app"].to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["app"].to_sym)
82
+ use = request["app"]
83
+ else
84
+ use = Cognizant::Controller.daemon.applications.keys.first
85
+ end
86
+
87
+ message = <<EOF
88
+ Welcome #{request['user']}! You are speaking to the Cognizant Monitoring Daemon.
89
+ EOF
90
+
91
+ { "message" => message, "use" => use }
92
+ end
93
+
94
+ # Used by cognizant shell.
95
+ command '_autocomplete_keywords' do |conn, request|
96
+ keywords = []
97
+ keywords += @@commands.keys.reject { |c| c =~ /^\_.+/ }
98
+ keywords += Cognizant::Controller.daemon.applications.keys
99
+ keywords += Cognizant::Controller.daemon.applications[request["app"].to_sym].processes.keys if request.has_key?("app") and request["app"].to_s.size > 0
100
+ keywords
101
+ end
102
+
103
+ # command('reload', 'Reload Cognizant') do |connection, _|
104
+ # # TODO: make reload actually work (command socket reopening is
105
+ # # an issue). Would also be nice if user got a confirmation that
106
+ # # the reload completed, though that's not strictly necessary.
107
+ #
108
+ # # In the normal case, this will do a write
109
+ # # synchronously. Otherwise, the bytes will be stuck into the
110
+ # # buffer and lost upon reload.
111
+ # send_message(connection, 'Reloading, as commanded')
112
+ # Cognizant.reload
113
+ #
114
+ # # Reload should not return
115
+ # raise "Not reachable"
116
+ # end
117
+
118
+ def self.processes_for_name_or_group(app, args)
119
+ processes = []
120
+ if app.to_s.size > 0
121
+ Cognizant::Controller.daemon.applications[app.to_sym].processes.values.each do |process|
122
+ processes << process if args.include?(process.name) or args.include?(process.group)
123
+ end
124
+ end
125
+ processes
126
+ end
127
+
128
+ def self.send_process_or_group_status(app, args = [])
129
+ output_processes = []
130
+ if args.size > 0
131
+ output_processes = processes_for_name_or_group(app, args)
132
+ return %Q{No such process or group: #{args.join(', ')}} if output_processes.size == 0
133
+ else
134
+ output_processes = Cognizant::Controller.daemon.applications[app.to_sym].processes.values
135
+ end
136
+
137
+ format_process_or_group_status(output_processes)
138
+ end
139
+
140
+ def self.format_process_or_group_status(output_processes)
141
+ output = []
142
+ output_processes.each do |process|
143
+ pid = process.cached_pid
144
+ output << {
145
+ "Process" => process.name,
146
+ "PID" => pid,
147
+ "Group" => process.group,
148
+ "State" => process.state,
149
+ "Since" => process.last_transition_time,
150
+ "% CPU" => Cognizant::System.cpu_usage(pid).to_f,
151
+ "Memory" => Cognizant::System.memory_usage(pid).to_f # in KBs.
152
+ }
153
+ end
154
+ output
155
+ end
156
+ end
157
+ end
158
+
159
+ require "cognizant/commands/actions"
160
+ require "cognizant/commands/help"
161
+ require "cognizant/commands/load"
162
+ require "cognizant/commands/shutdown"
163
+ require "cognizant/commands/status"
164
+ require "cognizant/commands/use"
@@ -0,0 +1,30 @@
1
+ module Cognizant
2
+ module Commands
3
+ [
4
+ ["monitor", "Monitor the specified process or group"],
5
+ ["unmonitor", "Unmonitor the specified process or group"],
6
+ ["start", "Start the specified process or group"],
7
+ ["stop", "Stop the specified process or group"],
8
+ ["restart", "Restart the specified process or group"]
9
+ ].each do |(name, description)|
10
+ command(name, description) do |connection, request|
11
+ args = request["args"]
12
+ unless args.size > 0
13
+ "Missing process name"
14
+ else
15
+ output_processes = []
16
+ output_processes = processes_for_name_or_group(request["app"], args)
17
+ if output_processes.size == 0
18
+ %Q{No such process or group: #{args.join(', ')}}
19
+ else
20
+ output_processes.each do |process|
21
+ process.handle_user_command(name)
22
+ end
23
+ # send_process_or_group_status(request["app"], args)
24
+ "OK"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module Cognizant
2
+ module Commands
3
+ command('help', 'Print out available commands') do
4
+ "You are speaking to the Cognizant command socket. You can run the following commands:
5
+
6
+ #{command_descriptions}
7
+ "
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("load", "Loads the process information from specified Ruby file") do |connection, request|
4
+ request["args"].each do |file|
5
+ Cognizant::Controller.daemon.load_file(file)
6
+ end
7
+ "OK"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("shutdown", "Stop the monitoring daemon without affecting managed processes") do |connection, _|
4
+ Cognizant::Controller.daemon.shutdown!
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("status", "Display status of managed process(es) or group(s)") do |connection, request|
4
+ if request.has_key?("app") and request["app"].to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["app"].to_sym)
5
+ send_process_or_group_status(request["app"], request["args"])
6
+ else
7
+ %Q{No such application: "#{request['app']}"}
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("use", "Switch the current application for use with process maintenance commands") do |connection, request|
4
+ puts "Cognizant::Controller.daemon.applications.keys: #{Cognizant::Controller.daemon.applications.keys}"
5
+ if request["args"].size > 0 and request["args"].first.to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["args"].first.to_sym)
6
+ app = request["args"].first
7
+ message = "OK"
8
+ else
9
+ app = request["app"]
10
+ message = %Q{No such application: "#{request['args'].first}"}
11
+ end
12
+ { "use" => app, "message" => message }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require "cognizant/daemon"
2
+
3
+ module Cognizant
4
+ module Controller
5
+ class << self
6
+ attr_accessor :daemon
7
+ end
8
+
9
+ def self.daemon
10
+ @daemon ||= Cognizant::Daemon.new
11
+ end
12
+
13
+ def self.start_daemon(options = {})
14
+ self.daemon.start(options)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,279 @@
1
+ require "eventmachine"
2
+
3
+ require "cognizant"
4
+ require "cognizant/log"
5
+ require "cognizant/process"
6
+ require "cognizant/application"
7
+ require "cognizant/system"
8
+ require "cognizant/util/transform_hash_keys"
9
+
10
+ module Cognizant
11
+ class Daemon
12
+ # Whether or not to the daemon in the background.
13
+ # @return [true, false] Defaults to true
14
+ attr_accessor :daemonize
15
+
16
+ attr_accessor :sockfile
17
+
18
+ # The pid lock file for the daemon.
19
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.pid
20
+ # @return [String] Defaults to /var/run/cognizant/cognizantd.pid
21
+ attr_accessor :pidfile
22
+
23
+ # The file to log the daemon's operations information into.
24
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.log
25
+ # @return [String] Defaults to /var/log/cognizant/cognizantd.log
26
+ attr_accessor :logfile
27
+
28
+ # Whether or not to log to syslog daemon instead of file.
29
+ # @return [true, false] Defaults to true
30
+ attr_accessor :syslog
31
+
32
+ # The level of information to log. This does not affect the log
33
+ # level of managed processes.
34
+ # @note The possible values must be one of the following:
35
+ # DEBUG, INFO, WARN, ERROR and FATAL or 0, 1, 2, 3, 4 (respectively).
36
+ # @return [Logger::Severity] Defaults to Logger::INFO
37
+ attr_accessor :loglevel
38
+
39
+ # Hash of applications being managed.
40
+ # @private
41
+ # @return [Hash]
42
+ attr_accessor :applications
43
+
44
+ # @private
45
+ attr_accessor :socket
46
+
47
+ def start(options = {})
48
+ self.reset!
49
+
50
+ load_files = []
51
+ rb_files = []
52
+ yml_files = []
53
+ apps = []
54
+
55
+ load_files = [*options.delete(:load)] if options and options.has_key?(:load)
56
+ load_files.each do |include|
57
+ Dir[File.expand_path(include)].each do |file|
58
+ Log[self].info "Including config from #{file}."
59
+ if file.end_with?(".yml")
60
+ yml_files << file
61
+ else
62
+ rb_files << file
63
+ end
64
+ end
65
+ end
66
+ apps << options.delete(:applications) if options and options.has_key?(:applications)
67
+
68
+ yml_files.each do |file|
69
+ file_opts = YAML.load_file(file)
70
+ if file_opts
71
+ file_opts.deep_symbolize_keys!
72
+ apps << file_opts.delete(:applications) if file_opts.has_key?(:applications)
73
+ end
74
+ end
75
+
76
+ # Attributes.
77
+ options.each do |key, value|
78
+ self.send("#{key}=", options.delete(key)) if self.respond_to?("#{key}=")
79
+ end
80
+
81
+ self.sockfile = File.expand_path(self.sockfile)
82
+ self.pidfile = File.expand_path(self.pidfile)
83
+ self.logfile = File.expand_path(self.logfile)
84
+
85
+ Log[self].info "Booting up Cognizant daemon..."
86
+ setup_directories
87
+ setup_logging
88
+ stop_previous_daemon
89
+ stop_previous_socket
90
+ trap_signals
91
+ daemonize_process
92
+ write_pid
93
+
94
+ EventMachine.run do
95
+ # Applications.
96
+ Log[self].info "Cognizant daemon running successfully."
97
+
98
+ apps.each do |app|
99
+ app.each do |key, value|
100
+ self.create_application(key, value)
101
+ end
102
+ end
103
+
104
+ [*rb_files].each do |rb_file|
105
+ load_file(rb_file)
106
+ end
107
+
108
+ EventMachine.start_unix_domain_server(self.sockfile, Cognizant::Interface)
109
+
110
+ EventMachine.add_periodic_timer(1) do
111
+ Cognizant::System.reset_data!
112
+ self.applications.values.each(&:tick)
113
+ end
114
+ end
115
+ end
116
+
117
+ def load_file(file)
118
+ if file.end_with?(".yml")
119
+ yml_config = YAML.load_file(file)
120
+ if yml_config
121
+ yml_config.deep_symbolize_keys!
122
+ yml_config_apps = yml_config.delete(:applications) if yml_config.has_key?(:applications)
123
+ yml_config_apps.each { |key, value| self.create_application(key, value) }
124
+ end
125
+ else
126
+ rb_file = File.expand_path(file)
127
+ Kernel.load(rb_file) if File.exists?(rb_file)
128
+ end
129
+ end
130
+
131
+ def reset!
132
+ self.daemonize = true
133
+ self.sockfile = "/var/run/cognizant/cognizantd.sock"
134
+ self.pidfile = "/var/run/cognizant/cognizantd.pid"
135
+ self.syslog = false
136
+ self.logfile = "/var/log/cognizant/cognizantd.log"
137
+ self.loglevel = Logging::LEVELS["INFO"]
138
+ self.applications.values.each(&:reset!) if self.applications.is_a?(Hash)
139
+ self.applications = {}
140
+ end
141
+
142
+ def create_application(name, options = {}, &block)
143
+ app = Cognizant::Application.new(name, options, &block)
144
+ self.applications[app.name.to_sym] = app
145
+ app
146
+ end
147
+
148
+ def setup_logging
149
+ Cognizant::Log.logger.root.level = self.loglevel
150
+
151
+ unless self.daemonize
152
+ stdout_appender = Cognizant::Log.stdout
153
+ Cognizant::Log.logger.root.add_appenders(stdout_appender)
154
+ end
155
+
156
+ if self.syslog
157
+ # TODO: Choose a non-default facility? (default: LOG_USR).
158
+ syslog_appender = Cognizant::Log.syslog("cognizantd")
159
+ Cognizant::Log.logger.root.add_appenders(syslog_appender)
160
+ elsif self.logfile
161
+ logfile_appender = Cognizant::Log.file(self.logfile)
162
+ Cognizant::Log.logger.root.add_appenders(logfile_appender)
163
+ end
164
+ end
165
+
166
+ def setup_directories
167
+ # Create the require directories.
168
+ System.mkdir(File.dirname(self.sockfile), File.dirname(self.pidfile), File.dirname(self.logfile))
169
+ end
170
+
171
+ # Stops the socket server and the tick loop, and performs cleanup.
172
+ def shutdown!
173
+ Log[self].info "Shutting down Cognizant daemon..."
174
+ EventMachine.next_tick do
175
+ EventMachine.add_shutdown_hook do
176
+ self.applications.values.each(&:shutdown!)
177
+ unlink_pid
178
+ # TODO: Close logger?
179
+ end
180
+ EventMachine.stop
181
+ end
182
+ end
183
+
184
+ def trap_signals
185
+ terminator = Proc.new do
186
+ Log[self].info "Received signal to shutdown."
187
+ shutdown!
188
+ end
189
+
190
+ Signal.trap('TERM', &terminator)
191
+ Signal.trap('INT', &terminator)
192
+ Signal.trap('QUIT', &terminator)
193
+ end
194
+
195
+ def stop_previous_daemon
196
+ if self.pidfile and File.exists?(self.pidfile)
197
+ if previous_daemon_pid = File.read(self.pidfile).to_i
198
+ # Only attempt to stop automatically if the daemon will run in background.
199
+ if self.daemonize and Cognizant::System.pid_running?(previous_daemon_pid)
200
+ # Ensure that the process stops within 5 seconds or force kill.
201
+ signals = ["TERM", "KILL"]
202
+ timeout = 2
203
+ catch :stopped do
204
+ signals.each do |stop_signal|
205
+ # Send the stop signal and wait for it to stop.
206
+ Cognizant::System.signal(stop_signal, previous_daemon_pid)
207
+
208
+ # Poll to see if it has stopped yet. Minimum 2 so that we check at least once again.
209
+ ([timeout / signals.size, 2].max).times do
210
+ throw :stopped unless Cognizant::System.pid_running?(previous_daemon_pid)
211
+ sleep 1
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ # Alert the user to manually stop the previous daemon, if it is [still] alive.
219
+ if Cognizant::System.pid_running?(previous_daemon_pid)
220
+ raise "There is already a daemon running with pid #{previous_daemon_pid}."
221
+ else
222
+ unlink_pid # This will be overwritten anyways.
223
+ end
224
+ end
225
+ end
226
+
227
+ def stop_previous_socket
228
+ # Socket isn't actually owned by anyone.
229
+ begin
230
+ sock = UNIXSocket.new(self.sockfile)
231
+ rescue Errno::ECONNREFUSED
232
+ # This happens with non-socket files and when the listening
233
+ # end of a socket has exited.
234
+ rescue Errno::ENOENT
235
+ # Socket doesn't exist.
236
+ return
237
+ else
238
+ # Rats, it's still active.
239
+ sock.close
240
+ raise Errno::EADDRINUSE.new("Another process or application is likely already listening on the socket at #{self.sockfile}.")
241
+ end
242
+
243
+ # Socket should still exist, so don't need to handle error.
244
+ stat = File.stat(self.sockfile)
245
+ unless stat.socket?
246
+ raise Errno::EADDRINUSE.new("Non-socket file present at socket file path #{self.sockfile}. Either remove that file and restart Cognizant, or change the socket file path.")
247
+ end
248
+
249
+ Log[self].info("Blowing away old socket file at #{self.sockfile}. This likely indicates a previous Cognizant application which did not shutdown gracefully.")
250
+
251
+ # Whee, blow it away.
252
+ unlink_sockfile
253
+ end
254
+
255
+ # Daemonize the current process and save it pid in a file.
256
+ def daemonize_process
257
+ if self.daemonize
258
+ Log[self].info "Daemonizing into the background..."
259
+ ::Process.daemon
260
+ end
261
+ end
262
+
263
+ def write_pid
264
+ pid = ::Process.pid
265
+ if self.pidfile
266
+ Log[self].info "Writing the daemon pid (#{pid}) to the pidfile..."
267
+ File.open(self.pidfile, "w") { |f| f.write(pid) }
268
+ end
269
+ end
270
+
271
+ def unlink_pid
272
+ Cognizant::System.unlink_file(self.pidfile) if self.pidfile
273
+ end
274
+
275
+ def unlink_sockfile
276
+ Cognizant::System.unlink_file(self.sockfile) if self.sockfile
277
+ end
278
+ end
279
+ end