sensu-redis 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
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