#!/bin/bash
set -o errexit

K8S_INSTALL_PATH="/usr/sbin/domeos/k8s/current"
VG_NAME="vgdomeos"
POOL_PERCENT="75%"
POOLMETA_PERCENT="5%"
AUTOEXTEND_THRESHOLD="80"
AUTOEXTEND_PERCENT="10"
DM_FS="ext4"
DOCKER_LVM_GRAPH_PATH="/var/lib/docker-lvm"
HOST_NAME=`hostname -s`
THINPOOL_PROFILE="/etc/lvm/profile/docker-thinpool.profile"
DOCKER_SERVICE_FILE="/usr/lib/systemd/system/docker.service"
DOCKER_SERVICE_FILE_BAK="/usr/lib/systemd/system/docker.service.bak.domeos"
DOCKER_CONFIG_FILE="/etc/sysconfig/docker"
DOCKER_CONFIG_FILE_BAK="/etc/sysconfig/docker.bak.domeos"
DOCKER_CONFIG_DEFAULT_FILE="/etc/default/docker"
DOCKER__CONFIG_DEFAULT__FILE_BAK="/etc/default/docker"
PV_LIST_FILE="/tmp/.pvlist.domeos"
VG_NAME_FILE="/tmp/.vg.name.domeos"
LVM_GRAPH_PATH="/tmp/.lvm.graph.domeos"

function command_exists ()
{
  command -v "$@" > /dev/null 2>&1
}

function restore()
{
  echo -e "\033[36m[INFO] Restore docker config......\033[0m"
  if [ -f "$DOCKER_SERVICE_FILE_BAK" ]; then
    yes | cp $DOCKER_SERVICE_FILE_BAK $DOCKER_SERVICE_FILE  
  fi

  if [ -f "$DOCKER_CONFIG_FILE_BAK" ]; then
    yes | cp $DOCKER_CONFIG_FILE_BAK $DOCKER_CONFIG_FILE
  fi
  systemctl daemon-reload
  echo -e "\033[36m[INFO] Stop docker......\033[0m"
  systemctl stop docker
  echo -e "\033[36m[INFO] Remove pv/lv/vg......\033[0m"
  if [ -f "$PV_LIST_FILE" ] && [ -f "$VG_NAME_FILE" ]; then
    vg_name=`cat $VG_NAME_FILE`
    disk_parts=`cat $PV_LIST_FILE`
    yes | lvremove $vg_name/thinpool
    yes | vgremove $vg_name
    yes | pvremove $disk_parts
    rm -rf $VG_NAME_FILE
    rm -rf $PV_LIST_FILE
  fi
  if [ -f "$THINPOOL_PROFILE" ]; then
    rm -rf $THINPOOL_PROFILE
  fi
  if [ -f "$LVM_GRAPH_PATH" ]; then
    lvm_graph=`cat $LVM_GRAPH_PATH`
    rm -rf $lvm_graph
  fi
  echo -e "\033[36m[INFO] Restore docker config successful, start docker......\033[0m"
  systemctl start docker
}

function help ()
{
  echo "
Usage:
  change_to_direct_lvm_ubuntu.sh [options]
  change_to_direct_lvm_ubuntu.sh [command]

Available Commands:
  help    show the help information about change_to_direct_lvm_ubuntu.sh
  restore restore docker config and remove lv/vg/pv
Options:
  --api-server            (REQUIRED) kube-apiserver address ('http://ip:port' or 'https://ip:port').
  --disk-parts            (REQUIRED) a comma-delimited list of disks or parts.
  --kube-node-name        The name of node that now run the shell script in the kubernetes.
  --vg-name               Logical volume group name (default $VG_NAME).
  --pool-percent          The percentage of thin-pool in volume group (default $POOL_PERCENT).
  --poolmeta-percent      The percentage of thin-poolmeta in volume group (default $POOLMETA_PERCENT).
  --autoextend-threshold  The autoextend threshold of thin pool (default $AUTOEXTEND_THRESHOLD).
  --autoextend-percent    The autoextend percent of thin pool (default $AUTOEXTEND_PERCENT).
  --devicemapper-fs       The devicemapper filesystem format (default $DM_FS).
  --lvm-graph-path        Root of the Docker runtime, should be different from the graph path of loop-lvm (default $DOCKER_LVM_GRAPH_PATH).
"
}
echo "
***************************************************************************
               Welcome to modify DomeOS Kubernetes Node!
                  Contact us: rdc-domeos@sohu-inc.com
***************************************************************************
This is a shell script for changing loop-lvm to direct-lvm after successful execution.

Attention:
1. The shell will stop docker and restart docker using new graph path and direct lvm.
1. Please sure that the disks or parts is not used (not mounted.eg), the signature of disks or parts will be wiped.
2. Use 'bash change_to_direct_lvm_ubuntu.sh help' to get more information.

Usage Example:
sudo bash change_to_direct_lvm_ubuntu.sh --api-server https://0.0.0.0:8080 --disk-parts /dev/sda,/dev/sdb1"

if [[ "$1" =~ "help" ]]; then
  help
  exit 1
fi
if [[ "$1" =~ "restore" ]]; then
  restore
  exit 1
fi
# STEP 01: check arguments
echo -e "\033[36m[INFO] STEP 01: Check input arguments...\033[0m"
OPTS=$(getopt -o : --long api-server:,disk-parts:,kube-node-name:,vg-name:,pool-percent:,poolmeta-percent:,autoextend-threshold:,autoextend-percent:,devicemapper-fs:,lvm-graph-path: -- "$@")
if [ $? != 0 ]
then
  echo -e "\033[31m[ERROR] change_to_direct_lvm_ubuntu.sh argument is illegal\033[0m"
  exit 1
fi
eval set -- "$OPTS"
api_server=
disk_parts=
kube_node_name=
vg_name=
pool_percent=
poolmeta_percent=
autoextend_threshold=
autoextend_percent=
devicemapper_fs=
lvm_graph_path=
while true ; do
  case "$1" in
    --api-server) api_server=$2; shift 2;;
    --disk-parts) disk_parts=$2; shift 2;;
    --kube-node-name) kube_node_name=$2; shift 2;;
    --vg-name) vg_name=$2; shift 2;;
    --pool-percent) pool_percent=$2; shift 2;;
    --poolmeta-percent) poolmeta_percent=$2; shift 2;;
    --autoextend-threshold) autoextend_threshold=$2; shift 2;;
    --autoextend-percent) autoextend_percent=$2; shift 2;;
    --devicemapper-fs) devicemapper_fs=$2; shift 2;;
    --lvm-graph-path) lvm_graph_path=$2; shift 2;;
    --) shift; break;;
  esac
done
if [ -z "$api_server" ]; then
  echo -e "\033[31m[ERROR] --api-server is absent\033[0m"
  exit 1
else
  echo "--api-server: $api_server"
fi
if [ -z "$disk_parts" ]; then
  echo -e "\033[31m[ERROR] --disk-parts is absent\033[0m"
  exit 1
else
  echo "--disk_parts: $disk_parts"
fi
if [ -z "$kube_node_name" ]; then
  echo -e "\033[36m[INFO] --kube-node-name is absent, default '$HOST_NAME'\033[0m"
  kube_node_name=$HOST_NAME
else
  echo "--kube-node-name: $kube_node_name"
fi
if [ -z "$vg_name" ]; then
  echo -e "\033[36m[INFO] --vg-name is absent, default '$VG_NAME'\033[0m"
  vg_name=$VG_NAME
else
  echo "--vg-name: $vg_name"
fi
if [ -z "$pool_percent" ]; then
  echo -e "\033[36m[INFO] --pool-percent is absent, default '$POOL_PERCENT'\033[0m"
  pool_percent=$POOL_PERCENT
else
  echo "--pool-percent: $pool_percent"
fi
if [ -z "$poolmeta_percent" ]; then
  echo -e "\033[36m[INFO] --poolmeta-percent is absent, default '$POOLMETA_PERCENT'\033[0m"
  poolmeta_percent=$POOLMETA_PERCENT
else
  echo "--poolmeta-percent: $poolmeta_percent"
fi
if [ -z "$autoextend_threshold" ]; then
  echo -e "\033[36m[INFO] --autoextend-threshold is absent, default '$AUTOEXTEND_THRESHOLD'\033[0m"
  autoextend_threshold=$AUTOEXTEND_THRESHOLD
else
  echo "--autoextend-threshold: $autoextend_threshold"
