| #!/bin/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. | |
| version=0.9.1 | |
| OPTIND=1 | |
| verb=0 | |
| logDir="/nsr/logs/cloudstack/" | |
| snapPrefix="CSBKP_"$RANDOM"_" | |
| clusterClient="" | |
| networkerServer="" | |
| hvVersion="" | |
| libvVersion="" | |
| apiVersion="" | |
| kvmDName="" | |
| kvmDUuid="" | |
| logFile="" | |
| mediaPool="" | |
| retentionTime="" | |
| log () { | |
| [[ "$verb" -eq 1 ]] && builtin echo "$@" | |
| if [[ "$1" == "-ne" || "$1" == "-e" || "$1" == "-n" ]]; then | |
| builtin echo -e "$(date '+%Y-%m-%d %H-%M-%S>')" "${@: 2}" >> "$logFile" | |
| else | |
| builtin echo "$(date '+%Y-%m-%d %H-%M-%S>')" "$@" >> "$logFile" | |
| fi | |
| } | |
| vercomp(){ | |
| local a b IFS=. -; set -f | |
| printf -v a %08d $1; printf -v b %08d $3 | |
| test $a "$2" $b | |
| } | |
| usage() { | |
| echo " | |
| Usage:[-v] [-h] [-l log_dir] [-dr] [-s networker_server] [-c networker_cluster_client] [-t target_vm] [-u target_uuid] [-p snapprefix] [-P media_pool ] [-R retention_time ] | |
| Options: | |
| -h Help and usage | |
| -v Enable verbose mode | |
| -l log_dir. Specify log directory. Default is /nsr/logs/cloudstack | |
| -s networker_server Specifiy the EMC Networker server we are going to use | |
| -c networker_cluster_client Specify the EMC Networker client CLUSTER to use | |
| -t target_vm KVM domain to backup | |
| -u target_uuid KVM domain to backup | |
| -p Snapshot Prefix for backups | |
| -P mediaPool EMC Networker Media Pool | |
| -R retention_time Backup retention time | |
| Supplements Apache Cloudstack B&R Framework EMC Networker plugin and performs the backup of the Virtual Machines | |
| " | |
| } | |
| sanity_checks() { | |
| log "Performing environment sanity checks..." | |
| log -ne "\t[1] Checking if Networker is installed\t" | |
| if [[ $(systemctl list-unit-files | grep networker) = *networker* ]]; then | |
| log "Success" | |
| else | |
| log "Failure" | |
| log -e "\n\tNetworker Service NOT FOUND. Make sure that Networker client is properly installed" | |
| exit 1 | |
| fi | |
| log -ne "\t[2] Checking if Networker is running\t" | |
| if [[ $(systemctl is-active networker) = *active* ]]; then | |
| log "Success" | |
| else | |
| log "Failure" | |
| log -e "\n\tNetworker Service is not running. Investigate Networker logs, startup server and try again" | |
| exit 2 | |
| fi | |
| log -ne "\t[3] Checking Networker DNS Resolution\t" | |
| if [[ $(getent hosts "$networkerServer") = *$networkerServer* ]]; then | |
| log "Success" | |
| else | |
| log "Failure" | |
| log -e "\n\tNetworker Server cannot be resolved. Backups will most probably fail. Consider adding the ip/hostname to /etc/host or fix DNS resolution" | |
| exit 3 | |
| fi | |
| log -ne "\t[4] Checking QEMU / Libvirt Versions \t" | |
| hvVersion=$(virsh version | grep hypervisor | awk '{print $(NF)}') | |
| libvVersion=$(virsh version | grep libvirt | awk '{print $(NF)}' | tail -n 1) | |
| apiVersion=$(virsh version | grep API | awk '{print $(NF)}') | |
| if vercomp "$hvVersion" \> 2.1.2; then | |
| log -n "Success" | |
| log -ne "\t\t [ Libvirt: $libvVersion apiVersion: $apiVersion ]" | |
| echo | |
| else | |
| log "Failure" | |
| log -e "\n\tYour QEMU version $hvVersion is unsupported. Consider upgrading at least to latest QEMU at branch 2" | |
| exit 4 | |
| fi | |
| log -ne "\t[4] Checking Permissions \t\t" | |
| if groups $USER | grep -q '\blibvirt\b'; then | |
| log -n "Success" | |
| log -ne "\t\t User $USER is part of libvirt group" | |
| echo | |
| else | |
| log "Failure - User $USER is not part of libvirt group" | |
| exit 6 | |
| fi | |
| log "Environment Sanity Checks successfully passed" | |
| } | |
| echo " | |
| Cloudstack B&R Framework - EMC Networker backup script | |
| Version $version | |
| " | |
| backup_domain() { | |
| name=$1 | |
| snapName=$2 | |
| log "Preparing snapshots and gathering information for backing up domain $name under snapshot name $snapName" | |
| log "Retention time is $retentionTime" | |
| declare -A TRGSRC | |
| while IFS=',' read -r TARGET SOURCE | |
| do | |
| if [[ $SOURCE != "-" ]]; then | |
| TRGSRC+=(["$TARGET"]="$SOURCE") | |
| fi | |
| done < <(virsh -c qemu:///system domblklist "$name" --details | grep file | grep -v 'cdrom' | grep -v 'floppy' | sed 's/ */,/g' | cut -d',' -f 4-) | |
| diskspec="" | |
| for target in "${!TRGSRC[@]}"; do | |
| log -e "\tDisk for $target is at ${TRGSRC[${target}]}" | |
| diskspec="$diskspec --diskspec $target,snapshot=external" | |
| disks="$disks ${TRGSRC[${target}]} " | |
| done | |
| cmd="$(virsh -c qemu:///system snapshot-create-as --domain "$name" --name "$snapName" --no-metadata --atomic --quiesce --disk-only "$diskspec")" | |
| retVal=$? | |
| log "$cmd" | |
| if [ "$retVal" -ne 0 ]; then | |
| log "Agent not responding, trying to snapshot directly" | |
| cmd="$(virsh -c qemu:///system snapshot-create-as --domain "$name" --name "$snapName" --no-metadata --atomic --disk-only "$diskspec")" | |
| retVal=$? | |
| if [ "$retVal" -ne 0 ]; then | |
| log "Failed to create snapshot for $name" | |
| exit 7 | |
| fi | |
| log "Created snapshot(s) for $name" | |
| fi | |
| cmd="$(save -LL -q -e "${retentionTime}" -s "$networkerServer" -c "$clusterClient" -N "$name" -b "$mediaPool" $disks)" | |
| retVal=$? | |
| log "$cmd" | |
| echo "$cmd" | grep -oE 'savetime=[0-9]{10}' | |
| if [ $retVal -ne 0 ]; then | |
| log "Unable to backup $disks for $name" | |
| else | |
| log "Backup $disks for $name completed!!" | |
| fi | |
| #Merge changes and conclude | |
| SNAPSHOTS="$(virsh -c qemu:///system domblklist "$name" --details | grep file | grep -v 'cdrom' | grep -v 'floppy' | awk '{print $4}')" | |
| for target in "${!TRGSRC[@]}"; do | |
| log "Merging Snasphots for $target" | |
| cmd="$(virsh -c qemu:///system blockcommit "$name" "$target" --active --pivot)" | |
| retVal=$? | |
| log "$cmd" | |
| if [ $retVal -ne 0 ]; then | |
| log "Unable to merge disk %target changes for domain $name" | |
| exit 8 | |
| fi | |
| done | |
| #Clean snapshots | |
| for snapshot in $SNAPSHOTS; do | |
| log "Deleting Snapshot $snapshot" | |
| cmd=$(rm -f "$snapshot") | |
| retVal=$? | |
| log "$cmd" | |
| if [ $retVal -ne 0 ]; then | |
| log "Unable to delete snapshot $snapshot" | |
| exit 8 | |
| fi | |
| log "Deleted Snapshot: $snapshot" | |
| done | |
| } | |
| while getopts "h?vs:l:c:t:u:p:P:R:" opt; do | |
| case "$opt" in | |
| h|\?) | |
| usage | |
| exit 254 | |
| ;; | |
| c) clusterClient="$OPTARG" | |
| ;; | |
| s) networkerServer="$OPTARG" | |
| ;; | |
| l) logDir="$OPTARG" | |
| ;; | |
| t) kvmDName="$OPTARG" | |
| ;; | |
| u) kvmDUuid="$OPTARG" | |
| ;; | |
| p) snapPrefix="$OPTARG" | |
| ;; | |
| P) mediaPool="$OPTARG" | |
| ;; | |
| R) retentionTime="$OPTARG" | |
| ;; | |
| v) verb=1 | |
| ;; | |
| esac | |
| done | |
| shift $((OPTIND-1)) | |
| [ "${1:-}" = "--" ] && shift | |
| if [[ -z "$networkerServer" || -z "$kvmDName" || -z "$clusterClient" || -z "$kvmDUuid" ]]; then | |
| usage | |
| exit 255 | |
| fi | |
| if [ ! -d "$logDir" ]; then | |
| mkdir -p "$logDir" | |
| fi | |
| logFile="$logDir/BACKUP-$kvmDName-$(date +'%Y_%m_%d_%I_%M_%p').log" | |
| # Perform Initial sanity checks | |
| sanity_checks | |
| log -e "\nLooking for domain $kvmDName with UUID $kvmDUuid" | |
| if [[ "$kvmDName" == $(virsh -c qemu:///system domname "$kvmDUuid" | head -1) && "$kvmDUuid" == $(virsh -c qemu:///system domuuid "$kvmDName" | head -1) ]]; then | |
| log "Domain found...." | |
| else | |
| log "Domain not found on this host. Aborting....." | |
| log "Check for the location of the Instance in the cloudstack management console" | |
| exit 5 | |
| fi | |
| backup_domain "$kvmDName" "$snapPrefix$kvmDName" | |
| exit 0 |