#!/usr/bin/bash
#
# This script is responsible for initialising both gluster servers (including volume
# creation) and gluster clients, when invoked with the -c flag the script is assumed
# to be running on a gluster server and will both create and start the gluster volume
# and then mount the filesystem (ie as a client).  When invoked without -c the script
# is assumed to be running on a client only and will simply perform a client mount of
# the volume from the cluster
#

set -e

LOGGER="logger"
[ -t 2 ] && LOGGER="logger --stderr"

usage() {
	cat 1>&2 <<-EOD
		Usage: $0 Options

		Options:
		  -h      Show this message
		  -1      hostname or ip address for the server1 (required)
		  -2      hostname or ip address for the server2 (required)
		  -m      mount point for the brick              (optional, unless -c is specified)
		  -p      volume mount point                     (required)
		  -o      volume mount options                   (optional)
		  -c      create and start the volume            (optional)
		  -v      volume name                            (optional, default: gv0)

	EOD
}

while getopts ":h1:2:b:m:p:o:v:g:l:c" opt; do
	case $opt in
		1)
			SERVER1=$OPTARG
			;;
		2)
			SERVER2=$OPTARG
			;;
		v)
			VOL=$OPTARG
			;;
		m)
			MOUNTPOINT=$OPTARG
			;;
		p)
			VOLMOUNTPOINT=$OPTARG
			;;
		o)
			VOLMOUNTOPTS=$OPTARG
			;;
		c)
			CREATEVOL=true
			;;
		h)
			usage
			exit 0
			;;
		:)
			usage
			echo "Option -$OPTARG requires an argument." >&2
			exit 1
			;;
		\?)
			usage
			echo "Invalid option: -$OPTARG" >&2
			exit 1
			;;
	esac
done

if [ $OPTIND -eq 1 ]; then
	usage
	exit 1
fi

VOL=${VOL:-gv0}

if [[ -n "$CREATEVOL" && -z $MOUNTPOINT ]]; then
	usage && echo "Error: -m is mandatory when -c is specified" >&2 && exit 1
fi

if [ -z $SERVER1 ]; then
	usage && echo "Error: -1 is mandatory" >&2 && exit 1
fi

if [[ -n "$CREATEVOL" && -z $SERVER2 ]]; then
	usage && echo "Error: -2 is mandatory when -c is passed" >&2 && exit 1
fi

if [ -z $VOLMOUNTPOINT ]; then
	usage && echo "Error: -p is mandatory" >&2 && exit 1
fi

function init_server()
{
	systemctl enable glusterd
	systemctl --quiet --no-block start glusterd

	if [ -n "$CREATEVOL" ]; then
		wait_until_peer_appears $SERVER2
		wait_until_peer_in_cluster $SERVER2

		if ! gluster volume list | grep -Pq $VOL; then
			# see https://joejulian.name/blog/glusterfs-path-or-a-prefix-of-it-is-already-part-of-a-volume/
			setfattr -x trusted.glusterfs.volume-id $MOUNTPOINT/$VOL >/dev/null 2>&1 || true
			setfattr -x trusted.gfid $MOUNTPOINT/$VOL >/dev/null 2>&1 || true
			rm -rf $MOUNTPOINT/$VOL/.glusterfs

			# With 2 replicas it's now necessary to use force to avoid an interactive prompt
			# https://bugzilla.redhat.com/show_bug.cgi?id=1495858
			gluster volume create $VOL replica 2 transport tcp $SERVER1:$MOUNTPOINT/$VOL $SERVER2:$MOUNTPOINT/$VOL force
			gluster volume start $VOL
		fi
	else
		# wait_until_peer_in_cluster $SERVER2
		wait_until_volume_started $VOL
	fi
}

function init_client()
{
	# Substitutes / with - and ignores the first character which would be - after substitution
	# Assumes there isn't a trailing slash
	local mount=${VOLMOUNTPOINT//\//-}
	if [ ! -e /usr/lib/systemd/system/${mount:1}.mount ]; then
		cat <<-EOD > /usr/lib/systemd/system/${mount:1}.mount
			[Unit]
			Description=${mount:1}
			After=network-online.target ng-waitfor-glusterd.service
			Requires=network-online.target ng-waitfor-glusterd.service
			Before=httpd.service
			Before=php-fpm-pool.service
			Before=ng-post-gluster.service
			Before=ng-makedb-init.service
			Before=home-oca.mount

			[Mount]
			What=$SERVER1:/$VOL
			Where=$VOLMOUNTPOINT
			Type=glusterfs
			Options=defaults,acl,selinux,_netdev,backupvolfile-server=$SERVER2${VOLMOUNTOPTS:+,${VOLMOUNTOPTS#,}}

			[Install]
			WantedBy=multi-user.target
			WantedBy=httpd.service
			WantedBy=php-fpm-pool.service
			WantedBy=ng-post-gluster.service
			WantedBy=ng-makedb-init.service
			WantedBy=home-oca.mount
		EOD

		systemctl daemon-reload
		systemctl enable ${mount:1}.mount
	fi

	systemctl start --quiet --no-block ${mount:1}.mount
}

function wait_until_peer_appears()
{
	local srv=$1
	local slp=${2:-5}
	# Initial log after 60 seconds, then every 300 (5min) thereafter
	local lastlogged=$(date +%s);
	local loginterval=60

	while true;
	do
		if gluster peer probe $srv 2>&1 >/dev/null; then
			break
		fi
		now=$(date +%s)
		if [ $lastlogged -lt $[$now-$loginterval] ]; then
			$LOGGER -t "$(basename $0)" "Waiting for peer $srv to appear"
			lastlogged=$now
			loginterval=300
		fi
		sleep $slp
	done
}

function wait_until_peer_in_cluster()
{
	local srv=$1
	local slp=${2:-5}
	# Initial log after 60 seconds, then every 300 (5min) thereafter
	local lastlogged=$(date +%s);
	local loginterval=60

	# Wait until the peer is in 'Peer in Cluster' state
	while true;
	do
		if gluster peer status --xml | xmllint --xpath "//peerStatus/peer/hostnames/hostname[text()='$srv']/../../stateStr/text()" - | grep -qi 'Peer in Cluster'; then
			break
		fi
		now=$(date +%s)
		if [ $lastlogged -lt $[$now-$loginterval] ]; then
			$LOGGER -t "$(basename $0)" "Waiting for peer $srv to join cluster"
			lastlogged=$now
			loginterval=300
		fi
		sleep $slp
	done
}

function wait_until_volume_started()
{
	local vol=$1
	local slp=${2:-5}
	local lastlogged=$(date +%s);
	local loginterval=60

	# Wait until the volume is started
	while true;
	do
		if gluster volume info --xml ${vol} |  xmllint --xpath "//volume/statusStr/text()" - | grep -qi 'Started'; then
			break
		fi
		# Initial log after 60 seconds, then every 300 (5min) thereafter
		now=$(date +%s)
		if [ $lastlogged -lt $[$now-60] ]; then
			$LOGGER -t "$(basename $0)" "Waiting for gluster volume $vol to start"
			lastlogged=$now
			loginterval=300
		fi
		sleep $slp
	done
}

if [ -n "$MOUNTPOINT" ]; then
	init_server
fi
init_client