fi
if [ -z "$autoextend_percent" ]; then
  echo -e "\033[36m[INFO] --autoextend-percent is absent, default '$AUTOEXTEND_PERCENT'\033[0m"
  autoextend_percent=$AUTOEXTEND_PERCENT
else
  echo "--autoextend-percent: $autoextend_percent"
fi
if [ -z "$devicemapper_fs" ]; then
  echo -e "\033[36m[INFO] --devicemapper-fs is absent, default '$DM_FS'\033[0m"
  devicemapper_fs=$DM_FS
else
  echo "--devicemapper-fs: $devicemapper_fs"
fi
if [ -z "$lvm_graph_path" ]; then
  echo -e "\033[36m[INFO] --lvm-graph-path is absent, default '$DOCKER_LVM_GRAPH_PATH'\033[0m"
  lvm_graph_path=$DOCKER_LVM_GRAPH_PATH
else
  echo "--lvm-graph-path: $lvm_graph_path"
fi
# STEP 2: check docker exist or not
if command_exists docker ; then
  echo -e "\033[36m[INFO] STEP 2: Docker command exists on this system.\033[0m"
  if command_exists systemctl ; then
    echo -e "\033[36m[/etc/sysconfig/docker and /lib/systemd/system/docker.service files will be reset.\033[0m"
  elif command_exists initctl ; then
    echo -e "\033[36m/etc/default/docker will be reset\033[0m"
  fi
else
  echo -e "\033[31m[ERROR] STEP 2: docker command not exists on this system...\033[0m"
  exit 1
fi
# STEP 3: check lvm2 is installed or not
if command_exists pvcreate ; then
  echo -e "\033[36m[INFO] STEP 3: Lvm2 has been installed on this system...\033[0m"
else
  echo -e "\033[31m[ERROR] STEP 3: Lvm2 is not installed on this system.\033[0m"
  echo -e "\033[36mLvm2 will be installed.\033[0m"
  echo -e "\033[36mYou may press Ctrl+C now to abort this script.\033[0m"
  echo -e "\033[36mWaiting for 10 seconds...\033[0m"
  sleep 10
  echo -e "\033[36m[INFO] STEP 3:apt-get install -y lvm2...\033[0m"
  apt-get update
  apt-get install -y lvm2
