sensu 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,12 +33,14 @@ module Sensu
33
33
  # @param handler [Hash] definition.
34
34
  # @param event_data [Object] provided to the spawned handler
35
35
  # process via STDIN.
36
- def pipe_handler(handler, event_data)
36
+ # @param event_id [String] event UUID
37
+ def pipe_handler(handler, event_data, event_id)
37
38
  options = {:data => event_data, :timeout => handler[:timeout]}
38
39
  Spawn.process(handler[:command], options) do |output, status|
39
40
  log_level = status == 0 ? :info : :error
40
41
  @logger.send(log_level, "handler output", {
41
42
  :handler => handler,
43
+ :event => { :id => event_id },
42
44
  :output => output.split("\n+")
43
45
  })
44
46
  @in_progress[:events] -= 1 if @in_progress
@@ -121,10 +123,12 @@ module Sensu
121
123
  #
122
124
  # @param handler [Hash] definition.
123
125
  # @param event_data [Object] to pass to the handler extension.
124
- def handler_extension(handler, event_data)
126
+ # @param event_id [String] event UUID
127
+ def handler_extension(handler, event_data, event_id)
125
128
  handler.safe_run(event_data) do |output, status|
126
129
  @logger.info("handler extension output", {
127
130
  :extension => handler.definition,
131
+ :event => { :id => event_id },
128
132
  :output => output,
129
133
  :status => status
130
134
  })
@@ -137,10 +141,11 @@ module Sensu
137
141
  #
138
142
  # @param handler [Hash] definition.
139
143
  # @param event_data [Object] to pass to the handler type method.
140
- def handler_type_router(handler, event_data)
144
+ # @param event_id [String] event UUID
145
+ def handler_type_router(handler, event_data, event_id)
141
146
  case handler[:type]
142
147
  when "pipe"
143
- pipe_handler(handler, event_data)
148
+ pipe_handler(handler, event_data, event_id)
144
149
  when "tcp"
145
150
  tcp_handler(handler, event_data)
146
151
  when "udp"
@@ -148,7 +153,7 @@ module Sensu
148
153
  when "transport"
149
154
  transport_handler(handler, event_data)
150
155
  when "extension"
151
- handler_extension(handler, event_data)
156
+ handler_extension(handler, event_data, event_id)
152
157
  end
153
158
  end
154
159
 
@@ -159,13 +164,15 @@ module Sensu
159
164
  #
160
165
  # @param handler [Hash] definition.
161
166
  # @param event_data [Object] to pass to an event handler.
162
- def handle_event(handler, event_data)
167
+ # @param event_id [String] event UUID
168
+ def handle_event(handler, event_data, event_id)
163
169
  definition = handler.is_a?(Hash) ? handler : handler.definition
164
170
  @logger.debug("handling event", {
165
171
  :event_data => event_data,
172
+ :event => { :id => event_id },
166
173
  :handler => definition
167
174
  })
168
- handler_type_router(handler, event_data)
175
+ handler_type_router(handler, event_data, event_id)
169
176
  end
170
177
  end
171
178
  end
@@ -258,7 +258,7 @@ module Sensu
258
258
  @in_progress[:events] += 1
259
259
  filter_event(handler, event) do |event|
260
260
  mutate_event(handler, event) do |event_data|
261
- handle_event(handler, event_data)
261
+ handle_event(handler, event_data, event[:id])
262
262
  end
263
263
  end
264
264
  end
@@ -627,6 +627,7 @@ module Sensu
627
627
  end
628
628
  else
629
629
  client = create_client(client_key)
630
+ client[:type] = "proxy" if result[:check][:source]
630
631
  update_client_registry(client) do
631
632
  yield(client)
632
633
  end
@@ -766,57 +767,145 @@ module Sensu
766
767
  end
767
768
  end
768
769
 
