sensu 0.13.0.alpha.2-java

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