#!/bin/sh
# Copyright (c) 2000-2015 Synology Inc. All rights reserved.

# remember in preupgrade the pkg_utils and dumpstring in /var/packages/${SYNOPKG_PKGNAME} are still old version
. ${SYNOPKG_PKGINST_TEMP_DIR}/../scripts/pkg_utils
. ${SYNOPKG_PKGINST_TEMP_DIR}/../scripts/dumpstring
. ${SYNOPKG_PKGINST_TEMP_DIR}/../scripts/pkg_compat

CLOG_DIR="${PKG_DIR}/target/Log"
UPGRADE_TMP_DIR="${SYNOPKG_PKGDEST_VOL}/@tmp/CCC_upgrade"
UPGRADE_CLOG_DIR="${UPGRADE_TMP_DIR}/Log"
CCC_WEBAPI_DIR_SRC="/var/packages/${SYNOPKG_PKGNAME}/target/webapi"
CCC_WEBAPI_DIR_DST="/usr/syno/synoman/webapi/${SYNOPKG_PKGNAME}"
CCC_WEBAPI_LIB_SRC="/var/packages/${SYNOPKG_PKGNAME}/target/webapi/SYNO.CCC.lib"
CCC_WEBAPI_LIB_DST="/usr/syno/synoman/webapi/SYNO.CCC.lib"
VIRTUALIZATION_CONF_PATH="/usr/syno/etc/ccc/virtualization.conf"
CCC_CONF_PATH="/usr/syno/etc/ccc/cccinfo.conf"
REBUILD_CLUSTER_KEY="rebuild_cluster"
RUN_CCC_KEY="runccc"

##
# @retval 1 if in cluster
# @retval 0 if not
#
NotInCluster()
{
	if [ -x "${CCC_PKG_TOOL}" ]; then
		${CCC_PKG_TOOL} --is-in-cluster
		return $?
	fi
	return 0
}

##
# Check if this is a package api incompatible upgrade
# @retval  3   is api incompatible upgrade and already in incompatible upgrade mode
# @retval  2   is incompatible upgrade but not in incompatible upgrade mode
# @retval  1   unknown error
# #retval  0   is api compatible upgrade
#
CheckPackageAPIIncompatibleUpgrade()
{
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`
	local api_compat_version=${PKG_LEAST_API_COMPATIBLE_VERSION}
	local api_compat_buildnum=`echo ${api_compat_version} | awk -F"-" '{print $2}'`
	local api_incompatible_upgrade_flag="/usr/syno/etc/ccc/incompatible.upgrading"

	if [ -z "${old_buildnum}" ] || [ -z "${api_compat_buildnum}" ]; then
		logger -p err -t "${PKG_TAG}" "Failed to acquire old version:\"${old_buildnum}\" or api compatible version:\"${api_compat_version}\""
		return 1
	fi

	logger -p err -t "${PKG_TAG}" "(old_ver, api_compat_ver)=(${old_version}, ${api_compat_version})"

	if [ "${old_buildnum}" -lt "${api_compat_buildnum}" ]; then
		if [ -f "${api_incompatible_upgrade_flag}" ]; then
			logger -p err -t "${PKG_TAG}" "Already in api incompatible upgrade mode"
			return 3
		else
			logger -p err -t "${PKG_TAG}" "Need api incompatible upgrade"
			return 2
		fi
	fi
	return 0
}

##
# Check if this is a package disallow local VM running upgrade
# @retval  2   is disallow local VM running upgrade
# @retval  1   unknown error
# #retval  0   is allow local vm running upgrade
#
CheckDisallowLocalGuestRunningUpgrade()
{
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`
	local allow_local_vm_running_version=${PKG_LEAST_LOCAL_VM_RUNNING_VERSION}
	local allow_local_vm_running_buildnum=`echo ${allow_local_vm_running_version} | awk -F"-" '{print $2}'`

	if [ -z "${old_buildnum}" ] || [ -z "${allow_local_vm_running_version}" ]; then
		logger -p err -t "${PKG_TAG}" "Failed to acquire old version:\"${old_buildnum}\" or allow local guest running version:\"${allow_local_vm_running_version}\""
		return 1
	fi

	logger -p err -t "${PKG_TAG}" "(old_ver, allow_local_vm_running_version)=(${old_version}, ${allow_local_vm_running_version})"

	if [ "${old_buildnum}" -lt "${allow_local_vm_running_buildnum}" ]; then
		logger -p err -t "${PKG_TAG}" "Need to shutdown all local guest."
		return 2
	fi
	return 0
}

DumpClusterDashboard()
{
	local SYNOETCD=/usr/local/bin/synoetcd

	${SYNOETCD} dump > ${CLOG_DIR}/prev_dashboard.dump
}