769
- # Calculate a check execution splay, taking into account the
770
- # current time and the execution interval to ensure it's
770
+ # Create and publish one or more proxy check requests. This
771
+ # method iterates through the Sensu client registry for clients
772
+ # that matched provided proxy request client attributes. A proxy
773
+ # check request is created for each client in the registry that
774
+ # matches the proxy request client attributes. Proxy check
775
+ # requests have their client tokens subsituted by the associated
776
+ # client attributes values. The check requests are published to
777
+ # the Transport via `publish_check_request()`.
778
+ #
779
+ # @param check [Hash] definition.
780
+ def publish_proxy_check_requests(check)
781
+ client_attributes = check[:proxy_requests][:client_attributes]
782
+ unless client_attributes.empty?
783
+ @redis.smembers("clients") do |clients|
784
+ clients.each do |client_name|
785
+ @redis.get("client:#{client_name}") do |client_json|
786
+ unless client_json.nil?
787
+ client = Sensu::JSON.load(client_json)
788
+ if attributes_match?(client, client_attributes)
789
+ @logger.debug("creating a proxy check request", {
790
+ :client => client,
791
+ :check => check
792
+ })
793
+ proxy_check, unmatched_tokens = object_substitute_tokens(check.dup, client)
794
+ if unmatched_tokens.empty?
795
+ proxy_check[:source] ||= client[:name]
796
+ publish_check_request(proxy_check)
797
+ else
798
+ @logger.warn("failed to publish a proxy check request", {
799
+ :reason => "unmatched client tokens",
800
+ :unmatched_tokens => unmatched_tokens,
801
+ :client => client,
802
+ :check => check
803
+ })
804
+ end
805
+ end
806
+ end
807
+ end
808
+ end
809
+ end
810
+ end
811
+ end
812
+
813
+ # Create a check request proc, used to publish check requests to
814
+ # for a check to the Sensu transport. Check requests are not
815
+ # published if subdued. This method determines if a check uses
816
+ # proxy check requests and calls the appropriate check request
817
+ # publish method.
818
+ #
819
+ # @param check [Hash] definition.
820
+ def create_check_request_proc(check)
821
+ Proc.new do
822
+ unless check_subdued?(check)
823
+ if check[:proxy_requests]
824
+ publish_proxy_check_requests(check)
825
+ else
826
+ publish_check_request(check)
827
+ end
828
+ else
829
+ @logger.info("check request was subdued", :check => check)
830
+ end
831
+ end
832
+ end
833
+
834
+ # Schedule a check request, using the check cron. This method
835
+ # determines the time until the next cron time (in seconds) and
836
+ # creats an EventMachine timer for the request. This method will
837
+ # be called after every check cron request for subsequent
838
+ # requests. The timer is stored in the timer hash under
839
+ # `:leader`, as check request publishing is a task for only the
840
+ # Sensu server leader, so it can be cancelled etc. The check
841
+ # cron request timer object is removed from the timer hash after
842
+ # the request is published, to stop the timer hash from growing
843
+ # infinitely.
844
+ #
845
+ # @param check [Hash] definition.
846
+ def schedule_check_cron_request(check)
847
+ cron_time = determine_check_cron_time(check)
848
+ @timers[:leader] << Timer.new(cron_time) do |timer|
849
+ create_check_request_proc(check).call
850
+ @timers[:leader].delete(timer)
851
+ schedule_check_cron_request(check)
852
+ end
853
+ end
854
+
855
+ # Calculate a check request splay, taking into account the
856
+ # current time and the request interval to ensure it's
771
857
  # consistent between process restarts.
772
858
  #
773
859
  # @param check [Hash] definition.
774
- def calculate_check_execution_splay(check)
860
+ def calculate_check_request_splay(check)
775
861
  splay_hash = Digest::MD5.digest(check[:name]).unpack('Q<').first
776
862
  current_time = (Time.now.to_f * 1000).to_i
777
863
  (splay_hash - current_time) % (check[:interval] * 1000) / 1000.0
778
864
  end
779
865
 
