#!/usr/bin/env bash

# Copyright 2014, Rackspace US, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


## Vars ----------------------------------------------------------------------
LINE='----------------------------------------------------------------------'
MAX_RETRIES=${MAX_RETRIES:-5}
MIN_LXC_VG_SIZE_GB=${MIN_LXC_VG_SIZE_GB:-250}
REPORT_DATA=${REPORT_DATA:-""}
ANSIBLE_PARAMETERS=${ANSIBLE_PARAMETERS:-""}
STARTTIME="${STARTTIME:-$(date +%s)}"

# the number of forks is set as the number of CPU's present
FORKS=${FORKS:-$(grep -c ^processor /proc/cpuinfo)}

## Functions -----------------------------------------------------------------
# Used to retry a process that may fail due to random issues.
function successerator {
  set +e
  # Get the time that the method was started.
  OP_START_TIME="$(date +%s)"
  RETRY=0
  # Set the initial return value to failure.
  false
  while [ $? -ne 0 -a ${RETRY} -lt ${MAX_RETRIES} ];do
    RETRY=$((${RETRY}+1))
    if [ ${RETRY} -gt 1 ];then
      $@ -vvvv
    else
      $@
    fi
  done
  # If max retires were hit, fail.
  if [ $? -ne 0 ] && [ ${RETRY} -eq ${MAX_RETRIES} ];then
    echo -e "\nHit maximum number of retries, giving up...\n"
    exit_fail
  fi
  # Print the time that the method completed.
  OP_TOTAL_SECONDS="$(( $(date +%s) - $OP_START_TIME ))"
  REPORT_OUTPUT="${OP_TOTAL_SECONDS} seconds"
  REPORT_DATA+="- Operation: [ $@ ]\t${REPORT_OUTPUT}\tNumber of Attempts [ ${RETRY} ]\n"
  echo -e "Run Time = ${REPORT_OUTPUT}"
  set -e
}

function install_bits {
  # Use the successerator to run openstack-ansible with
  # the appropriate number of forks
  successerator openstack-ansible ${ANSIBLE_PARAMETERS} --forks ${FORKS} $@
}

function configure_diskspace {
  # If there are any block devices available other than the one
  # used for the root disk, repurpose it for our needs.
  MIN_LXC_VG_SIZE_B=$((${MIN_LXC_VG_SIZE_GB} * 1024 * 1024 * 1024))

  # only do this if the lxc vg doesn't already exist
  if ! vgs lxc > /dev/null 2>&1; then
    blk_devices=$(lsblk -nrdo NAME,TYPE,RO | awk '/d[b-z]+ disk [^1]/ {print $1}')
    for blk_dev in ${blk_devices}; do
      # dismount any mount points on the device
      mount_points=$(awk "/^\/dev\/${blk_dev}[0-9]* / {print \$2}" /proc/mounts)
      for mount_point in ${mount_points}; do
        umount ${mount_point}
        sed -i ":${mount_point}:d" /etc/fstab
      done

      # add a vg for lxc
      blk_dev_size_b=$(lsblk -nrdbo NAME,TYPE,SIZE | awk "/^${blk_dev} disk/ {print \$3}")
      if [ "${blk_dev_size_b}" -gt "${MIN_LXC_VG_SIZE_B}" ]; then
        if ! vgs lxc > /dev/null 2>&1; then
          parted --script /dev/${blk_dev} mklabel gpt
          parted --align optimal --script /dev/${blk_dev} mkpart lxc 0% 80%
          part_num=$(parted /dev/${blk_dev} print --machine | awk -F':' '/lxc/ {print $1}')
          pvcreate -ff -y /dev/${blk_dev}${part_num}
          vgcreate lxc /dev/${blk_dev}${part_num}
        fi
        # add a vg for cinder volumes, but only if it doesn't already exist
        if ! vgs cinder-volumes > /dev/null 2>&1; then
          parted --align optimal --script /dev/${blk_dev} mkpart cinder 80% 100%
          part_num=$(parted /dev/${blk_dev} print --machine | awk -F':' '/cinder/ {print $1}')
          pvcreate -ff -y /dev/${blk_dev}${part_num}
          vgcreate cinder-volumes /dev/${blk_dev}${part_num}
        fi
      else
        if ! grep '/var/lib/lxc' /proc/mounts 2>&1; then
          parted --script /dev/${blk_dev} mklabel gpt
          parted --script /dev/${blk_dev} mkpart lxc ext4 0% 100%
          part_num=$(parted /dev/${blk_dev} print --machine | awk -F':' '/lxc/ {print $1}')
          # Format, Create, and Mount it all up.
          mkfs.ext4 /dev/${blk_dev}${part_num}
          mkdir -p /var/lib/lxc
          mount /dev/${blk_dev}${part_num} /var/lib/lxc
        fi
      fi
    done
  fi
}

function ssh_key_create {
  # Ensure that the ssh key exists and is an authorized_key
  key_path="${HOME}/.ssh"
  key_file="${key_path}/id_rsa"

  # Ensure that the .ssh directory exists and has the right mode
  if [ ! -d ${key_path} ]; then
    mkdir -p ${key_path}
    chmod 700 ${key_path}
  fi
  if [ ! -f "${key_file}" -a ! -f "${key_file}.pub" ]; then
    rm -f ${key_file}*
    ssh-keygen -t rsa -f ${key_file} -N ''
  fi

  # Ensure that the public key is included in the authorized_keys
  # for the default root directory and the current home directory
  key_content=$(cat "${key_file}.pub")
  if ! grep -q "${key_content}" ${key_path}/authorized_keys; then
    echo "${key_content}" | tee -a ${key_path}/authorized_keys
  fi
}

