cognizant 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +17 -0
- data/Gemfile +4 -1
- data/{LICENSE → License.md} +4 -2
- data/Rakefile +5 -0
- data/Readme.md +95 -0
- data/bin/cognizant +76 -122
- data/bin/cognizantd +28 -61
- data/cognizant.gemspec +8 -4
- data/examples/apps/redis-server.cz +42 -0
- data/examples/apps/redis-server.yml +29 -0
- data/examples/apps/redis-server_dsl.cz +54 -0
- data/examples/apps/resque.cz +17 -0
- data/examples/apps/thin.cz +32 -0
- data/examples/apps/thin.yml +48 -0
- data/examples/cognizantd.yml +18 -47
- data/features/child_process.feature +62 -0
- data/features/commands.feature +65 -0
- data/features/cpu_usage.feature +45 -0
- data/features/daemon.feature +12 -0
- data/features/flapping.feature +39 -0
- data/features/memory_usage.feature +45 -0
- data/features/shell.feature +30 -0
- data/features/step_definitions/common_steps.rb +14 -0
- data/features/step_definitions/daemon_steps.rb +25 -0
- data/features/step_definitions/shell_steps.rb +96 -0
- data/features/support/env.rb +54 -0
- data/lib/cognizant.rb +1 -5
- data/lib/cognizant/application.rb +122 -0
- data/lib/cognizant/application/dsl_proxy.rb +23 -0
- data/lib/cognizant/client.rb +61 -0
- data/lib/cognizant/commands.rb +164 -0
- data/lib/cognizant/commands/actions.rb +30 -0
- data/lib/cognizant/commands/help.rb +10 -0
- data/lib/cognizant/commands/load.rb +10 -0
- data/lib/cognizant/commands/shutdown.rb +7 -0
- data/lib/cognizant/commands/status.rb +11 -0
- data/lib/cognizant/commands/use.rb +15 -0
- data/lib/cognizant/controller.rb +17 -0
- data/lib/cognizant/daemon.rb +279 -0
- data/lib/cognizant/interface.rb +17 -0
- data/lib/cognizant/log.rb +25 -0
- data/lib/cognizant/process.rb +138 -94
- data/lib/cognizant/process/actions.rb +30 -41
- data/lib/cognizant/process/actions/restart.rb +73 -17
- data/lib/cognizant/process/actions/start.rb +35 -12
- data/lib/cognizant/process/actions/stop.rb +38 -17
- data/lib/cognizant/process/attributes.rb +41 -10
- data/lib/cognizant/process/children.rb +36 -0
- data/lib/cognizant/process/{condition_check.rb → condition_delegate.rb} +11 -13
- data/lib/cognizant/process/conditions.rb +7 -4
- data/lib/cognizant/process/conditions/cpu_usage.rb +5 -6
- data/lib/cognizant/process/conditions/memory_usage.rb +2 -6
- data/lib/cognizant/process/dsl_proxy.rb +23 -0
- data/lib/cognizant/process/execution.rb +16 -9
- data/lib/cognizant/process/pid.rb +16 -6
- data/lib/cognizant/process/status.rb +14 -2
- data/lib/cognizant/process/trigger_delegate.rb +57 -0
- data/lib/cognizant/process/triggers.rb +19 -0
- data/lib/cognizant/process/triggers/flapping.rb +68 -0
- data/lib/cognizant/process/triggers/transition.rb +22 -0
- data/lib/cognizant/process/triggers/trigger.rb +15 -0
- data/lib/cognizant/shell.rb +142 -0
- data/lib/cognizant/system.rb +16 -0
- data/lib/cognizant/system/ps.rb +1 -1
- data/lib/cognizant/system/signal.rb +2 -2
- data/lib/cognizant/util/dsl_proxy_methods_handler.rb +25 -0
- data/lib/cognizant/util/fixnum_percent.rb +5 -0
- data/lib/cognizant/util/transform_hash_keys.rb +33 -0
- data/lib/cognizant/validations.rb +142 -142
- data/lib/cognizant/version.rb +1 -1
- metadata +131 -71
- data/README.md +0 -221
- data/examples/redis-server.rb +0 -28
- data/examples/resque.rb +0 -10
- data/images/logo-small.png +0 -0
- data/images/logo.png +0 -0
- data/images/logo.pxm +0 -0
- data/lib/cognizant/logging.rb +0 -33
- data/lib/cognizant/process/conditions/flapping.rb +0 -57
- data/lib/cognizant/process/conditions/trigger_condition.rb +0 -52
- data/lib/cognizant/server.rb +0 -14
- data/lib/cognizant/server/commands.rb +0 -80
- data/lib/cognizant/server/daemon.rb +0 -277
- data/lib/cognizant/server/interface.rb +0 -86
- 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,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
|