780
- # Schedule check executions, using EventMachine periodic timers,
781
- # using a calculated execution splay. The timers are stored in
782
- # the timers hash under `:leader`, as check request publishing
783
- # is a task for only the Sensu server leader, so they can be
784
- # cancelled etc. Check requests are not published if subdued.
866
+ # Schedule check requests, using the check interval. This method
867
+ # using an intial calculated request splay EventMachine timer
868
+ # and an EventMachine periodic timer for subsequent check
869
+ # requests. The timers are stored in the timers hash under
870
+ # `:leader`, as check request publishing is a task for only the
871
+ # Sensu server leader, so they can be cancelled etc.
872
+ #
873
+ # @param check [Hash] definition.
874
+ def schedule_check_interval_requests(check)
875
+ request_splay = testing? ? 0 : calculate_check_request_splay(check)
876
+ interval = testing? ? 0.5 : check[:interval]
877
+ @timers[:leader] << Timer.new(request_splay) do
878
+ create_check_request = create_check_request_proc(check)
879
+ create_check_request.call
880
+ @timers[:leader] << PeriodicTimer.new(interval, &create_check_request)
881
+ end
882
+ end
883
+
884
+ # Schedule check requests. This method iterates through defined
885
+ # checks and uses the appropriate method of check request
886
+ # scheduling, either with the cron syntax or a numeric interval.
785
887
  #
786
888
  # @param checks [Array] of definitions.
787
- def schedule_check_executions(checks)
889
+ def schedule_checks(checks)
788
890
  checks.each do |check|
789
- create_check_request = Proc.new do
790
- unless check_subdued?(check)
791
- publish_check_request(check)
792
- else
793
- @logger.info("check request was subdued", :check => check)
794
- end
795
- end
796
- execution_splay = testing? ? 0 : calculate_check_execution_splay(check)
797
- interval = testing? ? 0.5 : check[:interval]
798
- @timers[:leader] << EM::Timer.new(execution_splay) do
799
- create_check_request.call
800
- @timers[:leader] << EM::PeriodicTimer.new(interval, &create_check_request)
891
+ if check[:cron]
892
+ schedule_check_cron_request(check)
893
+ else
894
+ schedule_check_interval_requests(check)
801
895
  end
802
896
  end
803
897
  end
804
898
 
805
899
  # Set up the check request publisher. This method creates an
806
900
  # array of check definitions, that are not standalone checks,
807
- # and do not have `:publish` set to `false`. The array of check
808
- # definitions includes those from standard checks and extensions
809
- # (with a defined execution `:interval`). The array is provided
810
- # to the `schedule_check_executions()` method.
901
+ # and do not have `:publish` set to `false`. The array is
902
+ # provided to the `schedule_checks()` method.
811
903
  def setup_check_request_publisher
812
904
  @logger.debug("scheduling check requests")
813
- standard_checks = @settings.checks.reject do |check|
905
+ checks = @settings.checks.reject do |check|
814
906
  check[:standalone] || check[:publish] == false
815
907
  end
816
- extension_checks = @extensions.checks.reject do |check|
817
- check[:standalone] || check[:publish] == false || !check[:interval].is_a?(Integer)
818
- end
819
- schedule_check_executions(standard_checks + extension_checks)
908
+ schedule_checks(checks)
820
909
  end
821
910
 
822
911
  # Publish a check result to the Transport for processing. A
@@ -948,7 +1037,7 @@ module Sensu
948
1037
  # stored in the timers hash under `:leader`.
949
1038
  def setup_client_monitor
950
1039
  @logger.debug("monitoring client keepalives")
951
- @timers[:leader] << EM::PeriodicTimer.new(30) do
1040
+ @timers[:leader] << PeriodicTimer.new(30) do
952
1041
  determine_stale_clients
953
1042
  end
954
1043
  end
@@ -994,7 +1083,7 @@ module Sensu
994
1083
  # is stored in the timers hash under `:leader`.
