Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support name_match option for Strategy.ErlangHosts #140

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 86 additions & 17 deletions lib/strategy/erlang_hosts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,27 @@ defmodule Cluster.Strategy.ErlangHosts do
topologies: [
erlang_hosts_example: [
strategy: #{__MODULE__},
config: [timeout: 30_000]
config: [timeout: 30_000, name_match: "a"]
]
]

An optional timeout can be specified in the config. This is the timeout that
will be used in the GenServer to connect the nodes. This defaults to
`:infinity` meaning that the connection process will only happen when the
## Options

* `timeout` - This is the timeout that will be used in the GenServer to connect the nodes.
This defaults to `:infinity` meaning that the connection process will only happen when the
worker is started. Any integer timeout will result in the connection process
being triggered. In the example above, it has been configured for 30 seconds.
being triggered. In the example above, it has been configured for 30 seconds. (optional; default: :infinity)
* `name_match` - Only connect that node which match the name of prefix. (optional; default: nil)

| :name_match | type | :"[email protected]" | :"[email protected]" | :"[email protected]" | :"[email protected]" |
| :a | atom() | √ | × | × | × |
| [:a, :b] | [atom()] | √ | √ | × | × |
| 'a1' | charlist() | × | × | √ | × |
| "1" | String.t() | × | × | √ | √ |
| ~r/^a/ | Regex.t() | √ | × | √ | × |
| &String.ends_with(&1, "1") | fun() | × | × | √ | √ |
| {String, :printable?} | {module(), atom()} | √ | √ | √ | √ |
| {String, :ends_with, ["1"]} | {module(), atom(), [term()]} | × | × | √ | √ |
"""
use GenServer
use Cluster.Strategy
Expand All @@ -48,15 +60,19 @@ defmodule Cluster.Strategy.ErlangHosts do
Cluster.Logger.warn(topology, "couldn't find .hosts.erlang file - not joining cluster")
:ignore

file ->
new_state = %State{state | :meta => file}
hosts ->
new_state = %State{state | :meta => hosts}
GenServer.start_link(__MODULE__, [new_state])
end
end

@impl true
def init([state]) do
new_state = connect_hosts(state)
new_state =
state
|> append_name_matcher()
|> connect_hosts()

{:ok, new_state, configured_timeout(new_state)}
end

Expand All @@ -70,27 +86,80 @@ defmodule Cluster.Strategy.ErlangHosts do
{:noreply, new_state, configured_timeout(new_state)}
end

defp append_name_matcher(%State{config: config, meta: hosts} = state) do
name_matcher =
case Keyword.get(config, :name_match) do
nil ->
nil

atom when is_atom(atom) ->
string = to_string(atom)
&(string == to_string(&1))

[atom | _] = atom_list when is_atom(atom) ->
list = Enum.map(atom_list, &to_string/1)
&Enum.member?(list, to_string(&1))

[int | _] = charlist when is_integer(int) ->
&(charlist == &1)

string when is_binary(string) ->
&String.contains?(to_string(&1), string)

%Regex{} = reg ->
&String.match?(to_string(&1), reg)

fun when is_function(fun, 1) ->
&fun.(to_string(&1))

{m, f} ->
&apply(m, f, [to_string(&1)])

{m, f, args} ->
&apply(m, f, [to_string(&1) | args])

_unsupported ->
nil
end

%{state | meta: {name_matcher, hosts}}
end

defp configured_timeout(%State{config: config}) do
Keyword.get(config, :timeout, :infinity)
end

defp connect_hosts(%State{meta: hosts_file} = state) do
defp connect_hosts(%State{meta: {name_matcher, hosts}} = state) do
nodes =
hosts_file
hosts
|> Enum.map(&{:net_adm.names(&1), &1})
|> gather_node_names([])
|> gather_node_names(name_matcher, [])
|> List.delete(node())

Cluster.Strategy.connect_nodes(state.topology, state.connect, state.list_nodes, nodes)
state
end

defp gather_node_names([], acc), do: acc

defp gather_node_names([{{:ok, names}, host} | rest], acc) do
names = Enum.map(names, fn {name, _} -> String.to_atom("#{name}@#{host}") end)
gather_node_names(rest, names ++ acc)
defp gather_node_names([], _name_matcher, acc), do: acc

defp gather_node_names([{{:ok, names}, host} | rest], name_matcher, acc) do
names =
if name_matcher do
Enum.reduce(names, acc, fn {name, _}, acc ->
if name_matcher.(name) do
node_name = String.to_atom("#{name}@#{host}")
[node_name | acc]
else
acc
end
end)
else
Enum.map(names, fn {name, _} -> String.to_atom("#{name}@#{host}") end) ++ acc
end

gather_node_names(rest, name_matcher, names)
end

defp gather_node_names([_ | rest], acc), do: gather_node_names(rest, acc)
defp gather_node_names([_ | rest], name_matcher, acc),
do: gather_node_names(rest, name_matcher, acc)
end