sensu-redis 0.1.6 → 0.1.7
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.
- checksums.yaml +4 -4
- data/lib/sensu/redis/{constants.rb → client/constants.rb} +1 -1
- data/lib/sensu/redis/{errors.rb → client/errors.rb} +0 -0
- data/lib/sensu/redis/client.rb +361 -0
- data/lib/sensu/redis.rb +22 -33
- data/sensu-redis.gemspec +1 -1
- metadata +4 -8
- data/lib/sensu/redis/commands.rb +0 -104
- data/lib/sensu/redis/connection.rb +0 -158
- data/lib/sensu/redis/parser.rb +0 -66
- data/lib/sensu/redis/processor.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79f8dbe376c6ccdf3c2116339f85c2b6553ed6fb
|
4
|
+
data.tar.gz: 01c24f59bb0ab213c92fe66ce893633698cf76ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f35de6079773cfde302fd3e501469fa56f4b687afb971469c4cc2f9ac729c2e41a8caa608721365ec251107748addcd51875791e03e69486437c99b526d1b871
|
7
|
+
data.tar.gz: 5c4877df053f59c9af1aa277b3d9bf8ef595e1b88163c9c5a00a1e4585356efa221e80019864e89d6753e7e2c5c2626da40de09afcedd2666433ee4d956e8923
|
File without changes
|
@@ -0,0 +1,361 @@
|
|
1
|
+
require "sensu/redis/client/constants"
|
2
|
+
require "sensu/redis/client/errors"
|
3
|
+
require "eventmachine"
|
4
|
+
|
5
|
+
module Sensu
|
6
|
+
module Redis
|
7
|
+
class Client < EM::Connection
|
8
|
+
include EM::Deferrable
|
9
|
+
|
10
|
+
# Initialize the connection, creating the Redis command methods,
|
11
|
+
# and setting the default connection options and callbacks.
|
12
|
+
def initialize(options={})
|
13
|
+
create_command_methods!
|
14
|
+
@host = options[:host]
|
15
|
+
@port = options[:port]
|
16
|
+
@db = (options[:db] || 0).to_i
|
17
|
+
@password = options[:password]
|
18
|
+
@auto_reconnect = options.fetch(:auto_reconnect, true)
|
19
|
+
@reconnect_on_error = options.fetch(:reconnect_on_error, true)
|
20
|
+
@error_callback = lambda do |error|
|
21
|
+
raise(error)
|
22
|
+
end
|
23
|
+
@reconnect_callbacks = {
|
24
|
+
:before => lambda {},
|
25
|
+
:after => lambda {}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the connection error callback. This callback is called
|
30
|
+
# when the connection encounters either a connection, protocol,
|
31
|
+
# or command error.
|
32
|
+
def on_error(&block)
|
33
|
+
@error_callback = block
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set the connection before reconnect callback. This callback is
|
37
|
+
# called after the connection closes but before a reconnect is
|
38
|
+
# attempted.
|
39
|
+
def before_reconnect(&block)
|
40
|
+
@reconnect_callbacks[:before] = block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the connection after reconnect callback. This callback is
|
44
|
+
# called after a successful reconnect, after the connection has
|
45
|
+
# been validated.
|
46
|
+
def after_reconnect(&block)
|
47
|
+
@reconnect_callbacks[:after] = block
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create an error and pass it to the connection error callback.
|
51
|
+
#
|
52
|
+
# @param klass [Class]
|
53
|
+
# @param message [String]
|
54
|
+
def error(klass, message)
|
55
|
+
redis_error = klass.new(message)
|
56
|
+
@error_callback.call(redis_error)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Determine if the connection is connected to Redis.
|
60
|
+
def connected?
|
61
|
+
@connected || false
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reconnect to Redis. The before reconnect callback is first
|
65
|
+
# called if not already reconnecting. This method uses a 1
|
66
|
+
# second delay before attempting a reconnect.
|
67
|
+
def reconnect!
|
68
|
+
@reconnect_callbacks[:before].call unless @reconnecting
|
69
|
+
@reconnecting = true
|
70
|
+
EM.add_timer(1) do
|
71
|
+
reconnect(@host, @port)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Close the Redis connection after writing the current
|
76
|
+
# Redis command data.
|
77
|
+
def close
|
78
|
+
@closing = true
|
79
|
+
close_connection_after_writing
|
80
|
+
end
|
81
|
+
|
82
|
+
# This method is called by EM when the connection closes, either
|
83
|
+
# intentionally or unexpectedly. This method is reponsible for
|
84
|
+
# starting the reconnect process when appropriate.
|
85
|
+
def unbind
|
86
|
+
@deferred_status = nil
|
87
|
+
if @closing
|
88
|
+
@reconnecting = false
|
89
|
+
elsif ((@connected || @reconnecting) && @auto_reconnect) || @reconnect_on_error
|
90
|
+
reconnect!
|
91
|
+
elsif @connected
|
92
|
+
error(ConnectionError, "connection closed")
|
93
|
+
else
|
94
|
+
error(ConnectionError, "unable to connect to redis server")
|
95
|
+
end
|
96
|
+
@connected = false
|
97
|
+
end
|
98
|
+
|
99
|
+
# Determine the byte size of a string.
|
100
|
+
#
|
101
|
+
# @param string [String]
|
102
|
+
# @return [Integer] string byte size.
|
103
|
+
def get_size(string)
|
104
|
+
string.respond_to?(:bytesize) ? string.bytesize : string.size
|
105
|
+
end
|
106
|
+
|
107
|
+
# Send a Redis command using RESP multi bulk. This method sends
|
108
|
+
# data to Redis using EM connection `send_data()`.
|
109
|
+
#
|
110
|
+
# @params [Array<Object>] *arguments
|
111
|
+
def send_command_data(*arguments)
|
112
|
+
data = "*#{arguments.size}#{DELIM}"
|
113
|
+
arguments.each do |value|
|
114
|
+
value = value.to_s
|
115
|
+
data << "$#{get_size(value)}#{DELIM}#{value}#{DELIM}"
|
116
|
+
end
|
117
|
+
send_data(data)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Send a Redis command and queue the associated response
|
121
|
+
# callback. This method calls `send_command_data()` for RESP
|
122
|
+
# multi bulk and transmission.
|
123
|
+
#
|
124
|
+
# @params command [String]
|
125
|
+
# @params [Array<Object>] *arguments
|
126
|
+
# @yield command reponse callback
|
127
|
+
def send_command(command, *arguments, &block)
|
128
|
+
send_command_data(command, *arguments)
|
129
|
+
@response_callbacks << [RESPONSE_PROCESSORS[command], block]
|
130
|
+
end
|
131
|
+
|
132
|
+
# Send a Redis command once the Redis connection has been
|
133
|
+
# established (EM Deferable succeeded).
|
134
|
+
#
|
135
|
+
# @params command [String]
|
136
|
+
# @params [Array<Object>] *arguments
|
137
|
+
# @yield command reponse callback
|
138
|
+
def redis_command(command, *arguments, &block)
|
139
|
+
if @deferred_status == :succeeded
|
140
|
+
send_command(command, *arguments, &block)
|
141
|
+
else
|
142
|
+
callback do
|
143
|
+
send_command(command, *arguments, &block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Create Redis command methods. Command methods wrap
|
149
|
+
# `redis_command()`. This method is called by `initialize()`.
|
150
|
+
def create_command_methods!
|
151
|
+
COMMANDS.each do |command|
|
152
|
+
self.class.send(:define_method, command.to_sym) do |*arguments, &block|
|
153
|
+
redis_command(command, *arguments, &block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Subscribe to a Redis PubSub channel.
|
159
|
+
#
|
160
|
+
# @param channel [String]
|
161
|
+
# @yield channel message callback
|
162
|
+
def subscribe(channel, &block)
|
163
|
+
@pubsub_callbacks ||= Hash.new([])
|
164
|
+
@pubsub_callbacks[channel] << block
|
165
|
+
redis_command(SUBSCRIBE_COMMAND, channel, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Unsubscribe to one or more Redis PubSub channels. If a channel
|
169
|
+
# is provided, this method will unsubscribe from it. If a
|
170
|
+
# channel is not provided, this method will unsubscribe from all
|
171
|
+
# Redis PubSub channels.
|
172
|
+
#
|
173
|
+
# @param channel [String]
|
174
|
+
# @yield unsubscribe callback
|
175
|
+
def unsubscribe(channel=nil, &block)
|
176
|
+
@pubsub_callbacks ||= Hash.new([])
|
177
|
+
arguments = [UNSUBSCRIBE_COMMAND]
|
178
|
+
if channel
|
179
|
+
@pubsub_callbacks[channel] = [block]
|
180
|
+
arguments << channel
|
181
|
+
else
|
182
|
+
@pubsub_callbacks.each_key do |key|
|
183
|
+
@pubsub_callbacks[key] = [block]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
redis_command(arguments)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Authenticate to Redis if a password has been set in the
|
190
|
+
# connection options. This method uses `send_command()`
|
191
|
+
# directly, as it assumes that the connection has been
|
192
|
+
# established. Redis authentication must be done prior to
|
193
|
+
# issuing other Redis commands.
|
194
|
+
#
|
195
|
+
# @yield the callback called once authenticated.
|
196
|
+
def authenticate
|
197
|
+
if @password
|
198
|
+
send_command(AUTH_COMMAND, @password) do |authenticated|
|
199
|
+
if authenticated
|
200
|
+
yield if block_given?
|
201
|
+
else
|
202
|
+
error(ConnectionError, "redis authenticate failed")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
else
|
206
|
+
yield if block_given?
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Select a Redis DB if a DB has been set in the connection
|
211
|
+
# options. This method (& Redis command) does not require a
|
212
|
+
# response callback.
|
213
|
+
def select_db
|
214
|
+
send_command(SELECT_COMMAND, @db) if @db
|
215
|
+
end
|
216
|
+
|
217
|
+
# Verify the version of Redis. Redis >= 2.0 RC 1 is required for
|
218
|
+
# certain Redis commands that Sensu uses. A connection error is
|
219
|
+
# created if the Redis version does not meet the requirements.
|
220
|
+
#
|
221
|
+
# @yield the callback called once verified.
|
222
|
+
def verify_version
|
223
|
+
send_command(INFO_COMMAND) do |redis_info|
|
224
|
+
if redis_info[:redis_version] < "1.3.14"
|
225
|
+
error(ConnectionError, "redis version must be >= 2.0 RC 1")
|
226
|
+
else
|
227
|
+
yield if block_given?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# This method is called by EM when the connection is
|
233
|
+
# established. This method is reponsible for validating the
|
234
|
+
# connection before Redis commands can be sent.
|
235
|
+
def connection_completed
|
236
|
+
@response_callbacks = []
|
237
|
+
@multibulk_count = false
|
238
|
+
@connected = true
|
239
|
+
authenticate do
|
240
|
+
select_db
|
241
|
+
verify_version do
|
242
|
+
succeed
|
243
|
+
@reconnect_callbacks[:after].call if @reconnecting
|
244
|
+
@reconnecting = false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Begin a multi bulk response array for an expected number of
|
250
|
+
# responses. Using this method causes `dispatch_response()` to
|
251
|
+
# wait until all of the expected responses have been added to
|
252
|
+
# the array, before the Redis command reponse callback is
|
253
|
+
# called.
|
254
|
+
#
|
255
|
+
# @param multibulk_count [Integer] number of expected responses.
|
256
|
+
def begin_multibulk(multibulk_count)
|
257
|
+
@multibulk_count = multibulk_count
|
258
|
+
@multibulk_values = []
|
259
|
+
end
|
260
|
+
|
261
|
+
# Dispatch a Redis error, dropping the associated Redis command
|
262
|
+
# response callback, and passing a Redis error object to the
|
263
|
+
# error callback (if set).
|
264
|
+
#
|
265
|
+
# @param code [String] Redis error code.
|
266
|
+
def dispatch_error(code)
|
267
|
+
@response_callbacks.shift
|
268
|
+
error(CommandError, code)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Dispatch a response. If a multi bulk response has begun, this
|
272
|
+
# method will build the completed response array before the
|
273
|
+
# associated Redis command response callback is called. If one
|
274
|
+
# or more pubsub callbacks are defined, the approprate pubsub
|
275
|
+
# callbacks are called, provided with the pubsub response. Redis
|
276
|
+
# command response callbacks may have an optional processor
|
277
|
+
# block, responsible for producing a value with the correct
|
278
|
+
# type, e.g. "1" -> true (boolean).
|
279
|
+
#
|
280
|
+
# @param value [Object]
|
281
|
+
def dispatch_response(value)
|
282
|
+
if @multibulk_count
|
283
|
+
@multibulk_values << value
|
284
|
+
@multibulk_count -= 1
|
285
|
+
if @multibulk_count == 0
|
286
|
+
value = @multibulk_values
|
287
|
+
@multibulk_count = false
|
288
|
+
else
|
289
|
+
return
|
290
|
+
end
|
291
|
+
end
|
292
|
+
if @pubsub_callbacks && value.is_a?(Array)
|
293
|
+
if PUBSUB_RESPONSES.include?(value[0])
|
294
|
+
@pubsub_callbacks[value[1]].each do |block|
|
295
|
+
block.call(*value) if block
|
296
|
+
end
|
297
|
+
return
|
298
|
+
end
|
299
|
+
end
|
300
|
+
processor, block = @response_callbacks.shift
|
301
|
+
if block
|
302
|
+
value = processor.call(value) if processor
|
303
|
+
block.call(value)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Parse a RESP line. This method is called by `receive_data()`.
|
308
|
+
# You can read about RESP @ http://redis.io/topics/protocol
|
309
|
+
#
|
310
|
+
# @param line [String]
|
311
|
+
def parse_resp_line(line)
|
312
|
+
# Trim off the response type and delimiter (\r\n).
|
313
|
+
response = line.slice(1..-3)
|
314
|
+
# First character indicates response type.
|
315
|
+
case line[0, 1]
|
316
|
+
when MINUS # Error, e.g. -ERR
|
317
|
+
dispatch_error(response)
|
318
|
+
when PLUS # String, e.g. +OK
|
319
|
+
dispatch_response(response)
|
320
|
+
when DOLLAR # Bulk string, e.g. $3\r\nfoo\r\n
|
321
|
+
response_size = Integer(response)
|
322
|
+
if response_size == -1 # No data, return nil.
|
323
|
+
dispatch_response(nil)
|
324
|
+
elsif @buffer.size >= response_size + 2 # Complete data.
|
325
|
+
dispatch_response(@buffer.slice!(0, response_size))
|
326
|
+
@buffer.slice!(0,2) # Discard delimeter (\r\n).
|
327
|
+
else # Incomplete, have data pushed back into buffer.
|
328
|
+
return INCOMPLETE
|
329
|
+
end
|
330
|
+
when COLON # Integer, e.g. :8
|
331
|
+
dispatch_response(Integer(response))
|
332
|
+
when ASTERISK # Array, e.g. *2\r\n$3\r\foo\r\n$3\r\nbar\r\n
|
333
|
+
multibulk_count = Integer(response)
|
334
|
+
if multibulk_count == -1 || multibulk_count == 0 # No data, return [].
|
335
|
+
dispatch_response([])
|
336
|
+
else
|
337
|
+
begin_multibulk(multibulk_count) # Accumulate responses.
|
338
|
+
end
|
339
|
+
else
|
340
|
+
error(ProtocolError, "response type not recognized: #{line.strip}")
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# This method is called by EM when the connection receives data.
|
345
|
+
# This method assumes that the incoming data is using RESP and
|
346
|
+
# it is parsed by `parse_resp_line()`.
|
347
|
+
#
|
348
|
+
# @param data [String]
|
349
|
+
def receive_data(data)
|
350
|
+
(@buffer ||= '') << data
|
351
|
+
while index = @buffer.index(DELIM)
|
352
|
+
line = @buffer.slice!(0, index+2)
|
353
|
+
if parse_resp_line(line) == INCOMPLETE
|
354
|
+
@buffer[0...0] = line
|
355
|
+
break
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
data/lib/sensu/redis.rb
CHANGED
@@ -1,45 +1,34 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
require "sensu/redis/
|
3
|
-
require "sensu/redis/processor"
|
4
|
-
require "sensu/redis/commands"
|
5
|
-
require "sensu/redis/connection"
|
2
|
+
require "sensu/redis/client"
|
6
3
|
require "eventmachine"
|
7
4
|
require "uri"
|
8
5
|
|
9
6
|
module Sensu
|
10
7
|
module Redis
|
11
|
-
class
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
{
|
23
|
-
:host => uri.host,
|
24
|
-
:port => uri.port,
|
25
|
-
:password => uri.password
|
26
|
-
}
|
27
|
-
rescue
|
28
|
-
raise ArgumentError, "invalid redis url"
|
29
|
-
end
|
8
|
+
class << self
|
9
|
+
def parse_url(url)
|
10
|
+
begin
|
11
|
+
uri = URI.parse(url)
|
12
|
+
{
|
13
|
+
:host => uri.host,
|
14
|
+
:port => uri.port,
|
15
|
+
:password => uri.password
|
16
|
+
}
|
17
|
+
rescue
|
18
|
+
raise ArgumentError, "invalid redis url"
|
30
19
|
end
|
20
|
+
end
|
31
21
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
options[:host] ||= "127.0.0.1"
|
40
|
-
options[:port] = (options[:port] || 6379).to_i
|
41
|
-
EM.connect(options[:host], options[:port], self, options)
|
22
|
+
def connect(options={})
|
23
|
+
case options
|
24
|
+
when String
|
25
|
+
options = parse_url(options)
|
26
|
+
when nil
|
27
|
+
options = {}
|
42
28
|
end
|
29
|
+
options[:host] ||= "127.0.0.1"
|
30
|
+
options[:port] = (options[:port] || 6379).to_i
|
31
|
+
EM.connect(options[:host], options[:port], Sensu::Redis::Client, options)
|
43
32
|
end
|
44
33
|
end
|
45
34
|
end
|
data/sensu-redis.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sensu-redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Porter
|
@@ -97,12 +97,9 @@ files:
|
|
97
97
|
- bin/console
|
98
98
|
- bin/setup
|
99
99
|
- lib/sensu/redis.rb
|
100
|
-
- lib/sensu/redis/
|
101
|
-
- lib/sensu/redis/
|
102
|
-
- lib/sensu/redis/
|
103
|
-
- lib/sensu/redis/errors.rb
|
104
|
-
- lib/sensu/redis/parser.rb
|
105
|
-
- lib/sensu/redis/processor.rb
|
100
|
+
- lib/sensu/redis/client.rb
|
101
|
+
- lib/sensu/redis/client/constants.rb
|
102
|
+
- lib/sensu/redis/client/errors.rb
|
106
103
|
- sensu-redis.gemspec
|
107
104
|
homepage: https://github.com/sensu/sensu-redis
|
108
105
|
licenses:
|
@@ -129,4 +126,3 @@ signing_key:
|
|
129
126
|
specification_version: 4
|
130
127
|
summary: The Sensu Redis client library
|
131
128
|
test_files: []
|
132
|
-
has_rdoc:
|
data/lib/sensu/redis/commands.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
require "sensu/redis/constants"
|
2
|
-
require "sensu/redis/errors"
|
3
|
-
|
4
|
-
module Sensu
|
5
|
-
module Redis
|
6
|
-
# Sensu Module for requesting Redis commands, intended to be
|
7
|
-
# included by Sensu::Redis::Client.
|
8
|
-
#
|
9
|
-
# You can read about RESP @ http://redis.io/topics/protocol
|
10
|
-
module Commands
|
11
|
-
# Determine the byte size of a string.
|
12
|
-
#
|
13
|
-
# @param string [String]
|
14
|
-
# @return [Integer] string byte size.
|
15
|
-
def get_size(string)
|
16
|
-
string.respond_to?(:bytesize) ? string.bytesize : string.size
|
17
|
-
end
|
18
|
-
|
19
|
-
# Send a Redis command using RESP multi bulk. This method sends
|
20
|
-
# data to Redis using EM connection `send_data()`.
|
21
|
-
#
|
22
|
-
# @params [Array<Object>] *arguments
|
23
|
-
def send_command_data(*arguments)
|
24
|
-
data = "*#{arguments.size}#{DELIM}"
|
25
|
-
arguments.each do |value|
|
26
|
-
value = value.to_s
|
27
|
-
data << "$#{get_size(value)}#{DELIM}#{value}#{DELIM}"
|
28
|
-
end
|
29
|
-
send_data(data)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Send a Redis command and queue the associated response
|
33
|
-
# callback. This method calls `send_command_data()` for RESP
|
34
|
-
# multi bulk and transmission.
|
35
|
-
#
|
36
|
-
# @params command [String]
|
37
|
-
# @params [Array<Object>] *arguments
|
38
|
-
# @yield command reponse callback
|
39
|
-
def send_command(command, *arguments, &block)
|
40
|
-
@response_callbacks ||= []
|
41
|
-
send_command_data(command, *arguments)
|
42
|
-
@response_callbacks << [RESPONSE_PROCESSORS[command], block]
|
43
|
-
end
|
44
|
-
|
45
|
-
# Send a Redis command once the Redis connection has been
|
46
|
-
# established (EM Deferable succeeded).
|
47
|
-
#
|
48
|
-
# @params command [String]
|
49
|
-
# @params [Array<Object>] *arguments
|
50
|
-
# @yield command reponse callback
|
51
|
-
def redis_command(command, *arguments, &block)
|
52
|
-
if @deferred_status == :succeeded
|
53
|
-
send_command(command, *arguments, &block)
|
54
|
-
else
|
55
|
-
callback do
|
56
|
-
send_command(command, *arguments, &block)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Create Redis command methods. Command methods wrap
|
62
|
-
# `redis_command()`. This method MUST be called in
|
63
|
-
# `initialize()`.
|
64
|
-
def create_command_methods!
|
65
|
-
REDIS_COMMANDS.each do |command|
|
66
|
-
self.class.send(:define_method, command.to_sym) do |*arguments, &block|
|
67
|
-
redis_command(command, *arguments, &block)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Subscribe to a Redis PubSub channel.
|
73
|
-
#
|
74
|
-
# @param channel [String]
|
75
|
-
# @yield channel message callback
|
76
|
-
def subscribe(channel, &block)
|
77
|
-
@pubsub_callbacks ||= Hash.new([])
|
78
|
-
@pubsub_callbacks[channel] << block
|
79
|
-
redis_command(SUBSCRIBE_COMMAND, channel, &block)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Unsubscribe to one or more Redis PubSub channels. If a channel
|
83
|
-
# is provided, this method will unsubscribe from it. If a
|
84
|
-
# channel is not provided, this method will unsubscribe from all
|
85
|
-
# Redis PubSub channels.
|
86
|
-
#
|
87
|
-
# @param channel [String]
|
88
|
-
# @yield unsubscribe callback
|
89
|
-
def unsubscribe(channel=nil, &block)
|
90
|
-
@pubsub_callbacks ||= Hash.new([])
|
91
|
-
arguments = [UNSUBSCRIBE_COMMAND]
|
92
|
-
if channel
|
93
|
-
@pubsub_callbacks[channel] = [block]
|
94
|
-
arguments << channel
|
95
|
-
else
|
96
|
-
@pubsub_callbacks.each_key do |key|
|
97
|
-
@pubsub_callbacks[key] = [block]
|
98
|
-
end
|
99
|
-
end
|
100
|
-
redis_command(arguments)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
@@ -1,158 +0,0 @@
|
|
1
|
-
require "sensu/redis/constants"
|
2
|
-
require "sensu/redis/errors"
|
3
|
-
|
4
|
-
module Sensu
|
5
|
-
module Redis
|
6
|
-
# Sensu Module connecting to Redis.
|
7
|
-
module Connection
|
8
|
-
# Initialize the connection, creating the Redis command methods,
|
9
|
-
# and setting the default connection options and callbacks.
|
10
|
-
def initialize(options={})
|
11
|
-
create_command_methods!
|
12
|
-
@host = options[:host]
|
13
|
-
@port = options[:port]
|
14
|
-
@db = (options[:db] || 0).to_i
|
15
|
-
@password = options[:password]
|
16
|
-
@auto_reconnect = options.fetch(:auto_reconnect, true)
|
17
|
-
@reconnect_on_error = options.fetch(:reconnect_on_error, true)
|
18
|
-
@error_callback = lambda do |error|
|
19
|
-
raise(error)
|
20
|
-
end
|
21
|
-
@reconnect_callbacks = {
|
22
|
-
:before => lambda{},
|
23
|
-
:after => lambda{}
|
24
|
-
}
|
25
|
-
end
|
26
|
-
|
27
|
-
# Set the connection error callback. This callback is called
|
28
|
-
# when the connection encounters either a connection, protocol,
|
29
|
-
# or command error.
|
30
|
-
def on_error(&block)
|
31
|
-
@error_callback = block
|
32
|
-
end
|
33
|
-
|
34
|
-
# Set the connection before reconnect callback. This callback is
|
35
|
-
# called after the connection closes but before a reconnect is
|
36
|
-
# attempted.
|
37
|
-
def before_reconnect(&block)
|
38
|
-
@reconnect_callbacks[:before] = block
|
39
|
-
end
|
40
|
-
|
41
|
-
# Set the connection after reconnect callback. This callback is
|
42
|
-
# called after a successful reconnect, after the connection has
|
43
|
-
# been validated.
|
44
|
-
def after_reconnect(&block)
|
45
|
-
@reconnect_callbacks[:after] = block
|
46
|
-
end
|
47
|
-
|
48
|
-
# Create an error and pass it to the connection error callback.
|
49
|
-
#
|
50
|
-
# @param klass [Class]
|
51
|
-
# @param message [String]
|
52
|
-
def error(klass, message)
|
53
|
-
redis_error = klass.new(message)
|
54
|
-
@error_callback.call(redis_error)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Determine if the connection is connected to Redis.
|
58
|
-
def connected?
|
59
|
-
@connected || false
|
60
|
-
end
|
61
|
-
|
62
|
-
# Reconnect to Redis. The before reconnect callback is first
|
63
|
-
# called if not already reconnecting. This method uses a 1
|
64
|
-
# second delay before attempting a reconnect.
|
65
|
-
def reconnect!
|
66
|
-
@reconnect_callbacks[:before].call unless @reconnecting
|
67
|
-
@reconnecting = true
|
68
|
-
EM.add_timer(1) do
|
69
|
-
reconnect(@host, @port)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Close the Redis connection after writing the current
|
74
|
-
# Redis command data.
|
75
|
-
def close
|
76
|
-
@closing = true
|
77
|
-
close_connection_after_writing
|
78
|
-
end
|
79
|
-
|
80
|
-
# This method is called by EM when the connection closes, either
|
81
|
-
# intentionally or unexpectedly. This method is reponsible for
|
82
|
-
# starting the reconnect process when appropriate.
|
83
|
-
def unbind
|
84
|
-
@deferred_status = nil
|
85
|
-
if @closing
|
86
|
-
@reconnecting = false
|
87
|
-
elsif ((@connected || @reconnecting) && @auto_reconnect) || @reconnect_on_error
|
88
|
-
reconnect!
|
89
|
-
elsif @connected
|
90
|
-
error(ConnectionError, "connection closed")
|
91
|
-
else
|
92
|
-
error(ConnectionError, "unable to connect to redis server")
|
93
|
-
end
|
94
|
-
@connected = false
|
95
|
-
end
|
96
|
-
|
97
|
-
# Authenticate to Redis if a password has been set in the
|
98
|
-
# connection options. This method uses `send_command()`
|
99
|
-
# directly, as it assumes that the connection has been
|
100
|
-
# established. Redis authentication must be done prior to
|
101
|
-
# issuing other Redis commands.
|
102
|
-
#
|
103
|
-
# @yield the callback called once authenticated.
|
104
|
-
def authenticate
|
105
|
-
if @password
|
106
|
-
send_command(AUTH_COMMAND, @password) do |authenticated|
|
107
|
-
if authenticated
|
108
|
-
yield if block_given?
|
109
|
-
else
|
110
|
-
error(ConnectionError, "redis authenticate failed")
|
111
|
-
end
|
112
|
-
end
|
113
|
-
else
|
114
|
-
yield if block_given?
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# Select a Redis DB if a DB has been set in the connection
|
119
|
-
# options. This method (& Redis command) does not require a
|
120
|
-
# response callback.
|
121
|
-
def select_db
|
122
|
-
send_command(SELECT_COMMAND, @db) if @db
|
123
|
-
end
|
124
|
-
|
125
|
-
# Verify the version of Redis. Redis >= 2.0 RC 1 is required for
|
126
|
-
# certain Redis commands that Sensu uses. A connection error is
|
127
|
-
# created if the Redis version does not meet the requirements.
|
128
|
-
#
|
129
|
-
# @yield the callback called once verified.
|
130
|
-
def verify_version
|
131
|
-
send_command(INFO_COMMAND) do |redis_info|
|
132
|
-
if redis_info[:redis_version] < "1.3.14"
|
133
|
-
error(ConnectionError, "redis version must be >= 2.0 RC 1")
|
134
|
-
else
|
135
|
-
yield if block_given?
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# This method is called by EM when the connection is
|
141
|
-
# established. This method is reponsible for validating the
|
142
|
-
# connection before Redis commands can be sent.
|
143
|
-
def connection_completed
|
144
|
-
@response_callbacks = []
|
145
|
-
@multibulk_count = false
|
146
|
-
@connected = true
|
147
|
-
authenticate do
|
148
|
-
select_db
|
149
|
-
verify_version do
|
150
|
-
succeed
|
151
|
-
@reconnect_callbacks[:after].call if @reconnecting
|
152
|
-
@reconnecting = false
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
data/lib/sensu/redis/parser.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
require "sensu/redis/constants"
|
2
|
-
require "sensu/redis/errors"
|
3
|
-
|
4
|
-
module Sensu
|
5
|
-
module Redis
|
6
|
-
# Sensu Module for parsing RESP (REdis Serialization Protocol).
|
7
|
-
# You can read about RESP @ http://redis.io/topics/protocol
|
8
|
-
# This module calls methods provided by other Sensu Redis modules:
|
9
|
-
# Sensu::Redis::Processor.dispatch_error()
|
10
|
-
# Sensu::Redis::Processor.dispatch_response()
|
11
|
-
# Sensu::Redis::Processor.begin_multibulk()
|
12
|
-
# Sensu::Redis::Connection.error()
|
13
|
-
module Parser
|
14
|
-
# Parse a RESP line.
|
15
|
-
#
|
16
|
-
# @param line [String]
|
17
|
-
def parse_line(line)
|
18
|
-
# Trim off the response type and delimiter (\r\n).
|
19
|
-
response = line.slice(1..-3)
|
20
|
-
# First character indicates response type.
|
21
|
-
case line[0, 1]
|
22
|
-
when MINUS # Error, e.g. -ERR
|
23
|
-
dispatch_error(response)
|
24
|
-
when PLUS # String, e.g. +OK
|
25
|
-
dispatch_response(response)
|
26
|
-
when DOLLAR # Bulk string, e.g. $3\r\nfoo\r\n
|
27
|
-
response_size = Integer(response)
|
28
|
-
if response_size == -1 # No data, return nil.
|
29
|
-
dispatch_response(nil)
|
30
|
-
elsif @buffer.size >= response_size + 2 # Complete data.
|
31
|
-
dispatch_response(@buffer.slice!(0, response_size))
|
32
|
-
@buffer.slice!(0,2) # Discard delimeter (\r\n).
|
33
|
-
else # Incomplete, have data pushed back into buffer.
|
34
|
-
return INCOMPLETE
|
35
|
-
end
|
36
|
-
when COLON # Integer, e.g. :8
|
37
|
-
dispatch_response(Integer(response))
|
38
|
-
when ASTERISK # Array, e.g. *2\r\n$3\r\foo\r\n$3\r\nbar\r\n
|
39
|
-
multibulk_count = Integer(response)
|
40
|
-
if multibulk_count == -1 || multibulk_count == 0 # No data, return [].
|
41
|
-
dispatch_response([])
|
42
|
-
else
|
43
|
-
begin_multibulk(multibulk_count) # Accumulate responses.
|
44
|
-
end
|
45
|
-
else
|
46
|
-
error(ProtocolError, "response type not recognized: #{line.strip}")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# EM connection receive data, parse incoming data using RESP
|
51
|
-
# (`parse_line()`).
|
52
|
-
#
|
53
|
-
# @param data [String]
|
54
|
-
def receive_data(data)
|
55
|
-
(@buffer ||= '') << data
|
56
|
-
while index = @buffer.index(DELIM)
|
57
|
-
line = @buffer.slice!(0, index+2)
|
58
|
-
if parse_line(line) == INCOMPLETE
|
59
|
-
@buffer[0...0] = line
|
60
|
-
break
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
require "sensu/redis/constants"
|
2
|
-
require "sensu/redis/errors"
|
3
|
-
|
4
|
-
module Sensu
|
5
|
-
module Redis
|
6
|
-
# Sensu Module for processing Redis responses.
|
7
|
-
# This module calls methods provided by other Sensu Redis modules:
|
8
|
-
# Sensu::Redis::Connection.error()
|
9
|
-
module Processor
|
10
|
-
# Fetch the next Redis command response callback. Response
|
11
|
-
# callbacks may include an optional response processor block,
|
12
|
-
# i.e. "1" -> true.
|
13
|
-
#
|
14
|
-
# @return [Array] processor, callback.
|
15
|
-
def fetch_response_callback
|
16
|
-
@response_callbacks ||= []
|
17
|
-
@response_callbacks.shift
|
18
|
-
end
|
19
|
-
|
20
|
-
# Begin a multi bulk response array for an expected number of
|
21
|
-
# responses. Using this method causes `dispatch_response()` to
|
22
|
-
# wait until all of the expected responses have been added to
|
23
|
-
# the array, before the Redis command reponse callback is
|
24
|
-
# called.
|
25
|
-
#
|
26
|
-
# @param multibulk_count [Integer] number of expected responses.
|
27
|
-
def begin_multibulk(multibulk_count)
|
28
|
-
@multibulk_count = multibulk_count
|
29
|
-
@multibulk_values = []
|
30
|
-
end
|
31
|
-
|
32
|
-
# Dispatch a Redis error, dropping the associated Redis command
|
33
|
-
# response callback, and passing a Redis error object to the
|
34
|
-
# error callback (if set).
|
35
|
-
#
|
36
|
-
# @param code [String] Redis error code.
|
37
|
-
def dispatch_error(code)
|
38
|
-
fetch_response_callback
|
39
|
-
error(CommandError, code)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Dispatch a response. If a multi bulk response has begun, this
|
43
|
-
# method will build the completed response array before the
|
44
|
-
# associated Redis command response callback is called. If one
|
45
|
-
# or more pubsub callbacks are defined, the approprate pubsub
|
46
|
-
# callbacks are called, provided with the pubsub response. Redis
|
47
|
-
# command response callbacks may have an optional processor
|
48
|
-
# block, responsible for producing a value with the correct
|
49
|
-
# type, e.g. "1" -> true (boolean).
|
50
|
-
#
|
51
|
-
# @param value [Object]
|
52
|
-
def dispatch_response(value)
|
53
|
-
if @multibulk_count
|
54
|
-
@multibulk_values << value
|
55
|
-
@multibulk_count -= 1
|
56
|
-
if @multibulk_count == 0
|
57
|
-
value = @multibulk_values
|
58
|
-
@multibulk_count = false
|
59
|
-
else
|
60
|
-
return
|
61
|
-
end
|
62
|
-
end
|
63
|
-
if @pubsub_callbacks && value.is_a?(Array)
|
64
|
-
if PUBSUB_RESPONSES.include?(value[0])
|
65
|
-
@pubsub_callbacks[value[1]].each do |block|
|
66
|
-
block.call(*value) if block
|
67
|
-
end
|
68
|
-
return
|
69
|
-
end
|
70
|
-
end
|
71
|
-
processor, block = fetch_response_callback
|
72
|
-
if block
|
73
|
-
value = processor.call(value) if processor
|
74
|
-
block.call(value)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|