Skip to content

Commit 5673f83

Browse files
committed
Merge pull request #223 from javanthropus/support_host_list
Add the ability to provide a list of hosts for a connection
2 parents fd8aff7 + c0db1d1 commit 5673f83

File tree

3 files changed

+93
-18
lines changed

3 files changed

+93
-18
lines changed

lib/net/ldap.rb

+5
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ def self.result2string(code) #:nodoc:
432432

433433
attr_accessor :host
434434
attr_accessor :port
435+
attr_accessor :hosts
435436
attr_accessor :base
436437

437438
# Instantiate an object of type Net::LDAP to perform directory operations.
@@ -440,6 +441,8 @@ def self.result2string(code) #:nodoc:
440441
# described below. The following arguments are supported:
441442
# * :host => the LDAP server's IP-address (default 127.0.0.1)
442443
# * :port => the LDAP server's TCP port (default 389)
444+
# * :hosts => an enumerable of pairs of hosts and corresponding ports with
445+
# which to attempt opening connections (default [[host, port]])
443446
# * :auth => a Hash containing authorization parameters. Currently
444447
# supported values include: {:method => :anonymous} and {:method =>
445448
# :simple, :username => your_user_name, :password => your_password }
@@ -468,6 +471,7 @@ def self.result2string(code) #:nodoc:
468471
def initialize(args = {})
469472
@host = args[:host] || DefaultHost
470473
@port = args[:port] || DefaultPort
474+
@hosts = args[:hosts]
471475
@verbose = false # Make this configurable with a switch on the class.
472476
@auth = args[:auth] || DefaultAuth
473477
@base = args[:base] || DefaultTreebase
@@ -1230,6 +1234,7 @@ def new_connection
12301234
Net::LDAP::Connection.new \
12311235
:host => @host,
12321236
:port => @port,
1237+
:hosts => @hosts,
12331238
:encryption => @encryption,
12341239
:instrumentation_service => @instrumentation_service
12351240
end

lib/net/ldap/connection.rb

+50-18
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,56 @@ class Net::LDAP::Connection #:nodoc:
88

99
def initialize(server)
1010
@instrumentation_service = server[:instrumentation_service]
11+
server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?
1112

13+
if server[:socket]
14+
prepare_socket(server)
15+
else
16+
open_connection(server)
17+
end
18+
19+
yield self if block_given?
20+
end
21+
22+
def prepare_socket(server)
23+
@conn = server[:socket]
24+
25+
if server[:encryption]
26+
setup_encryption server[:encryption]
27+
end
28+
end
29+
30+
def open_connection(server)
31+
errors = []
32+
server[:hosts].each do |host, port|
33+
begin
34+
return connect_to_host(host, port, server)
35+
rescue Net::LDAP::Error
36+
errors << $!
37+
end
38+
end
39+
40+
raise errors.first if errors.size == 1
41+
raise Net::LDAP::Error,
42+
"Unable to connect to any given server: \n #{errors.join("\n ")}"
43+
end
44+
45+
def connect_to_host(host, port, server)
1246
begin
13-
@conn = server[:socket] || TCPSocket.new(server[:host], server[:port])
47+
@conn = TCPSocket.new(host, port)
1448
rescue SocketError
1549
raise Net::LDAP::Error, "No such address or other socket error."
1650
rescue Errno::ECONNREFUSED
17-
raise Net::LDAP::ConnectionRefusedError, "Server #{server[:host]} refused connection on port #{server[:port]}."
51+
raise Net::LDAP::ConnectionRefusedError, "Server #{host} refused connection on port #{port}."
1852
rescue Errno::EHOSTUNREACH => error
19-
raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})"
53+
raise Net::LDAP::Error, "Host #{host} was unreachable (#{error.message})"
2054
rescue Errno::ETIMEDOUT
21-
raise Net::LDAP::Error, "Connection to #{server[:host]} timed out."
55+
raise Net::LDAP::Error, "Connection to #{host} timed out."
2256
end
2357

2458
if server[:encryption]
2559
setup_encryption server[:encryption]
2660
end
27-
28-
yield self if block_given?
2961
end
3062

3163
module GetbyteForSSLSocket
@@ -63,18 +95,18 @@ def self.wrap_with_ssl(io, tls_options = {})
6395
end
6496

6597
#--
66-
# Helper method called only from new, and only after we have a
67-
# successfully-opened @conn instance variable, which is a TCP connection.
68-
# Depending on the received arguments, we establish SSL, potentially
69-
# replacing the value of @conn accordingly. Don't generate any errors here
70-
# if no encryption is requested. DO raise Net::LDAP::Error objects if encryption
71-
# is requested and we have trouble setting it up. That includes if OpenSSL
72-
# is not set up on the machine. (Question: how does the Ruby OpenSSL
73-
# wrapper react in that case?) DO NOT filter exceptions raised by the
74-
# OpenSSL library. Let them pass back to the user. That should make it
75-
# easier for us to debug the problem reports. Presumably (hopefully?) that
76-
# will also produce recognizable errors if someone tries to use this on a
77-
# machine without OpenSSL.
98+
# Helper method called only from prepare_socket or open_connection, and only
99+
# after we have a successfully-opened @conn instance variable, which is a TCP
100+
# connection. Depending on the received arguments, we establish SSL,
101+
# potentially replacing the value of @conn accordingly. Don't generate any
102+
# errors here if no encryption is requested. DO raise Net::LDAP::Error objects
103+
# if encryption is requested and we have trouble setting it up. That includes
104+
# if OpenSSL is not set up on the machine. (Question: how does the Ruby
105+
# OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
106+
# OpenSSL library. Let them pass back to the user. That should make it easier
107+
# for us to debug the problem reports. Presumably (hopefully?) that will also
108+
# produce recognizable errors if someone tries to use this on a machine
109+
# without OpenSSL.
78110
#
79111
# The simple_tls method is intended as the simplest, stupidest, easiest
80112
# solution for people who want nothing more than encrypted comms with the

test/test_ldap_connection.rb

+38
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,44 @@ def capture_stderr
99
$stderr = stderr
1010
end
1111

12+
def test_list_of_hosts_with_first_host_successful
13+
hosts = [
14+
['test.mocked.com', 636],
15+
['test2.mocked.com', 636],
16+
['test3.mocked.com', 636],
17+
]
18+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_return(nil)
19+
flexmock(TCPSocket).should_receive(:new).ordered.never
20+
Net::LDAP::Connection.new(:hosts => hosts)
21+
end
22+
23+
def test_list_of_hosts_with_first_host_failure
24+
hosts = [
25+
['test.mocked.com', 636],
26+
['test2.mocked.com', 636],
27+
['test3.mocked.com', 636],
28+
]
29+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
30+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_return(nil)
31+
flexmock(TCPSocket).should_receive(:new).ordered.never
32+
Net::LDAP::Connection.new(:hosts => hosts)
33+
end
34+
35+
def test_list_of_hosts_with_all_hosts_failure
36+
hosts = [
37+
['test.mocked.com', 636],
38+
['test2.mocked.com', 636],
39+
['test3.mocked.com', 636],
40+
]
41+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
42+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_raise(SocketError)
43+
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[2]).once.and_raise(SocketError)
44+
flexmock(TCPSocket).should_receive(:new).ordered.never
45+
assert_raise Net::LDAP::Error do
46+
Net::LDAP::Connection.new(:hosts => hosts)
47+
end
48+
end
49+
1250
def test_unresponsive_host
1351
assert_raise Net::LDAP::Error do
1452
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)

0 commit comments

Comments
 (0)