Skip to content

Commit

Permalink
Add mod_libcluster
Browse files Browse the repository at this point in the history
  • Loading branch information
badlop committed Jul 16, 2024
1 parent 7b7c8c2 commit bd16d2c
Show file tree
Hide file tree
Showing 9 changed files with 673 additions and 0 deletions.
342 changes: 342 additions & 0 deletions mod_libcluster/COPYING

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions mod_libcluster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
mod_libcluster - Join nodes into cluster
========================================

Requires:
- ejabberd 24.xx or higher compiled with Elixir support


This small module connects this ejabberd node to other nodes
using [libcluster](https://github.com/bitwalker/libcluster).

Options
-------

This module supports several configurable options.
There are some example configuration files in the `test/` directory.
Options supported by this module:

* `strategy`

Sets the clustering strategy to use.

Supported values are:
- `local_epmd`: connect to all nodes managed by the local epmd program
- `epmd`: connect to the nodes you specify in the `config: hosts` option
- `kubernetes`

Default value: `local_epmd`.

* `hosts`

List of erlang node names to connect.
This is required, and only useful, when using the `epmd` strategy.
Default value: `[]`.

* `timeut`

Timeout to connect to a node, expressed in milliseconds.
This is only useful when using the `epmd` strategy.
Default value: `infinity`.

For details of options supported please check the
[libcluster documentation](https://hexdocs.pm/libcluster).

Test
----

There's a test script in `test/test.sh` that you can run.
It compiles ejabberd, installs in `/tmp/` several nodes,
and builds a cluster using the desired strategy.
24 changes: 24 additions & 0 deletions mod_libcluster/conf/mod_libcluster.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#modules:
# 'ModLibcluster':

# Connect with any other local node
#strategy: local_epmd

# Connect only to those specific nodes
#strategy: epmd
#config:
# hosts:
# - ejabberd1@127.0.0.1
# - ejabberd2@127.0.0.1
# - ejabberd3@127.0.0.1

#strategy: kubernetes
#config:
# mode: hostname
# kubernetes_ip_lookup_mode: pods
# kubernetes_namespace: "ejabberd"
# kubernetes_selector: "app=ejabberd"
# kubernetes_node_basename: "ejabberd"
# kubernetes_service_name: "ejabberd-headless"
# polling_interval: 10000

73 changes: 73 additions & 0 deletions mod_libcluster/lib/mod_libcluster.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
defmodule ModLibcluster do
use Ejabberd.Module

def start(_host, opts) do

strategy = case opts[:strategy] do
:local_epmd ->
:"Elixir.Cluster.Strategy.LocalEpmd"
:epmd ->
:"Elixir.Cluster.Strategy.Epmd"
:kubernetes ->
:"Elixir.Cluster.Strategy.Kubernetes"
other_strategy ->
other_strategy
end
info("Starting ejabberd module Libcluster with stategy #{inspect(strategy)}")

config = [
hosts: opts[:hosts],
timeout: opts[:timeout]
]
info("Starting ejabberd module Libcluster with config #{inspect(config)}")

topologies = [
ejabberd_cluster: [
strategy: strategy,
config: config,
connect: {:ejabberd_admin, :join_cluster, []},
disconnect: {:ejabberd_admin, :leave_cluster, []}
]
]
children = [
{Cluster.Supervisor, [topologies, [name: Ejabberd.ClusterSupervisor]]},
]
Supervisor.start_link(children, strategy: :one_for_one, name: Ejabberd.Supervisor)
info("Started ejabberd module Libcluster Demo")
:ok
end

def stop(_host) do
info("Stopping ejabberd module Libcluster Demo")
:ok
end

def depends(_host, _opts) do
[]
end

def mod_opt_type(:hosts) do
:econf.list(:econf.atom)
end

def mod_opt_type(:strategy) do
:econf.atom
end

def mod_opt_type(:timeout) do
:econf.either(:infinity, :econf.int());
end

def mod_options(_host) do
[
{:hosts, []},
{:strategy, :local_epmd},
{:timeout, :infinity}
]
end

def mod_doc() do
%{:desc => "This is just an empty string."}
end

end
5 changes: 5 additions & 0 deletions mod_libcluster/mod_libcluster.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
author: "Badlop <badlop at process-one.net>"
category: "cluster"
summary: "Join nodes into cluster"
home: "https://github.com/processone/ejabberd-contrib/tree/master/"
url: "git@github.com:processone/ejabberd-contrib.git"
4 changes: 4 additions & 0 deletions mod_libcluster/rebar.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{deps, [
{jason, "1.5.0-alpha.2", {git, "https://github.com/michalmuskala/jason", {branch, "master"}}},
{libcluster, "3.3.3", {git, "https://github.com/bitwalker/libcluster", {branch, "main"}}}
]}.
9 changes: 9 additions & 0 deletions mod_libcluster/test/epmd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
modules:
'ModLibcluster':
# Connect only to those specific nodes
strategy: epmd
timeout: 5000
hosts:
- ejabberd1@127.0.0.1
- ejabberd2@127.0.0.1
- ejabberd3@127.0.0.1
4 changes: 4 additions & 0 deletions mod_libcluster/test/local_epmd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
modules:
'ModLibcluster':
# Connect with any other local node
strategy: local_epmd
163 changes: 163 additions & 0 deletions mod_libcluster/test/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/bin/bash

COLOUR="\e[1;49;33m"
NEUTRAL="\033[00m"

log() {
echo ""
echo -e $COLOUR"==> $1"$NEUTRAL
}

MOD=$(pwd)

TOOL=$1
STR=$2
SRC=$3
if [ -n "$TOOL" ] && [ -n "$STR" ] && [ -n "$SRC" ] ; then
log "Using tool: $TOOL"
log "Using strategy: $STR"
log "Using ejabberd source code from: $SRC"
else
echo "Usage: $0 <tool> <strategy> <path>"
echo " <tool> = mix | rebar3"
echo " <strategy> = local_epmd | epmd"
echo " <path> = path to the ejabberd source code"
echo "For example: $0 mix local_epmd /home/user1/git/ejabberd/"
exit 1
fi

cd "$SRC" || exit 1

log "Compile ejabberd release..."

compile() {
./autogen.sh
./configure \
--with-rebar=$TOOL \
--enable-all
make
rm _build/prod/*.tar.gz
make rel
}

compile

log "Preparing ejabberd nodes..."

case "$TOOL" in
mix)
echo "Setting up 'mix' tool..."
FILE=$(ls -1 _build/prod/*.tar.gz)
;;
rebar3)
echo "Setting up 'rebar3' tool..."
FILE=$(ls -1 _build/prod/rel/ejabberd/*.tar.gz)
;;
esac

create_node() {
N=$1
log "Preparing node $N using file $FILE..."
rm -rf /tmp/libcluster/"$N"
mkdir -p /tmp/libcluster/"$N"
tar -xzf "$FILE" -C /tmp/libcluster/"$N"
sed -i "s|#' POLL|EJABBERD_BYPASS_WARNINGS=true\n\n#' POLL|g" /tmp/libcluster/"$N"/conf/ejabberdctl.cfg
sed -i "s|#ERLANG_NODE=.*|ERLANG_NODE=ejabberd$N@127.0.0.1|" /tmp/libcluster/"$N"/conf/ejabberdctl.cfg
sed -i "s| port: \([0-9]*\)| port: \1$N|g" /tmp/libcluster/"$N"/conf/ejabberd.yml
sed -i "s|mod_proxy65:|mod_proxy65:\n port: 777$N|" /tmp/libcluster/"$N"/conf/ejabberd.yml
node[N]=/tmp/libcluster/"$N"/bin/ejabberdctl
}

create_node 1
create_node 2
create_node 3

FIRST=${node[1]}
SECOND=${node[2]}
THIRD=${node[3]}

log "Let's start the first node..."

echo "first: $FIRST"
$FIRST start
$FIRST started
$FIRST set_master self

log "Uninstall and install mod_libcluster:"

$FIRST module_uninstall mod_libcluster
rm -rf $MOD/deps/
rm -rf $MOD/ebin/
$FIRST module_install mod_libcluster || exit 1

log "Configure mod_libcluster:"

case "$STR" in
local_epmd)
echo "Setting up 'local_epmd' config..."
cp "$MOD"/test/local_epmd.yml \
"$HOME"/.ejabberd-modules/mod_libcluster/conf/mod_libcluster.yml
;;
epmd)
echo "Setting up 'epmd' config..."
cp "$MOD"/test/epmd.yml \
"$HOME"/.ejabberd-modules/mod_libcluster/conf/mod_libcluster.yml
;;
esac

log "Initial cluster of first node (should be just this node):"

$FIRST list_cluster

log "Let's start the second node, it should connect automatically to the first node..."

$SECOND start
$SECOND started
sleep 5 # wait a few seconds to let the second node join the cluster properly
$SECOND set_master ejabberd1@127.0.0.1

log "Let's start the third node, it should connect automatically to the first node..."

$THIRD start
$THIRD started
sleep 5 # wait a few seconds to let the second node join the cluster properly

log "Cluster as seen by first node:"

$FIRST list_cluster

log "Cluster as seen by second node:"

$SECOND list_cluster

log "Cluster as seen by third node:"

$THIRD list_cluster

log "Stop first node, and check cluster as seen by second node:"

$FIRST stop
$FIRST stopped
$SECOND list_cluster

log "... as seen by third node:"

$THIRD list_cluster

log "Start again first node, and check cluster as seen by all the nodes:"

$FIRST start
$FIRST started
sleep 5
echo "First: "
$FIRST list_cluster
echo "Second: "
$SECOND list_cluster
echo "Third: "
$THIRD list_cluster

log "Stopping all nodes..."

$FIRST stop
$SECOND stop
$THIRD stop

0 comments on commit bd16d2c

Please sign in to comment.