BackupLog()
{
	rm -rf $UPGRADE_TMP_DIR
	mkdir -p $UPGRADE_CLOG_DIR
	/bin/mv "${CLOG_DIR}/log.sql" $UPGRADE_CLOG_DIR
	/bin/mv "${CLOG_DIR}/locallog.sql" $UPGRADE_CLOG_DIR
	/bin/mv "${CLOG_DIR}/prev_dashboard.dump" $UPGRADE_CLOG_DIR
}

LinkWebapi()
{
	# only SYNO.CCC webapi is needed, no need SYNO.Virtualization webapi
	ln -sf ${CCC_WEBAPI_DIR_SRC} ${CCC_WEBAPI_DIR_DST}
	ln -sf ${CCC_WEBAPI_LIB_SRC} ${CCC_WEBAPI_LIB_DST}
}

UnlinkWebapi()
{
	rm -f ${CCC_WEBAPI_LIB_DST}
	rm -f ${CCC_WEBAPI_DIR_DST}
}

StartDaemon()
{
	/sbin/initctl start ${PKG_UPSTART_JOB_PREFIX}etcd
	/sbin/initctl start ${PKG_UPSTART_JOB_PREFIX}libvirtd
}

StopDaemon()
{
	${CCC_PKG_TOOL} --pkg-stop-clean
}

ServiceStopAndClear()
{
	StopDaemon
	UnlinkWebapi
	UnlinkLibvirt
}

##
# use either synoccctool or curl to check etcd is ready
# @retval 0 if dashboard is ready
# @retval 1 if not
#
WaitDashboardReady()
{
	local ETCD_URL="http://127.0.0.1:2379/v2/keys/?quorum=true"
	local MAX_RETRY=12
	local RETRY=0
	local RET_CCC_TOOL=""

	# first, use synoccctool to ask if dashboard is ready
	if [ -x "${CCC_PKG_TOOL}" ]; then
		${CCC_PKG_TOOL} --is-dashboard-ready
		RET_CCC_TOOL="$?"
		logger -p err -t "${PKG_TAG}" "synoccctool returns ${RET_CCC_TOOL}"
	fi

	if [ 0 -ne "${RET_CCC_TOOL}" ] && [ 1 -ne "${RET_CCC_TOOL}" ]; then
		# fallback to use curl
		while [ "${RETRY}" -lt "${MAX_RETRY}" ]; do
			curl --max-time 5 ${ETCD_URL}
			RET_CCC_TOOL="$?"
			logger -p err -t "${PKG_TAG}" "curl returns ${RET_CCC_TOOL}"
			if [ 0 -eq "${RET_CCC_TOOL}" ]; then
				break
			fi
			RETRY=`expr ${RETRY} + 1`
		done
	fi

	if [ 0 -ne "${RET_CCC_TOOL}" ]; then
		DumpString error pkg_upgrade_error_dashboard_not_ready > ${SYNOPKG_TEMP_LOGFILE}
		StopDaemon
		UnlinkLibvirt
		exit 1
	fi
}

PrepareUpgradeRollback()
{
	ServiceStopAndClear
	UnsetLocalIncompUpgrade
}

##
# call synoccctool to become api incompatible upgrade mode
# @retval  1   error happened, may be vdsm is running or synoccctool returns non-zero value
# @retval  0   success
#
PrepareAPIIncompatibleUpgrade()
{
	local BIN_UPGRADE_PREPARE=""
	local BIN_UPGRADE_PREPARE_PARAM=""
	local RET_UPGRADE_PREPARE=0
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`

	if [ "${old_buildnum}" -lt "7426" ]; then
		BIN_UPGRADE_PREPARE="/var/packages/Virtualization/target/bin/synocccupgradeprepare"
		BIN_UPGRADE_PREPARE_PARAM=""
	else
		BIN_UPGRADE_PREPARE="/var/packages/Virtualization/target/bin/synoccctool"
		BIN_UPGRADE_PREPARE_PARAM="--dsm-upgrade-prepare"
	fi

	if [ ! -x "${BIN_UPGRADE_PREPARE}" ]; then
		logger -p err -t "${PKG_TAG}" "upgradeprepare binary ${BIN_UPGRADE_PREPARE} not found"
		DumpString error pkg_upgrade_error_prepare_fail > ${SYNOPKG_TEMP_LOGFILE}
		PrepareUpgradeRollback
		exit 1
	fi

	#/bin/env CONTENT_TYPE="application/x-www-form-urlencoded" ${BIN_UPGRADE_PREPARE}
	${BIN_UPGRADE_PREPARE} ${BIN_UPGRADE_PREPARE_PARAM}
	RET_UPGRADE_PREPARE="$?"

	logger -p err -t "${PKG_TAG}" "synocccupgradeprepare returns ${RET_UPGRADE_PREPARE}"

	if [ "${RET_UPGRADE_PREPARE}" -eq "3" ]; then
		if [ "${old_buildnum}" -le 9206 ]; then
			DumpString error pkg_upgrade_error_vdsm_running_in_cluster > ${SYNOPKG_TEMP_LOGFILE}
		else
			DumpString error pkg_upgrade_error_remote_vm_running_in_cluster > ${SYNOPKG_TEMP_LOGFILE}
		fi
		PrepareUpgradeRollback
		exit 1
	elif [ "${RET_UPGRADE_PREPARE}" -eq "2" ]; then
		DumpString error pkg_upgrade_error_host_not_ready_in_cluster > ${SYNOPKG_TEMP_LOGFILE}
		PrepareUpgradeRollback
		exit 1
	elif [ "${RET_UPGRADE_PREPARE}" -ne "0" ]; then
		DumpString error pkg_upgrade_error_prepare_fail > ${SYNOPKG_TEMP_LOGFILE}
		PrepareUpgradeRollback
		exit 1
	fi

	return 0
}

##
# call synoccctool to check if all VM stop
# @retval  1   error happened, maybe some local VMs are still running or ccctool returns non-zero value
# @retval  0   success
#
DisallowAllVMRunningPrepare()
{
	local BIN_CCCTOOL="/var/packages/Virtualization/target/bin/synoccctool"
	local BIN_CCCTOOL_PARAM="--is-all-vm-stop-in-cluster"
	local RET_DISALLOW_ALL_VM_RUNNING_PREPARE=0
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`

	if [ "${old_buildnum}" -le "9206" ]; then
		return 0
	fi

	if [ ! -x "${BIN_CCCTOOL}" ]; then
		logger -p err -t "${PKG_TAG}" "synoccctool binary ${BIN_CCCTOOL} not found"
		DumpString error pkg_upgrade_error_prepare_fail > ${SYNOPKG_TEMP_LOGFILE}
		ServiceStopAndClear
		exit 1
	fi

	#/bin/env CONTENT_TYPE="application/x-www-form-urlencoded" ${BIN_UPGRADE_PREPARE}
	${BIN_CCCTOOL} ${BIN_CCCTOOL_PARAM}
	RET_DISALLOW_ALL_VM_RUNNING_PREPARE="$?"

	logger -p err -t "${PKG_TAG}" "is-all-vm-stop-in-cluster returns ${RET_DISALLOW_ALL_VM_RUNNING_PREPARE}"

	if [ "${RET_DISALLOW_ALL_VM_RUNNING_PREPARE}" -ne "0" ]; then
		DumpString error pkg_upgrade_error_vdsm_running_in_cluster > ${SYNOPKG_TEMP_LOGFILE}
		ServiceStopAndClear
		exit 1
	fi
}

##
# call synoccctool to check if all local VM stop
# @retval  1   error happened, maybe some local VMs are still running or ccctool returns non-zero value
# @retval  0   success
#
DisallowLocalVMRunningPrepare()
{
	local BIN_CCCTOOL="/var/packages/Virtualization/target/bin/synoccctool"
	local BIN_CCCTOOL_PARAM="--disallow-local-vm-running-prepare"
	local RET_DISALLOW_LOCAL_VM_RUNNING_PREPARE=0
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`

	if [ "${old_buildnum}" -le "9206" ]; then
		return 0
	fi

	if [ ! -x "${BIN_CCCTOOL}" ]; then
		logger -p err -t "${PKG_TAG}" "synoccctool binary ${BIN_CCCTOOL} not found"
		DumpString error pkg_upgrade_error_prepare_fail > ${SYNOPKG_TEMP_LOGFILE}
		PrepareUpgradeRollback
		exit 1
	fi

	#/bin/env CONTENT_TYPE="application/x-www-form-urlencoded" ${BIN_UPGRADE_PREPARE}
	${BIN_CCCTOOL} ${BIN_CCCTOOL_PARAM}
	RET_DISALLOW_LOCAL_VM_RUNNING_PREPARE="$?"

	logger -p err -t "${PKG_TAG}" "disallow-local-vm-running-prepare returns ${RET_DISALLOW_LOCAL_VM_RUNNING_PREPARE}"

	if [ "${RET_DISALLOW_LOCAL_VM_RUNNING_PREPARE}" -ne "0" ]; then
		DumpString error pkg_upgrade_error_local_vm_running_in_cluster > ${SYNOPKG_TEMP_LOGFILE}
		PrepareUpgradeRollback
		exit 1
	fi
}

TouchUpgradeFlag()
{
	local UPGRADE_TMP_FILE="/var/packages/Virtualization/etc/virtualization.upgrade.flag"

	if [ -f "${UPGRADE_TMP_FILE}" ]; then
		local old_version="${SYNOPKG_OLD_PKGVER}"
		local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`
		local flag_version=`cat "${UPGRADE_TMP_FILE}"`
		local flag_buildnum=`echo ${flag_version} | awk -F"-" '{print $2}'`

		if [ "${flag_buildnum}" -le "${old_buildnum}" ]; then
			return 0
		fi
	fi
	echo "${SYNOPKG_OLD_PKGVER}" > ${UPGRADE_TMP_FILE}
}

TouchNonstopUpgradeFlag()
{
	local VMCOMM_VM_NONSTOP_FLAG="/var/packages/Virtualization/etc/vmcomm.vm.nonstop.flag"
	local old_version="${SYNOPKG_OLD_PKGVER}"
	local old_buildnum=`echo ${old_version} | awk -F"-" '{print $2}'`

	if [ "${old_buildnum}" -ge "8415" ]; then
		touch ${VMCOMM_VM_NONSTOP_FLAG}
	fi
}

PkgUpgradeCheckAndPrepare()
{
	CheckPackageAPIIncompatibleUpgrade
	local IsAPIIncompatibleUpgrade="$?"
	CheckDisallowLocalGuestRunningUpgrade
	local IsDisallowLocalGuestRunningUpgrade="$?"

	case "${IsAPIIncompatibleUpgrade}${IsDisallowLocalGuestRunningUpgrade}" in
		00)
			TouchNonstopUpgradeFlag
			;;
		01)
			exit 1
			;;
		02)
			SetLocalIncompUpgradeAndGenerateWebapiConnList
			LinkWebapi
			DisallowLocalVMRunningPrepare
			UnsetLocalIncompUpgrade
			;;
		1*)
			exit 1
			;;
		2*)
			if IsEtcdRunning; then
				SetLocalIncompUpgradeAndGenerateWebapiConnList
				LinkWebapi
				PrepareAPIIncompatibleUpgrade
				case "${IsDisallowLocalGuestRunningUpgrade}" in
					1)
						ServiceStopAndClear
						exit 1
						;;
					2)
						DisallowAllVMRunningPrepare
						;;
				esac
			elif ! IsSingleHostMode; then
				logger -p err -t "${PKG_TAG}" "Detect etcd not running, disks may be migrated from old dsm, require rebuild cluster"
				SetRebuildCluster
				exit 0
			fi
			;;
		31)
			exit 1
			;;
	esac
}

SetLocalIncompUpgradeAndGenerateWebapiConnList()
{
	${CCC_PKG_TOOL} --set-local-incompatible-upgrade
}

UnsetLocalIncompUpgrade()
{
	${CCC_PKG_TOOL} --set-local-incompatible-upgrade-rollback
}

IsSingleHostMode()
{
	grep "ETCD_LISTEN_PEER_URLS" "/usr/syno/etc/ccc/etcd.env" | grep -q "127.0.0.1"
}

IsEtcdRunning()
{
	test -e /var/run/etcd.pid
	return $?
}

SetRebuildCluster()
{
	/usr/syno/bin/synosetkeyvalue "${CCC_CONF_PATH}" "${RUN_CCC_KEY}" "no"
	if [ "$?" -ne 0 ]; then
		logger -p err -t "${PKG_TAG}" "Failed to set runccc key to no"
	fi

	/bin/mkdir -p `/bin/dirname ${VIRTUALIZATION_CONF_PATH}`
	if [ "$?" -ne 0 ]; then
		logger -p err -t "${PKG_TAG}" "Failed to mkdir virtualization.conf path"
	fi

	/usr/syno/bin/synosetkeyvalue "${VIRTUALIZATION_CONF_PATH}" "${REBUILD_CLUSTER_KEY}" "yes"
	if [ "$?" -ne 0 ]; then
		logger -p err -t "${PKG_TAG}" "Failed to set rebuild cluster key to yes"
	fi
}

ShutdownLocalVM()
{
	${CCC_GUEST_TOOL} -S
}

# check host is in cluster and exit if not
NotInCluster && exit 0

# check and prepare api incompatible & disallow local VM running
PkgUpgradeCheckAndPrepare

# backup dashboard info
DumpClusterDashboard

# stop all daemons and unlink libvirt & webapi
ServiceStopAndClear

# Single host mode can leave incompatible mode earlier
if IsSingleHostMode; then
	UnsetLocalIncompUpgrade
fi

# backup cluster log
BackupLog

# touch upgrade flag
TouchUpgradeFlag

exit 0
