Packages

Clusterable makes clustering easier

Current section

Files

Jump to
clusterable lib clusterable epmd.ex
Raw

lib/clusterable/epmd.ex

# Shamelessly borrowed from Magnus Henoch's fantastic Erlang Solutions article:
# https://www.erlang-solutions.com/blog/erlang-and-elixir-distribution-without-epmd.html
# You want to read that! :)
defmodule Clusterable.EPMD.Service do
@moduledoc false
def port(name) when is_atom(name) do
port Atom.to_string name
end
def port(name) when is_list(name) do
port List.to_string name
end
def port(name) when is_binary(name) do
# Figure out the base port. If not specified using the
# inet_dist_base_port kernel environment variable, default to
# 4370, one above the epmd port.
base_port = :application.get_env :kernel, :inet_dist_base_port, 4370
# Now, figure out our "offset" on top of the base port. The
# offset is the integer just to the left of the @ sign in our node
# name. If there is no such number, the offset is 0.
#
# Also handle the case when no hostname was specified.
node_name = Regex.replace ~r/@.*$/, name, ""
offset =
case Regex.run ~r/[0-9]+$/, node_name do
nil ->
0
[offset_as_string] ->
String.to_integer offset_as_string
end
base_port + offset
end
end
defmodule Clusterable.EPMD.Service_dist do
@moduledoc false
def listen(name) do
port = Clusterable.EPMD.Service.port name
# Set both "min" and "max" variables, to force the port number to this one.
:ok = :application.set_env :kernel, :inet_dist_listen_min, port
:ok = :application.set_env :kernel, :inet_dist_listen_max, port
:inet_tcp_dist.listen name
end
def select(node) do
:inet_tcp_dist.select node
end
def accept(listen) do
:inet_tcp_dist.accept listen
end
def accept_connection(accept_pid, socket, my_node, allowed, setup_time) do
require Logger
Logger.debug("Accepting connection! #{inspect my_node}")
:inet_tcp_dist.accept_connection accept_pid, socket, my_node, allowed, setup_time
end
def setup(node, type, my_node, long_or_short_names, setup_time) do
:inet_tcp_dist.setup node, type, my_node, long_or_short_names, setup_time
end
def close(listen) do
:inet_tcp_dist.close listen
end
def childspecs do
#:inet_tcp_dist.childspecs
[]
end
end
defmodule Clusterable.EPMD.Client do
@moduledoc false
# erl_distribution wants us to start a worker process. We don't
# need one, though.
def start_link do
:ignore
end
# As of Erlang/OTP 19.1, register_node/3 is used instead of
# register_node/2, passing along the address family, 'inet_tcp' or
# 'inet6_tcp'. This makes no difference for our purposes.
def register_node(name, port, _family) do
register_node(name, port)
end
def register_node(_name, _port) do
# This is where we would connect to epmd and tell it which port
# we're listening on, but since we're epmd-less, we don't do that.
# Need to return a "creation" number between 1 and 3.
creation = :rand.uniform 3
{:ok, creation}
end
def port_please(name, _ip) do
port = Clusterable.EPMD.Service.port name
# The distribution protocol version number has been 5 ever since
# Erlang/OTP R6.
version = 5
{:port, port, version}
end
def names(_hostname) do
# Since we don't have epmd, we don't really know what other nodes
# there are.
{:error, :address}
end
end