Adds incremental backup functionality.
Signed-off-by: Ishani Chugh <chugh.ishani@research.iiit.ac.in>
---
contrib/backup/qemu-backup.py | 101 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 99 insertions(+), 2 deletions(-)
diff --git a/contrib/backup/qemu-backup.py b/contrib/backup/qemu-backup.py
index 248ca9f..7a3077a 100644
--- a/contrib/backup/qemu-backup.py
+++ b/contrib/backup/qemu-backup.py
@@ -24,11 +24,13 @@ from __future__ import print_function
from argparse import ArgumentParser
import os
import errno
+from string import Template
from socket import error as socket_error
try:
import configparser
except ImportError:
import ConfigParser as configparser
+from configparser import NoOptionError
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',
'scripts', 'qmp'))
@@ -41,7 +43,6 @@ class BackupTool(object):
'/.config/qemu/qemu-backup-config'):
if "QEMU_BACKUP_CONFIG" in os.environ:
self.config_file = os.environ["QEMU_BACKUP_CONFIG"]
-
else:
self.config_file = config_file
try:
@@ -129,6 +130,97 @@ class BackupTool(object):
drive_list.remove(event['data']['device'])
print("Backup Complete")
+ def _inc_backup(self, guest_name):
+ """
+ Performs Incremental backup
+ """
+ if guest_name not in self.config.sections():
+ print ("Cannot find specified guest", file=sys.stderr)
+ exit(1)
+
+ self.verify_guest_running(guest_name)
+ connection = QEMUMonitorProtocol(
+ self.get_socket_address(
+ self.config[guest_name]['qmp']))
+ connection.connect()
+ backup_cmd = {"execute": "transaction",
+ "arguments": {"actions": [], "properties":
+ {"completion-mode": "grouped"}}}
+ bitmap_cmd = {"execute": "transaction", "arguments": {"actions": []}}
+ for key in self.config[guest_name]:
+ if key.startswith("drive_"):
+ drive = key[len('drive_'):]
+ target = self.config.get(guest_name, key).rsplit('/', 1)[0]
+ inc_backup_pattern = Template('${drive}_inc_${N}')
+ bitmap = 'qemu_backup_'+guest_name
+ try:
+ query_block_cmd = {'execute': 'query-block'}
+ returned_json = connection.cmd_obj(query_block_cmd)
+ device_present = False
+ for device in returned_json['return']:
+ if device['device'] == drive:
+ device_present = True
+ bitmap_present = False
+ for bitmaps in device['dirty-bitmaps']:
+ if bitmap == bitmaps['name']:
+ bitmap_present = True
+ if os.path.isfile(self.config.get(
+ guest_name,
+ 'inc_'+drive)) is False:
+ print("Initial Backup does not exist")
+ bitmap_remove = {"execute":
+ "block-dirty" +
+ "-bitmap-remove",
+ "arguments":
+ {"node": drive,
+ "name":
+ "qemu_backup_" +
+ guest_name}}
+ connection.cmd_obj(bitmap_remove)
+ bitmap_present = False
+ if bitmap_present is False:
+ raise NoOptionError(guest_name, 'inc_'+drive)
+ break
+
+ if not device_present:
+ print("No such drive in guest", file=sys.stderr)
+ sys.exit(1)
+ N = int(self.config.get(guest_name, drive+'_N'))+1
+ target = self.config.get(guest_name, key).rsplit(
+ '/', 1)[0]\
+ + '/' + inc_backup_pattern.substitute(drive=drive, N=N)
+ os.system("qemu-img create -f qcow2 " + target + " -b " +
+ self.config.get(guest_name, 'inc_' +
+ drive) + " -F qcow2")
+ sub_cmd = {"type": "drive-backup",
+ "data": {"device": drive, "bitmap": bitmap,
+ "mode": "existing",
+ "sync": "incremental",
+ "target": target}}
+ backup_cmd['arguments']['actions'].append(sub_cmd)
+ self.config.set(guest_name, drive+'_N',
+ str(int(self.config.get(guest_name,
+ drive+'_N'))+1))
+ self.config.set(guest_name, 'inc_'+drive, target)
+ except (NoOptionError, KeyError) as e:
+ target = self.config.get(guest_name, key).rsplit(
+ '/', 1)[0]\
+ + '/' + inc_backup_pattern.substitute(drive=drive, N=0)
+ sub_cmd_1 = {"type": "block-dirty-bitmap-add",
+ "data": {"node": drive, "name": bitmap,
+ "persistent": True,
+ "autoload": True}}
+ sub_cmd_2 = {"type": "drive-backup",
+ "data": {"device": drive, "target": target,
+ "sync": "full", "format": "qcow2"}}
+ self.config.set(guest_name, drive+'_N', '0')
+ self.config.set(guest_name, 'inc_'+drive, target)
+ bitmap_cmd['arguments']['actions'].append(sub_cmd_1)
+ bitmap_cmd['arguments']['actions'].append(sub_cmd_2)
+ connection.cmd_obj(bitmap_cmd)
+ connection.cmd_obj(backup_cmd)
+ self.write_config()
+
def _drive_add(self, drive_id, guest_name, target=None):
"""
Adds drive for backup
@@ -275,7 +367,10 @@ class BackupTool(object):
"""
Wrapper for _full_backup method
"""
- self._full_backup(args.guest)
+ if args.inc is False:
+ self._full_backup(args.guest)
+ else:
+ self._inc_backup(args.guest)
def restore_wrapper(self, args):
"""
@@ -329,6 +424,8 @@ def main():
backup_parser = subparsers.add_parser('backup', help='Creates backup')
backup_parser.add_argument('--guest', action='store',
type=str, help='Name of the guest')
+ backup_parser.add_argument('--inc', nargs='?',
+ default=False, help='Destination path')
backup_parser.set_defaults(func=backup_tool.fullbackup_wrapper)
backup_parser = subparsers.add_parser('restore', help='Restores drives')
--
2.7.4
On Thu, 08/31 00:45, Ishani Chugh wrote:
> Adds incremental backup functionality.
>
> Signed-off-by: Ishani Chugh <chugh.ishani@research.iiit.ac.in>
> ---
> contrib/backup/qemu-backup.py | 101 +++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 99 insertions(+), 2 deletions(-)
>
> diff --git a/contrib/backup/qemu-backup.py b/contrib/backup/qemu-backup.py
> index 248ca9f..7a3077a 100644
> --- a/contrib/backup/qemu-backup.py
> +++ b/contrib/backup/qemu-backup.py
> @@ -24,11 +24,13 @@ from __future__ import print_function
> from argparse import ArgumentParser
> import os
> import errno
> +from string import Template
> from socket import error as socket_error
> try:
> import configparser
> except ImportError:
> import ConfigParser as configparser
> +from configparser import NoOptionError
> import sys
> sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',
> 'scripts', 'qmp'))
> @@ -41,7 +43,6 @@ class BackupTool(object):
> '/.config/qemu/qemu-backup-config'):
> if "QEMU_BACKUP_CONFIG" in os.environ:
> self.config_file = os.environ["QEMU_BACKUP_CONFIG"]
> -
> else:
> self.config_file = config_file
> try:
> @@ -129,6 +130,97 @@ class BackupTool(object):
> drive_list.remove(event['data']['device'])
> print("Backup Complete")
>
> + def _inc_backup(self, guest_name):
> + """
> + Performs Incremental backup
> + """
> + if guest_name not in self.config.sections():
> + print ("Cannot find specified guest", file=sys.stderr)
s/print (/print(/
> + exit(1)
> +
> + self.verify_guest_running(guest_name)
> + connection = QEMUMonitorProtocol(
> + self.get_socket_address(
> + self.config[guest_name]['qmp']))
> + connection.connect()
> + backup_cmd = {"execute": "transaction",
> + "arguments": {"actions": [], "properties":
> + {"completion-mode": "grouped"}}}
> + bitmap_cmd = {"execute": "transaction", "arguments": {"actions": []}}
> + for key in self.config[guest_name]:
From here on the indentation level is launched straight into outer space. Please
either extract code blocks into functions, or at least try to rearrange it like:
> + if key.startswith("drive_"):
if not key.startswith("drive_"):
continue
drive = ...
...
> + drive = key[len('drive_'):]
> + target = self.config.get(guest_name, key).rsplit('/', 1)[0]
> + inc_backup_pattern = Template('${drive}_inc_${N}')
> + bitmap = 'qemu_backup_'+guest_name
Whitespaces missing around +.
> + try:
> + query_block_cmd = {'execute': 'query-block'}
> + returned_json = connection.cmd_obj(query_block_cmd)
> + device_present = False
> + for device in returned_json['return']:
> + if device['device'] == drive:
if device['device'] != drive:
continue
device_present = True
...
> + device_present = True
> + bitmap_present = False
> + for bitmaps in device['dirty-bitmaps']:
> + if bitmap == bitmaps['name']:
if bitmap != bitmaps['name']:
continue
bitmap_present = True
...
> + bitmap_present = True
> + if os.path.isfile(self.config.get(
> + guest_name,
> + 'inc_'+drive)) is False:
> + print("Initial Backup does not exist")
> + bitmap_remove = {"execute":
> + "block-dirty" +
> + "-bitmap-remove",
Please fix the indentation level and join the string: "block-dirty-bitmap-remove".
Even if you cannot control the indentation level, long line is still better than
cutting it into halves. It makes the code hard to read and unfriendly to grep.
> + "arguments":
> + {"node": drive,
> + "name":
> + "qemu_backup_" +
> + guest_name}}
> + connection.cmd_obj(bitmap_remove)
> + bitmap_present = False
> + if bitmap_present is False:
Please "don't compare boolean values to True or False using ==", or "is":
s/bitmap_present is False/not bitmap_present/
> + raise NoOptionError(guest_name, 'inc_'+drive)
> + break
> +
> + if not device_present:
> + print("No such drive in guest", file=sys.stderr)
> + sys.exit(1)
> + N = int(self.config.get(guest_name, drive+'_N'))+1
> + target = self.config.get(guest_name, key).rsplit(
> + '/', 1)[0]\
> + + '/' + inc_backup_pattern.substitute(drive=drive, N=N)
> + os.system("qemu-img create -f qcow2 " + target + " -b " +
> + self.config.get(guest_name, 'inc_' +
> + drive) + " -F qcow2")
> + sub_cmd = {"type": "drive-backup",
> + "data": {"device": drive, "bitmap": bitmap,
> + "mode": "existing",
> + "sync": "incremental",
> + "target": target}}
> + backup_cmd['arguments']['actions'].append(sub_cmd)
> + self.config.set(guest_name, drive+'_N',
> + str(int(self.config.get(guest_name,
> + drive+'_N'))+1))
> + self.config.set(guest_name, 'inc_'+drive, target)
> + except (NoOptionError, KeyError) as e:
> + target = self.config.get(guest_name, key).rsplit(
> + '/', 1)[0]\
> + + '/' + inc_backup_pattern.substitute(drive=drive, N=0)
> + sub_cmd_1 = {"type": "block-dirty-bitmap-add",
> + "data": {"node": drive, "name": bitmap,
> + "persistent": True,
> + "autoload": True}}
> + sub_cmd_2 = {"type": "drive-backup",
> + "data": {"device": drive, "target": target,
> + "sync": "full", "format": "qcow2"}}
> + self.config.set(guest_name, drive+'_N', '0')
> + self.config.set(guest_name, 'inc_'+drive, target)
> + bitmap_cmd['arguments']['actions'].append(sub_cmd_1)
> + bitmap_cmd['arguments']['actions'].append(sub_cmd_2)
> + connection.cmd_obj(bitmap_cmd)
> + connection.cmd_obj(backup_cmd)
> + self.write_config()
> +
> def _drive_add(self, drive_id, guest_name, target=None):
> """
> Adds drive for backup
> @@ -275,7 +367,10 @@ class BackupTool(object):
> """
> Wrapper for _full_backup method
> """
> - self._full_backup(args.guest)
> + if args.inc is False:
> + self._full_backup(args.guest)
> + else:
> + self._inc_backup(args.guest)
if not args.inc:
self._full_backup(...)
else:
...
>
> def restore_wrapper(self, args):
> """
> @@ -329,6 +424,8 @@ def main():
> backup_parser = subparsers.add_parser('backup', help='Creates backup')
> backup_parser.add_argument('--guest', action='store',
> type=str, help='Name of the guest')
> + backup_parser.add_argument('--inc', nargs='?',
> + default=False, help='Destination path')
> backup_parser.set_defaults(func=backup_tool.fullbackup_wrapper)
>
> backup_parser = subparsers.add_parser('restore', help='Restores drives')
> --
> 2.7.4
>
>
© 2016 - 2026 Red Hat, Inc.