[{"id":1766043,"web_url":"http://patchwork.ozlabs.org/comment/1766043/","msgid":"<20170911050200.GB32341@lemon.lan>","list_archive_url":null,"date":"2017-09-11T05:02:00","subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","submitter":{"id":24872,"url":"http://patchwork.ozlabs.org/api/people/24872/","name":"Fam Zheng","email":"famz@redhat.com"},"content":"On Fri, 09/08 22:11, Ishani Chugh wrote:\n> +def build_parser():\n> +    backup_tool = BackupTool()\n> +    parser = ArgumentParser()\n> +    subparsers = parser.add_subparsers(title='Subcommands',\n> +                                       description='Valid Subcommands',\n> +                                       help='Subcommand help')\n> +    guest_parser = subparsers.add_parser('guest', help='Manage guest(s)')\n> +    guest_subparsers = guest_parser.add_subparsers(title='Guest Subparser')\n> +#   Guest list\n\nOdd indentation of comments. Please align the # at 4th colume line other code\nlines.  The same to below.\n\n> +    guest_list_parser = guest_subparsers.add_parser('list',\n> +                                                    help='Lists all guests')\n> +    guest_list_parser.set_defaults(func=backup_tool.list)\n> +\n> +#   Guest add\n> +    guest_add_parser = guest_subparsers.add_parser('add', help='Adds a guest')\n> +    guest_add_required = guest_add_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    guest_add_required.add_argument('--guest', action='store', type=str,\n> +                                    help='Name of the guest', required=True)\n> +    guest_add_required.add_argument('--qmp', action='store', type=str,\n> +                                    help='Path of socket', required=True)\n> +    guest_add_parser.set_defaults(func=backup_tool.guest_add_wrapper)\n> +\n> +#   Guest Remove\n> +    guest_remove_parser = guest_subparsers.add_parser('remove',\n> +                                                      help='Removes a guest')\n> +    guest_remove_required = guest_remove_parser.add_argument_group('Required \\\n> +                                                                    Arguments')\n> +    guest_remove_required.add_argument('--guest', action='store', type=str,\n> +                                       help='Name of the guest', required=True)\n> +    guest_remove_parser.set_defaults(func=backup_tool.guest_remove_wrapper)\n> +\n> +    drive_parser = subparsers.add_parser('drive',\n> +                                         help='Adds drive(s) for backup')\n> +    drive_subparsers = drive_parser.add_subparsers(title='Add subparser',\n> +                                                   description='Drive \\\n> +                                                                subparser')\n> +#   Drive Add\n> +    drive_add_parser = drive_subparsers.add_parser('add',\n> +                                                   help='Adds new \\\n> +                                                         drive for backup')\n> +    drive_add_required = drive_add_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    drive_add_required.add_argument('--guest', action='store', type=str,\n> +                                    help='Name of the guest', required=True)\n> +    drive_add_required.add_argument('--id', action='store',\n> +                                    type=str, help='Drive ID', required=True)\n> +    drive_add_parser.add_argument('--target', nargs='?',\n> +                                  default=None, help='Destination path')\n> +    drive_add_parser.set_defaults(func=backup_tool.drive_add_wrapper)\n> +\n> +    backup_parser = subparsers.add_parser('backup', help='Creates backup')\n> +\n> +#   Backup\n> +    backup_parser_required = backup_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    backup_parser_required.add_argument('--guest', action='store', type=str,\n> +                                        help='Name of the guest',\n> +                                        required=True)\n> +    backup_parser.set_defaults(func=backup_tool.fullbackup_wrapper)\n> +\n> +#   Restore\n> +    restore_parser = subparsers.add_parser('restore', help='Restores drives')\n> +    restore_parser_required = restore_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    restore_parser_required.add_argument('--guest', action='store',\n> +                                         type=str, help='Name of the guest',\n> +                                         required=True)\n> +    restore_parser.set_defaults(func=backup_tool.restore_wrapper)\n> +\n> +    return parser\n> +\n> +\n> +def main():\n> +    parser = build_parser()\n> +    args = parser.parse_args()\n> +    args.func(args)\n> +\n> +if __name__ == '__main__':\n> +    main()\n> --\n> 2.7.4\n> \n\nFam","headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@bilbo.ozlabs.org","Authentication-Results":["ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=nongnu.org\n\t(client-ip=2001:4830:134:3::11; helo=lists.gnu.org;\n\tenvelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n\treceiver=<UNKNOWN>)","ext-mx08.extmail.prod.ext.phx2.redhat.com;\n\tdmarc=none (p=none dis=none) header.from=redhat.com","ext-mx08.extmail.prod.ext.phx2.redhat.com;\n\tspf=fail smtp.mailfrom=famz@redhat.com"],"Received":["from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11])\n\t(using TLSv1 with cipher AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xrG5D4y3Vz9s81\n\tfor <incoming@patchwork.ozlabs.org>;\n\tMon, 11 Sep 2017 15:02:39 +1000 (AEST)","from localhost ([::1]:55427 helo=lists.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1drGrl-0001nf-3i\n\tfor incoming@patchwork.ozlabs.org; Mon, 11 Sep 2017 01:02:37 -0400","from eggs.gnu.org ([2001:4830:134:3::10]:33662)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <famz@redhat.com>) id 1drGrO-0001nT-HZ\n\tfor qemu-devel@nongnu.org; Mon, 11 Sep 2017 01:02:15 -0400","from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <famz@redhat.com>) id 1drGrL-0007BP-D7\n\tfor qemu-devel@nongnu.org; Mon, 11 Sep 2017 01:02:14 -0400","from mx1.redhat.com ([209.132.183.28]:35414)\n\tby eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\n\t(Exim 4.71) (envelope-from <famz@redhat.com>) id 1drGrL-0007AT-4K\n\tfor qemu-devel@nongnu.org; Mon, 11 Sep 2017 01:02:11 -0400","from smtp.corp.redhat.com\n\t(int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15])\n\t(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby mx1.redhat.com (Postfix) with ESMTPS id 84EBEC070477;\n\tMon, 11 Sep 2017 05:02:08 +0000 (UTC)","from localhost (ovpn-12-28.pek2.redhat.com [10.72.12.28])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id 6F5695EDFA;\n\tMon, 11 Sep 2017 05:02:01 +0000 (UTC)"],"DMARC-Filter":"OpenDMARC Filter v1.3.2 mx1.redhat.com 84EBEC070477","Date":"Mon, 11 Sep 2017 13:02:00 +0800","From":"Fam Zheng <famz@redhat.com>","To":"Ishani Chugh <chugh.ishani@research.iiit.ac.in>","Message-ID":"<20170911050200.GB32341@lemon.lan>","References":"<1504888905-22396-1-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>","User-Agent":"Mutt/1.8.3 (2017-05-23)","X-Scanned-By":"MIMEDefang 2.79 on 10.5.11.15","X-Greylist":"Sender IP whitelisted, not delayed by milter-greylist-4.5.16\n\t(mx1.redhat.com [10.5.110.32]);\n\tMon, 11 Sep 2017 05:02:09 +0000 (UTC)","X-detected-operating-system":"by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\n\t[fuzzy]","X-Received-From":"209.132.183.28","Subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.21","Precedence":"list","List-Id":"<qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<http://lists.nongnu.org/archive/html/qemu-devel/>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Cc":"jsnow@redhat.com, qemu-devel@nongnu.org, stefanha@redhat.com","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"\"Qemu-devel\"\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>"}},{"id":1768113,"web_url":"http://patchwork.ozlabs.org/comment/1768113/","msgid":"<4da9f6d9-ef16-cbc6-a1b9-1bf6f3465a6e@redhat.com>","list_archive_url":null,"date":"2017-09-13T20:04:23","subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","submitter":{"id":64343,"url":"http://patchwork.ozlabs.org/api/people/64343/","name":"John Snow","email":"jsnow@redhat.com"},"content":"On 09/11/2017 01:02 AM, Fam Zheng wrote:\n> On Fri, 09/08 22:11, Ishani Chugh wrote:\n>> +def build_parser():\n>> +    backup_tool = BackupTool()\n>> +    parser = ArgumentParser()\n>> +    subparsers = parser.add_subparsers(title='Subcommands',\n>> +                                       description='Valid Subcommands',\n>> +                                       help='Subcommand help')\n>> +    guest_parser = subparsers.add_parser('guest', help='Manage guest(s)')\n>> +    guest_subparsers = guest_parser.add_subparsers(title='Guest Subparser')\n>> +#   Guest list\n> \n> Odd indentation of comments. Please align the # at 4th colume line other code\n> lines.  The same to below.\n> \n\nSorry, that's probably my fault based on how I suggested the comments in\nthe previous email review. (:\n\nFam is right though, the pythonic way to comment is by aligning the\noctothorpe to column 4 instead of leaving it at column 0.\n\n--js","headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@bilbo.ozlabs.org","Authentication-Results":["ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=nongnu.org\n\t(client-ip=2001:4830:134:3::11; helo=lists.gnu.org;\n\tenvelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n\treceiver=<UNKNOWN>)","ext-mx04.extmail.prod.ext.phx2.redhat.com;\n\tdmarc=none (p=none dis=none) header.from=redhat.com","ext-mx04.extmail.prod.ext.phx2.redhat.com;\n\tspf=fail smtp.mailfrom=jsnow@redhat.com"],"Received":["from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11])\n\t(using TLSv1 with cipher AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xst1S4Rjtz9s7f\n\tfor <incoming@patchwork.ozlabs.org>;\n\tThu, 14 Sep 2017 06:04:59 +1000 (AEST)","from localhost ([::1]:44391 helo=lists.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1dsDu5-0006Av-2o\n\tfor incoming@patchwork.ozlabs.org; Wed, 13 Sep 2017 16:04:57 -0400","from eggs.gnu.org ([2001:4830:134:3::10]:44366)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jsnow@redhat.com>) id 1dsDti-0006Ab-Lw\n\tfor qemu-devel@nongnu.org; Wed, 13 Sep 2017 16:04:35 -0400","from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jsnow@redhat.com>) id 1dsDtf-00033b-Hq\n\tfor qemu-devel@nongnu.org; Wed, 13 Sep 2017 16:04:34 -0400","from mx1.redhat.com ([209.132.183.28]:39968)\n\tby eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\n\t(Exim 4.71) (envelope-from <jsnow@redhat.com>) id 1dsDtf-00033G-BV\n\tfor qemu-devel@nongnu.org; Wed, 13 Sep 2017 16:04:31 -0400","from smtp.corp.redhat.com\n\t(int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16])\n\t(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby mx1.redhat.com (Postfix) with ESMTPS id 88FD67EA97;\n\tWed, 13 Sep 2017 20:04:29 +0000 (UTC)","from [10.18.17.231] (dhcp-17-231.bos.redhat.com [10.18.17.231])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id EB65A6178A;\n\tWed, 13 Sep 2017 20:04:23 +0000 (UTC)"],"DMARC-Filter":"OpenDMARC Filter v1.3.2 mx1.redhat.com 88FD67EA97","To":"Fam Zheng <famz@redhat.com>,\n\tIshani Chugh <chugh.ishani@research.iiit.ac.in>","References":"<1504888905-22396-1-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<20170911050200.GB32341@lemon.lan>","From":"John Snow <jsnow@redhat.com>","Message-ID":"<4da9f6d9-ef16-cbc6-a1b9-1bf6f3465a6e@redhat.com>","Date":"Wed, 13 Sep 2017 16:04:23 -0400","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101\n\tThunderbird/52.3.0","MIME-Version":"1.0","In-Reply-To":"<20170911050200.GB32341@lemon.lan>","Content-Type":"text/plain; charset=utf-8","Content-Language":"en-US","Content-Transfer-Encoding":"7bit","X-Scanned-By":"MIMEDefang 2.79 on 10.5.11.16","X-Greylist":"Sender IP whitelisted, not delayed by milter-greylist-4.5.16\n\t(mx1.redhat.com [10.5.110.28]);\n\tWed, 13 Sep 2017 20:04:30 +0000 (UTC)","X-detected-operating-system":"by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\n\t[fuzzy]","X-Received-From":"209.132.183.28","Subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.21","Precedence":"list","List-Id":"<qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<http://lists.nongnu.org/archive/html/qemu-devel/>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Cc":"qemu-devel@nongnu.org, stefanha@redhat.com","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"\"Qemu-devel\"\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>"}},{"id":1769372,"web_url":"http://patchwork.ozlabs.org/comment/1769372/","msgid":"<83bba737-1630-d739-6d72-7c87fdd7fe7b@redhat.com>","list_archive_url":null,"date":"2017-09-15T19:13:34","subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","submitter":{"id":64343,"url":"http://patchwork.ozlabs.org/api/people/64343/","name":"John Snow","email":"jsnow@redhat.com"},"content":"On 09/08/2017 12:41 PM, Ishani Chugh wrote:\n> qemu-backup will be a command-line tool for performing full and\n> incremental disk backups on running VMs. It is intended as a\n> reference implementation for management stack and backup developers\n> to see QEMU's backup features in action. The tool writes details of\n> guest in a configuration file and the data is retrieved from the file\n> while creating a backup. The location of config file can be set as an\n> environment variable QEMU_BACKUP_CONFIG. The usage is as follows:\n> \n> Add a guest\n> python qemu-backup.py guest add --guest <guest_name> --qmp <socket_path>\n> \n> Remove a guest\n> python qemu-backup.py guest remove --guest <guest_name>\n> \n> List all guest configs in configuration file:\n> python qemu-backup.py guest list\n> \n> Add a drive for backup in a specified guest\n> python qemu-backup.py drive add --guest <guest_name> --id <drive_id> [--target <target_file_path>]\n> \n> Create backup of the added drives:\n> python qemu-backup.py backup --guest <guest_name>\n> \n> Restore operation\n> python qemu-backup.py restore --guest <guest-name>\n> \n> \n> Signed-off-by: Ishani Chugh <chugh.ishani@research.iiit.ac.in>\n> ---\n>  contrib/backup/qemu-backup.py | 373 ++++++++++++++++++++++++++++++++++++++++++\n>  1 file changed, 373 insertions(+)\n>  create mode 100755 contrib/backup/qemu-backup.py\n> \n> diff --git a/contrib/backup/qemu-backup.py b/contrib/backup/qemu-backup.py\n> new file mode 100755\n> index 0000000..7077f68\n> --- /dev/null\n> +++ b/contrib/backup/qemu-backup.py\n> @@ -0,0 +1,373 @@\n> +#!/usr/bin/python\n> +# -*- coding: utf-8 -*-\n> +#\n> +# Copyright (C) 2017 Ishani Chugh <chugh.ishani@research.iiit.ac.in>\n> +#\n> +# This program is free software; you can redistribute it and/or modify\n> +# it under the terms of the GNU General Public License as published by\n> +# the Free Software Foundation; either version 2 of the License, or\n> +# (at your option) any later version.\n> +#\n> +# This program is distributed in the hope that it will be useful,\n> +# but WITHOUT ANY WARRANTY; without even the implied warranty of\n> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n> +# GNU General Public License for more details.\n> +#\n> +# You should have received a copy of the GNU General Public License\n> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n> +#\n> +\n> +\"\"\"\n> +This file is an implementation of backup tool\n> +\"\"\"\n> +from __future__ import print_function\n> +from argparse import ArgumentParser\n> +import os\n> +import errno\n> +from socket import error as socket_error\n> +try:\n> +    import configparser\n> +except ImportError:\n> +    import ConfigParser as configparser\n> +import sys\n> +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',\n> +                             'scripts', 'qmp'))\n> +from qmp import QEMUMonitorProtocol\n> +\n> +\n> +class BackupTool(object):\n> +    \"\"\"BackupTool Class\"\"\"\n> +    def __init__(self, config_file=os.path.expanduser('~') +\n> +                 '/.config/qemu/qemu-backup-config'):\n> +        if \"QEMU_BACKUP_CONFIG\" in os.environ:\n> +            self.config_file = os.environ[\"QEMU_BACKUP_CONFIG\"]\n> +\n> +        else:\n> +            self.config_file = config_file\n> +            try:\n> +                if not os.path.isdir(os.path.dirname(self.config_file)):\n> +                    os.makedirs(os.path.dirname(self.config_file))\n> +            except:\n> +                print(\"Cannot create config directory\", file=sys.stderr)\n> +                sys.exit(1)\n> +        self.config = configparser.ConfigParser()\n> +        self.config.read(self.config_file)\n> +        try:\n> +            if self.config.get('general', 'version') != '1.0':\n> +                    print(\"Version Conflict in config file\", file=sys.stderr)\n> +                    sys.exit(1)\n> +        except:\n> +            self.config['general'] = {'version': '1.0'}\n> +            self.write_config()\n> +\n> +    def write_config(self):\n> +        \"\"\"\n> +        Writes configuration to ini file.\n> +        \"\"\"\n> +        config_file = open(self.config_file + \".tmp\", 'w')\n> +        self.config.write(config_file)\n> +        config_file.flush()\n> +        os.fsync(config_file.fileno())\n> +        config_file.close()\n> +        os.rename(self.config_file + \".tmp\", self.config_file)\n> +\n> +    def get_socket_address(self, socket_address):\n> +        \"\"\"\n> +        Return Socket address in form of string or tuple\n> +        \"\"\"\n> +        if socket_address.startswith('tcp'):\n> +            return (socket_address.split(':')[1],\n> +                    int(socket_address.split(':')[2]))\n> +        return socket_address.split(':', 2)[1]\n> +\n> +    def _full_backup(self, guest_name):\n> +        \"\"\"\n> +        Performs full backup of guest\n> +        \"\"\"\n> +        self.verify_guest_present(guest_name)\n> +        self.verify_guest_running(guest_name)\n> +        connection = QEMUMonitorProtocol(\n> +                                         self.get_socket_address(\n> +                                             self.config[guest_name]['qmp']))\n> +        connection.connect()\n> +        cmd = {\"execute\": \"transaction\", \"arguments\": {\"actions\": []}}\n> +        drive_list = []\n> +        for key in self.config[guest_name]:\n> +            if key.startswith(\"drive_\"):\n> +                drive = key[len('drive_'):]\n> +                drive_list.append(drive)\n> +                target = self.config[guest_name][key]\n> +                sub_cmd = {\"type\": \"drive-backup\", \"data\": {\"device\": drive,\n> +                                                            \"target\": target,\n> +                                                            \"sync\": \"full\"}}\n> +                cmd['arguments']['actions'].append(sub_cmd)\n> +        qmp_return = connection.cmd_obj(cmd)\n> +        if 'error' in qmp_return:\n> +            print(qmp_return['error']['desc'], file=sys.stderr)\n> +            sys.exit(1)\n> +        print(\"Backup Started\")\n> +        while drive_list:\n> +            event = connection.pull_event(wait=True)\n> +            if event['event'] == 'SHUTDOWN':\n> +                print(\"The guest was SHUT DOWN\", file=sys.stderr)\n> +                sys.exit(1)\n> +\n> +            if event['event'] == 'BLOCK_JOB_COMPLETED':\n> +                if event['data']['device'] in drive_list and \\\n> +                        event['data']['type'] == 'backup':\n> +                        print(\"*\" + event['data']['device'])\n> +                        drive_list.remove(event['data']['device'])\n> +\n> +            if event['event'] == 'BLOCK_JOB_ERROR':\n> +                if event['data']['device'] in drive_list and \\\n> +                        event['data']['type'] == 'backup':\n> +                        print(event['data']['device'] + \" Backup Failed\",\n> +                              file=sys.stderr)\n> +                        drive_list.remove(event['data']['device'])\n> +        print(\"Backup Complete\")\n> +\n> +    def _drive_add(self, drive_id, guest_name, target=None):\n> +        \"\"\"\n> +        Adds drive for backup\n> +        \"\"\"\n> +        if target is None:\n> +            target = os.path.abspath(drive_id)\n> +\n> +        if os.path.isdir(os.path.dirname(target)) is False:\n> +            print(\"Cannot find target directory\", file=sys.stderr)\n> +            sys.exit(1)\n> +\n> +        self.verify_guest_present(guest_name)\n> +        if \"drive_\" + drive_id in self.config[guest_name]:\n> +            print(\"Drive already marked for backup\", file=sys.stderr)\n> +            sys.exit(1)\n> +\n> +        self.verify_guest_running(guest_name)\n> +\n> +        connection = QEMUMonitorProtocol(\n> +                                         self.get_socket_address(\n> +                                             self.config[guest_name]['qmp']))\n> +        connection.connect()\n> +        cmd = {'execute': 'query-block'}\n> +        returned_json = connection.cmd_obj(cmd)\n> +        device_present = False\n> +        for device in returned_json['return']:\n> +            if device['device'] == drive_id:\n> +                device_present = True\n> +                break\n> +\n> +        if not device_present:\n> +            print(\"No such drive in guest\", file=sys.stderr)\n> +            sys.exit(1)\n> +\n> +        drive_id = \"drive_\" + drive_id\n> +        for d_id in self.config[guest_name]:\n> +            if self.config[guest_name][d_id] == target:\n> +                print(\"Please choose different target\", file=sys.stderr)\n> +                sys.exit(1)\n> +        self.config.set(guest_name, drive_id, target)\n> +        self.write_config()\n> +        print(\"Successfully Added Drive\")\n> +\n> +    def verify_guest_running(self, guest_name):\n> +        \"\"\"\n> +        Checks whether specified guest is running or not\n> +        \"\"\"\n> +        socket_address = self.config.get(guest_name, 'qmp')\n> +        try:\n> +            connection = QEMUMonitorProtocol(self.get_socket_address(\n> +                                             socket_address))\n> +            connection.connect()\n> +        except socket_error:\n> +            if socket_error.errno != errno.ECONNREFUSED:\n> +                print(\"Connection to guest refused\", file=sys.stderr)\n> +                sys.exit(1)\n> +            print(\"Cannot connect to guest\", file=sys.stderr)\n> +            sys.exit(1)\n> +\n> +    def verify_guest_present(self, guest_name):\n> +        \"\"\"\n> +        Checks if guest is present in config file\n> +        \"\"\"\n> +        if guest_name == 'general':\n> +            print(\"Cannot use \\'general\\' as guest name\")\n> +            sys.exit(1)\n> +        if guest_name not in self.config.sections():\n> +            print(\"Guest Not present in config file\", file=sys.stderr)\n> +            sys.exit(1)\n> +\n> +    def _guest_add(self, guest_name, socket_address):\n> +        \"\"\"\n> +        Adds a guest to the config file\n> +        \"\"\"\n> +        if guest_name in self.config.sections():\n> +            print(\"ID already exists. Please choose a different guest name\",\n> +                  file=sys.stderr)\n> +            sys.exit(1)\n> +        if socket_address.split(':', 1)[0] != 'tcp' \\\n> +                and socket_address.split(':', 1)[0] != 'unix':\n> +            print(\"Invalid Socket\", file=sys.stderr)\n> +            sys.exit(1)\n> +        self.config[guest_name] = {'qmp': socket_address}\n> +        self.verify_guest_running(guest_name)\n> +        self.write_config()\n> +        print(\"Successfully Added Guest\")\n> +\n> +    def _guest_remove(self, guest_name):\n> +        \"\"\"\n> +        Removes a guest from config file\n> +        \"\"\"\n> +        self.verify_guest_present(guest_name)\n> +        self.config.remove_section(guest_name)\n> +        print(\"Guest successfully deleted\")\n> +\n> +    def _restore(self, guest_name):\n> +        \"\"\"\n> +        Prints Steps to perform restore operation\n> +        \"\"\"\n> +        self.verify_guest_present(guest_name)\n> +        self.verify_guest_running(guest_name)\n> +        connection = QEMUMonitorProtocol(\n> +                                         self.get_socket_address(\n> +                                             self.config[guest_name]['qmp']))\n> +        connection.connect()\n> +        print(\"To perform restore:\")\n> +        print(\"Shut down guest\")\n> +        for key in self.config[guest_name]:\n> +            if key.startswith(\"drive_\"):\n> +                drive = key[len('drive_'):]\n> +                target = self.config[guest_name][key]\n> +                cmd = {'execute': 'query-block'}\n> +                returned_json = connection.cmd_obj(cmd)\n> +                device_present = False\n> +                for device in returned_json['return']:\n> +                    if device['device'] == drive:\n> +                        device_present = True\n> +                        location = device['inserted']['image']['filename']\n> +                        print(\"qemu-img convert \" + target + \" \" + location)\n> +\n> +                if not device_present:\n> +                    print(\"No such drive in guest\", file=sys.stderr)\n> +                    sys.exit(1)\n> +\n> +    def guest_remove_wrapper(self, args):\n> +        \"\"\"\n> +        Wrapper for _guest_remove method.\n> +        \"\"\"\n> +        guest_name = args.guest\n> +        self._guest_remove(guest_name)\n> +        self.write_config()\n> +\n> +    def list(self, args):\n> +        \"\"\"\n> +        Prints guests present in Config file\n> +        \"\"\"\n> +        for guest_name in self.config.sections():\n> +            if guest_name != 'general':\n> +                print(guest_name)\n> +\n\njhuston@probe (review) ~/s/q/c/backup> ./qemu-backup.py guest add\n--guest general --qmp tcp:localhost:4444\nID already exists. Please choose a different guest name\n\nThe reason I suggested to try to namespace VMs was so that if you tried\nto add a VM (that just so happened to be named general) that you\nwouldn't get a confusing error message, because when we go to confirm\nwhat VMs are here, it's not going to print \"general.\"\n\nIn the actual grand scheme of things this isn't that important, but\nyou've created a bit of a \"dead space\" here arbitrarily where a certain\nmagical name is not available for use.\n\nAh, well, consider it more of an educational consideration at this point.\n\n> +    def guest_add_wrapper(self, args):\n> +        \"\"\"\n> +        Wrapper for _guest_add method\n> +        \"\"\"\n> +        self._guest_add(args.guest, args.qmp)\n> +\n> +    def drive_add_wrapper(self, args):\n> +        \"\"\"\n> +        Wrapper for _drive_add method\n> +        \"\"\"\n> +        self._drive_add(args.id, args.guest, args.target)\n> +\n> +    def fullbackup_wrapper(self, args):\n> +        \"\"\"\n> +        Wrapper for _full_backup method\n> +        \"\"\"\n> +        self._full_backup(args.guest)\n> +\n> +    def restore_wrapper(self, args):\n> +        \"\"\"\n> +        Wrapper for restore\n> +        \"\"\"\n> +        self._restore(args.guest)\n> +\n> +\n> +def build_parser():\n> +    backup_tool = BackupTool()\n> +    parser = ArgumentParser()\n> +    subparsers = parser.add_subparsers(title='Subcommands',\n> +                                       description='Valid Subcommands',\n> +                                       help='Subcommand help')\n> +    guest_parser = subparsers.add_parser('guest', help='Manage guest(s)')\n> +    guest_subparsers = guest_parser.add_subparsers(title='Guest Subparser')\n> +#   Guest list\n> +    guest_list_parser = guest_subparsers.add_parser('list',\n> +                                                    help='Lists all guests')\n> +    guest_list_parser.set_defaults(func=backup_tool.list)\n> +\n> +#   Guest add\n> +    guest_add_parser = guest_subparsers.add_parser('add', help='Adds a guest')\n> +    guest_add_required = guest_add_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    guest_add_required.add_argument('--guest', action='store', type=str,\n> +                                    help='Name of the guest', required=True)\n> +    guest_add_required.add_argument('--qmp', action='store', type=str,\n> +                                    help='Path of socket', required=True)\n> +    guest_add_parser.set_defaults(func=backup_tool.guest_add_wrapper)\n> +\n> +#   Guest Remove\n> +    guest_remove_parser = guest_subparsers.add_parser('remove',\n> +                                                      help='Removes a guest')\n> +    guest_remove_required = guest_remove_parser.add_argument_group('Required \\\n> +                                                                    Arguments')\n> +    guest_remove_required.add_argument('--guest', action='store', type=str,\n> +                                       help='Name of the guest', required=True)\n> +    guest_remove_parser.set_defaults(func=backup_tool.guest_remove_wrapper)\n> +\n> +    drive_parser = subparsers.add_parser('drive',\n> +                                         help='Adds drive(s) for backup')\n> +    drive_subparsers = drive_parser.add_subparsers(title='Add subparser',\n> +                                                   description='Drive \\\n> +                                                                subparser')\n> +#   Drive Add\n> +    drive_add_parser = drive_subparsers.add_parser('add',\n> +                                                   help='Adds new \\\n> +                                                         drive for backup')\n> +    drive_add_required = drive_add_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    drive_add_required.add_argument('--guest', action='store', type=str,\n> +                                    help='Name of the guest', required=True)\n> +    drive_add_required.add_argument('--id', action='store',\n> +                                    type=str, help='Drive ID', required=True)\n> +    drive_add_parser.add_argument('--target', nargs='?',\n> +                                  default=None, help='Destination path')\n> +    drive_add_parser.set_defaults(func=backup_tool.drive_add_wrapper)\n> +\n> +    backup_parser = subparsers.add_parser('backup', help='Creates backup')\n> +\n> +#   Backup\n> +    backup_parser_required = backup_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    backup_parser_required.add_argument('--guest', action='store', type=str,\n> +                                        help='Name of the guest',\n> +                                        required=True)\n> +    backup_parser.set_defaults(func=backup_tool.fullbackup_wrapper)\n> +\n> +#   Restore\n> +    restore_parser = subparsers.add_parser('restore', help='Restores drives')\n> +    restore_parser_required = restore_parser.add_argument_group('Required \\\n> +                                                                Arguments')\n> +    restore_parser_required.add_argument('--guest', action='store',\n> +                                         type=str, help='Name of the guest',\n> +                                         required=True)\n> +    restore_parser.set_defaults(func=backup_tool.restore_wrapper)\n> +\n> +    return parser\n> +\n> +\n> +def main():\n> +    parser = build_parser()\n> +    args = parser.parse_args()\n> +    args.func(args)\n> +\n> +if __name__ == '__main__':\n> +    main()\n> --\n> 2.7.4\n> \n\nLooks good. I'm happy giving it my R-B.\n\nReviewed-by: John Snow <jsnow@redhat.com>","headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@bilbo.ozlabs.org","Authentication-Results":["ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=nongnu.org\n\t(client-ip=2001:4830:134:3::11; helo=lists.gnu.org;\n\tenvelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n\treceiver=<UNKNOWN>)","ext-mx07.extmail.prod.ext.phx2.redhat.com;\n\tdmarc=none (p=none dis=none) header.from=redhat.com","ext-mx07.extmail.prod.ext.phx2.redhat.com;\n\tspf=fail smtp.mailfrom=jsnow@redhat.com"],"Received":["from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11])\n\t(using TLSv1 with cipher AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xv4qF4b08z9s7g\n\tfor <incoming@patchwork.ozlabs.org>;\n\tSat, 16 Sep 2017 05:15:21 +1000 (AEST)","from localhost ([::1]:54720 helo=lists.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1dsw59-0002GJ-O3\n\tfor incoming@patchwork.ozlabs.org; Fri, 15 Sep 2017 15:15:19 -0400","from eggs.gnu.org ([2001:4830:134:3::10]:55582)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <jsnow@redhat.com>) id 1dsw40-0001ei-HY\n\tfor qemu-devel@nongnu.org; Fri, 15 Sep 2017 15:14:10 -0400","from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <jsnow@redhat.com>) id 1dsw3x-0003Z0-AM\n\tfor qemu-devel@nongnu.org; Fri, 15 Sep 2017 15:14:08 -0400","from mx1.redhat.com ([209.132.183.28]:39884)\n\tby eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\n\t(Exim 4.71) (envelope-from <jsnow@redhat.com>) id 1dsw3x-0003Yl-0C\n\tfor qemu-devel@nongnu.org; Fri, 15 Sep 2017 15:14:05 -0400","from smtp.corp.redhat.com\n\t(int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13])\n\t(using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby mx1.redhat.com (Postfix) with ESMTPS id 27568C079981;\n\tFri, 15 Sep 2017 19:14:04 +0000 (UTC)","from [10.18.17.130] (dhcp-17-130.bos.redhat.com [10.18.17.130])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id 5AC3A7F38D;\n\tFri, 15 Sep 2017 19:13:34 +0000 (UTC)"],"DMARC-Filter":"OpenDMARC Filter v1.3.2 mx1.redhat.com 27568C079981","From":"John Snow <jsnow@redhat.com>","To":"Ishani Chugh <chugh.ishani@research.iiit.ac.in>, qemu-devel@nongnu.org","References":"<1504888905-22396-1-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>","Message-ID":"<83bba737-1630-d739-6d72-7c87fdd7fe7b@redhat.com>","Date":"Fri, 15 Sep 2017 15:13:34 -0400","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101\n\tThunderbird/52.3.0","MIME-Version":"1.0","In-Reply-To":"<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>","Content-Type":"text/plain; charset=utf-8","Content-Language":"en-US","Content-Transfer-Encoding":"7bit","X-Scanned-By":"MIMEDefang 2.79 on 10.5.11.13","X-Greylist":"Sender IP whitelisted, not delayed by milter-greylist-4.5.16\n\t(mx1.redhat.com [10.5.110.31]);\n\tFri, 15 Sep 2017 19:14:04 +0000 (UTC)","X-detected-operating-system":"by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\n\t[fuzzy]","X-Received-From":"209.132.183.28","Subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.21","Precedence":"list","List-Id":"<qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<http://lists.nongnu.org/archive/html/qemu-devel/>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Cc":"famz@redhat.com, stefanha@redhat.com","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"\"Qemu-devel\"\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>"}},{"id":1770507,"web_url":"http://patchwork.ozlabs.org/comment/1770507/","msgid":"<1896814790.665667.1505769195286.JavaMail.zimbra@research.iiit.ac.in>","list_archive_url":null,"date":"2017-09-18T21:13:15","subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","submitter":{"id":71382,"url":"http://patchwork.ozlabs.org/api/people/71382/","name":"Ishani","email":"chugh.ishani@research.iiit.ac.in"},"content":"----- On Sep 16, 2017, at 12:43 AM, jsnow jsnow@redhat.com wrote:\n\n> On 09/08/2017 12:41 PM, Ishani Chugh wrote:\n>> qemu-backup will be a command-line tool for performing full and\n>> incremental disk backups on running VMs. It is intended as a\n>> reference implementation for management stack and backup developers\n>> to see QEMU's backup features in action. The tool writes details of\n>> guest in a configuration file and the data is retrieved from the file\n>> while creating a backup. The location of config file can be set as an\n>> environment variable QEMU_BACKUP_CONFIG. The usage is as follows:\n>> \n>> Add a guest\n>> python qemu-backup.py guest add --guest <guest_name> --qmp <socket_path>\n>> \n>> Remove a guest\n>> python qemu-backup.py guest remove --guest <guest_name>\n>> \n>> List all guest configs in configuration file:\n>> python qemu-backup.py guest list\n>> \n>> Add a drive for backup in a specified guest\n>> python qemu-backup.py drive add --guest <guest_name> --id <drive_id> [--target\n>> <target_file_path>]\n>> \n>> Create backup of the added drives:\n>> python qemu-backup.py backup --guest <guest_name>\n>> \n>> Restore operation\n>> python qemu-backup.py restore --guest <guest-name>\n>> \n>> \n>> Signed-off-by: Ishani Chugh <chugh.ishani@research.iiit.ac.in>\n>> ---\n>>  contrib/backup/qemu-backup.py | 373 ++++++++++++++++++++++++++++++++++++++++++\n>>  1 file changed, 373 insertions(+)\n>>  create mode 100755 contrib/backup/qemu-backup.py\n>> \n>> diff --git a/contrib/backup/qemu-backup.py b/contrib/backup/qemu-backup.py\n>> new file mode 100755\n>> index 0000000..7077f68\n>> --- /dev/null\n>> +++ b/contrib/backup/qemu-backup.py\n>> @@ -0,0 +1,373 @@\n>> +#!/usr/bin/python\n>> +# -*- coding: utf-8 -*-\n>> +#\n>> +# Copyright (C) 2017 Ishani Chugh <chugh.ishani@research.iiit.ac.in>\n>> +#\n>> +# This program is free software; you can redistribute it and/or modify\n>> +# it under the terms of the GNU General Public License as published by\n>> +# the Free Software Foundation; either version 2 of the License, or\n>> +# (at your option) any later version.\n>> +#\n>> +# This program is distributed in the hope that it will be useful,\n>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of\n>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n>> +# GNU General Public License for more details.\n>> +#\n>> +# You should have received a copy of the GNU General Public License\n>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n>> +#\n>> +\n>> +\"\"\"\n>> +This file is an implementation of backup tool\n>> +\"\"\"\n>> +from __future__ import print_function\n>> +from argparse import ArgumentParser\n>> +import os\n>> +import errno\n>> +from socket import error as socket_error\n>> +try:\n>> +    import configparser\n>> +except ImportError:\n>> +    import ConfigParser as configparser\n>> +import sys\n>> +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',\n>> +                             'scripts', 'qmp'))\n>> +from qmp import QEMUMonitorProtocol\n>> +\n>> +\n>> +class BackupTool(object):\n>> +    \"\"\"BackupTool Class\"\"\"\n>> +    def __init__(self, config_file=os.path.expanduser('~') +\n>> +                 '/.config/qemu/qemu-backup-config'):\n>> +        if \"QEMU_BACKUP_CONFIG\" in os.environ:\n>> +            self.config_file = os.environ[\"QEMU_BACKUP_CONFIG\"]\n>> +\n>> +        else:\n>> +            self.config_file = config_file\n>> +            try:\n>> +                if not os.path.isdir(os.path.dirname(self.config_file)):\n>> +                    os.makedirs(os.path.dirname(self.config_file))\n>> +            except:\n>> +                print(\"Cannot create config directory\", file=sys.stderr)\n>> +                sys.exit(1)\n>> +        self.config = configparser.ConfigParser()\n>> +        self.config.read(self.config_file)\n>> +        try:\n>> +            if self.config.get('general', 'version') != '1.0':\n>> +                    print(\"Version Conflict in config file\", file=sys.stderr)\n>> +                    sys.exit(1)\n>> +        except:\n>> +            self.config['general'] = {'version': '1.0'}\n>> +            self.write_config()\n>> +\n>> +    def write_config(self):\n>> +        \"\"\"\n>> +        Writes configuration to ini file.\n>> +        \"\"\"\n>> +        config_file = open(self.config_file + \".tmp\", 'w')\n>> +        self.config.write(config_file)\n>> +        config_file.flush()\n>> +        os.fsync(config_file.fileno())\n>> +        config_file.close()\n>> +        os.rename(self.config_file + \".tmp\", self.config_file)\n>> +\n>> +    def get_socket_address(self, socket_address):\n>> +        \"\"\"\n>> +        Return Socket address in form of string or tuple\n>> +        \"\"\"\n>> +        if socket_address.startswith('tcp'):\n>> +            return (socket_address.split(':')[1],\n>> +                    int(socket_address.split(':')[2]))\n>> +        return socket_address.split(':', 2)[1]\n>> +\n>> +    def _full_backup(self, guest_name):\n>> +        \"\"\"\n>> +        Performs full backup of guest\n>> +        \"\"\"\n>> +        self.verify_guest_present(guest_name)\n>> +        self.verify_guest_running(guest_name)\n>> +        connection = QEMUMonitorProtocol(\n>> +                                         self.get_socket_address(\n>> +                                             self.config[guest_name]['qmp']))\n>> +        connection.connect()\n>> +        cmd = {\"execute\": \"transaction\", \"arguments\": {\"actions\": []}}\n>> +        drive_list = []\n>> +        for key in self.config[guest_name]:\n>> +            if key.startswith(\"drive_\"):\n>> +                drive = key[len('drive_'):]\n>> +                drive_list.append(drive)\n>> +                target = self.config[guest_name][key]\n>> +                sub_cmd = {\"type\": \"drive-backup\", \"data\": {\"device\": drive,\n>> +                                                            \"target\": target,\n>> +                                                            \"sync\": \"full\"}}\n>> +                cmd['arguments']['actions'].append(sub_cmd)\n>> +        qmp_return = connection.cmd_obj(cmd)\n>> +        if 'error' in qmp_return:\n>> +            print(qmp_return['error']['desc'], file=sys.stderr)\n>> +            sys.exit(1)\n>> +        print(\"Backup Started\")\n>> +        while drive_list:\n>> +            event = connection.pull_event(wait=True)\n>> +            if event['event'] == 'SHUTDOWN':\n>> +                print(\"The guest was SHUT DOWN\", file=sys.stderr)\n>> +                sys.exit(1)\n>> +\n>> +            if event['event'] == 'BLOCK_JOB_COMPLETED':\n>> +                if event['data']['device'] in drive_list and \\\n>> +                        event['data']['type'] == 'backup':\n>> +                        print(\"*\" + event['data']['device'])\n>> +                        drive_list.remove(event['data']['device'])\n>> +\n>> +            if event['event'] == 'BLOCK_JOB_ERROR':\n>> +                if event['data']['device'] in drive_list and \\\n>> +                        event['data']['type'] == 'backup':\n>> +                        print(event['data']['device'] + \" Backup Failed\",\n>> +                              file=sys.stderr)\n>> +                        drive_list.remove(event['data']['device'])\n>> +        print(\"Backup Complete\")\n>> +\n>> +    def _drive_add(self, drive_id, guest_name, target=None):\n>> +        \"\"\"\n>> +        Adds drive for backup\n>> +        \"\"\"\n>> +        if target is None:\n>> +            target = os.path.abspath(drive_id)\n>> +\n>> +        if os.path.isdir(os.path.dirname(target)) is False:\n>> +            print(\"Cannot find target directory\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +\n>> +        self.verify_guest_present(guest_name)\n>> +        if \"drive_\" + drive_id in self.config[guest_name]:\n>> +            print(\"Drive already marked for backup\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +\n>> +        self.verify_guest_running(guest_name)\n>> +\n>> +        connection = QEMUMonitorProtocol(\n>> +                                         self.get_socket_address(\n>> +                                             self.config[guest_name]['qmp']))\n>> +        connection.connect()\n>> +        cmd = {'execute': 'query-block'}\n>> +        returned_json = connection.cmd_obj(cmd)\n>> +        device_present = False\n>> +        for device in returned_json['return']:\n>> +            if device['device'] == drive_id:\n>> +                device_present = True\n>> +                break\n>> +\n>> +        if not device_present:\n>> +            print(\"No such drive in guest\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +\n>> +        drive_id = \"drive_\" + drive_id\n>> +        for d_id in self.config[guest_name]:\n>> +            if self.config[guest_name][d_id] == target:\n>> +                print(\"Please choose different target\", file=sys.stderr)\n>> +                sys.exit(1)\n>> +        self.config.set(guest_name, drive_id, target)\n>> +        self.write_config()\n>> +        print(\"Successfully Added Drive\")\n>> +\n>> +    def verify_guest_running(self, guest_name):\n>> +        \"\"\"\n>> +        Checks whether specified guest is running or not\n>> +        \"\"\"\n>> +        socket_address = self.config.get(guest_name, 'qmp')\n>> +        try:\n>> +            connection = QEMUMonitorProtocol(self.get_socket_address(\n>> +                                             socket_address))\n>> +            connection.connect()\n>> +        except socket_error:\n>> +            if socket_error.errno != errno.ECONNREFUSED:\n>> +                print(\"Connection to guest refused\", file=sys.stderr)\n>> +                sys.exit(1)\n>> +            print(\"Cannot connect to guest\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +\n>> +    def verify_guest_present(self, guest_name):\n>> +        \"\"\"\n>> +        Checks if guest is present in config file\n>> +        \"\"\"\n>> +        if guest_name == 'general':\n>> +            print(\"Cannot use \\'general\\' as guest name\")\n>> +            sys.exit(1)\n>> +        if guest_name not in self.config.sections():\n>> +            print(\"Guest Not present in config file\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +\n>> +    def _guest_add(self, guest_name, socket_address):\n>> +        \"\"\"\n>> +        Adds a guest to the config file\n>> +        \"\"\"\n>> +        if guest_name in self.config.sections():\n>> +            print(\"ID already exists. Please choose a different guest name\",\n>> +                  file=sys.stderr)\n>> +            sys.exit(1)\n>> +        if socket_address.split(':', 1)[0] != 'tcp' \\\n>> +                and socket_address.split(':', 1)[0] != 'unix':\n>> +            print(\"Invalid Socket\", file=sys.stderr)\n>> +            sys.exit(1)\n>> +        self.config[guest_name] = {'qmp': socket_address}\n>> +        self.verify_guest_running(guest_name)\n>> +        self.write_config()\n>> +        print(\"Successfully Added Guest\")\n>> +\n>> +    def _guest_remove(self, guest_name):\n>> +        \"\"\"\n>> +        Removes a guest from config file\n>> +        \"\"\"\n>> +        self.verify_guest_present(guest_name)\n>> +        self.config.remove_section(guest_name)\n>> +        print(\"Guest successfully deleted\")\n>> +\n>> +    def _restore(self, guest_name):\n>> +        \"\"\"\n>> +        Prints Steps to perform restore operation\n>> +        \"\"\"\n>> +        self.verify_guest_present(guest_name)\n>> +        self.verify_guest_running(guest_name)\n>> +        connection = QEMUMonitorProtocol(\n>> +                                         self.get_socket_address(\n>> +                                             self.config[guest_name]['qmp']))\n>> +        connection.connect()\n>> +        print(\"To perform restore:\")\n>> +        print(\"Shut down guest\")\n>> +        for key in self.config[guest_name]:\n>> +            if key.startswith(\"drive_\"):\n>> +                drive = key[len('drive_'):]\n>> +                target = self.config[guest_name][key]\n>> +                cmd = {'execute': 'query-block'}\n>> +                returned_json = connection.cmd_obj(cmd)\n>> +                device_present = False\n>> +                for device in returned_json['return']:\n>> +                    if device['device'] == drive:\n>> +                        device_present = True\n>> +                        location = device['inserted']['image']['filename']\n>> +                        print(\"qemu-img convert \" + target + \" \" + location)\n>> +\n>> +                if not device_present:\n>> +                    print(\"No such drive in guest\", file=sys.stderr)\n>> +                    sys.exit(1)\n>> +\n>> +    def guest_remove_wrapper(self, args):\n>> +        \"\"\"\n>> +        Wrapper for _guest_remove method.\n>> +        \"\"\"\n>> +        guest_name = args.guest\n>> +        self._guest_remove(guest_name)\n>> +        self.write_config()\n>> +\n>> +    def list(self, args):\n>> +        \"\"\"\n>> +        Prints guests present in Config file\n>> +        \"\"\"\n>> +        for guest_name in self.config.sections():\n>> +            if guest_name != 'general':\n>> +                print(guest_name)\n>> +\n> \n> jhuston@probe (review) ~/s/q/c/backup> ./qemu-backup.py guest add\n> --guest general --qmp tcp:localhost:4444\n> ID already exists. Please choose a different guest name\n> \n> The reason I suggested to try to namespace VMs was so that if you tried\n> to add a VM (that just so happened to be named general) that you\n> wouldn't get a confusing error message, because when we go to confirm\n> what VMs are here, it's not going to print \"general.\"\n> \n> In the actual grand scheme of things this isn't that important, but\n> you've created a bit of a \"dead space\" here arbitrarily where a certain\n> magical name is not available for use.\n> \n> Ah, well, consider it more of an educational consideration at this point.\n\nOkay. I understand now. I think this will be a better approach but will \nrequire a lot of restructuring the code. I will look into it in a separate\nrevision after the present code gets merged(if that is okay).\n\n>> +    def guest_add_wrapper(self, args):\n>> +        \"\"\"\n>> +        Wrapper for _guest_add method\n>> +        \"\"\"\n>> +        self._guest_add(args.guest, args.qmp)\n>> +\n>> +    def drive_add_wrapper(self, args):\n>> +        \"\"\"\n>> +        Wrapper for _drive_add method\n>> +        \"\"\"\n>> +        self._drive_add(args.id, args.guest, args.target)\n>> +\n>> +    def fullbackup_wrapper(self, args):\n>> +        \"\"\"\n>> +        Wrapper for _full_backup method\n>> +        \"\"\"\n>> +        self._full_backup(args.guest)\n>> +\n>> +    def restore_wrapper(self, args):\n>> +        \"\"\"\n>> +        Wrapper for restore\n>> +        \"\"\"\n>> +        self._restore(args.guest)\n>> +\n>> +\n>> +def build_parser():\n>> +    backup_tool = BackupTool()\n>> +    parser = ArgumentParser()\n>> +    subparsers = parser.add_subparsers(title='Subcommands',\n>> +                                       description='Valid Subcommands',\n>> +                                       help='Subcommand help')\n>> +    guest_parser = subparsers.add_parser('guest', help='Manage guest(s)')\n>> +    guest_subparsers = guest_parser.add_subparsers(title='Guest Subparser')\n>> +#   Guest list\n>> +    guest_list_parser = guest_subparsers.add_parser('list',\n>> +                                                    help='Lists all guests')\n>> +    guest_list_parser.set_defaults(func=backup_tool.list)\n>> +\n>> +#   Guest add\n>> +    guest_add_parser = guest_subparsers.add_parser('add', help='Adds a guest')\n>> +    guest_add_required = guest_add_parser.add_argument_group('Required \\\n>> +                                                                Arguments')\n>> +    guest_add_required.add_argument('--guest', action='store', type=str,\n>> +                                    help='Name of the guest', required=True)\n>> +    guest_add_required.add_argument('--qmp', action='store', type=str,\n>> +                                    help='Path of socket', required=True)\n>> +    guest_add_parser.set_defaults(func=backup_tool.guest_add_wrapper)\n>> +\n>> +#   Guest Remove\n>> +    guest_remove_parser = guest_subparsers.add_parser('remove',\n>> +                                                      help='Removes a guest')\n>> +    guest_remove_required = guest_remove_parser.add_argument_group('Required \\\n>> +                                                                    Arguments')\n>> +    guest_remove_required.add_argument('--guest', action='store', type=str,\n>> +                                       help='Name of the guest', required=True)\n>> +    guest_remove_parser.set_defaults(func=backup_tool.guest_remove_wrapper)\n>> +\n>> +    drive_parser = subparsers.add_parser('drive',\n>> +                                         help='Adds drive(s) for backup')\n>> +    drive_subparsers = drive_parser.add_subparsers(title='Add subparser',\n>> +                                                   description='Drive \\\n>> +                                                                subparser')\n>> +#   Drive Add\n>> +    drive_add_parser = drive_subparsers.add_parser('add',\n>> +                                                   help='Adds new \\\n>> +                                                         drive for backup')\n>> +    drive_add_required = drive_add_parser.add_argument_group('Required \\\n>> +                                                                Arguments')\n>> +    drive_add_required.add_argument('--guest', action='store', type=str,\n>> +                                    help='Name of the guest', required=True)\n>> +    drive_add_required.add_argument('--id', action='store',\n>> +                                    type=str, help='Drive ID', required=True)\n>> +    drive_add_parser.add_argument('--target', nargs='?',\n>> +                                  default=None, help='Destination path')\n>> +    drive_add_parser.set_defaults(func=backup_tool.drive_add_wrapper)\n>> +\n>> +    backup_parser = subparsers.add_parser('backup', help='Creates backup')\n>> +\n>> +#   Backup\n>> +    backup_parser_required = backup_parser.add_argument_group('Required \\\n>> +                                                                Arguments')\n>> +    backup_parser_required.add_argument('--guest', action='store', type=str,\n>> +                                        help='Name of the guest',\n>> +                                        required=True)\n>> +    backup_parser.set_defaults(func=backup_tool.fullbackup_wrapper)\n>> +\n>> +#   Restore\n>> +    restore_parser = subparsers.add_parser('restore', help='Restores drives')\n>> +    restore_parser_required = restore_parser.add_argument_group('Required \\\n>> +                                                                Arguments')\n>> +    restore_parser_required.add_argument('--guest', action='store',\n>> +                                         type=str, help='Name of the guest',\n>> +                                         required=True)\n>> +    restore_parser.set_defaults(func=backup_tool.restore_wrapper)\n>> +\n>> +    return parser\n>> +\n>> +\n>> +def main():\n>> +    parser = build_parser()\n>> +    args = parser.parse_args()\n>> +    args.func(args)\n>> +\n>> +if __name__ == '__main__':\n>> +    main()\n>> --\n>> 2.7.4\n>> \n> \n> Looks good. I'm happy giving it my R-B.\n> \n> Reviewed-by: John Snow <jsnow@redhat.com>","headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@bilbo.ozlabs.org","Authentication-Results":["ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=nongnu.org\n\t(client-ip=2001:4830:134:3::11; helo=lists.gnu.org;\n\tenvelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n\treceiver=<UNKNOWN>)","ozlabs.org; dkim=pass (1024-bit key;\n\tunprotected) header.d=research.iiit.ac.in\n\theader.i=@research.iiit.ac.in header.b=\"rdHNiJ9I\"; \n\tdkim-atps=neutral"],"Received":["from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11])\n\t(using TLSv1 with cipher AES256-SHA (256/256 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xwzJm1PDNz9s7m\n\tfor <incoming@patchwork.ozlabs.org>;\n\tTue, 19 Sep 2017 07:13:59 +1000 (AEST)","from localhost ([::1]:38943 helo=lists.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.71) (envelope-from\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>)\n\tid 1du3Ma-0007EK-Jv\n\tfor incoming@patchwork.ozlabs.org; Mon, 18 Sep 2017 17:13:56 -0400","from eggs.gnu.org ([2001:4830:134:3::10]:43279)\n\tby lists.gnu.org with esmtp (Exim 4.71)\n\t(envelope-from <chugh.ishani@research.iiit.ac.in>)\n\tid 1du3MB-0007E1-9k\n\tfor qemu-devel@nongnu.org; Mon, 18 Sep 2017 17:13:33 -0400","from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)\n\t(envelope-from <chugh.ishani@research.iiit.ac.in>)\n\tid 1du3M7-000290-1h\n\tfor qemu-devel@nongnu.org; Mon, 18 Sep 2017 17:13:31 -0400","from research.iiit.ac.in ([196.12.53.8]:44326)\n\tby eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32)\n\t(Exim 4.71) (envelope-from <chugh.ishani@research.iiit.ac.in>)\n\tid 1du3M6-00025f-31\n\tfor qemu-devel@nongnu.org; Mon, 18 Sep 2017 17:13:26 -0400","from localhost (localhost [127.0.0.1])\n\tby research.iiit.ac.in (Postfix) with ESMTP id 0CD61741AB0;\n\tTue, 19 Sep 2017 02:43:20 +0530 (IST)","from research.iiit.ac.in ([127.0.0.1])\n\tby localhost (research.iiit.ac.in [127.0.0.1]) (amavisd-new,\n\tport 10032)\n\twith ESMTP id xOAGmax7Pqcg; Tue, 19 Sep 2017 02:43:15 +0530 (IST)","from localhost (localhost [127.0.0.1])\n\tby research.iiit.ac.in (Postfix) with ESMTP id 83013741B0A;\n\tTue, 19 Sep 2017 02:43:15 +0530 (IST)","from research.iiit.ac.in ([127.0.0.1])\n\tby localhost (research.iiit.ac.in [127.0.0.1]) (amavisd-new,\n\tport 10026)\n\twith ESMTP id u9qitFg8gN05; Tue, 19 Sep 2017 02:43:15 +0530 (IST)","from research.iiit.ac.in (localhost [127.0.0.1])\n\tby research.iiit.ac.in (Postfix) with ESMTP id 66ADE741AB0;\n\tTue, 19 Sep 2017 02:43:15 +0530 (IST)"],"DKIM-Filter":"OpenDKIM Filter v2.9.2 research.iiit.ac.in 83013741B0A","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=research.iiit.ac.in; \n\ts=4E8815E6-5B55-11E4-B758-8D4964374E96; t=1505769195;\n\tbh=vvsggEG9+313jJStE+8XW69hzhuRNtI72GUn8JDaPrw=;\n\th=Date:From:To:Message-ID:Subject:MIME-Version:Content-Type:\n\tContent-Transfer-Encoding;\n\tb=rdHNiJ9IADT5lmFWXgrLI+KuoAc2vpdzF5uUMTangZX8VFm/8fV2btYU71Y3F+/d3\n\tTKHsBrrC/SSVZiXLQIpUhSduDwUZZYlKdD64IL0L+BEct1ylsPJ501AQQp1KxufkO+\n\t+nJuWCe8Rjd/tD453xOhUv1RSGaz/Hkp1F1EsIRY=","X-Virus-Scanned":"amavisd-new at research.iiit.ac.in","Date":"Tue, 19 Sep 2017 02:43:15 +0530 (IST)","From":"Ishani <chugh.ishani@research.iiit.ac.in>","To":"jsnow <jsnow@redhat.com>","Message-ID":"<1896814790.665667.1505769195286.JavaMail.zimbra@research.iiit.ac.in>","In-Reply-To":"<83bba737-1630-d739-6d72-7c87fdd7fe7b@redhat.com>","References":"<1504888905-22396-1-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<1504888905-22396-3-git-send-email-chugh.ishani@research.iiit.ac.in>\n\t<83bba737-1630-d739-6d72-7c87fdd7fe7b@redhat.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"7bit","X-Originating-IP":"[10.2.24.155]","X-Mailer":"Zimbra 8.6.0_GA_1194 (ZimbraWebClient - FF55 (Linux)/8.6.0_GA_1194)","Thread-Topic":"backup: Adds Backup Tool","Thread-Index":"WHDXoqDYjTzQigcRnZ1T6ZnvQc8/og==","X-detected-operating-system":"by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic]\n\t[fuzzy]","X-Received-From":"196.12.53.8","Subject":"Re: [Qemu-devel] [PATCH v4 2/3] backup: Adds Backup Tool","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.21","Precedence":"list","List-Id":"<qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<http://lists.nongnu.org/archive/html/qemu-devel/>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n\t<mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Cc":"famz@redhat.com, qemu-devel@nongnu.org, stefanha <stefanha@redhat.com>","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"\"Qemu-devel\"\n\t<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>"}}]