fi
# STEP 4: create pv
disk_parts=(${disk_parts//,/ })
format_disk_parts=
for i in ${disk_parts[@]}
do
  format_disk_parts="$format_disk_parts $i"
done
yes | pvcreate $format_disk_parts
echo $format_disk_parts > $PV_LIST_FILE
echo $vg_name > $VG_NAME_FILE
if [ $? -eq 0 ]; then
  echo -e "\033[36m[INFO] STEP 4: Create physical volume successful...\033[0m"  
else
  echo -e "\033[31m[ERROR] STEP 4: The disks or parts may be used (mounted.eg) or not exist now\033[0m"
  exit 1
fi
pvdisplay
#STEP 5:create vg
vgcreate $vg_name $format_disk_parts
if [ $? -eq 0 ]; then
  echo -e "\033[36m[INFO] STEP 5: Create volume group successful...\033[0m"  
else
  echo -e "\033[31m[ERROR] STEP 5: Failed to create volume group\033[0m"
  exit 1
fi
vgdisplay
#STEP 6:create lv
yes | lvcreate --wipesignatures y -n thinpool -l ${pool_percent}VG $vg_name
yes | lvcreate --wipesignatures y -n thinpoolmeta -l ${poolmeta_percent}VG $vg_name
if [ $? -eq 0 ]; then
  echo -e "\033[36m[INFO] STEP 6: Create logical volume successful...\033[0m"  
else
  echo -e "\033[31m[ERROR] STEP 6: Failed to create logical volume\033[0m"
  exit 1
fi
lvdisplay
#STEP 7:convert logical volume to thinpool
lvconvert -y --zero n -c 512K --thinpool $vg_name/thinpool --poolmetadata $vg_name/thinpoolmeta
if [ $? -eq 0 ]; then
  echo -e "\033[36m[INFO] STEP 7: Convert logical volume to thinpool successful...\033[0m"  
else
  echo -e "\033[31m[ERROR] STEP 7: Failed to convert logical volume\033[0m"
  exit 1
fi
#STEP 8:set logical volume autoextend
echo "
activation {
    thin_pool_autoextend_threshold=$autoextend_threshold
    thin_pool_autoextend_percent=$autoextend_percent
}
" > $THINPOOL_PROFILE
lvchange --metadataprofile docker-thinpool $vg_name/thinpool
if [ $? -eq 0 ]; then
  echo -e "\033[36m[INFO] STEP 8: Set logical volume autoextend successful...\033[0m"  
else
  echo -e "\033[31m[ERROR] STEP 8: Failed to set logical volume autoextend\033[0m"
  exit 1
fi
#STEP 9:setup docker storage config
echo $lvm_graph_path > $LVM_GRAPH_PATH
docker_storage_options="--graph $lvm_graph_path --storage-driver devicemapper --storage-opt dm.fs=$devicemapper_fs --storage-opt dm.thinpooldev=/dev/mapper/$vg_name-thinpool --storage-opt dm.use_deferred_removal=true --storage-opt dm.use_deferred_deletion=true"
if command_exists systemctl ; then
  yes | cp $DOCKER_CONFIG_FILE $DOCKER_CONFIG_FILE_BAK
  yes | cp $DOCKER_SERVICE_FILE $DOCKER_SERVICE_FILE_BAK
  sed -i '/DOCKER_STORAGE_OPTIONS/, 1d' $DOCKER_CONFIG_FILE
  sed -i '/\/etc\/sysconfig\/docker-storage/,1d' $DOCKER_SERVICE_FILE
  docker_opts=`cat $DOCKER_CONFIG_FILE | sed -r 's/.*"(.+)".*/\1/'`
  echo "DOCKER_OPTS=\"$docker_opts $docker_storage_options\"" > $DOCKER_CONFIG_FILE
  systemctl daemon-reload
elif command_exists initctl ; then
  yes | cp $DOCKER_CONFIG_DEFAULT_FILE $DOCKER_CONFIG_DEFAULT_FILE_BAK
  sed -i '/DOCKER_STORAGE_OPTIONS/, 1d' $DOCKER_CONFIG_DEFAULT_FILE
  docker_opts=`cat $DOCKER_CONFIG_DEFAULT_FILE | sed -r 's/.*"(.+)".*/\1/'`
  echo "DOCKER_OPTS=\"$docker_opts $docker_storage_options\"" > $DOCKER_CONFIG_DEFAULT_FILE
fi
echo -e "\033[36m[INFO] STEP 9: Setup docker storage config successful...\033[0m" 
#STEP 10:drain kubelet node
echo -e "\033[36m[INFO] STEP 10: Drain kubelet node $kube_node_name\033[0m"
kubectlpath = $K8S_INSTALL_PATH/kubectl
if [ -f "$kubectlpath" ]; then
  $kubectlpath -s $api_server drain $kube_node_name --ignore-daemonsets --force --delete-local-data
  if [ $? -eq 0 ]; then
    echo -e "\033[36m Drain kubelet node $kube_node_name successful...\033[0m"
  else
    echo -e "\033[31m Failed to drain kubelet node $kube_node_name\033[0m"
  fi
else
  echo -e "\033[31m $kubectlpath not exist, failed to drain kubelet node $kube_node_name\033[0m"
fi
#STEP 11:start docker
echo -e "\033[36m[INFO] STEP 11: Start docker...\033[0m"
if command_exists systemctl ; then
  systemctl stop docker
  sleep 5
  systemctl start docker
elif command_exists initctl ; then
  initctl stop docker
  sleep 5
  initctl start docker
fi
if [ $? -eq 0 ]; then
  echo -e "\033[36m Start docker successful...\033[0m"  
else
  echo -e "\033[31m Failed to start docker\033[0m"
  exit 1
fi
docker info
#STEP 12:uncordon kubelet node
echo -e "\033[36m[INFO] STEP 12: Uncordon kubelet node $kube_node_name...\033[0m" 
if [ -f "$kubectlpath" ]; then
  $kubectlpath -s $api_server uncordon $kube_node_name
  if [ $? -eq 0 ]; then
    echo -e "\033[36m Uncordon kubelet node $kube_node_name successful...\033[0m"  
  else
    echo -e "\033[31m Failed to uncordon kubelet node $kube_node_name\033[0m"
    exit 1
  fi
else
  echo -e "\033[31m $kubectlpath not exist, failed to uncordon kubelet node $kube_node_name\033[0m"
fi
