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.
- 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
|