Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Managing External Snapshots With BASH Scripts

Below are some BASH scripts I came up with for managing VMs that use external snapshots. I do this purely through qemu-img commands and for now it's a good idea to not mix these with internal snapshots taken with virsh. Also, if you are using Ubuntu 16.04, there seems to be an issue with Apparmor when there is a backing file chain which you will need to address (the workaround is in that link).

These scripts assume you have the following setup:

  • The scripts are inside a folder dedicated to the guest.
  • The script that creates the snapshots is called create-snapshot.sh.
  • This folder contains all the disk images/snapshots for only that guest.
  • You are not using virsh to manage snapshots for this guest at all.
  • If you are starting out and have one disk image, that disk image needs to be named head.qcow2 and the guest needs to be configured to use that (virsh edit)

Create Snapshot

When crating an external snapshot, we rename the current head file to the current timestamp, and then create a new head file where future changes will be made. This way the guest doesn't need to be reconfigured because it always points to head.qcow2. Naming the snapshot after the current timestamp allows us to know what point in time that file contains changes until.

#!/bin/bash
# This script will create an external snapshot
UNIX_TIMESTAMP=$(date +%s)
CURRENT_BACKING_FILE=`readlink head.qcow2`
NEW_NAME_FOR_CURRENT_HEAD_FILE="`pwd`/`echo $UNIX_TIMESTAMP`.qcow2"
CURRENT_HEAD_FILE="`pwd`/head.qcow2"

# Give the user the opportunity to name the snapshot.
echo -n "Do you want to give a name to the snapshot? [Y/n] "
read REPLY
echo ""

if [[ $REPLY =~ ^[Yy]$ ]]
then
    echo -n "Enter the name for the snapshot: "
    read NEW_NAME_FOR_CURRENT_HEAD_FILE

    # append the qcow2 extension
    NEW_NAME_FOR_CURRENT_HEAD_FILE="`echo $NEW_NAME_FOR_CURRENT_HEAD_FILE`.qcow2"
fi

# Rename the head file to whatever the current timestamp is.
# This way we know that this file contains changes up to this point in time
mv $CURRENT_HEAD_FILE $NEW_NAME_FOR_CURRENT_HEAD_FILE

# Create the new "head" disk image where all future changes are made.
qemu-img create \
  -f qcow2 \
  -b $NEW_NAME_FOR_CURRENT_HEAD_FILE \
  $CURRENT_HEAD_FILE

Restore Snapshot

When restoring a snapshot, it is important that we create a new empty head file that uses that snapshot file as a backing file, rather than using a symlink that points to it or moving the file to become head.qcow2. This is because there may be other guests or snapshots also using that snapshot file as a backing image and any further changes to it could corrupt them. Once head file has been timestamped to become a snapshot, it should e considered read-only forever.

#!/bin/bash
# this script will restore a snapshot

# The path to the head file. This is always the path to the file that
# will take new changes/writes.
HEAD_FILE="`pwd`/head.qcow2"

# list the snapshot files
ls -1

echo -n "Enter the name of the snapshot you wish to restore: "
read BACKING_FILE

if ! [ -f $BACKING_FILE ]; then
    echo "Could not find that snapshot file!"
    exit;
fi

# Create a snapshot of the current state so we dont lose it
read -p "Do you want to take a snapshot of the current state first? " -n 1 -r

if [[ $REPLY =~ ^[Yy]$ ]]
then
    bash create-snapshot.sh
fi


# remove the current head file which will either be empty
# or full of changes we dont wish to keep.
rm $HEAD_FILE

# Create the new "head" disk image where all future changes are made.
qemu-img create \
  -f qcow2 \
  -b $BACKING_FILE \
  $HEAD_FILE

Saving Confusion

If things get confusing because you are managing 2+ "threads" of snapshots, remember that giving appropriate names can be a huge help. Also this command will list the chain of snapshots backing the file:

qemu-img info --backing-chain $FILENAME