995
1084
  def setup_check_result_monitor(interval = 30)
996
1085
  @logger.debug("monitoring check results")
997
- @timers[:leader] << EM::PeriodicTimer.new(interval) do
1086
+ @timers[:leader] << PeriodicTimer.new(interval) do
998
1087
  determine_stale_check_results(interval)
999
1088
  end
1000
1089
  end
@@ -1018,24 +1107,24 @@ module Sensu
1018
1107
  (Time.now.to_f * 1000).to_i
1019
1108
  end
1020
1109
 
1021
- # Create/return the unique Sensu server leader ID for the
1022
- # current process.
1110
+ # Create/return the unique Sensu server ID for the current
1111
+ # process.
1023
1112
  #
1024
1113
  # @return [String]
1025
- def leader_id
1026
- @leader_id ||= random_uuid
1114
+ def server_id
1115
+ @server_id ||= random_uuid
1027
1116
  end
1028
1117
 
1029
1118
  # Become the Sensu server leader, responsible for specific
1030
1119
  # duties (`leader_duties()`). Unless the current process is
1031
1120
  # already the leader, this method sets the leader ID stored in
1032
- # Redis to the unique random leader ID for the process. If the
1121
+ # Redis to the unique random server ID for the process. If the
1033
1122
  # leader ID in Redis is successfully updated, `@is_leader` is
1034
1123
  # set to true and `leader_duties()` is called to begin the
1035
1124
  # tasks/duties of the Sensu server leader.
1036
1125
  def become_the_leader
1037
1126
  unless @is_leader
1038
- @redis.set("leader", leader_id) do
1127
+ @redis.set("leader", server_id) do
1039
1128
  @logger.info("i am now the leader")
1040
1129
  @is_leader = true
1041
1130
  leader_duties
@@ -1066,7 +1155,7 @@ module Sensu
1066
1155
  end
1067
1156
 
1068
1157
  # Updates the Sensu server leader lock timestamp. The current
1069
- # leader ID is retrieved from Redis and compared with the leader
1158
+ # leader ID is retrieved from Redis and compared with the server
1070
1159
  # ID of the current process to determine if it is still the
1071
1160
  # Sensu server leader. If the current process is still the
1072
1161
  # leader, the leader lock timestamp is updated. If the current
@@ -1075,7 +1164,7 @@ module Sensu
1075
1164
  # more than one leader.
1076
1165
  def update_leader_lock
1077
1166
  @redis.get("leader") do |current_leader_id|
1078
- if current_leader_id == leader_id
1167
+ if current_leader_id == server_id
1079
1168
  @redis.set("lock:leader", create_lock_timestamp) do
1080
1169
  @logger.debug("updated leader lock timestamp")
1081
1170
  end
@@ -1125,10 +1214,10 @@ module Sensu
1125
1214
  # every 10 seconds. The timers are stored in the timers hash
1126
1215
  # under `:run`.
1127
1216
  def setup_leader_monitor
1128
- @timers[:run] << EM::Timer.new(2) do
1217
+ @timers[:run] << Timer.new(2) do
1129
1218
  request_leader_election
1130
1219
  end
1131
- @timers[:run] << EM::PeriodicTimer.new(10) do
1220
+ @timers[:run] << PeriodicTimer.new(10) do
1132
1221
  if @is_leader
1133
1222
  update_leader_lock
1134
1223
  else
@@ -1137,6 +1226,52 @@ module Sensu
1137
1226
  end
1138
1227
  end
1139
1228
 
