Packages

Keeping nodes connected

Current section

Files

Jump to
liaison lib strategy empd.ex
Raw

lib/strategy/empd.ex

defmodule Liaison.Strategy.Epmd do
@moduledoc """
Strategy for node connections via EPMD
```elixir
config :liaison,
strategy: [
[
strategy: #{__MODULE__},
reconnect_period: 10,
nodes: []
]
]
```
## Staying Connected
Staying conencted via the Epmd strategy is now easier than ever, just specify
the nodes you want to connect to, and how often you want to attempt re-connect
"""
@type strategy_config :: Keyword.t()
@default_config [
nodes: [],
reconnect_period: 10
]
alias Liaison.Logger
alias Liaison.NodeHelper
use GenServer
@doc false
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]}
}
end
@spec start_link(keyword) :: :ignored | {:error, any()} | {:ok, pid}
def start_link(strategy_config) do
config =
merge_config(strategy_config)
|> expand_nodes()
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
defp merge_config(config1 \\ @default_config, config2) do
Keyword.merge(config1, config2)
end
defp expand_nodes(config) do
nodes = Enum.map(config[:nodes], &NodeHelper.to_nodename/1)
Keyword.put(config, :nodes, nodes)
end
@doc false
@spec init(any) :: {:ok, any}
def init(state) do
log(:info, "Monitoring started")
Process.flag(:trap_exit, true)
:net_kernel.monitor_nodes(true)
send(self(), :ensure_connected)
{:ok, state}
end
# ---------------- node up and down management --------------
def handle_info(:ensure_connected, state) do
{_already_con, connected, not_connected} = connect_all(state[:nodes])
case connected do
[] -> nil
con -> log(:info, "Connected: #{inspect(con)}")
end
case not_connected do
[] ->
nil
not_connected ->
log(:warn, "Couldnt connect: #{inspect(not_connected)}")
schedule(:ensure_connected, state[:reconnect_period])
end
{:noreply, state}
end
def handle_info({:nodedown, node}, state) do
log(:debug, "node #{node} down")
send(self(), :ensure_connected)
display_nodes()
{:noreply, state}
end
def handle_info({:nodeup, node}, state) do
log(:debug, "node #{node} up")
display_nodes()
{:noreply, state}
end
# --------------------- server calls --------------
def handle_call(:get_config, _from, state) do
{:reply, state, state}
end
def handle_call({:update_strategy, strategy}, _from, old_state) do
new_state =
merge_config(old_state, strategy)
|> expand_nodes()
{:reply, {old_state, new_state}, new_state}
end
# ---------------------- helper functions ------------
defp log(level, msg) do
Logger.log(level, "Epmd", msg)
end
defp schedule(msg, seconds) do
Process.send_after(self(), msg, seconds * 1000)
end
defp display_nodes() do
log(:debug, "node list: #{inspect(get_nodes())}")
end
# returns {already_connected, connected, not_connected}
defp connect_all(nodes) do
default = %{already_connected: [], connected: [], not_connected: []}
Enum.reduce(nodes, default, fn node, acc ->
case Enum.member?(get_nodes(), node) do
true ->
Map.update!(acc, :already_connected, &[node | &1])
_ ->
case connect(node) do
true -> Map.update!(acc, :connected, &[node | &1])
_ -> Map.update!(acc, :not_connected, &[node | &1])
end
end
end)
|> Map.values()
|> List.to_tuple()
end
defp connect(node), do: Node.connect(node)
defp get_nodes(), do: Node.list()
# -------------------- client calls ----------------
@doc """
Returns the current configuration of the strategy
"""
@spec get_config :: keyword
def get_config() do
GenServer.call(__MODULE__, :get_config)
end
@doc """
Updates the given module to adopt a new strategy. Returns {old config, new config}
"""
@spec update_strategy(strategy_config) :: {:ok, {strategy_config, strategy_config}}
def update_strategy(strat_config) do
change = GenServer.call(__MODULE__, {:update_strategy, strat_config})
{:ok, change}
end
end