Skip to content

Commit

Permalink
Don't exit when a connection fails and adds Paddle.reconnect/1 (#21)
Browse files Browse the repository at this point in the history
- Introduces a `reconnect` function
- Change start_link/1 and init/1 to accept options
- `start_link/1` and `reconnect/1` can return `{:error, reason}`
  • Loading branch information
shamanime authored and minijackson committed Aug 12, 2018
1 parent efaba26 commit aa9370b
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 45 deletions.
144 changes: 99 additions & 45 deletions lib/paddle.ex
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ defmodule Paddle do
alias Paddle.Filters
alias Paddle.Attributes

@typep ldap_conn :: :eldap.handle
@typep ldap_conn :: :eldap.handle | {:not_connected, binary}
@type ldap_entry :: %{required(binary) => binary}
@type auth_status :: :ok | {:error, atom}

Expand All @@ -121,74 +121,65 @@ defmodule Paddle do
@spec start_link(term) :: Genserver.on_start

@doc false
def start_link(_args) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end

@spec init(:ok) :: {:ok, ldap_conn}
@spec init([]) :: {:ok, ldap_conn}

@impl GenServer
def init(:ok) do
ssl = config(:ssl)
ipv6 = config(:ipv6)
tcpopts = config(:tcpopts)
sslopts = config(:sslopts)
host = config(:host)
port = config(:port)
timeout = config(:timeout)

Logger.info("Connecting to ldap#{if ssl, do: "s"}://#{inspect host}:#{port}")

tcpopts = if ipv6 do
[:inet6 | tcpopts]
else
tcpopts
end

options = [ssl: ssl,
port: port,
tcpopts: tcpopts,
log: &eldap_log_callback/3]

options = if timeout do
Keyword.put(options, :timeout, timeout)
else
options
end

options = if ssl do
Keyword.put(options, :sslopts, sslopts)
else
options
def init(opts \\ []) do
case do_connect(opts) do
{:ok, ldap_conn} -> {:ok, ldap_conn}
{:error, reason} -> {:ok, {:not_connected, reason}}
end

Logger.debug("Effective :eldap options: #{inspect options}")

{:ok, ldap_conn} = :eldap.open(host, options)

:eldap.controlling_process(ldap_conn, self())
Logger.info("Connected to LDAP")
{:ok, ldap_conn}
end

@type reason :: :normal | :shutdown | {:shutdown, term} | term

@spec terminate(reason, ldap_conn) :: :ok

@impl GenServer
def terminate(_reason, ldap_conn) do
def terminate(_shutdown_reason, {:not_connected, _reason}) do
:ok
Logger.info("Stopped LDAP, state was not connected")
end

@impl GenServer
def terminate(_shutdown_reason, ldap_conn) do
:eldap.close(ldap_conn)
Logger.info("Stopped LDAP")
end

@spec handle_call({:authenticate, charlist, charlist} |
{:reconnect, list} |
{:get, Paddle.Filters.t, dn, atom} |
{:get_single, Paddle.Filters.t, dn, atom} |
{:add, dn, attributes, atom} |
{:delete, dn, atom} |
{:modify, dn, atom, [mod]}, GenServer.from, ldap_conn) ::
{:reply, term, ldap_conn}

@impl GenServer
def handle_call({:reconnect, opts}, _from, ldap_conn) do
case ldap_conn do
{:not_connected, _reason} -> nil
pid -> :eldap.close(pid)
end

Logger.info("Reconnecting")

case do_connect(opts) do
{:ok, ldap_conn} -> {:reply, {:ok, :connected}, ldap_conn}
{:error, reason} -> {:reply, {:error, {:not_connected, reason}}, {:not_connected, reason}}
end
end

@impl GenServer
def handle_call(_message, _from, {:not_connected, _reason} = state) do
{:reply, {:error, :not_connected}, state}
end

@impl GenServer
def handle_call({:authenticate, dn, password}, _from, ldap_conn) do
Logger.debug "Authenticating with dn: #{dn}"
Expand Down Expand Up @@ -299,6 +290,23 @@ defmodule Paddle do
GenServer.call(Paddle, {:authenticate, dn, :binary.bin_to_list(password)})
end

@doc ~S"""
Closes the current connection and opens a new one.
Accepts connection information as arguments.
Not specified values will be fetched from the config.
Example:
iex> Paddle.reconnect(host: ['example.com'])
{:error, {:not_connected, "connect failed"}}
iex> Paddle.reconnect()
{:ok, :connected}
"""
def reconnect(opts \\ []) do
GenServer.call(Paddle, {:reconnect, opts})
end

@spec get_dn(Paddle.Class.t) :: {:ok, binary} | {:error, :missing_unique_identifier}

@doc ~S"""
Expand Down Expand Up @@ -674,4 +682,50 @@ defmodule Paddle do
end
end

defp do_connect(opts \\ []) do
ssl = Keyword.get(opts, :ssl, config(:ssl))
ipv6 = Keyword.get(opts, :ipv6, config(:ipv6))
tcpopts = Keyword.get(opts, :tcpopts, config(:tcpopts))
sslopts = Keyword.get(opts, :sslopts, config(:sslopts))
host = Keyword.get(opts, :host, config(:host))
port = Keyword.get(opts, :port, config(:port))
timeout = Keyword.get(opts, :timeout, config(:timeout))

Logger.info("Connecting to ldap#{if ssl, do: "s"}://#{inspect host}:#{port}")

tcpopts = if ipv6 do
[:inet6 | tcpopts]
else
tcpopts
end

options = [ssl: ssl,
port: port,
tcpopts: tcpopts,
log: &eldap_log_callback/3]

options = if timeout do
Keyword.put(options, :timeout, timeout)
else
options
end

options = if ssl do
Keyword.put(options, :sslopts, sslopts)
else
options
end

Logger.debug("Effective :eldap options: #{inspect options}")

case :eldap.open(host, options) do
{:ok, ldap_conn} ->
:eldap.controlling_process(ldap_conn, self())
Logger.info("Connected to LDAP")
{:ok, ldap_conn}
{:error, reason} ->
Logger.info("Failed to connect to LDAP")
{:error, Kernel.to_string(reason)}
end
end
end
8 changes: 8 additions & 0 deletions test/paddle_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ defmodule PaddleTest do
assert Paddle.get!(%MyApp.Room{roomNumber: 42}) == [%MyApp.Room{cn: ["meetingRoom"], description: ["The Room where meetings happens"], roomNumber: ["42"]}]
end

test "when a connection cannot be open" do
Paddle.reconnect(host: ['example.com'])

assert Paddle.authenticate([cn: "admin"], "password") == {:error, :not_connected}
assert Paddle.get(base: [uid: "testuser", ou: "People"]) == {:error, :not_connected}

Paddle.reconnect()
end
end

0 comments on commit aa9370b

Please sign in to comment.