cognizant 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ module Cognizant
2
+ module Server
3
+ module Commands
4
+ def self.load(config_file)
5
+ Cognizant::Server.daemon.load(config_file)
6
+ yield("OK")
7
+ end
8
+
9
+ def self.status(*args)
10
+ if process_name = args.shift
11
+ Cognizant::Server.daemon.processes.each do |name, process|
12
+ if process.name.eql?(process_name)
13
+ yield("#{process.name}: #{process.state}")
14
+ return yield("OK")
15
+ end
16
+ end
17
+ yield("ERR: No such process")
18
+ return yield("OK")
19
+ end
20
+ yield("OK")
21
+ end
22
+
23
+ %w(monitor start stop restart unmonitor).each do |action|
24
+ class_eval <<-END
25
+ def self.#{action}(*args)
26
+ unless process_name = args.shift
27
+ yield("ERR: Missing process name")
28
+ return yield("OK")
29
+ end
30
+ Cognizant::Server.daemon.processes.each do |name, process|
31
+ if process.name.eql?(process_name)
32
+ process.#{action}
33
+ return yield("OK")
34
+ end
35
+ end
36
+ yield("ERR: No such process")
37
+ yield("OK")
38
+ end
39
+ END
40
+ end
41
+
42
+ def self.shutdown
43
+ Cognizant::Server.daemon.shutdown
44
+ yield("OK")
45
+ end
46
+
47
+ def self.method_missing(command, *args)
48
+ yield("ERR: Unknown command '#{command}'")
49
+ yield("OK")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,239 @@
1
+ require "eventmachine"
2
+
3
+ require "cognizant"
4
+ require "cognizant/logging"
5
+ require "cognizant/process"
6
+ require "cognizant/server/interface"
7
+
8
+ module Cognizant
9
+ module Server
10
+ class Daemon
11
+ include Cognizant::Logging
12
+
13
+ # Whether or not to the daemon in the background.
14
+ # @return [true, false] Defaults to true
15
+ attr_accessor :daemonize
16
+
17
+ # The pid lock file for the daemon.
18
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.pid
19
+ # @return [String] Defaults to /var/run/cognizant/cognizantd.pid
20
+ attr_accessor :pidfile
21
+
22
+ # The file to log the daemon's operations information into.
23
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.log
24
+ # @return [String] Defaults to /var/log/cognizant/cognizantd.log
25
+ attr_accessor :logfile
26
+
27
+ # The level of information to log. This does not affect the log
28
+ # level of managed processes.
29
+ # @note The possible values must be one of the following:
30
+ # Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR and
31
+ # Logger::FATAL or 0, 1, 2, 3, 4 (respectively).
32
+ # @return [Logger::Severity] Defaults to Logger::INFO
33
+ attr_accessor :loglevel
34
+
35
+ # The socket lock file for the server. This file is ignored if valid
36
+ # bind address and port are given.
37
+ # e.g. /Users/Gurpartap/.cognizant/cognizant-server.sock
38
+ # @return [String] Defaults to /var/run/cognizant/cognizantd.sock
39
+ attr_accessor :socket
40
+
41
+ # The TCP address and port to start the server with. e.g. 8081,
42
+ # "127.0.0.1:8081", "0.0.0.0:8081".
43
+ # @return [String] Defaults to nil
44
+ attr_accessor :port
45
+
46
+ # Username for securing server access. e.g. "cognizant-user"
47
+ # @return [String] Defaults to nil
48
+ attr_accessor :username
49
+
50
+ # Password to accompany the username.
51
+ # e.g. "areallyverylongpasswordbecauseitmatters"
52
+ # @return [String] Defaults to nil
53
+ attr_accessor :password
54
+
55
+ # Directory to store the pid files of managed processes, when required.
56
+ # e.g. /Users/Gurpartap/.cognizant/pids/
57
+ # @return [String] Defaults to /var/run/cognizant/pids/
58
+ attr_accessor :pids_dir
59
+
60
+ # Directory to store the log files of managed processes, when required.
61
+ # e.g. /Users/Gurpartap/.cognizant/logs/
62
+ # @return [String] Defaults to /var/log/cognizant/logs/
63
+ attr_accessor :logs_dir
64
+
65
+ # Environment variables for managed processes to inherit.
66
+ # @return [Hash] Defaults to {}
67
+ attr_accessor :env
68
+
69
+ # The current working directory for the managed processes to start with.
70
+ # @return [String] Defaults to nil
71
+ attr_accessor :chdir
72
+
73
+ # Limit the permission modes for files and directories created by the
74
+ # daemon and the managed processes.
75
+ # @return [Integer] Defaults to nil
76
+ attr_accessor :umask
77
+
78
+ # Run the daemon and managed processes as the given user.
79
+ # e.g. "deploy", 1000
80
+ # @return [String] Defaults to nil
81
+ attr_accessor :user
82
+
83
+ # Run the daemon and managed processes as the given user group.
84
+ # e.g. "deploy"
85
+ # @return [String] Defaults to nil
86
+ attr_accessor :group
87
+
88
+ # Array of processes being managed.
89
+ # @private
90
+ # @return [Array]
91
+ attr_accessor :processes
92
+
93
+ # Initializes and starts the cognizant daemon with the given options
94
+ # as instance attributes.
95
+ # @param [Hash] options A hash of instance attributes and their values.
96
+ def initialize(options = {})
97
+ # Daemon config.
98
+ @daemonize = options.has_key?(:daemonize) ? options[:daemonize] : true
99
+ @pidfile = options[:pidfile] || "/var/run/cognizant/cognizantd.pid"
100
+ @logfile = options[:logfile] || "/var/log/cognizant/cognizantd.log"
101
+ @loglevel = options[:loglevel].to_i || Logger::INFO
102
+ @socket = options[:socket] || "/var/run/cognizant/cognizantd.sock"
103
+ @port = options[:port] || nil
104
+ @username = options[:username] || nil
105
+ @password = options[:password] || nil
106
+ @trace = options[:trace] || nil
107
+
108
+ # Processes config.
109
+ @pids_dir = options[:pids_dir] || "/var/run/cognizant/pids/"
110
+ @logs_dir = options[:logs_dir] || "/var/log/cognizant/logs/"
111
+ @env = options[:env] || {}
112
+ @chdir = options[:chdir] || nil
113
+ @umask = options[:umask] || nil
114
+ @user = options[:user] || nil
115
+ @group = options[:group] || nil
116
+
117
+ # Expand paths.
118
+ @socket = File.expand_path(@socket)
119
+ @pidfile = File.expand_path(@pidfile)
120
+ @logfile = File.expand_path(@logfile)
121
+ @pids_dir = File.expand_path(@pids_dir)
122
+ @logs_dir = File.expand_path(@logs_dir)
123
+
124
+ self.processes = {}
125
+
126
+ # # Only available through a config file/stdin.
127
+ # load_processes(options[:monitor]) if options.has_key?(:monitor)
128
+ end
129
+
130
+ def bootup
131
+ setup_prerequisites
132
+ trap_signals
133
+ log.info "Booting up cognizantd..."
134
+ EventMachine.run do
135
+ start_interface_server
136
+ start_periodic_ticks
137
+ daemonize
138
+ end
139
+ end
140
+
141
+ def load(config_file)
142
+ config_file = File.expand_path(config_file)
143
+ log.info "Loading config from #{config_file}..."
144
+ # config = YAML.load_file(config_file)
145
+ # config = config.inject({}) { |c,(k,v)| c[k.gsub("-", "_").to_sym] = v; c }
146
+ # load_processes(config[:monitor]) if config.has_key?(:monitor)
147
+ Kernel.load(config_file)
148
+ end
149
+
150
+ def monitor(process_name = nil, attributes = {}, &block)
151
+ process = Cognizant::Process.new(process_name, attributes, &block)
152
+ self.processes[process_name] = process
153
+ process.monitor
154
+ end
155
+
156
+ # Stops the TCP server and the tick loop, and performs cleanup.
157
+ def shutdown
158
+ log.info "Shutting down cognizantd..."
159
+
160
+ EventMachine.next_tick do
161
+ EventMachine.stop
162
+ logger.close
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def load_processes(processes_to_load)
169
+ if processes_to_load
170
+ processes_to_load.each do |name, attributes|
171
+ monitor(name, attributes)
172
+ end
173
+ end
174
+ end
175
+
176
+ # Starts the TCP server with the set socket lock file or port.
177
+ def start_interface_server
178
+ if port = @port
179
+ log.info "Starting the TCP server at #{@port}..."
180
+ host = "127.0.0.1"
181
+ splitted = port.to_s.split(":")
182
+ host, port = splitted if splitted.size > 1
183
+ EventMachine.start_server(host, port, Server::Interface)
184
+ else
185
+ log.info "Starting the UNIX domain server with socket #{@socket}..."
186
+ EventMachine.start_unix_domain_server(@socket, Server::Interface)
187
+ end
188
+ end
189
+
190
+ # Starts the loop that defines the time window for determining and acting upon process states.
191
+ def start_periodic_ticks
192
+ log.info "Starting the periodic tick..."
193
+ EventMachine.add_periodic_timer(1) do
194
+ self.processes.each do |group, process|
195
+ process.tick
196
+ end
197
+ end
198
+ end
199
+
200
+ def setup_prerequisites
201
+ # Create the require directories.
202
+ [File.dirname(@pidfile), File.dirname(@logfile), @pids_dir, @logs_dir, File.dirname(@socket)].each do |directory|
203
+ FileUtils.mkdir_p(directory)
204
+ end
205
+
206
+ # Setup logging.
207
+ add_log_adapter(File.open(@logfile, "a"))
208
+ add_log_adapter($stdout) unless @daemonize
209
+ log.level = if @trace then Logger::DEBUG else @loglevel end
210
+ end
211
+
212
+ def trap_signals
213
+ terminator = Proc.new do
214
+ log.info "Received signal to shutdown."
215
+ shutdown
216
+ end
217
+
218
+ Signal.trap('TERM', &terminator)
219
+ Signal.trap('INT', &terminator)
220
+ Signal.trap('QUIT', &terminator)
221
+ end
222
+
223
+ # Daemonize the current process and save it pid in a file.
224
+ def daemonize
225
+ if @daemonize
226
+ log.info "Daemonizing into the background..."
227
+ ::Process.daemon
228
+
229
+ pid = ::Process.pid
230
+
231
+ if @pidfile
232
+ log.info "Writing the daemon pid (#{pid}) to the pidfile..."
233
+ File.open(@pidfile, "w") { |f| f.write(pid) }
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,60 @@
1
+ require "json"
2
+ require "eventmachine"
3
+
4
+ require "cognizant/server/commands"
5
+
6
+ module Cognizant
7
+ module Server
8
+ class Interface < EventMachine::Connection
9
+ def post_init
10
+ # puts "-- someone connected to the server!"
11
+ end
12
+
13
+ def receive_data(args)
14
+ args = [*args.split]
15
+ command = args.shift.to_s.strip.downcase
16
+ if command.size > 0
17
+ begin
18
+ Commands.send(command, *args) do |response|
19
+ send_data "#{response.to_json}\r\n\r\n"
20
+ end
21
+ rescue => e
22
+ send_data "#{e.inspect.to_json}\r\n\r\n"
23
+ end
24
+ end
25
+ # close_connection_after_writing
26
+ end
27
+
28
+ def unbind
29
+ # puts "-- someone disconnected from the server!"
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # module Cognizant
36
+ # module Server
37
+ # class Interface < EventMachine::Connection
38
+ # include EventMachine::Protocols::ObjectProtocol
39
+ # # include EventMachine::Protocols::SASLauth
40
+ #
41
+ # def post_init
42
+ # @obj = Hash.new
43
+ # puts "-- someone connected to the echo server!"
44
+ # end
45
+ #
46
+ # def receive_object(method)
47
+ # send_object @obj.send(*method)
48
+ # end
49
+ #
50
+ # def unbind
51
+ # @obj = nil
52
+ # puts "-- someone disconnected from the echo server!"
53
+ # end
54
+ #
55
+ # # def validate(usr, psw, sys, realm)
56
+ # # usr == TestUser and psw == TestPsw
57
+ # # end
58
+ # end
59
+ # end
60
+ # end
@@ -0,0 +1,14 @@
1
+ require "cognizant/server/daemon"
2
+
3
+ module Cognizant
4
+ module Server
5
+ def self.start(options = {})
6
+ @daemon = Cognizant::Server::Daemon.new(options)
7
+ @daemon.bootup
8
+ end
9
+
10
+ def self.daemon
11
+ @daemon
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,142 @@
1
+ module Cognizant
2
+ module Validations
3
+ class ValidationError < StandardError; end
4
+
5
+ # Validations the user/group ownership to the given file. Attempts to
6
+ # create the directory+file if it doesn't already exist.
7
+ # @param [String] file Path to a file
8
+ def self.validate_file_writable(file, type = "file")
9
+ file = File.expand_path(file)
10
+
11
+ begin
12
+ filetype = File.ftype(file)
13
+
14
+ unless filetype.eql?(type)
15
+ raise ValidationError, "\"#{file}\" is a #{filetype}. File required."
16
+ end
17
+ rescue Errno::ENOENT
18
+ self.validate_directory_writable(File.dirname(file))
19
+
20
+ begin
21
+ unless File.open(file, "w")
22
+ raise ValidationError, "The file \"#{file}\" is not writable."
23
+ end
24
+ rescue Errno::EACCES
25
+ raise ValidationError, "The file \"#{file}\" could not be created due to the lack of privileges."
26
+ end
27
+ end
28
+
29
+ unless File.owned?(file) or File.grpowned?(file)
30
+ raise ValidationError, "The file \"#{file}\" is not owned by the process user."
31
+ end
32
+
33
+ begin
34
+ unless File.open(file, "w")
35
+ raise ValidationError, "The file \"#{file}\" is not writable."
36
+ end
37
+ rescue Errno::EACCES
38
+ raise ValidationError, "The file \"#{file}\" is not accessible due to the lack of privileges."
39
+ rescue
40
+ raise ValidationError, "The file \"#{file}\" is not writable."
41
+ end
42
+ end
43
+
44
+ # Validations the user/group ownership to the given directory. Attempts to
45
+ # create the directory if it doesn't already exist.
46
+ # @param [String] directory Path to a directory
47
+ def self.validate_directory_writable(directory)
48
+ directory = File.expand_path(directory)
49
+
50
+ begin
51
+ filetype = File.ftype(directory)
52
+
53
+ unless filetype.eql?("directory")
54
+ raise ValidationError, "\"#{directory}\" is a #{filetype}. Directory required."
55
+ end
56
+ rescue Errno::ENOENT
57
+ begin
58
+ FileUtils.mkdir_p(directory)
59
+ rescue Errno::EACCES
60
+ raise ValidationError, "The directory \"#{directory}\" could not be created due to the lack of privileges."
61
+ end
62
+ end
63
+
64
+ unless File.owned?(directory) or File.grpowned?(directory)
65
+ raise ValidationError, "The directory \"#{directory}\" is not owned by the process user."
66
+ end
67
+
68
+ begin
69
+ unless File.open(File.join(directory, ".testfile"), "w")
70
+ raise ValidationError, "The directory \"#{directory}\" is not writable."
71
+ end
72
+ rescue Errno::EACCES
73
+ raise ValidationError, "The directory \"#{directory}\" is not accessible due to the lack of privileges."
74
+ rescue
75
+ raise ValidationError, "The directory \"#{directory}\" is not writable."
76
+ ensure
77
+ File.delete(directory, ".testfile") rescue nil
78
+ end
79
+ end
80
+
81
+ # Validates the inclusion of a value in an array.
82
+ # @param [Object] value The value to validate.
83
+ # @param [Array] array The array of allowed values.
84
+ def self.validate_includes(value, array)
85
+ unless [*array].include?(value)
86
+ raise ValidationError, "#{value} is an invalid option type. It must be one of the following: #{[*array].join(', ')}."
87
+ end
88
+ end
89
+
90
+ # Validates the env variable to be a hash.
91
+ # @param [Object] env The value to validate.
92
+ def self.validate_env(env)
93
+ return unless env
94
+ if not env.respond_to?(:is_a?) or not env.is_a?(Hash)
95
+ raise Validations::ValidationError, %{"env" needs to be a hash. e.g. { "PATH" => "/usr/local/bin" }}
96
+ end
97
+ end
98
+
99
+ # Validates the umask variable to be a number.
100
+ # @param [Object] umask The value to validate.
101
+ def self.validate_umask(umask)
102
+ return unless umask
103
+ Float(umask) rescue raise Validations::ValidationError, %{The umask "#{umask}" is invalid.}
104
+ end
105
+
106
+ # Validates the existence of the user in the system.
107
+ # @param [String, Integer] user The user ID or name to validate.
108
+ def self.validate_user(user)
109
+ return unless user
110
+ begin
111
+ if self.is_number?(user)
112
+ Etc.getpwuid(user)
113
+ else
114
+ Etc.getpwnam(user)
115
+ end
116
+ rescue ArgumentError
117
+ raise Validations::ValidationError, %{The user "#{user}" does not exist.}
118
+ end
119
+ end
120
+
121
+ # Validates the existence of the user group in the system.
122
+ # @param [String, Integer] group The user group ID or name to validate.
123
+ def self.validate_user_group(group)
124
+ return unless group
125
+ begin
126
+ if self.is_number?(group)
127
+ Etc.getgrgid(group)
128
+ else
129
+ Etc.getgrname(group)
130
+ end
131
+ rescue ArgumentError
132
+ raise Validations::ValidationError, %{The group "#{group}" does not exist.}
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def self.is_number?(value)
139
+ true if Float(value) rescue false
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Cognizant
2
+ VERSION = "0.0.1"
3
+ end
data/lib/cognizant.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "cognizant/server"
2
+
3
+ module Cognizant
4
+ def self.monitor(process_name, &block)
5
+ Cognizant::Server.daemon.monitor(process_name, &block)
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cognizant
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gurpartap Singh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: redcarpet
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: eventmachine
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: state_machine
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Process monitoring and management framework
95
+ email:
96
+ - contact@gurpartap.com
97
+ executables:
98
+ - cognizant
99
+ - cognizantd
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - .yardopts
105
+ - Gemfile
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/cognizant
110
+ - bin/cognizantd
111
+ - cognizant.gemspec
112
+ - examples/cognizantd.yml
113
+ - examples/redis-server.rb
114
+ - examples/resque.rb
115
+ - images/logo-small.png
116
+ - images/logo.png
117
+ - images/logo.pxm
118
+ - lib/cognizant.rb
119
+ - lib/cognizant/client/cli.rb
120
+ - lib/cognizant/client/interface.rb
121
+ - lib/cognizant/logging.rb
122
+ - lib/cognizant/process.rb
123
+ - lib/cognizant/process/actions.rb
124
+ - lib/cognizant/process/actions/restart.rb
125
+ - lib/cognizant/process/actions/start.rb
126
+ - lib/cognizant/process/actions/stop.rb
127
+ - lib/cognizant/process/attributes.rb
128
+ - lib/cognizant/process/execution.rb
129
+ - lib/cognizant/process/pid.rb
130
+ - lib/cognizant/process/status.rb
131
+ - lib/cognizant/server.rb
132
+ - lib/cognizant/server/commands.rb
133
+ - lib/cognizant/server/daemon.rb
134
+ - lib/cognizant/server/interface.rb
135
+ - lib/cognizant/validations.rb
136
+ - lib/cognizant/version.rb
137
+ homepage: http://gurpartap.github.com/cognizant/
138
+ licenses: []
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ segments:
150
+ - 0
151
+ hash: 2933141880858945843
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ segments:
159
+ - 0
160
+ hash: 2933141880858945843
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 1.8.24
164
+ signing_key:
165
+ specification_version: 3
166
+ summary: Cognizant is an advanced and efficient process monitoring and management
167
+ framework.
168
+ test_files: []
169
+ has_rdoc: