diff --git a/kvm-backup-zvol-iscsi.sh b/kvm-backup-zvol-iscsi.sh index f06020a..ea5c07a 100644 --- a/kvm-backup-zvol-iscsi.sh +++ b/kvm-backup-zvol-iscsi.sh @@ -5,6 +5,13 @@ # Runs on the ZFS server where the zvols live. VM domains are # discovered via virsh on remote libvirt hosts connected over iSCSI, # matching the domain-driven approach of the other kvm-backup scripts. +# +# Prerequisites: +# Set volmode=dev on each zvol before running this script to prevent +# kernel partition scanning on snapshot devices. This must be done +# once per zvol (not toggled at runtime) to avoid a deadlock in +# zvol_create_minors when combined with snapdev toggling. +# zfs set volmode=dev tank/kvm/ # A file to prevent overlapping runs. PIDFILE="/tmp/backup-zvol-iscsi.pid" @@ -133,7 +140,7 @@ backupDomains() { local HOST_LABEL="$2" DOMLIST_STATUS_TMP="/tmp/backup-zvol-domlist-$HOST_LABEL-tmp" - while read -r _ DOMAIN _; do + while read -r _ DOMAIN _ <&3; do # If the domain is empty, skip. if [[ -z "$DOMAIN" ]]; then continue @@ -166,12 +173,21 @@ backupDomains() { continue fi - # Verify the zvol exists locally. - ZVOL="$ZFS_PARENT/$ZVOL_NAME" - if ! zfs list "$ZVOL" &>/dev/null; then - echo "Warning: zvol $ZVOL does not exist locally, skipping" + # Resolve the zvol name case-insensitively, since iSCSI IQN + # target names are typically lowercased. + RESOLVED_NAME=$(zfs list -o name -H -r "$ZFS_PARENT" -d 1 | while read -r ZN; do + BN="${ZN##*/}" + if [[ "${BN,,}" == "${ZVOL_NAME,,}" ]]; then + echo "$BN" + break + fi + done) + if [[ -z "$RESOLVED_NAME" ]]; then + echo "Warning: zvol matching $ZFS_PARENT/$ZVOL_NAME does not exist locally, skipping" continue fi + ZVOL_NAME="$RESOLVED_NAME" + ZVOL="$ZFS_PARENT/$ZVOL_NAME" DEVS+=("$DEV") ZVOL_NAMES+=("$ZVOL_NAME") @@ -202,19 +218,33 @@ backupDomains() { ZVOL_NAME=${ZVOL_NAMES[$i]} ZVOL="$ZFS_PARENT/$ZVOL_NAME" + # Make snapshot block devices visible so we can read them. + zfs set snapdev=visible "$ZVOL" + # Create a ZFS snapshot. echo "Creating snapshot: $ZVOL@$SNAPSHOT_NAME" if ! zfs snapshot "$ZVOL@$SNAPSHOT_NAME"; then fail "Failed to create snapshot for $ZVOL" fi + # Wait for the snapshot block device to appear. + SNAP_DEV="/dev/zvol/$ZVOL@$SNAPSHOT_NAME" + udevadm settle + for _ in $(seq 1 30); do + [[ -e "$SNAP_DEV" ]] && break + sleep 1 + done + if [[ ! -e "$SNAP_DEV" ]]; then + fail "Snapshot device $SNAP_DEV did not appear" + fi + # Read the raw disk image from the snapshot's block device. # This produces a portable raw image that can be restored to # any virtual disk or converted with qemu-img, without # requiring ZFS on the restore target. - SNAP_DEV="/dev/zvol/$ZVOL@$SNAPSHOT_NAME" + ZVOL_SIZE=$(zfs list -Hp -o volsize "$ZVOL") echo "Creating backup for $DOMAIN ($DEV [$ZVOL])" - if ! dd if="$SNAP_DEV" bs=4M status=none | pv | borg create \ + if ! dd if="$SNAP_DEV" bs=4M status=none | pv -s "$ZVOL_SIZE" | borg create \ --verbose \ --stats \ --show-rc \ @@ -233,6 +263,9 @@ backupDomains() { fi fi + # Hide snapshot devices again. + zfs set snapdev=hidden "$ZVOL" + # Cleanup old ZFS snapshots. cleanupSnapshots "$ZVOL" done @@ -267,7 +300,7 @@ backupDomains() { fail "Failed to prune $DOMAIN xml" fi fi - done < <( + done 3< <( if [[ -n "$SSH_PREFIX" ]]; then $SSH_PREFIX "virsh list --all" 2>/dev/null | tail -n +3 else