Packages
cloister
0.1.0
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
The helper application to manage cluster, that uses hash ring to route requests to nodes. Automatically keeps track of connected nodes, provides helpers to determine where the term is to be executed, to multicast to all the nodes in the cluster and to retrieve current state of the cluster.
Current section
Files
Jump to
Current section
Files
lib/cloister/node.ex
defmodule Cloister.Node do
@moduledoc """
The state of the cloister. This process runs under supervision and makes sure
the cluster is up-to-date with the expectations.
"""
alias Cloister.Node, as: N
use GenServer
defstruct otp_app: :cloister, clustered?: false, sentry?: false, alive?: false
@typedoc "Internal representation of the Node managed by Cloister"
@type t :: %__MODULE__{}
@doc false
def start_link(opts \\ []),
do: GenServer.start_link(__MODULE__, struct(Cloister.Node, opts), name: __MODULE__)
@impl GenServer
@doc false
def init(state), do: {:ok, state, {:continue, :quorum}}
@impl GenServer
@doc false
def handle_continue(:quorum, %N{} = state),
do: do_handle_quorum(Node.alive?(), state)
@spec do_handle_quorum(boolean(), state :: t()) ::
{:noreply, new_state} | {:noreply, new_state, {:continue, term()}}
when new_state: term()
@doc false
defp do_handle_quorum(true, %N{otp_app: otp_app} = state) do
active_sentry =
for sentry <- Application.fetch_env!(otp_app, :sentry),
Node.connect(sentry),
do: sentry
if active_sentry != [] do
{:noreply,
%N{
state
| alive?: true,
sentry?: Enum.member?(active_sentry, Node.self()),
clustered?: true
}}
else
{:noreply, state, {:continue, :quorum}}
end
end
@doc false
defp do_handle_quorum(false, state),
do: {:noreply, %N{state | sentry?: true, clustered?: false}}
##############################################################################
@spec state :: t()
@doc "Returns an internal state of the Node"
def state, do: GenServer.call(__MODULE__, :state)
@spec siblings :: boolean()
@doc "Returns whether the requested amount of nodes in the cluster are connected"
def siblings, do: GenServer.call(__MODULE__, :siblings)
@spec multicast(name :: GenServer.name(), request :: term()) :: :abcast
@doc "Casts the request to all the nodes connected to this node"
def multicast(name, request),
do: GenServer.abcast(name, request)
##############################################################################
@impl GenServer
@doc false
def handle_call(:state, _from, state), do: {:reply, state, state}
@impl GenServer
@doc false
def handle_call(:siblings, _from, state) do
connected =
:connected
|> Elixir.Node.list()
|> Enum.count()
|> Kernel.+(1)
expected = Application.fetch_env!(state.otp_app, :consensus)
result =
case connected - expected do
0 -> :ok
i when i > 0 -> {:ok, [expected: expected, connected: connected]}
i when i < 0 -> {:error, [expected: expected, connected: connected]}
end
{:reply, result, state}
end
end