Skip to content

Commit

Permalink
Merge pull request #223 from javanthropus/support_host_list
Browse files Browse the repository at this point in the history
Add the ability to provide a list of hosts for a connection
  • Loading branch information
jch committed Sep 29, 2015
2 parents fd8aff7 + c0db1d1 commit 5673f83
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 18 deletions.
5 changes: 5 additions & 0 deletions lib/net/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ def self.result2string(code) #:nodoc:

attr_accessor :host
attr_accessor :port
attr_accessor :hosts
attr_accessor :base

# Instantiate an object of type Net::LDAP to perform directory operations.
Expand All @@ -440,6 +441,8 @@ def self.result2string(code) #:nodoc:
# described below. The following arguments are supported:
# * :host => the LDAP server's IP-address (default 127.0.0.1)
# * :port => the LDAP server's TCP port (default 389)
# * :hosts => an enumerable of pairs of hosts and corresponding ports with
# which to attempt opening connections (default [[host, port]])
# * :auth => a Hash containing authorization parameters. Currently
# supported values include: {:method => :anonymous} and {:method =>
# :simple, :username => your_user_name, :password => your_password }
Expand Down Expand Up @@ -468,6 +471,7 @@ def self.result2string(code) #:nodoc:
def initialize(args = {})
@host = args[:host] || DefaultHost
@port = args[:port] || DefaultPort
@hosts = args[:hosts]
@verbose = false # Make this configurable with a switch on the class.
@auth = args[:auth] || DefaultAuth
@base = args[:base] || DefaultTreebase
Expand Down Expand Up @@ -1230,6 +1234,7 @@ def new_connection
Net::LDAP::Connection.new \
:host => @host,
:port => @port,
:hosts => @hosts,
:encryption => @encryption,
:instrumentation_service => @instrumentation_service
end
Expand Down
68 changes: 50 additions & 18 deletions lib/net/ldap/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,56 @@ class Net::LDAP::Connection #:nodoc:

def initialize(server)
@instrumentation_service = server[:instrumentation_service]
server[:hosts] = [[server[:host], server[:port]]] if server[:hosts].nil?

if server[:socket]
prepare_socket(server)
else
open_connection(server)
end

yield self if block_given?
end

def prepare_socket(server)
@conn = server[:socket]

if server[:encryption]
setup_encryption server[:encryption]
end
end

def open_connection(server)
errors = []
server[:hosts].each do |host, port|
begin
return connect_to_host(host, port, server)
rescue Net::LDAP::Error
errors << $!
end
end

raise errors.first if errors.size == 1
raise Net::LDAP::Error,
"Unable to connect to any given server: \n #{errors.join("\n ")}"
end

def connect_to_host(host, port, server)
begin
@conn = server[:socket] || TCPSocket.new(server[:host], server[:port])
@conn = TCPSocket.new(host, port)
rescue SocketError
raise Net::LDAP::Error, "No such address or other socket error."
rescue Errno::ECONNREFUSED
raise Net::LDAP::ConnectionRefusedError, "Server #{server[:host]} refused connection on port #{server[:port]}."
raise Net::LDAP::ConnectionRefusedError, "Server #{host} refused connection on port #{port}."
rescue Errno::EHOSTUNREACH => error
raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})"
raise Net::LDAP::Error, "Host #{host} was unreachable (#{error.message})"
rescue Errno::ETIMEDOUT
raise Net::LDAP::Error, "Connection to #{server[:host]} timed out."
raise Net::LDAP::Error, "Connection to #{host} timed out."
end

if server[:encryption]
setup_encryption server[:encryption]
end

yield self if block_given?
end

module GetbyteForSSLSocket
Expand Down Expand Up @@ -63,18 +95,18 @@ def self.wrap_with_ssl(io, tls_options = {})
end

#--
# Helper method called only from new, and only after we have a
# successfully-opened @conn instance variable, which is a TCP connection.
# Depending on the received arguments, we establish SSL, potentially
# replacing the value of @conn accordingly. Don't generate any errors here
# if no encryption is requested. DO raise Net::LDAP::Error objects if encryption
# is requested and we have trouble setting it up. That includes if OpenSSL
# is not set up on the machine. (Question: how does the Ruby OpenSSL
# wrapper react in that case?) DO NOT filter exceptions raised by the
# OpenSSL library. Let them pass back to the user. That should make it
# easier for us to debug the problem reports. Presumably (hopefully?) that
# will also produce recognizable errors if someone tries to use this on a
# machine without OpenSSL.
# Helper method called only from prepare_socket or open_connection, and only
# after we have a successfully-opened @conn instance variable, which is a TCP
# connection. Depending on the received arguments, we establish SSL,
# potentially replacing the value of @conn accordingly. Don't generate any
# errors here if no encryption is requested. DO raise Net::LDAP::Error objects
# if encryption is requested and we have trouble setting it up. That includes
# if OpenSSL is not set up on the machine. (Question: how does the Ruby
# OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the
# OpenSSL library. Let them pass back to the user. That should make it easier
# for us to debug the problem reports. Presumably (hopefully?) that will also
# produce recognizable errors if someone tries to use this on a machine
# without OpenSSL.
#
# The simple_tls method is intended as the simplest, stupidest, easiest
# solution for people who want nothing more than encrypted comms with the
Expand Down
38 changes: 38 additions & 0 deletions test/test_ldap_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,44 @@ def capture_stderr
$stderr = stderr
end

def test_list_of_hosts_with_first_host_successful
hosts = [
['test.mocked.com', 636],
['test2.mocked.com', 636],
['test3.mocked.com', 636],
]
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_return(nil)
flexmock(TCPSocket).should_receive(:new).ordered.never
Net::LDAP::Connection.new(:hosts => hosts)
end

def test_list_of_hosts_with_first_host_failure
hosts = [
['test.mocked.com', 636],
['test2.mocked.com', 636],
['test3.mocked.com', 636],
]
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_return(nil)
flexmock(TCPSocket).should_receive(:new).ordered.never
Net::LDAP::Connection.new(:hosts => hosts)
end

def test_list_of_hosts_with_all_hosts_failure
hosts = [
['test.mocked.com', 636],
['test2.mocked.com', 636],
['test3.mocked.com', 636],
]
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[0]).once.and_raise(SocketError)
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[1]).once.and_raise(SocketError)
flexmock(TCPSocket).should_receive(:new).ordered.with(*hosts[2]).once.and_raise(SocketError)
flexmock(TCPSocket).should_receive(:new).ordered.never
assert_raise Net::LDAP::Error do
Net::LDAP::Connection.new(:hosts => hosts)
end
end

def test_unresponsive_host
assert_raise Net::LDAP::Error do
Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636)
Expand Down

0 comments on commit 5673f83

Please sign in to comment.