lib/process_hub/strategy/partition_tolerance/static_quorum.ex

defmodule ProcessHub.Strategy.PartitionTolerance.StaticQuorum do
  @moduledoc """
  The static quorum strategy for partition tolerance is used when the `ProcessHub` cluster is
  concerned with partition failures, and the cluster size is known.

  When a node leaves or joins the `ProcessHub` cluster, the static quorum strategy will check if the
  quorum is present. If the quorum is not present, the `ProcessHub` will be considered in a network
  partition, and the distributed supervisor process of the `ProcessHub` will be terminated along with
  all the child processes.
  """

  alias ProcessHub.Strategy.PartitionTolerance.Base, as: PartitionToleranceStrategy
  alias ProcessHub.Service.State
  alias PrcoessHub.Service.Cluster

  @typedoc """
  Static quorum strategy configuration.

  - `quorum_size` - The quorum size is measured in the number of nodes. For example, `3` means that
    there should be at least 3 nodes in the cluster for the `ProcessHub` to be considered healthy.
  - `startup_confirm` - If set to `true`, the `ProcessHub` will check if the quorum is present
    when the `ProcessHub` is starting up. If the quorum is not present, it will be
    considered as a network partition. The default value is `false`.
  """
  @type t() :: %__MODULE__{
          quorum_size: non_neg_integer(),
          startup_confirm: boolean()
        }
  @enforce_keys [:quorum_size]
  defstruct [:quorum_size, startup_confirm: false]

  defimpl PartitionToleranceStrategy, for: ProcessHub.Strategy.PartitionTolerance.StaticQuorum do
    alias ProcessHub.Service.Cluster

    @impl true
    def init(strategy, hub_id) do
      cluster_nodes = Cluster.nodes(hub_id, [:include_local])

      if strategy.startup_confirm do
        unless quorum_present?(strategy, cluster_nodes) do
          State.toggle_quorum_failure(hub_id)
        end
      end
    end

    @impl true
    def toggle_lock?(strategy, hub_id, _down_node) do
      cluster_nodes = Cluster.nodes(hub_id, [:include_local])

      !quorum_present?(strategy, cluster_nodes)
    end

    @impl true
    def toggle_unlock?(strategy, hub_id, _up_node) do
      cluster_nodes = Cluster.nodes(hub_id, [:include_local])

      quorum_present?(strategy, cluster_nodes)
    end

    defp quorum_present?(strategy, cluster_nodes) do
      strategy.quorum_size <= length(cluster_nodes)
    end
  end
end