sensu 0.27.1 → 0.28.0

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