diff --git a/client-cli/scripts/README b/client-cli/scripts/README new file mode 100644 index 00000000..34590a49 --- /dev/null +++ b/client-cli/scripts/README @@ -0,0 +1,32 @@ +# PVC helper scripts + +These helper scripts are included with the PVC client to aid administrators in some meta-functions. + +The following scripts are provided for use: + +## `migrate_vm` + +Migrates a VM, with downtime, from one PVC cluster to another. + +`migrate_vm ` + +### Arguments + + * `vm`: The virtual machine to migrate + * `source_cluster`: The source PVC cluster; must be a valid cluster to the local PVC client + * `destination_cluster`: The destination PVC cluster; must be a valid cluster to the local PVC client + +## `import_vm` + +Imports a VM from another platform into a PVC cluster. + +## `export_vm` + +Exports a (stopped) VM from a PVC cluster to another platform. + +`export_vm ` + +### Arguments + + * `vm`: The virtual machine to migrate + * `source_cluster`: The source PVC cluster; must be a valid cluster to the local PVC client diff --git a/client-cli/scripts/export_vm b/client-cli/scripts/export_vm new file mode 100755 index 00000000..9e8a5675 --- /dev/null +++ b/client-cli/scripts/export_vm @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +# export_vm - Exports a VM from a PVC cluster to local files +# Part of the Parallel Virtual Cluster (PVC) system +# +# Copyright (C) 2018-2020 Joshua M. Boniface +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################### + +set -o errexit +set -o pipefail + +usage() { + echo -e "Export a VM from a PVC cluster to local files." + echo -e "Usage:" + echo -e " $0 []" + echo -e "" + echo -e "Important information:" + echo -e " * The local user must have valid SSH access to the primary coordinator in the source_cluster." + echo -e " * The user on the cluster primary coordinator must have 'sudo' access." + echo -e " * If the VM is not in 'stop' state, it will be shut down." + echo -e " * Do not switch the cluster primary coordinator while the script is running." + echo -e " * Ensure you have enough space in to store all VM disk images." +} + +fail() { + echo -e "$@" + exit 1 +} + +# Arguments +if [[ -z ${1} || -z ${2} ]]; then + usage + exit 1 +fi +source_vm="${1}" +source_cluster="${2}" +if [[ -n "${3}" ]]; then + destination_directory="${3}" +else + destination_directory="." +fi + +# Verify the cluster is reachable +pvc -c ${source_cluster} status &>/dev/null || fail "Specified source_cluster is not accessible" + +# Determine the connection IP +cluster_address="$( pvc cluster list 2>/dev/null | grep -i "^${source_cluster}" | awk '{ print $2 }' )" + +# Attempt to connect to the cluster address +ssh ${cluster_address} which pvc &>/dev/null || fail "Could not SSH to source_cluster primary coordinator host" + +# Verify that the VM exists +pvc -c ${source_cluster} vm info ${source_vm} &>/dev/null || fail "Specified VM is not present on the cluster" + +echo "Verification complete." + +# Shut down the VM +echo -n "Shutting down VM..." +set +o errexit +pvc -c ${source_cluster} vm shutdown ${source_vm} &>/dev/null +shutdown_success=$? +while ! pvc -c ${source_cluster} vm info ${source_vm} 2>/dev/null | grep '^State' | grep -q -E 'stop|disable'; do + sleep 1 + echo -n "." +done +set -o errexit +echo " done." + +# Dump the XML file +echo -n "Exporting VM configuration file... " +pvc -c ${source_cluster} vm dump ${source_vm} 1> ${destination_directory}/${source_vm}.xml 2>/dev/null +echo "done". + +# Determine the list of volumes in this VM +volume_list="$( pvc -c ${source_cluster} vm info --long ${source_vm} 2>/dev/null | grep -w 'rbd' | awk '{ print $3 }' )" +for volume in ${volume_list}; do + volume_pool="$( awk -F '/' '{ print $1 }' <<<"${volume}" )" + volume_name="$( awk -F '/' '{ print $2 }' <<<"${volume}" )" + volume_size="$( pvc -c ${source_cluster} storage volume list -p ${volume_pool} ${volume_name} 2>/dev/null | grep "^${volume_name}" | awk '{ print $3 }' )" + echo -n "Exporting disk ${volume_name} (${volume_size})... " + ssh ${cluster_address} sudo rbd map ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to map volume ${volume}" + ssh ${cluster_address} sudo dd if="/dev/rbd/${volume_pool}/${volume_name}" bs=1M 2>/dev/null | dd bs=1M of="${destination_directory}/${volume_name}.img" 2>/dev/null + ssh ${cluster_address} sudo rbd unmap ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to unmap volume ${volume}" + echo "done." +done diff --git a/client-cli/scripts/import_vm b/client-cli/scripts/import_vm new file mode 100755 index 00000000..fb694f7d --- /dev/null +++ b/client-cli/scripts/import_vm @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# import_vm - Imports a VM to a PVC cluster from local files +# Part of the Parallel Virtual Cluster (PVC) system +# +# Copyright (C) 2018-2020 Joshua M. Boniface +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################### + +set -o errexit +set -o pipefail + +usage() { + echo -e "Import a VM to a PVC cluster from local files." + echo -e "Usage:" + echo -e " $0 [] [...]" + echo -e "" + echo -e "Important information:" + echo -e " * At least one disk must be specified; all disks that are present in vm_configuration_file" + echo -e " should be specified, though this is not strictly requireda." + echo -e " * Do not switch the cluster primary coordinator while the script is running." + echo -e " * Ensure you have enough space on the destination cluster to store all VM disks." +} + +fail() { + echo -e "$@" + exit 1 +} + +# Arguments +if [[ -z ${1} || -z ${2} || -z ${3} || -z ${4} ]]; then + usage + exit 1 +fi +destination_cluster="${1}"; shift +destination_pool="${1}"; shift +vm_config_file="${1}"; shift +vm_disk_files=( ${@} ) + +# Verify the cluster is reachable +pvc -c ${destination_cluster} status &>/dev/null || fail "Specified destination_cluster is not accessible" + +# Determine the connection IP +cluster_address="$( pvc cluster list 2>/dev/null | grep -i "^${destination_cluster}" | awk '{ print $2 }' )" + +echo "Verification complete." + +# Determine information about the VM from the config file +parse_xml_field() { + field="${1}" + line="$( grep -F "<${field}>" ${vm_config_file} )" + awk -F '>|<' '{ print $3 }' <<<"${line}" +} +vm_name="$( parse_xml_field name )" +echo "Importing VM ${vm_name}..." +pvc -c ${destination_cluster} vm define ${vm_config_file} 2>/dev/null + +# Create the disks on the cluster +for disk_file in ${vm_disk_files[@]}; do + disk_file_basename="$( basename ${disk_file} )" + disk_file_ext="${disk_file_basename##*.}" + disk_file_name="$( basename ${disk_file_basename} .${disk_file_ext} )" + disk_file_size="$( stat --format="%s" ${disk_file} )" + + echo "Importing disk ${disk_file_name}... " + pvc -c ${destination_cluster} storage volume add ${destination_pool} ${disk_file_name} ${disk_file_size}B 2>/dev/null + pvc -c ${destination_cluster} storage volume upload ${destination_pool} ${disk_file_name} ${disk_file} 2>/dev/null +done diff --git a/client-cli/scripts/migrate_vm b/client-cli/scripts/migrate_vm new file mode 100755 index 00000000..3de48164 --- /dev/null +++ b/client-cli/scripts/migrate_vm @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# migrate_vm - Exports a VM from a PVC cluster to another PVC cluster +# Part of the Parallel Virtual Cluster (PVC) system +# +# Copyright (C) 2018-2020 Joshua M. Boniface +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################### + +set -o errexit +set -o pipefail + +usage() { + echo -e "Export a VM from a PVC cluster to another PVC cluster." + echo -e "Usage:" + echo -e " $0 " + echo -e "" + echo -e "Important information:" + echo -e " * The local user must have valid SSH access to the primary coordinator in the source_cluster." + echo -e " * The user on the cluster primary coordinator must have 'sudo' access." + echo -e " * If the VM is not in 'stop' state, it will be shut down." + echo -e " * Do not switch the cluster primary coordinator on either cluster while the script is running." + echo -e " * Ensure you have enough space on the target cluster to store all VM disks." +} + +fail() { + echo -e "$@" + exit 1 +} + +# Arguments +if [[ -z ${1} || -z ${2} || -z ${3} || -z ${4} ]]; then + usage + exit 1 +fi +source_vm="${1}" +source_cluster="${2}" +destination_cluster="${3}" +destination_pool="${4}" + +# Verify each cluster is reachable +pvc -c ${source_cluster} status &>/dev/null || fail "Specified source_cluster is not accessible" +pvc -c ${destination_cluster} status &>/dev/null || fail "Specified destination_cluster is not accessible" + +# Determine the connection IPs +source_cluster_address="$( pvc cluster list 2>/dev/null | grep -i "^${source_cluster}" | awk '{ print $2 }' )" +destination_cluster_address="$( pvc cluster list 2>/dev/null | grep -i "^${destination_cluster}" | awk '{ print $2 }' )" + +# Attempt to connect to the cluster addresses +ssh ${source_cluster_address} which pvc &>/dev/null || fail "Could not SSH to source_cluster primary coordinator host" +ssh ${destination_cluster_address} which pvc &>/dev/null || fail "Could not SSH to destination_cluster primary coordinator host" + +# Verify that the VM exists +pvc -c ${source_cluster} vm info ${source_vm} &>/dev/null || fail "Specified VM is not present on the source cluster" + +echo "Verification complete." + +# Shut down the VM +echo -n "Shutting down VM..." +set +o errexit +pvc -c ${source_cluster} vm shutdown ${source_vm} &>/dev/null +shutdown_success=$? +while ! pvc -c ${source_cluster} vm info ${source_vm} 2>/dev/null | grep '^State' | grep -q -E 'stop|disable'; do + sleep 1 + echo -n "." +done +set -o errexit +echo " done." + +tempfile="$( mktemp )" + +# Dump the XML file +echo -n "Exporting VM configuration file from source cluster... " +pvc -c ${source_cluster} vm dump ${source_vm} 1> ${tempfile} 2>/dev/null +echo "done." + +# Import the XML file +echo -n "Importing VM configuration file to destination cluster... " +pvc -c ${destination_cluster} vm define ${tempfile} +echo "done." + +rm -f ${tempfile} + +# Determine the list of volumes in this VM +volume_list="$( pvc -c ${source_cluster} vm info --long ${source_vm} 2>/dev/null | grep -w 'rbd' | awk '{ print $3 }' )" + +# Parse and migrate each volume +for volume in ${volume_list}; do + volume_pool="$( awk -F '/' '{ print $1 }' <<<"${volume}" )" + volume_name="$( awk -F '/' '{ print $2 }' <<<"${volume}" )" + volume_size="$( pvc -c ${source_cluster} storage volume list -p ${volume_pool} ${volume_name} 2>/dev/null | grep "^${volume_name}" | awk '{ print $3 }' )" + echo "Transferring disk ${volume_name} (${volume_size})... " + pvc -c ${destination_cluster} storage volume add ${destination_pool} ${volume_name} ${volume_size} 2>/dev/null + ssh ${source_cluster_address} sudo rbd map ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to map volume ${volume} on source cluster" + ssh ${destination_cluster_address} sudo rbd map ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to map volume ${volume} on destination cluster" + ssh ${source_cluster_address} sudo dd if="/dev/rbd/${volume_pool}/${volume_name}" bs=1M 2>/dev/null | pv | ssh ${destination_cluster_address} sudo dd bs=1M of="/dev/rbd/${destination_pool}/${volume_name}" 2>/dev/null + ssh ${source_cluster_address} sudo rbd unmap ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to unmap volume ${volume} on source cluster" + ssh ${destination_cluster_address} sudo rbd unmap ${volume_pool}/${volume_name} &>/dev/null || fail "Failed to unmap volume ${volume} on destination cluster" +done + +if [[ ${shutdown_success} -eq 0 ]]; then + pvc -c ${destination_cluster} vm start ${source_vm} +fi diff --git a/debian/pvc-client-cli.install b/debian/pvc-client-cli.install index 1af21dd5..01a41a81 100644 --- a/debian/pvc-client-cli.install +++ b/debian/pvc-client-cli.install @@ -1,2 +1,3 @@ client-cli/pvc.py usr/share/pvc client-cli/cli_lib usr/share/pvc +client-cli/scripts usr/share/pvc