Skip to content

Commit

Permalink
move rebase code to different file
Browse files Browse the repository at this point in the history
  • Loading branch information
abbbi committed Jan 26, 2024
1 parent 865a81b commit fa85ce4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 91 deletions.
106 changes: 106 additions & 0 deletions libqmpbackup/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
qmpbackup: Full an incremental backup using Qemus
dirty bitmap feature
Copyright (C) 2022 Michael Ablassmeier
Authors:
Michael Ablassmeier <[email protected]>
This work is licensed under the terms of the GNU GPL, version 3. See
the LICENSE file in the top-level directory.
"""
import os
import logging
import subprocess

log = logging.getLogger(__name__)


def rebase(directory, dry_run, until):
"""Rebase and commit all images in a directory"""
if not os.path.exists(directory):
log.error("Unable to find target directory")
return False

os.chdir(directory)
image_files = filter(os.path.isfile, os.listdir(directory))
images = [os.path.join(directory, f) for f in image_files]
images_flat = [os.path.basename(f) for f in images]
if until is not None and until not in images_flat:
log.error(
"Image file specified by --until option [%s] does not exist in backup directory",
until,
)
return False

# sort files by creation date
images.sort(key=os.path.getmtime)
images_flat.sort(key=os.path.getmtime)

if dry_run:
log.info("Dry run activated, not applying any changes")

if len(images) == 0:
log.error("No image files found in specified directory")
return False

if ".partial" in " ".join(images_flat):
log.error("Partial backup file found, backup chain might be broken.")
log.error("Consider removing file before attempting to rebase.")
return False

if "FULL-" not in images[0]:
log.error("First image file is not a FULL base image")
return False

if "FULL-" in images[-1]:
log.error("No incremental images found, nothing to commit")
return False

idx = len(images) - 1

if until is not None:
sidx = images_flat.index(until)
for image in reversed(images):
idx = idx - 1
if until is not None and idx >= sidx:
log.info("Skipping checkpoint: %s as requested with --until option", image)
continue

if images.index(image) == 0 or "FULL-" in images[images.index(image)]:
log.info(
"Rollback of latest [FULL]<-[INC] chain complete, ignoring older chains"
)
break

log.debug('"%s" is based on "%s"', images[idx], image)

# before rebase we check consistency of all files
check_cmd = f"qemu-img check '{image}'"
try:
log.info(check_cmd)
if not dry_run:
subprocess.check_output(check_cmd, shell=True)
except subprocess.CalledProcessError as errmsg:
log.error("Error while file check: %s", errmsg)
return False

try:
rebase_cmd = (
f'qemu-img rebase -f qcow2 -F qcow2 -b "{images[idx]}" "{image}" -u'
)
if not dry_run:
subprocess.check_output(rebase_cmd, shell=True)
log.info(rebase_cmd)
commit_cmd = f"qemu-img commit '{image}'"
log.info(commit_cmd)
if not dry_run:
subprocess.check_output(commit_cmd, shell=True)
os.remove(image)
except subprocess.CalledProcessError as errmsg:
log.error("Error while rollback: %s", errmsg)
return False

return True
90 changes: 0 additions & 90 deletions libqmpbackup/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from json import dumps as json_dumps
from glob import glob
import logging
import subprocess
from libqmpbackup.qaclient import QemuGuestAgentClient


Expand Down Expand Up @@ -65,95 +64,6 @@ def check_for_partial(backupdir, node):

return False

def rebase(self, directory, dry_run, until):
"""Rebase and commit all images in a directory"""
if not os.path.exists(directory):
self._log.error("Unable to find target directory")
return False

os.chdir(directory)
image_files = filter(os.path.isfile, os.listdir(directory))
images = [os.path.join(directory, f) for f in image_files]
images_flat = [os.path.basename(f) for f in images]
if until is not None and until not in images_flat:
self._log.error(
"Image file specified by --until option [%s] does not exist in backup directory",
until,
)
return False

# sort files by creation date
images.sort(key=os.path.getmtime)
images_flat.sort(key=os.path.getmtime)

if dry_run:
self._log.info("Dry run activated, not applying any changes")

if len(images) == 0:
self._log.error("No image files found in specified directory")
return False

if ".partial" in " ".join(images_flat):
self._log.error("Partial backup file found, backup chain might be broken.")
self._log.error("Consider removing file before attempting to rebase.")
return False

if "FULL-" not in images[0]:
self._log.error("First image file is not a FULL base image")
return False

if "FULL-" in images[-1]:
self._log.error("No incremental images found, nothing to commit")
return False

idx = len(images) - 1

if until is not None:
sidx = images_flat.index(until)
for image in reversed(images):
idx = idx - 1
if until is not None and idx >= sidx:
self._log.info(
"Skipping checkpoint: %s as requested with --until option", image
)
continue

if images.index(image) == 0 or "FULL-" in images[images.index(image)]:
self._log.info(
"Rollback of latest [FULL]<-[INC] chain complete, ignoring older chains"
)
break

self._log.debug('"%s" is based on "%s"', images[idx], image)

# before rebase we check consistency of all files
check_cmd = f"qemu-img check '{image}'"
try:
self._log.info(check_cmd)
if not dry_run:
subprocess.check_output(check_cmd, shell=True)
except subprocess.CalledProcessError as errmsg:
self._log.error("Error while file check: %s", errmsg)
return False

try:
rebase_cmd = (
f'qemu-img rebase -f qcow2 -F qcow2 -b "{images[idx]}" "{image}" -u'
)
if not dry_run:
subprocess.check_output(rebase_cmd, shell=True)
self._log.info(rebase_cmd)
commit_cmd = f"qemu-img commit '{image}'"
self._log.info(commit_cmd)
if not dry_run:
subprocess.check_output(commit_cmd, shell=True)
os.remove(image)
except subprocess.CalledProcessError as errmsg:
self._log.error("Error while rollback: %s", errmsg)
return False

return True

def check_bitmap_state(self, node, bitmaps):
"""Check if the bitmap state is ready for backup
Expand Down
3 changes: 2 additions & 1 deletion qmprebase
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import sys
import argparse
from libqmpbackup import version
from libqmpbackup import image
from libqmpbackup.lib import QmpBackup

parser = argparse.ArgumentParser(prog=sys.argv[0])
Expand Down Expand Up @@ -48,7 +49,7 @@ log = common.setup_log()
log.info("Version: %s Arguments: %s", version.VERSION, " ".join(sys.argv))

if action == "rebase":
if common.rebase(argv.dir, argv.dry_run, argv.until):
if image.rebase(argv.dir, argv.dry_run, argv.until):
log.info("Image files rollback successful.")
sys.exit(0)
sys.exit(1)

0 comments on commit fa85ce4

Please sign in to comment.