cognizant 0.0.1

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