#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.



# resizevolume.sh -- resize a volume

usage() {
  printf "Usage: %s: -c <current-volume-size> -s <new-volume-size> -p <volume path> -v <vm instance name> -t <storage-type> -r <shrink-bool>\n" $(basename $0) >&2
}

getdevmappername() {
  local path=$1
  local devmappername=`readlink -f $path |cut -d/ -f3`
  if [[ $devmappername =~ "dm-" ]]
  then
    dmname=$devmappername
    return 0
  else
    return 1;
  fi
}

getdevmappersize() {
  local dm=$1
  if [ ! -e "/sys/block/${dm}/size" ]
  then
    log "unable to find ${dm} in /sys/block" 1
    exit 1
  fi
  actualsize=$((`cat /sys/block/${dm}/size`*512));

  if [[ -z "$actualsize" ]]
  then
    log "unable to find actual size of ${dm}" 1
    exit 1
  fi
  return 0
}

# log "message" 1  <-- prints to stdout as well as log, to pass error up to cloudstack
# log "message" prints only to log file
# variable shouldwelog controls whether we print to log file
log() {
  local d=`date`
  local msg=${1}
  local stdout=${2}

  if [ ! -z "$stdout" ]
  then
    echo $1
  fi

  if [ $shouldwelog -eq 1 ]
  then
    echo "$d - $1" >> /var/log/cloudstack/agent/resizevolume.log
  fi
}

failshrink() {
  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
  if [ $actualsize -gt $newsize ]
  then
    if [ "$shrink" == "false" ]
    then
      log "result would shrink the volume from $actualsize to $newsize, but confirmation to shrink wasn't passed. Shrink='$shrink'" 1
      exit 1
    fi
  fi
}

notifyqemu() {
  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
  #we try to inform hypervisor of new size, but don't fail if we can't
  if `virsh help 2>/dev/null | grep -q blockresize`
  then
    if `virsh domstate $vmname >/dev/null 2>&1`
    then
      sizeinkb=$(($newsize/1024))
      devicepath=$(virsh domblklist $vmname | grep $path | awk '{print $1}')
      virsh blockresize --path $devicepath --size $sizeinkb $vmname >/dev/null 2>&1
      retval=$?
      if [ -z $retval ] || [ $retval -ne 0 ]
      then
        log "failed to live resize $path to size of $sizeinkb kb" 1
      else
        liveresize='true'
      fi
    fi
  fi
}

resizelvm() {
  local dmname=''
  local actualsize=''
  local liveresize='false'

  ##### sanity checks #####
  if ! `lvresize --version > /dev/null 2>&1`
  then
    log "unable to resolve executable 'lvresize'" 1
    exit 1
  fi

  if ! `virsh --version > /dev/null 2>&1`
  then
    log "unable to resolve executable 'virsh'" 1
    exit 1
  fi
  ##### end sanity #####

  if ! getdevmappername $path
  then
    log "unable to resolve a device mapper dev from $path" 1
    exit 1
  fi

  getdevmappersize $dmname

  if [ $actualsize -ne $currentsize ]
  then
    log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
  fi

  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
  failshrink

  output=`lvresize -f -L ${newsize}B $path 2>&1`
  retval=$?

  if [ -z $retval ] || [ $retval -ne 0 ]
  then
    log "lvresize failed: $output " 1
    exit 1
  fi

  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
  #we try to inform hypervisor of new size, but don't fail if we can't
  notifyqemu

  log "performed successful resize - dm:$dmname currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
}

resizeqcow2() {

   ##### sanity checks #####
  if [ ! -e "$path" ]
  then
    log "unable to find file $path" 1
    exit 1
  fi

  if ! `qemu-img info /dev/null > /dev/null 2>&1`
  then
    log "unable to resolve executable 'qemu-img'" 1
    exit 1
  fi

  if ! `virsh --version > /dev/null 2>&1`
  then
    log "unable to resolve executable 'virsh'" 1
    exit 1
  fi
  ##### end sanity #####

  regex=".*version\s([0-9]+)\.([0-9]+).*"
  content=$(qemu-img --version | grep version)

  qemu_force_share_flag=""
  if [[ $content =~ $regex ]]
    then
       version_first_element="${BASH_REMATCH[1]}"
       version_second_element="${BASH_REMATCH[2]}"
       if [[ ${version_first_element} -gt 2 ]] || [[ ${version_first_element} -eq 2 && ${version_second_element} -ge 10 ]]
       then
          qemu_force_share_flag=" -U "
       fi
    else
      echo "Could not retrieve qemu version. Skipping validation to add --force-share flag."
  fi

  actualsize=`qemu-img info $qemu_force_share_flag $path | grep "virtual size" | sed -re  's/^.*\(([0-9]+).*$/\1/g'`

  if [ $actualsize -ne $currentsize ]
  then
    log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
  fi

  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
  failshrink

  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
  #we try to inform hypervisor of new size, but don't fail if we can't
  if `virsh help 2>/dev/null | grep -q blockresize`
  then
    if `virsh domstate $vmname >/dev/null 2>&1`
    then
        log "vm $vmname is running, use 'virsh blockresize' to resize the volume"
        notifyqemu
        if [ $? -eq 0 ]
        then
            log "performed successful resize - currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
            exit 0
        fi
    fi
  fi

  output=`qemu-img resize $path $newsize 2>&1`
  retval=$?

  if [ -z $retval ] || [ $retval -ne 0 ]
  then
    log "qemu-img resize failed: $output" 1
    exit 1
  fi

  log "performed successful resize - currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
}

sflag=
cflag=
pflag=
vflag=
tflag=
rflag=

while getopts 'c:s:v:p:t:r:' OPTION
do
  case $OPTION in
  s)	sflag=1
		newsize="$OPTARG"
		;;
  c)    cflag=1
                currentsize="$OPTARG"
                ;;
  v)	vflag=1
		vmname="$OPTARG"
		;;
  p)	dflag=1
		path="$OPTARG"
		;;
  t)    tflag=1
                ptype="$OPTARG"
                ;;
  r)    rflag=1
                shrink="$OPTARG"
                ;;
  ?)	usage
		exit 2
		;;
  esac
done

shouldwelog=1 #set this to 1 while debugging to get output in /var/log/cloudstack/agent/resizevolume.log

if [ "$ptype" == "CLVM" ]
then
  resizelvm
elif [ "$ptype" == "QCOW2" ]
then
  resizeqcow2
elif [ "$ptype" == "NOTIFYONLY" ]
then
  notifyqemu
else
  echo "unsupported type $ptype"
  exit 1;
fi

exit 0
