ruby-activeldap 0.7.4 → 0.8.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.
- data/CHANGES +375 -0
- data/COPYING +340 -0
- data/LICENSE +58 -0
- data/Manifest.txt +33 -0
- data/README +63 -0
- data/Rakefile +37 -0
- data/TODO +31 -0
- data/benchmark/bench-al.rb +152 -0
- data/lib/{activeldap.rb → active_ldap.rb} +280 -263
- data/lib/active_ldap/adaptor/base.rb +29 -0
- data/lib/active_ldap/adaptor/ldap.rb +466 -0
- data/lib/active_ldap/association/belongs_to.rb +38 -0
- data/lib/active_ldap/association/belongs_to_many.rb +40 -0
- data/lib/active_ldap/association/collection.rb +80 -0
- data/lib/active_ldap/association/has_many.rb +48 -0
- data/lib/active_ldap/association/has_many_wrap.rb +56 -0
- data/lib/active_ldap/association/proxy.rb +89 -0
- data/lib/active_ldap/associations.rb +162 -0
- data/lib/active_ldap/attributes.rb +199 -0
- data/lib/active_ldap/base.rb +1343 -0
- data/lib/active_ldap/callbacks.rb +19 -0
- data/lib/active_ldap/command.rb +46 -0
- data/lib/active_ldap/configuration.rb +96 -0
- data/lib/active_ldap/connection.rb +137 -0
- data/lib/{activeldap → active_ldap}/ldap.rb +1 -1
- data/lib/active_ldap/object_class.rb +70 -0
- data/lib/active_ldap/schema.rb +258 -0
- data/lib/{activeldap → active_ldap}/timeout.rb +0 -0
- data/lib/{activeldap → active_ldap}/timeout_stub.rb +0 -0
- data/lib/active_ldap/user_password.rb +92 -0
- data/lib/active_ldap/validations.rb +78 -0
- data/rails/plugin/active_ldap/README +54 -0
- data/rails/plugin/active_ldap/init.rb +6 -0
- data/test/TODO +2 -0
- data/test/al-test-utils.rb +337 -0
- data/test/command.rb +62 -0
- data/test/config.yaml +8 -0
- data/test/config.yaml.sample +6 -0
- data/test/run-test.rb +17 -0
- data/test/test-unit-ext.rb +2 -0
- data/test/test_associations.rb +334 -0
- data/test/test_attributes.rb +71 -0
- data/test/test_base.rb +345 -0
- data/test/test_base_per_instance.rb +32 -0
- data/test/test_bind.rb +53 -0
- data/test/test_callback.rb +35 -0
- data/test/test_connection.rb +38 -0
- data/test/test_connection_per_class.rb +50 -0
- data/test/test_find.rb +36 -0
- data/test/test_groupadd.rb +50 -0
- data/test/test_groupdel.rb +46 -0
- data/test/test_groupls.rb +107 -0
- data/test/test_groupmod.rb +51 -0
- data/test/test_lpasswd.rb +75 -0
- data/test/test_object_class.rb +32 -0
- data/test/test_reflection.rb +173 -0
- data/test/test_schema.rb +166 -0
- data/test/test_user.rb +209 -0
- data/test/test_user_password.rb +93 -0
- data/test/test_useradd-binary.rb +59 -0
- data/test/test_useradd.rb +55 -0
- data/test/test_userdel.rb +48 -0
- data/test/test_userls.rb +86 -0
- data/test/test_usermod-binary-add-time.rb +62 -0
- data/test/test_usermod-binary-add.rb +61 -0
- data/test/test_usermod-binary-del.rb +64 -0
- data/test/test_usermod-lang-add.rb +57 -0
- data/test/test_usermod.rb +56 -0
- data/test/test_validation.rb +38 -0
- metadata +94 -21
- data/lib/activeldap/associations.rb +0 -170
- data/lib/activeldap/base.rb +0 -1456
- data/lib/activeldap/configuration.rb +0 -59
- data/lib/activeldap/schema2.rb +0 -217
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveLdap
|
2
|
+
module Adaptor
|
3
|
+
class Base
|
4
|
+
def initialize(config={})
|
5
|
+
@connection = nil
|
6
|
+
@config = config.dup
|
7
|
+
@logger = @config.delete(:logger)
|
8
|
+
%w(host port method timeout retry_on_timeout
|
9
|
+
retry_limit retry_wait bind_dn password
|
10
|
+
password_block try_sasl allow_anonymous
|
11
|
+
store_password).each do |name|
|
12
|
+
instance_variable_set("@#{name}", config[name.to_sym])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def with_timeout(try_reconnect=true, &block)
|
18
|
+
begin
|
19
|
+
Timeout.alarm(@timeout, &block)
|
20
|
+
rescue Timeout::Error => e
|
21
|
+
@logger.error {'Requested action timed out.'}
|
22
|
+
retry if try_reconnect and @retry_on_timeout and reconnect
|
23
|
+
@logger.error {e.message}
|
24
|
+
raise TimeoutError, e.message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,466 @@
|
|
1
|
+
require 'ldap'
|
2
|
+
require 'ldap/ldif'
|
3
|
+
require 'ldap/schema'
|
4
|
+
|
5
|
+
require 'active_ldap/ldap'
|
6
|
+
require 'active_ldap/schema'
|
7
|
+
|
8
|
+
require 'active_ldap/adaptor/base'
|
9
|
+
|
10
|
+
class LDAP::Mod
|
11
|
+
unless instance_method(:to_s).arity.zero?
|
12
|
+
def to_s
|
13
|
+
inspect
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :_initialize, :initialize
|
18
|
+
def initialize(op, type, vals)
|
19
|
+
if (LDAP::VERSION.split(/\./).collect {|x| x.to_i} <=> [0, 9, 7]) <= 0
|
20
|
+
@op, @type, @vals = op, type, vals # to protect from GC
|
21
|
+
end
|
22
|
+
_initialize(op, type, vals)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ActiveLdap
|
27
|
+
module Adaptor
|
28
|
+
class Ldap < Base
|
29
|
+
module Method
|
30
|
+
class SSL
|
31
|
+
def connect(host, port)
|
32
|
+
LDAP::SSLConn.new(host, port, false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TLS
|
37
|
+
def connect(host, port)
|
38
|
+
LDAP::SSLConn.new(host, port, true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Plain
|
43
|
+
def connect(host, port)
|
44
|
+
LDAP::Conn.new(host, port)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
SCOPE = {
|
50
|
+
:base => LDAP::LDAP_SCOPE_BASE,
|
51
|
+
:sub => LDAP::LDAP_SCOPE_SUBTREE,
|
52
|
+
:one => LDAP::LDAP_SCOPE_ONELEVEL,
|
53
|
+
}
|
54
|
+
|
55
|
+
def connect(options={})
|
56
|
+
method = ensure_method(options[:method] || @method)
|
57
|
+
host = options[:host] || @host
|
58
|
+
port = options[:port] || @port
|
59
|
+
|
60
|
+
@connection = method.connect(host, port)
|
61
|
+
operation(options) do
|
62
|
+
@connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
|
63
|
+
end
|
64
|
+
bind(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def schema(options={})
|
68
|
+
@schema ||= operation(options) do
|
69
|
+
base = options[:base]
|
70
|
+
attrs = options[:attrs]
|
71
|
+
sec = options[:sec] || 0
|
72
|
+
usec = options[:usec] || 0
|
73
|
+
|
74
|
+
attrs ||= [
|
75
|
+
'objectClasses',
|
76
|
+
'attributeTypes',
|
77
|
+
'matchingRules',
|
78
|
+
'matchingRuleUse',
|
79
|
+
'dITStructureRules',
|
80
|
+
'dITContentRules',
|
81
|
+
'nameForms',
|
82
|
+
'ldapSyntaxes',
|
83
|
+
]
|
84
|
+
key = 'subschemaSubentry'
|
85
|
+
base ||= @connection.root_dse([key], sec, usec)[0][key][0]
|
86
|
+
base ||= 'cn=schema'
|
87
|
+
result = @connection.search2(base, LDAP::LDAP_SCOPE_BASE,
|
88
|
+
'(objectClass=subschema)', attrs, false,
|
89
|
+
sec, usec).first
|
90
|
+
Schema.new(result)
|
91
|
+
end
|
92
|
+
# rescue
|
93
|
+
# raise ConnectionError.new("Unable to retrieve schema from " +
|
94
|
+
# "server (#{@method.class.downcase})")
|
95
|
+
end
|
96
|
+
|
97
|
+
def disconnect!(options={})
|
98
|
+
return if @connection.nil?
|
99
|
+
begin
|
100
|
+
unbind(options)
|
101
|
+
#rescue
|
102
|
+
end
|
103
|
+
@connection = nil
|
104
|
+
# Make sure it is cleaned up
|
105
|
+
# This causes Ruby/LDAP memory corruption.
|
106
|
+
# GC.start
|
107
|
+
end
|
108
|
+
|
109
|
+
def unbind(options={})
|
110
|
+
return unless bound?
|
111
|
+
operation(options) do
|
112
|
+
@connection.unbind
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rebind(options={})
|
117
|
+
unbind(options) if bound?
|
118
|
+
connect(options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def bind(options={})
|
122
|
+
bind_dn = options[:bind_dn] || @bind_dn
|
123
|
+
try_sasl = options.has_key?(:try_sasl) ? options[:try_sasl] : @try_sasl
|
124
|
+
if options.has_key?(:allow_anonymous)
|
125
|
+
allow_anonymous = options[:allow_anonymous]
|
126
|
+
else
|
127
|
+
allow_anonymous = @allow_anonymous
|
128
|
+
end
|
129
|
+
|
130
|
+
# Rough bind loop:
|
131
|
+
# Attempt 1: SASL if available
|
132
|
+
# Attempt 2: SIMPLE with credentials if password block
|
133
|
+
# Attempt 3: SIMPLE ANONYMOUS if 1 and 2 fail (or pwblock returns '')
|
134
|
+
if try_sasl and sasl_bind(bind_dn, options)
|
135
|
+
@logger.info {'Bound SASL'}
|
136
|
+
elsif simple_bind(bind_dn, options)
|
137
|
+
@logger.info {'Bound simple'}
|
138
|
+
elsif allow_anonymous and bind_as_anonymous(options)
|
139
|
+
@logger.info {'Bound anonymous'}
|
140
|
+
else
|
141
|
+
if @connection.err.zero?
|
142
|
+
message = 'All authentication methods exhausted.'
|
143
|
+
else
|
144
|
+
message = LDAP.err2string(@connection.err)
|
145
|
+
end
|
146
|
+
raise AuthenticationError, message
|
147
|
+
end
|
148
|
+
|
149
|
+
bound?
|
150
|
+
end
|
151
|
+
|
152
|
+
def bind_as_anonymous(options={})
|
153
|
+
@logger.info {"Attempting anonymous authentication"}
|
154
|
+
operation(options) do
|
155
|
+
@connection.bind
|
156
|
+
true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def connecting?
|
161
|
+
not @connection.nil?
|
162
|
+
end
|
163
|
+
|
164
|
+
def bound?
|
165
|
+
connecting? and @connection.bound?
|
166
|
+
end
|
167
|
+
|
168
|
+
# search
|
169
|
+
#
|
170
|
+
# Wraps Ruby/LDAP connection.search to make it easier to search for
|
171
|
+
# specific data without cracking open Base.connection
|
172
|
+
def search(options={})
|
173
|
+
filter = options[:filter] || 'objectClass=*'
|
174
|
+
attrs = options[:attributes] || []
|
175
|
+
scope = ensure_scope(options[:scope])
|
176
|
+
base = options[:base]
|
177
|
+
limit = options[:limit] || 0
|
178
|
+
limit = nil if limit <= 0
|
179
|
+
|
180
|
+
values = []
|
181
|
+
attrs = attrs.to_a # just in case
|
182
|
+
|
183
|
+
begin
|
184
|
+
operation(options) do
|
185
|
+
i = 0
|
186
|
+
@connection.search(base, scope, filter, attrs) do |m|
|
187
|
+
i += 1
|
188
|
+
attributes = {}
|
189
|
+
m.attrs.each do |attr|
|
190
|
+
attributes[attr] = m.vals(attr)
|
191
|
+
end
|
192
|
+
value = [m.dn, attributes]
|
193
|
+
value = yield(value) if block_given?
|
194
|
+
values.push(value)
|
195
|
+
break if limit and limit >= i
|
196
|
+
end
|
197
|
+
end
|
198
|
+
rescue LDAP::Error
|
199
|
+
# Do nothing on failure
|
200
|
+
@logger.debug {"Ignore error #{$!.class}(#{$!.message}) " +
|
201
|
+
"for #{filter} and attrs #{attrs.inspect}"}
|
202
|
+
rescue RuntimeError
|
203
|
+
if $!.message == "no result returned by search"
|
204
|
+
@logger.debug {"No matches for #{filter} and attrs " +
|
205
|
+
"#{attrs.inspect}"}
|
206
|
+
else
|
207
|
+
raise
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
values
|
212
|
+
end
|
213
|
+
|
214
|
+
def to_ldif(dn, attributes)
|
215
|
+
ldif = LDAP::LDIF.to_ldif("dn", [dn.dup])
|
216
|
+
attributes.sort_by do |key, value|
|
217
|
+
key
|
218
|
+
end.each do |key, values|
|
219
|
+
ldif << LDAP::LDIF.to_ldif(key, values)
|
220
|
+
end
|
221
|
+
ldif
|
222
|
+
end
|
223
|
+
|
224
|
+
def load(ldifs, options={})
|
225
|
+
operation(options) do
|
226
|
+
ldifs.split(/(?:\r?\n){2,}/).each do |ldif|
|
227
|
+
LDAP::LDIF.parse_entry(ldif).send(@connection)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def delete(targets, options={})
|
233
|
+
targets = [targets] unless targets.is_a?(Array)
|
234
|
+
return if targets.empty?
|
235
|
+
target = nil
|
236
|
+
begin
|
237
|
+
operation(options) do
|
238
|
+
targets.each do |target|
|
239
|
+
@connection.delete(target)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
rescue LDAP::NoSuchObject
|
243
|
+
raise EntryNotFound, "No such entry: #{target}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def add(dn, entries, options={})
|
248
|
+
begin
|
249
|
+
operation(options) do
|
250
|
+
@connection.add(dn, parse_entries(entries))
|
251
|
+
end
|
252
|
+
rescue LDAP::NoSuchObject
|
253
|
+
raise EntryNotFound, "No such entry: #{dn}"
|
254
|
+
rescue LDAP::InvalidDnSyntax
|
255
|
+
raise DistinguishedNameInvalid.new(dn)
|
256
|
+
rescue LDAP::AlreadyExists
|
257
|
+
raise EntryAlreadyExist, "#{$!.message}: #{dn}"
|
258
|
+
rescue LDAP::StrongAuthRequired
|
259
|
+
raise StrongAuthenticationRequired, "#{$!.message}: #{dn}"
|
260
|
+
rescue LDAP::ObjectClassViolation
|
261
|
+
raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
|
262
|
+
rescue LDAP::UnwillingToPerform
|
263
|
+
raise UnwillingToPerform, "#{$!.message}: #{dn}"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def modify(dn, entries, options={})
|
268
|
+
begin
|
269
|
+
operation(options) do
|
270
|
+
@connection.modify(dn, parse_entries(entries))
|
271
|
+
end
|
272
|
+
rescue LDAP::UndefinedType
|
273
|
+
raise
|
274
|
+
rescue LDAP::ObjectClassViolation
|
275
|
+
raise RequiredAttributeMissed, "#{$!.message}: #{dn}"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
def operation(options={}, &block)
|
281
|
+
reconnect_if_need
|
282
|
+
try_reconnect = !options.has_key?(:try_reconnect) ||
|
283
|
+
options[:try_reconnect]
|
284
|
+
with_timeout(try_reconnect) do
|
285
|
+
begin
|
286
|
+
block.call
|
287
|
+
rescue LDAP::ResultError
|
288
|
+
raise *LDAP::err2exception(@connection.err) if @connection.err != 0
|
289
|
+
raise
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def with_timeout(try_reconnect=true, &block)
|
295
|
+
begin
|
296
|
+
super
|
297
|
+
rescue LDAP::ServerDown => e
|
298
|
+
@logger.error {"#{e.class} exception occurred in with_timeout block"}
|
299
|
+
@logger.error {"Exception message: #{e.message}"}
|
300
|
+
@logger.error {"Exception backtrace: #{e.backtrace}"}
|
301
|
+
retry if try_reconnect and reconnect
|
302
|
+
raise ConnectionError.new(e.message)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def ensure_method(method)
|
307
|
+
Method.constants.each do |name|
|
308
|
+
if method.to_s.downcase == name.downcase
|
309
|
+
return Method.const_get(name).new
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
available_methods = Method.constants.collect do |name|
|
314
|
+
name.downcase.to_sym.inspect
|
315
|
+
end.join(", ")
|
316
|
+
raise ConfigurationError,
|
317
|
+
"#{method.inspect} is not one of the available connect " +
|
318
|
+
"methods #{available_methods}"
|
319
|
+
end
|
320
|
+
|
321
|
+
def ensure_scope(scope)
|
322
|
+
value = SCOPE[scope || :sub]
|
323
|
+
if value.nil?
|
324
|
+
available_scopes = SCOPE.keys.collect {|s| s.inspect}
|
325
|
+
raise ArgumentError, "#{scope.inspect} is not one of the available " +
|
326
|
+
"LDAP scope #{available_scopes}"
|
327
|
+
end
|
328
|
+
value
|
329
|
+
end
|
330
|
+
|
331
|
+
# Bind to LDAP with the given DN using any available SASL methods
|
332
|
+
def sasl_bind(bind_dn, options={})
|
333
|
+
# Get all SASL mechanisms
|
334
|
+
#
|
335
|
+
mechanisms = nil
|
336
|
+
exc = ConnectionError.new('Root DSE query failed')
|
337
|
+
mechanisms = operation do
|
338
|
+
@connection.root_dse[0]['supportedSASLMechanisms']
|
339
|
+
end
|
340
|
+
|
341
|
+
# Use GSSAPI if available
|
342
|
+
# Currently only GSSAPI is supported with Ruby/LDAP from
|
343
|
+
# http://caliban.org/files/redhat/RPMS/i386/ruby-ldap-0.8.2-4.i386.rpm
|
344
|
+
# TODO: Investigate further SASL support
|
345
|
+
return false unless (mechanisms || []).include?('GSSAPI')
|
346
|
+
operation do
|
347
|
+
@connection.sasl_quiet = @sasl_quiet unless @sasl_quit.nil?
|
348
|
+
@connection.sasl_bind(bind_dn, 'GSSAPI')
|
349
|
+
true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Bind to LDAP with the given DN and password
|
354
|
+
def simple_bind(bind_dn, options={})
|
355
|
+
# Bail if we have no password or password block
|
356
|
+
if @password.nil? and @password_block.nil?
|
357
|
+
@logger.error {'Skipping simple bind: ' +
|
358
|
+
'@password_block and @password options are empty.'}
|
359
|
+
return false
|
360
|
+
end
|
361
|
+
|
362
|
+
if @password
|
363
|
+
password = @password
|
364
|
+
else
|
365
|
+
# TODO: Give a warning to reconnect users with password clearing
|
366
|
+
# Get the passphrase for the first time, or anew if we aren't storing
|
367
|
+
unless @password_block.respond_to?(:call)
|
368
|
+
@logger.error {'Skipping simple bind: ' +
|
369
|
+
'@password_block not nil or Proc object. Ignoring.'}
|
370
|
+
return false
|
371
|
+
end
|
372
|
+
password = @password_block.call(bind_dn)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Store the password for quick reference later
|
376
|
+
@password = @store_password ? password : nil
|
377
|
+
|
378
|
+
begin
|
379
|
+
operation do
|
380
|
+
@connection.bind(bind_dn, password)
|
381
|
+
true
|
382
|
+
end
|
383
|
+
rescue LDAP::InvalidDnSyntax
|
384
|
+
@logger.debug {"DN is invalid: #{bind_dn}"}
|
385
|
+
raise DistinguishedNameInvalid.new(bind_dn)
|
386
|
+
rescue LDAP::InvalidCredentials
|
387
|
+
false
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def parse_entries(entries)
|
392
|
+
result = []
|
393
|
+
entries.each do |type, key, attributes|
|
394
|
+
mod_type = ensure_mod_type(type)
|
395
|
+
binary = schema.binary?(key)
|
396
|
+
mod_type |= LDAP::LDAP_MOD_BVALUES if binary
|
397
|
+
attributes.each do |name, values|
|
398
|
+
result << LDAP.mod(mod_type, name, values)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
result
|
402
|
+
end
|
403
|
+
|
404
|
+
def ensure_mod_type(type)
|
405
|
+
case type
|
406
|
+
when :replace, :add
|
407
|
+
LDAP.const_get("LDAP_MOD_#{type.to_s.upcase}")
|
408
|
+
else
|
409
|
+
raise ArgumentError, "unknown type: #{type}"
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
# Attempts to reconnect up to the number of times allowed
|
414
|
+
# If forced, try once then fail with ConnectionError if not connected.
|
415
|
+
def reconnect(options={})
|
416
|
+
options = options.dup
|
417
|
+
force = options[:force]
|
418
|
+
retry_limit = options[:retry_limit] || @retry_limit
|
419
|
+
retry_wait = options[:retry_wait] || @retry_wait
|
420
|
+
options[:reconnect_attempts] ||= 0
|
421
|
+
|
422
|
+
loop do
|
423
|
+
unless can_reconnect?(options)
|
424
|
+
raise ConnectionError,
|
425
|
+
'Giving up trying to reconnect to LDAP server.'
|
426
|
+
end
|
427
|
+
|
428
|
+
@logger.debug {'Attempting to reconnect'}
|
429
|
+
disconnect!
|
430
|
+
|
431
|
+
# Reset the attempts if this was forced.
|
432
|
+
options[:reconnect_attempts] = 0 if force
|
433
|
+
options[:reconnect_attempts] += 1 if retry_limit >= 0
|
434
|
+
begin
|
435
|
+
connect(options)
|
436
|
+
break
|
437
|
+
rescue => detail
|
438
|
+
@logger.error {"Reconnect to server failed: #{detail.exception}"}
|
439
|
+
@logger.error {"Reconnect to server failed backtrace: " +
|
440
|
+
detail.backtrace.join("\n")}
|
441
|
+
# Do not loop if forced
|
442
|
+
raise ConnectionError, detail.message if force
|
443
|
+
end
|
444
|
+
|
445
|
+
# Sleep before looping
|
446
|
+
sleep retry_wait
|
447
|
+
end
|
448
|
+
|
449
|
+
true
|
450
|
+
end
|
451
|
+
|
452
|
+
def reconnect_if_need(options={})
|
453
|
+
reconnect(options) if !connecting? and can_reconnect?(options)
|
454
|
+
end
|
455
|
+
|
456
|
+
# Determine if we have exceed the retry limit or not.
|
457
|
+
# True is reconnecting is allowed - False if not.
|
458
|
+
def can_reconnect?(options={})
|
459
|
+
retry_limit = options[:retry_limit] || @retry_limit
|
460
|
+
reconnect_attempts = options[:reconnect_attempts] || 0
|
461
|
+
|
462
|
+
retry_limit < 0 or reconnect_attempts < (retry_limit - 1)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|