1229
+ # Update the Sensu server registry, stored in Redis. This method
1230
+ # adds the local/current Sensu server info to the registry,
1231
+ # including its id, hostname, address, if its the current
1232
+ # leader, and some metrics. Sensu server registry entries expire
1233
+ # in 30 seconds unless updated.
1234
+ #
1235
+ # @yield [success] passes success status to optional
1236
+ # callback/block.
1237
+ # @yieldparam success [TrueClass,FalseClass] indicating if the
1238
+ # server registry update was a success.
1239
+ def update_server_registry
1240
+ @logger.debug("updating the server registry")
1241
+ process_cpu_times do |cpu_user, cpu_system, _, _|
1242
+ info = {
1243
+ :id => server_id,
1244
+ :hostname => system_hostname,
1245
+ :address => system_address,
1246
+ :is_leader => @is_leader,
1247
+ :metrics => {
1248
+ :cpu => {
1249
+ :user => cpu_user,
1250
+ :system => cpu_system
1251
+ }
1252
+ },
1253
+ :timestamp => Time.now.to_i
1254
+ }
1255
+ @redis.sadd("servers", server_id)
1256
+ server_key = "server:#{server_id}"
1257
+ @redis.set(server_key, Sensu::JSON.dump(info)) do
1258
+ @redis.expire(server_key, 30)
1259
+ @logger.info("updated server registry", :server => info)
1260
+ yield(true) if block_given?
1261
+ end
1262
+ end
1263
+ end
1264
+
1265
+ # Set up the server registry updater. A periodic timer is
1266
+ # used to update the Sensu server info stored in Redis. The
1267
+ # timer is stored in the timers hash under `:run`.
1268
+ def setup_server_registry_updater
1269
+ update_server_registry
1270
+ @timers[:run] << PeriodicTimer.new(10) do
1271
+ update_server_registry
1272
+ end
1273
+ end
1274
+
1140
1275
  # Unsubscribe from transport subscriptions (all of them). This
1141
1276
  # method is called when there are issues with connectivity, or
1142
1277
  # the process is stopping.
@@ -1169,6 +1304,7 @@ module Sensu
1169
1304
  setup_keepalives
1170
1305
  setup_results
1171
1306
  setup_leader_monitor
1307
+ setup_server_registry_updater
1172
1308
  @state = :running
1173
1309
  end
1174
1310
 
@@ -18,7 +18,7 @@ module Sensu
18
18
  #
19
19
  # @param timeout [Numeric] in seconds.
20
20
  def set_timeout(timeout)
21
- @timeout_timer = EM::Timer.new(timeout) do
21
+ @timeout_timer = Timer.new(timeout) do
22
22
  @timed_out = true
23
23
  close_connection
24
24
  end
@@ -0,0 +1,26 @@
1
+ require "eventmachine"
2
+
3
+ module Sensu
4
+ class Timer < EventMachine::Timer; end
5
+
6
+ # This fix comes from http://soohwan.blogspot.ca/2011/02/fix-eventmachineperiodictimer.html
7
+ class PeriodicTimer < EventMachine::PeriodicTimer
8
+ alias :original_initialize :initialize
9
+ alias :original_schedule :schedule
10
+
11
+ # Record initial start time and the fixed interval, used for
12
+ # compensating for timer drift when scheduling the next call.
13
+ def initialize(interval, callback=nil, &block)
14
+ @start = Time.now
15
+ @fixed_interval = interval
16
+ original_initialize(interval, callback, &block)
17
+ end
18
+
19
+ # Calculate the timer drift and compensate for it.
20
+ def schedule
21
+ compensation = (Time.now - @start) % @fixed_interval
22
+ @interval = @fixed_interval - compensation
23
+ original_schedule
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,14 @@
1
+ gem "parse-cron", "0.1.4"
2
+
1
3
  require "securerandom"
4
+ require "sensu/sandbox"
5
+ require "parse-cron"
6
+ require "socket"
2
7
 
3
8
  module Sensu
4
9
  module Utilities
10
+ EVAL_PREFIX = "eval:".freeze
11
+
5
12
  # Determine if Sensu is being tested, using the process name.
6
13
  # Sensu is being test if the process name is "rspec",
7
14
  #
@@ -16,7 +23,7 @@ module Sensu
16
23
  # @param wait [Numeric] time to delay block calls.
