#!/usr/bin/env python3
# group: rw quick
#
# Test what happens when a stream job completes in a blk_drain().
#
# Copyright (C) 2022 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import os
import iotests
from iotests import imgfmt, qemu_img_create, qemu_io, QMPTestCase


image_size = 1 * 1024 * 1024
data_size = 64 * 1024
base = os.path.join(iotests.test_dir, 'base.img')
top = os.path.join(iotests.test_dir, 'top.img')


# We want to test completing a stream job in a blk_drain().
#
# The blk_drain() we are going to use is a virtio-scsi device resetting,
# which we can trigger by resetting the system.
#
# In order to have the block job complete on drain, we (1) throttle its
# base image so we can start the drain after it has begun, but before it
# completes, and (2) make it encounter an I/O error on the ensuing write.
# (If it completes regularly, the completion happens after the drain for
# some reason.)

class TestStreamErrorOnReset(QMPTestCase):
    def setUp(self) -> None:
        """
        Create two images:
        - base image {base} with {data_size} bytes allocated
        - top image {top} without any data allocated

        And the following VM configuration:
        - base image throttled to {data_size}
        - top image with a blkdebug configuration so the first write access
          to it will result in an error
        - top image is attached to a virtio-scsi device
        """
        qemu_img_create('-f', imgfmt, base, str(image_size))
        qemu_io('-c', f'write 0 {data_size}', base)
        qemu_img_create('-f', imgfmt, top, str(image_size))

        self.vm = iotests.VM()
        self.vm.add_args('-accel', 'tcg') # Make throttling work properly
        self.vm.add_object(self.vm.qmp_to_opts({
            'qom-type': 'throttle-group',
            'id': 'thrgr',
            'x-bps-total': str(data_size)
        }))
        self.vm.add_blockdev(self.vm.qmp_to_opts({
            'driver': imgfmt,
            'node-name': 'base',
            'file': {
                'driver': 'throttle',
                'throttle-group': 'thrgr',
                'file': {
                    'driver': 'file',
                    'filename': base
                }
            }
        }))
        self.vm.add_blockdev(self.vm.qmp_to_opts({
            'driver': imgfmt,
            'node-name': 'top',
            'file': {
                'driver': 'blkdebug',
                'node-name': 'top-blkdebug',
                'inject-error': [{
                    'event': 'pwritev',
                    'immediately': 'true',
                    'once': 'true'
                }],
                'image': {
                    'driver': 'file',
                    'filename': top
                }
            },
            'backing': 'base'
        }))
        self.vm.add_device(self.vm.qmp_to_opts({
            'driver': 'virtio-scsi',
            'id': 'vscsi'
        }))
        self.vm.add_device(self.vm.qmp_to_opts({
            'driver': 'scsi-hd',
            'bus': 'vscsi.0',
            'drive': 'top'
        }))
        self.vm.launch()

    def tearDown(self) -> None:
        self.vm.shutdown()
        os.remove(top)
        os.remove(base)

    def test_stream_error_on_reset(self) -> None:
        # Launch a stream job, which will take at least a second to
        # complete, because the base image is throttled (so we can
        # get in between it having started and it having completed)
        self.vm.cmd('block-stream', job_id='stream', device='top')

        while True:
            ev = self.vm.event_wait('JOB_STATUS_CHANGE')
            if ev['data']['status'] == 'running':
                # Once the stream job is running, reset the system, which
                # forces the virtio-scsi device to be reset, thus draining
                # the stream job, and making it complete.  Completing
                # inside of that drain should not result in a segfault.
                self.vm.cmd('system_reset')
            elif ev['data']['status'] == 'null':
                # The test is done once the job is gone
                break


if __name__ == '__main__':
    # Passes with any format with backing file support, but qed and
    # qcow1 do not seem to exercise the used-to-be problematic code
    # path, so there is no point in having them in this list
    iotests.main(supported_fmts=['qcow2', 'vmdk'],
                 supported_protocols=['file'])
