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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bcfdf0b6608146e540e20c6f387455d0a34a4b5d
4
- data.tar.gz: 522e12100ed683a303de2003ac92588327e4b23b
3
+ metadata.gz: 79f8dbe376c6ccdf3c2116339f85c2b6553ed6fb
4
+ data.tar.gz: 01c24f59bb0ab213c92fe66ce893633698cf76ee
5
5
  SHA512:
6
- metadata.gz: 9e0c01fc853d422b50389602cf1f4c201e6144ba278719d120117fc3c386365e802f5b94bb343df2ee1b21e4a3e36786b8ccd8d68bebb5fc0e491e46a88ca18c
7
- data.tar.gz: 8db45541c882d821c500cd570faac609630c4c35be0d339fbf1fb5d665cc69cb401497f1e7f095c103a913b39fed7a2b3f5f51b7c6d89ac049545c5480b9e703
6
+ metadata.gz: f35de6079773cfde302fd3e501469fa56f4b687afb971469c4cc2f9ac729c2e41a8caa608721365ec251107748addcd51875791e03e69486437c99b526d1b871
7
+ data.tar.gz: 5c4877df053f59c9af1aa277b3d9bf8ef595e1b88163c9c5a00a1e4585356efa221e80019864e89d6753e7e2c5c2626da40de09afcedd2666433ee4d956e8923
@@ -20,7 +20,7 @@ module Sensu
20
20
  PUBSUB_RESPONSES = %w[message unsubscribe].freeze
21
21
 
22
22
  # Redis commands that are supported by this library.
23
- REDIS_COMMANDS = [
23
+ COMMANDS = [
24
24
  "set",
25
25
  "setnx",
26
26
  "get",
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/parser"
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 Client < EM::Connection
12
- include EM::Deferrable
13
- include Parser
14
- include Processor
15
- include Commands
16
- include Connection
17
-
18
- class << self
19
- def parse_url(url)
20
- begin
21
- uri = URI.parse(url)
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
- def connect(options={})
33
- case options
34
- when String
35
- options = parse_url(options)
36
- when nil
37
- options = {}
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "sensu-redis"
5
- spec.version = "0.1.6"
5
+ spec.version = "0.1.7"
6
6
  spec.authors = ["Sean Porter"]
7
7
  spec.email = ["portertech@gmail.com"]
8
8
  spec.summary = "The Sensu Redis client library"
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.6
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/commands.rb
101
- - lib/sensu/redis/connection.rb
102
- - lib/sensu/redis/constants.rb
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:
@@ -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
@@ -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