17
24
  # @param block [Proc] to call that needs to return true.
18
25
  def retry_until_true(wait=0.5, &block)
19
- EM::Timer.new(wait) do
26
+ Timer.new(wait) do
20
27
  unless block.call
21
28
  retry_until_true(wait, &block)
22
29
  end
@@ -36,7 +43,7 @@ module Sensu
36
43
  when hash_one[key].is_a?(Hash) && value.is_a?(Hash)
37
44
  deep_merge(hash_one[key], value)
38
45
  when hash_one[key].is_a?(Array) && value.is_a?(Array)
39
- hash_one[key].concat(value).uniq
46
+ (hash_one[key] + value).uniq
40
47
  else
41
48
  value
42
49
  end
@@ -44,6 +51,37 @@ module Sensu
44
51
  merged
45
52
  end
46
53
 
54
+ # Retrieve the system hostname. If the hostname cannot be
55
+ # determined and an error is thrown, `nil` will be returned.
56
+ #
57
+ # @return [String] system hostname.
58
+ def system_hostname
59
+ ::Socket.gethostname rescue nil
60
+ end
61
+
62
+ # Retrieve the system IP address. If a valid non-loopback
63
+ # IPv4 address cannot be found and an error is thrown,
64
+ # `nil` will be returned.
65
+ #
66
+ # @return [String] system ip address
67
+ def system_address
68
+ ::Socket.ip_address_list.find { |address|
69
+ address.ipv4? && !address.ipv4_loopback?
70
+ }.ip_address rescue nil
71
+ end
72
+
73
+ # Retrieve the process CPU times. If the cpu times cannot be
74
+ # determined and an error is thrown, `[nil, nil, nil, nil]` will
75
+ # be returned.
76
+ #
77
+ # @return [Array] CPU times: utime, stime, cutime, cstime
78
+ def process_cpu_times(&callback)
79
+ determine_cpu_times = Proc.new do
80
+ ::Process.times.to_a rescue [nil, nil, nil, nil]
81
+ end
82
+ EM::defer(determine_cpu_times, callback)
83
+ end
84
+
47
85
  # Generate a random universally unique identifier.
48
86
  #
49
87
  # @return [String] random UUID.
@@ -124,6 +162,124 @@ module Sensu
124
162
  [substituted, unmatched_tokens]
125
163
  end
126
164
 
