Sovite.Core.Cluster (sovite v0.6.0)

Copy Markdown View Source

Runs Sovite as one node of a cluster ([cluster]), over Sovite.Cluster (Partisan, with mutual TLS).

[cluster]
enabled = true
node_name = "mx1@10.0.0.1"
address = "10.0.0.1"
seeds = ["mx2@10.0.0.2", "mx3@10.0.0.3"]
cert_file = "/etc/sovite/cluster/node.pem"
key_file = "/etc/sovite/cluster/node.key"
ca_file = "/etc/sovite/cluster/ca.pem"
shared_config = "/var/lib/sovite/cluster.toml"

Each node knows a few others (seeds); it joins them every join_interval until connected, and learns about the rest from them. Nodes connect over TLS 1.3, and accept only peers whose certificate ca_file signed, so that CA must be the cluster's own. The peer port (port, 4370) should only be reachable from the other nodes.

The nodes share:

  • their configuration (Sovite.Core.Cluster.SharedConfig);
  • rate limits, failed-login bans, suspended accounts, and with SQLite, greylisting (Sovite.Core.Cluster.Sync): nodes that share a PostgreSQL or MySQL database share the rest of the database anyway, so use one for the domains, users, and other tables;
  • their queues (Sovite.Core.Cluster.Queue): copies of each queued message on other nodes, which deliver it when its node stays down; mail handed to the node that sends from the right IP pool, or that owns its destination; and the queue of a node that leaves.

This process starts Partisan, which then keeps running until the VM stops, and answers other nodes' questions about this one.

Telemetry

See Sovite.Cluster.Joiner for nodes coming and going, and:

  • [:sovite, :cluster, :config, :applied] - %{version}, %{origin}, when this node takes a new shared configuration.
  • [:sovite, :cluster, :config, :rejected] - %{version}, %{origin, reason}, when it cannot.

Summary

Functions

Returns a specification to start this module under a supervisor.

The cluster's child specs, in start order, or none when the cluster is off.

Joins the cluster of the node at peer (name@host:port).

Takes this node out of the cluster: drains it (see Sovite.Core.Control), so it stops receiving and delivering mail, hands its queue to the connected nodes (Sovite.Core.Cluster.Queue), and leaves. Stop the node then. Returns how many messages were handed over, and how many stay here.

Sends content (TOML) to every node as the new shared configuration, after checking that this node can run with it. Returns the new version, or the reasons it does not work here.

The cluster as this node sees it: its name, the members and whether each is connected, and the shared configuration's version. With remote: true, each connected node is also asked for its version, host name, uptime, and queue sizes. :server is this process, if not registered under its module name.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

child_specs(config, runtime)

@spec child_specs(Sovite.Core.Config.t(), map()) :: [
  Supervisor.child_spec() | {module(), term()}
]

The cluster's child specs, in start order, or none when the cluster is off.

join(server \\ __MODULE__, peer)

@spec join(GenServer.server(), String.t()) :: :ok | {:error, String.t()}

Joins the cluster of the node at peer (name@host:port).

leave(server \\ __MODULE__)

@spec leave(GenServer.server()) ::
  {:ok, %{handed_over: non_neg_integer(), failed: non_neg_integer()}}
  | {:error, :no_peers}

Takes this node out of the cluster: drains it (see Sovite.Core.Control), so it stops receiving and delivering mail, hands its queue to the connected nodes (Sovite.Core.Cluster.Queue), and leaves. Stop the node then. Returns how many messages were handed over, and how many stay here.

push_config(server \\ __MODULE__, content)

@spec push_config(GenServer.server(), String.t()) ::
  {:ok, non_neg_integer()} | {:error, [String.t()]}

Sends content (TOML) to every node as the new shared configuration, after checking that this node can run with it. Returns the new version, or the reasons it does not work here.

status(opts \\ [])

@spec status(keyword()) :: map()

The cluster as this node sees it: its name, the members and whether each is connected, and the shared configuration's version. With remote: true, each connected node is also asked for its version, host name, uptime, and queue sizes. :server is this process, if not registered under its module name.