sensu 0.13.0.alpha.2-java

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sensu/cli.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'optparse'
2
+ require 'sensu/logger/constants'
3
+
4
+ module Sensu
5
+ class CLI
6
+ def self.read(arguments=ARGV)
7
+ options = Hash.new
8
+ optparse = OptionParser.new do |opts|
9
+ opts.on('-h', '--help', 'Display this message') do
10
+ puts opts
11
+ exit
12
+ end
13
+ opts.on('-V', '--version', 'Display version') do
14
+ puts VERSION
15
+ exit
16
+ end
17
+ opts.on('-c', '--config FILE', 'Sensu JSON config FILE') do |file|
18
+ options[:config_file] = file
19
+ end
20
+ opts.on('-d', '--config_dir DIR[,DIR]', 'DIR or comma-delimited DIR list for Sensu JSON config files') do |dir|
21
+ options[:config_dirs] = dir.split(',')
22
+ end
23
+ opts.on('-e', '--extension_dir DIR', 'DIR for Sensu extensions') do |dir|
24
+ options[:extension_dir] = dir
25
+ end
26
+ opts.on('-l', '--log FILE', 'Log to a given FILE. Default: STDOUT') do |file|
27
+ options[:log_file] = file
28
+ end
29
+ opts.on('-L', '--log_level LEVEL', 'Log severity LEVEL') do |level|
30
+ log_level = level.to_s.downcase.to_sym
31
+ unless Logger::LEVELS.include?(log_level)
32
+ puts 'Unknown log level: ' + level.to_s
33
+ exit 1
34
+ end
35
+ options[:log_level] = log_level
36
+ end
37
+ opts.on('-v', '--verbose', 'Enable verbose logging') do
38
+ options[:log_level] = :debug
39
+ end
40
+ opts.on('-b', '--background', 'Fork into the background') do
41
+ options[:daemonize] = true
42
+ end
43
+ opts.on('-p', '--pid_file FILE', 'Write the PID to a given FILE') do |file|
44
+ options[:pid_file] = file
45
+ end
46
+ end
47
+ optparse.parse!(arguments)
48
+ options
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,261 @@
1
+ require 'sensu/daemon'
2
+ require 'sensu/socket'
3
+
4
+ module Sensu
5
+ class Client
6
+ include Daemon
7
+
8
+ attr_accessor :safe_mode
9
+
10
+ def self.run(options={})
11
+ client = self.new(options)
12
+ EM::run do
13
+ client.start
14
+ client.setup_signal_traps
15
+ end
16
+ end
17
+
18
+ def initialize(options={})
19
+ super
20
+ @safe_mode = @settings[:client][:safe_mode] || false
21
+ @checks_in_progress = Array.new
22
+ end
23
+
24
+ def publish_keepalive
25
+ keepalive = @settings[:client].merge({
26
+ :version => VERSION,
27
+ :timestamp => Time.now.to_i
28
+ })
29
+ payload = redact_sensitive(keepalive, @settings[:client][:redact])
30
+ @logger.debug('publishing keepalive', {
31
+ :payload => payload
32
+ })
33
+ @transport.publish(:direct, 'keepalives', MultiJson.dump(payload)) do |info|
34
+ if info[:error]
35
+ @logger.error('failed to publish keepalive', {
36
+ :payload => payload,
37
+ :error => info[:error].to_s
38
+ })
39
+ end
40
+ end
41
+ end
42
+
43
+ def setup_keepalives
44
+ @logger.debug('scheduling keepalives')
45
+ publish_keepalive
46
+ @timers[:run] << EM::PeriodicTimer.new(20) do
47
+ unless @state == :paused
48
+ publish_keepalive
49
+ end
50
+ end
51
+ end
52
+
53
+ def publish_result(check)
54
+ payload = {
55
+ :client => @settings[:client][:name],
56
+ :check => check
57
+ }
58
+ @logger.info('publishing check result', {
59
+ :payload => payload
60
+ })
61
+ @transport.publish(:direct, 'results', MultiJson.dump(payload)) do |info|
62
+ if info[:error]
63
+ @logger.error('failed to publish check result', {
64
+ :payload => payload,
65
+ :error => info[:error].to_s
66
+ })
67
+ end
68
+ end
69
+ end
70
+
71
+ def substitute_command_tokens(check)
72
+ unmatched_tokens = Array.new
73
+ substituted = check[:command].gsub(/:::([^:]*?):::/) do
74
+ token, default = $1.to_s.split('|', -1)
75
+ matched = token.split('.').inject(@settings[:client]) do |client, attribute|
76
+ if client[attribute].nil?
77
+ default.nil? ? break : default
78
+ else
79
+ client[attribute]
80
+ end
81
+ end
82
+ if matched.nil?
83
+ unmatched_tokens << token
84
+ end
85
+ matched
86
+ end
87
+ [substituted, unmatched_tokens]
88
+ end
89
+
90
+ def execute_check_command(check)
91
+ @logger.debug('attempting to execute check command', {
92
+ :check => check
93
+ })
94
+ unless @checks_in_progress.include?(check[:name])
95
+ @checks_in_progress << check[:name]
96
+ command, unmatched_tokens = substitute_command_tokens(check)
97
+ if unmatched_tokens.empty?
98
+ check[:executed] = Time.now.to_i
99
+ started = Time.now.to_f
100
+ Spawn.process(command, :timeout => check[:timeout]) do |output, status|
101
+ check[:duration] = ('%.3f' % (Time.now.to_f - started)).to_f
102
+ check[:output] = output
103
+ check[:status] = status
104
+ publish_result(check)
105
+ @checks_in_progress.delete(check[:name])
106
+ end
107
+ else
108
+ check[:output] = 'Unmatched command tokens: ' + unmatched_tokens.join(', ')
109
+ check[:status] = 3
110
+ check[:handle] = false
111
+ publish_result(check)
112
+ @checks_in_progress.delete(check[:name])
113
+ end
114
+ else
115
+ @logger.warn('previous check command execution in progress', {
116
+ :check => check
117
+ })
118
+ end
119
+ end
120
+
121
+ def run_check_extension(check)
122
+ @logger.debug('attempting to run check extension', {
123
+ :check => check
124
+ })
125
+ check[:executed] = Time.now.to_i
126
+ extension = @extensions[:checks][check[:name]]
127
+ extension.safe_run do |output, status|
128
+ check[:output] = output
129
+ check[:status] = status
130
+ publish_result(check)
131
+ end
132
+ end
133
+
134
+ def process_check(check)
135
+ @logger.debug('processing check', {
136
+ :check => check
137
+ })
138
+ if check.has_key?(:command)
139
+ if @settings.check_exists?(check[:name])
140
+ check.merge!(@settings[:checks][check[:name]])
141
+ execute_check_command(check)
142
+ elsif @safe_mode
143
+ check[:output] = 'Check is not locally defined (safe mode)'
144
+ check[:status] = 3
145
+ check[:handle] = false
146
+ check[:executed] = Time.now.to_i
147
+ publish_result(check)
148
+ else
149
+ execute_check_command(check)
150
+ end
151
+ else
152
+ if @extensions.check_exists?(check[:name])
153
+ run_check_extension(check)
154
+ else
155
+ @logger.warn('unknown check extension', {
156
+ :check => check
157
+ })
158
+ end
159
+ end
160
+ end
161
+
162
+ def setup_subscriptions
163
+ @logger.debug('subscribing to client subscriptions')
164
+ @settings[:client][:subscriptions].each do |subscription|
165
+ @logger.debug('subscribing to a subscription', {
166
+ :subscription => subscription
167
+ })
168
+ funnel = [@settings[:client][:name], VERSION, Time.now.to_i].join('-')
169
+ @transport.subscribe(:fanout, subscription, funnel) do |message_info, message|
170
+ check = MultiJson.load(message)
171
+ @logger.info('received check request', {
172
+ :check => check
173
+ })
174
+ process_check(check)
175
+ end
176
+ end
177
+ end
178
+
179
+ def schedule_checks(checks)
180
+ check_count = 0
181
+ stagger = testing? ? 0 : 2
182
+ checks.each do |check|
183
+ check_count += 1
184
+ scheduling_delay = stagger * check_count % 30
185
+ @timers[:run] << EM::Timer.new(scheduling_delay) do
186
+ interval = testing? ? 0.5 : check[:interval]
187
+ @timers[:run] << EM::PeriodicTimer.new(interval) do
188
+ unless @state == :paused
189
+ check[:issued] = Time.now.to_i
190
+ process_check(check.dup)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ def setup_standalone
198
+ @logger.debug('scheduling standalone checks')
199
+ standard_checks = @settings.checks.select do |check|
200
+ check[:standalone]
201
+ end
202
+ extension_checks = @extensions.checks.select do |check|
203
+ check[:standalone] && check[:interval].is_a?(Integer)
204
+ end
205
+ schedule_checks(standard_checks + extension_checks)
206
+ end
207
+
208
+ def setup_sockets
209
+ options = @settings[:client][:socket] || Hash.new
210
+ options[:bind] ||= '127.0.0.1'
211
+ options[:port] ||= 3030
212
+ @logger.debug('binding client tcp and udp sockets', {
213
+ :options => options
214
+ })
215
+ EM::start_server(options[:bind], options[:port], Socket) do |socket|
216
+ socket.logger = @logger
217
+ socket.settings = @settings
218
+ socket.transport = @transport
219
+ end
220
+ EM::open_datagram_socket(options[:bind], options[:port], Socket) do |socket|
221
+ socket.logger = @logger
222
+ socket.settings = @settings
223
+ socket.transport = @transport
224
+ socket.reply = false
225
+ end
226
+ end
227
+
228
+ def complete_checks_in_progress(&block)
229
+ @logger.info('completing checks in progress', {
230
+ :checks_in_progress => @checks_in_progress
231
+ })
232
+ retry_until_true do
233
+ if @checks_in_progress.empty?
234
+ block.call
235
+ true
236
+ end
237
+ end
238
+ end
239
+
240
+ def start
241
+ setup_transport
242
+ setup_keepalives
243
+ setup_subscriptions
244
+ setup_standalone
245
+ setup_sockets
246
+ super
247
+ end
248
+
249
+ def stop
250
+ @logger.warn('stopping')
251
+ @timers[:run].each do |timer|
252
+ timer.cancel
253
+ end
254
+ @transport.unsubscribe
255
+ complete_checks_in_progress do
256
+ @transport.close
257
+ super
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,9 @@
1
+ module Sensu
2
+ unless defined?(Sensu::VERSION)
3
+ VERSION = '0.13.0.alpha.2'
4
+
5
+ SEVERITIES = %w[ok warning critical unknown]
6
+
7
+ STOP_SIGNALS = %w[INT TERM]
8
+ end
9
+ end
@@ -0,0 +1,221 @@
1
+ require 'rubygems'
2
+
3
+ gem 'multi_json', '1.10.1'
4
+
5
+ gem 'sensu-em', '2.0.0'
6
+ gem 'sensu-logger', '0.0.1'
7
+ gem 'sensu-settings', '0.0.5'
8
+ gem 'sensu-extension', '0.0.3'
9
+ gem 'sensu-extensions', '0.0.5'
10
+ gem 'sensu-transport', '0.0.2'
11
+ gem 'sensu-spawn', '0.0.3'
12
+
13
+ require 'time'
14
+ require 'uri'
15
+
16
+ require 'sensu/logger'
17
+ require 'sensu/settings'
18
+ require 'sensu/extensions'
19
+ require 'sensu/transport'
20
+ require 'sensu/spawn'
21
+
22
+ require 'sensu/constants'
23
+ require 'sensu/utilities'
24
+ require 'sensu/cli'
25
+ require 'sensu/redis'
26
+
27
+ MultiJson.load_options = {:symbolize_keys => true}
28
+
29
+ module Sensu
30
+ module Daemon
31
+ include Utilities
32
+
33
+ attr_reader :state
34
+
35
+ def initialize(options={})
36
+ @state = :initializing
37
+ @timers = {
38
+ :run => Array.new
39
+ }
40
+ setup_logger(options)
41
+ load_settings(options)
42
+ load_extensions(options)
43
+ setup_process(options)
44
+ end
45
+
46
+ def setup_logger(options={})
47
+ @logger = Logger.get(options)
48
+ @logger.setup_signal_traps
49
+ end
50
+
51
+ def log_concerns(concerns=[], level=:warn)
52
+ concerns.each do |concern|
53
+ message = concern.delete(:message)
54
+ @logger.send(level, message, redact_sensitive(concern))
55
+ end
56
+ end
57
+
58
+ def load_settings(options={})
59
+ @settings = Settings.get(options)
60
+ log_concerns(@settings.warnings)
61
+ failures = @settings.validate
62
+ unless failures.empty?
63
+ @logger.fatal('invalid settings')
64
+ log_concerns(failures, :fatal)
65
+ @logger.fatal('SENSU NOT RUNNING!')
66
+ exit 2
67
+ end
68
+ end
69
+
70
+ def load_extensions(options={})
71
+ @extensions = Extensions.get(options)
72
+ log_concerns(@extensions.warnings)
73
+ extension_settings = @settings.to_hash.dup
74
+ @extensions.all.each do |extension|
75
+ extension.logger = @logger
76
+ extension.settings = extension_settings
77
+ end
78
+ end
79
+
80
+ def setup_process(options)
81
+ if options[:daemonize]
82
+ daemonize
83
+ end
84
+ if options[:pid_file]
85
+ write_pid(options[:pid_file])
86
+ end
87
+ end
88
+
89
+ def start
90
+ @state = :running
91
+ end
92
+
93
+ def pause
94
+ @state = :paused
95
+ end
96
+
97
+ def resume
98
+ @state = :running
99
+ end
100
+
101
+ def stop
102
+ @state = :stopped
103
+ @logger.warn('stopping reactor')
104
+ EM::stop_event_loop
105
+ end
106
+
107
+ def setup_signal_traps
108
+ @signals = Array.new
109
+ STOP_SIGNALS.each do |signal|
110
+ Signal.trap(signal) do
111
+ @signals << signal
112
+ end
113
+ end
114
+ EM::PeriodicTimer.new(1) do
115
+ signal = @signals.shift
116
+ if STOP_SIGNALS.include?(signal)
117
+ @logger.warn('received signal', {
118
+ :signal => signal
119
+ })
120
+ stop
121
+ end
122
+ end
123
+ end
124
+
125
+ def setup_transport
126
+ if @settings[:transport].is_a?(Hash)
127
+ transport_name = @settings[:transport][:name]
128
+ end
129
+ transport_name ||= 'rabbitmq'
130
+ transport_settings = @settings[transport_name.to_sym]
131
+ @logger.debug('connecting to transport', {
132
+ :name => transport_name,
133
+ :settings => transport_settings
134
+ })
135
+ @transport = Transport.connect(transport_name, transport_settings)
136
+ @transport.logger = @logger
137
+ @transport.on_error do |error|
138
+ @logger.fatal('transport connection error', {
139
+ :error => error.to_s
140
+ })
141
+ stop
142
+ end
143
+ @transport.before_reconnect do
144
+ unless testing?
145
+ @logger.warn('reconnecting to transport')
146
+ pause
147
+ end
148
+ end
149
+ @transport.after_reconnect do
150
+ @logger.info('reconnected to transport')
151
+ resume
152
+ end
153
+ end
154
+
155
+ def setup_redis
156
+ @logger.debug('connecting to redis', {
157
+ :settings => @settings[:redis]
158
+ })
159
+ @redis = Redis.connect(@settings[:redis])
160
+ @redis.on_error do |error|
161
+ @logger.fatal('redis connection error', {
162
+ :error => error.to_s
163
+ })
164
+ stop
165
+ end
166
+ @redis.before_reconnect do
167
+ unless testing?
168
+ @logger.warn('reconnecting to redis')
169
+ pause
170
+ end
171
+ end
172
+ @redis.after_reconnect do
173
+ @logger.info('reconnected to redis')
174
+ resume
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ def write_pid(file)
181
+ begin
182
+ File.open(file, 'w') do |pid_file|
183
+ pid_file.puts(Process.pid)
184
+ end
185
+ rescue
186
+ @logger.fatal('could not write to pid file', {
187
+ :pid_file => file
188
+ })
189
+ @logger.fatal('SENSU NOT RUNNING!')
190
+ exit 2
191
+ end
192
+ end
193
+
194
+ def daemonize
195
+ Kernel.srand
196
+ if Kernel.fork
197
+ exit
198
+ end
199
+ unless Process.setsid
200
+ @logger.fatal('cannot detach from controlling terminal')
201
+ @logger.fatal('SENSU NOT RUNNING!')
202
+ exit 2
203
+ end
204
+ Signal.trap('SIGHUP', 'IGNORE')
205
+ if Kernel.fork
206
+ exit
207
+ end
208
+ Dir.chdir('/')
209
+ ObjectSpace.each_object(IO) do |io|
210
+ unless [STDIN, STDOUT, STDERR].include?(io)
211
+ begin
212
+ unless io.closed?
213
+ io.close
214
+ end
215
+ rescue
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end