165
+ # Perform token substitution for an object. String values are
166
+ # passed to `substitute_tokens()`, arrays and sub-hashes are
167
+ # processed recursively. Numeric values are ignored.
168
+ #
169
+ # @param object [Object]
170
+ # @param attributes [Hash]
171
+ # @return [Array] containing the updated object with substituted
172
+ # values and an array of unmatched tokens.
173
+ def object_substitute_tokens(object, attributes)
174
+ unmatched_tokens = []
175
+ case object
176
+ when Hash
177
+ object.each do |key, value|
178
+ object[key], unmatched = object_substitute_tokens(value, attributes)
179
+ unmatched_tokens.push(*unmatched)
180
+ end
181
+ when Array
182
+ object.map! do |value|
183
+ value, unmatched = object_substitute_tokens(value, attributes)
184
+ unmatched_tokens.push(*unmatched)
185
+ value
186
+ end
187
+ when String
188
+ object, unmatched_tokens = substitute_tokens(object, attributes)
189
+ end
190
+ [object, unmatched_tokens.uniq]
191
+ end
192
+
193
+ # Process an eval attribute value, a Ruby `eval()` string
194
+ # containing an expression to be evaluated within the
195
+ # scope/context of a sandbox. This methods strips away the
196
+ # expression prefix, `eval:`, and substitues any dot notation
197
+ # tokens with the corresponding event data values. If there are
198
+ # unmatched tokens, this method will return `nil`.
199
+ #
200
+ # @object [Hash]
201
+ # @raw_eval_string [String]
202
+ # @return [String] processed eval string.
203
+ def process_eval_string(object, raw_eval_string)
204
+ eval_string = raw_eval_string.slice(5..-1)
205
+ eval_string, unmatched_tokens = substitute_tokens(eval_string, object)
206
+ if unmatched_tokens.empty?
207
+ eval_string
208
+ else
209
+ @logger.error("attribute value eval unmatched tokens", {
210
+ :object => object,
211
+ :raw_eval_string => raw_eval_string,
212
+ :unmatched_tokens => unmatched_tokens
213
+ })
214
+ nil
215
+ end
216
+ end
217
+
218
+ # Ruby `eval()` a string containing an expression, within the
219
+ # scope/context of a sandbox. This method is for attribute values
220
+ # starting with "eval:", with the Ruby expression following the
221
+ # colon. A single variable is provided to the expression, `value`,
222
+ # equal to the corresponding object attribute value. Dot notation
223
+ # tokens in the expression, e.g. `:::mysql.user:::`, are
224
+ # substituted with the corresponding object attribute values prior
225
+ # to evaluation. The expression is expected to return a boolean
226
+ # value.
227
+ #
228
+ # @param object [Hash]
229
+ # @param raw_eval_string [String] containing the Ruby
230
+ # expression to be evaluated.
231
+ # @param raw_value [Object] of the corresponding object
232
+ # attribute value.
233
+ # @return [TrueClass, FalseClass]
234
+ def eval_attribute_value(object, raw_eval_string, raw_value)
235
+ eval_string = process_eval_string(object, raw_eval_string)
236
+ unless eval_string.nil?
237
+ begin
238
+ value = Marshal.load(Marshal.dump(raw_value))
239
+ !!Sandbox.eval(eval_string, value)
240
+ rescue StandardError, SyntaxError => error
241
+ @logger.error("attribute value eval error", {
242
+ :object => object,
243
+ :raw_eval_string => raw_eval_string,
244
+ :raw_value => raw_value,
245
+ :error => error.to_s
246
+ })
247
+ false
248
+ end
249
+ else
250
+ false
251
+ end
252
+ end
253
+
254
+ # Determine if all attribute values match those of the
255
+ # corresponding object attributes. Attributes match if the value
256
+ # objects are equivalent, are both hashes with matching key/value
257
+ # pairs (recursive), have equal string values, or evaluate to true
258
+ # (Ruby eval).
259
+ #
260
+ # @param object [Hash]
261
+ # @param match_attributes [Object]
262
+ # @param object_attributes [Object]
263
+ # @return [TrueClass, FalseClass]
264
+ def attributes_match?(object, match_attributes, object_attributes=nil)
265
+ object_attributes ||= object
266
+ match_attributes.all? do |key, value_one|
267
+ value_two = object_attributes[key]
268
+ case
269
+ when value_one == value_two
270
+ true
271
+ when value_one.is_a?(Hash) && value_two.is_a?(Hash)
272
+ attributes_match?(object, value_one, value_two)
273
+ when value_one.to_s == value_two.to_s
274
+ true
275
+ when value_one.is_a?(String) && value_one.start_with?(EVAL_PREFIX)
276
+ eval_attribute_value(object, value_one, value_two)
277
+ else
278
+ false
279
+ end
280
+ end
281
+ end
282
+
127
283
  # Determine if the current time falls within a time window. The
128
284
  # provided condition must have a `:begin` and `:end` time, eg.
129
285
  # "11:30:00 PM", or `false` will be returned.
@@ -189,5 +345,15 @@ module Sensu
189
345
  false
190
346
  end
191
347
  end
348
+
349
+ # Determine the next check cron time.
350
+ #
351
+ # @param check [Hash] definition.
352
+ def determine_check_cron_time(check)
353
+ cron_parser = CronParser.new(check[:cron])
354
+ current_time = Time.now
355
+ next_cron_time = cron_parser.next(current_time)
356
+ next_cron_time - current_time
357
+ end
192
358
  end
193
359
  end