function loopback_create {
  LOOP_FILENAME=${1}
  LOOP_FILESIZE=${2}
  LOOP_FILE_TYPE=${3}  # thin, thick
  LOOP_MOUNT_METHOD=${4}  # swap, rc, none

  if [ ! -f "${LOOP_FILENAME}" ]; then
    if [ "${LOOP_FILE_TYPE}" = "thin" ]; then
      truncate -s ${LOOP_FILESIZE} ${LOOP_FILENAME}
    elif [ "${LOOP_FILE_TYPE}" = "thick" ]; then
      fallocate -l ${LOOP_FILESIZE} ${LOOP_FILENAME} &> /dev/null || \
      dd if=/dev/zero of=${LOOP_FILENAME} bs=1M count=$(( ${LOOP_FILESIZE} / 1024 / 1024 ))
    else
      exit_fail "No valid option ${LOOP_FILE_TYPE} found."
    fi
  fi

  if [ "${LOOP_MOUNT_METHOD}" = "rc" ]; then
    if ! losetup -a | grep -q "(${LOOP_FILENAME})$"; then
      LOOP_DEVICE=$(losetup -f)
      losetup ${LOOP_DEVICE} ${LOOP_FILENAME}
    fi
    if ! grep -q ${LOOP_FILENAME} /etc/rc.local; then
      sed -i "\$i losetup \$(losetup -f) ${LOOP_FILENAME}" /etc/rc.local
    fi
  fi

  if [ "${LOOP_MOUNT_METHOD}" = "swap" ]; then
    if ! swapon -s | grep -q ${LOOP_FILENAME}; then
      mkswap ${LOOP_FILENAME}
      swapon -a
    fi
    if ! grep -q "^${LOOP_FILENAME} " /etc/fstab; then
      echo "${LOOP_FILENAME} none swap loop 0 0" >> /etc/fstab
    fi
  fi
}

function exit_state {
  set +x
  TOTALSECONDS="$(( $(date +%s) - $STARTTIME ))"
  info_block "Run Time = ${TOTALSECONDS} seconds || $(($TOTALSECONDS / 60)) minutes"
  if [ "${1}" == 0 ];then
    info_block "Status: Success"
  else
    info_block "Status: Failure"
  fi
  exit ${1}
}

function exit_success {
  set +x
  exit_state 0
}

function exit_fail {
  set +x
  log_instance_info
  info_block "Error Info - $@"
  exit_state 1
}

function print_info {
  PROC_NAME="- [ $@ ] -"
  printf "\n%s%s\n" "$PROC_NAME" "${LINE:${#PROC_NAME}}"
}

function info_block {
  echo "${LINE}"
  print_info "$@"
  echo "${LINE}"
}

function log_instance_info {
  set +x
  # Get host information post initial setup and reset verbosity
  if [ ! -d "/openstack/log/instance-info" ];then
    mkdir -p "/openstack/log/instance-info"
  fi
  get_instance_info &> /openstack/log/instance-info/host_info_$(date +%s).log
  set -x
}

function get_repos_info {
  for i in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
    echo -e "\n$i"
    cat $i
  done
}

# Get instance info
function get_instance_info {
  set +x
  info_block 'Current User'
  whoami
  info_block 'Available Memory'
  free -mt || true
  info_block 'Available Disk Space'
  df -h || true
  info_block 'Mounted Devices'
  mount || true
  info_block 'Block Devices'
  lsblk -i || true
  info_block 'Block Devices Information'
  blkid || true
  info_block 'Block Device Partitions'
  for i in /dev/xv* /dev/sd* /dev/vd*; do
    if [ -b "$i" ];then
      parted --script $i print || true
    fi
  done
  info_block 'PV Information'
  pvs || true
  info_block 'VG Information'
  vgs || true
  info_block 'LV Information'
  lvs || true
  info_block 'CPU Information'
  which lscpu && lscpu || true
  info_block 'Kernel Information'
  uname -a || true
  info_block 'Container Information'
  which lxc-ls && lxc-ls --fancy || true
  info_block 'Firewall Information'
  iptables -vnL || true
  iptables -t nat -vnL || true
  iptables -t mangle -vnL || true
  info_block 'Network Devices'
  ip a || true
  info_block 'Network Routes'
  ip r || true
  info_block 'DNS Configuration'
  cat /etc/resolv.conf
  info_block 'Trace Path from google'
  tracepath 8.8.8.8 -m 5 || true
  info_block 'XEN Server Information'
  if (which xenstore-read);then
    xenstore-read vm-data/provider_data/provider || echo "\nxenstore Read Failed - Skipping\n"
  else
    echo -e "\nNo xenstore Information\n"
  fi
  get_repos_info &> /openstack/log/instance-info/host_repo_info_$(date +%s).log || true
  dpkg-query --list &> /openstack/log/instance-info/host_packages_info_$(date +%s).log
}

function print_report {
  # Print the stored report data
  echo -e "${REPORT_DATA}"
}


## Signal traps --------------------------------------------------------------
# Trap all Death Signals and Errors
trap "exit_fail ${LINENO} $? 'Received STOP Signal'" SIGHUP SIGINT SIGTERM
trap "exit_fail ${LINENO} $?" ERR


## Pre-flight check ----------------------------------------------------------
# Make sure only root can run our script
if [ "$(id -u)" != "0" ]; then
  info_block "This script must be run as root"
  exit_state 1
fi

# Check that we are in the root path of the cloned repo
if [ ! -d "etc" -a ! -d "scripts" -a ! -d "playbooks" ]; then
  info_block "** ERROR **"
  echo "Please execute this script from the root directory of the cloned source code."
  echo -e "Example: /opt/openstack-ansible/\n"
  exit_state 1
fi


## Exports -------------------------------------------------------------------
# Export known paths
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# Export the home directory just in case it's not set
export HOME="/root"