From patchwork Wed Feb 26 00:44:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Snow X-Patchwork-Id: 1244588 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=fuyQHhD0; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48Rxsn3G4yz9sPK for ; Wed, 26 Feb 2020 11:45:17 +1100 (AEDT) Received: from localhost ([::1]:36502 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j6kp9-0005Gj-DE for incoming@patchwork.ozlabs.org; Tue, 25 Feb 2020 19:45:15 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:46606) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j6koS-0004zE-Ia for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:34 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j6koQ-0003aL-NK for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:32 -0500 Received: from us-smtp-delivery-1.mimecast.com ([205.139.110.120]:53498 helo=us-smtp-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1j6koQ-0003ZS-Eo for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:30 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1582677870; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=IUFlbAeNNCdo97bP+hIQ1Xb7c3PXEcLn7/CnGvCsdqI=; b=fuyQHhD0tJ0VfiUu9E2A388Z9+LjRmRVH5IzrAlZ+XrCM9qv9O/N34d0qh6xm1pMxOh3FA PaXrP/nf2KY9p3M7HjkG6oPXteaM6gA5dQYnZiC2RSJiUTFtDT0oXaEomLjfMV2oh4oXS1 /RTyCgsoP1mKNqwfrB5Ktib5mQiF/Lc= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-358-EKDFS2o0MsOVjTuqgSLqig-1; Tue, 25 Feb 2020 19:44:28 -0500 X-MC-Unique: EKDFS2o0MsOVjTuqgSLqig-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 5445318FF661; Wed, 26 Feb 2020 00:44:27 +0000 (UTC) Received: from probe.bos.redhat.com (dhcp-17-182.bos.redhat.com [10.18.17.182]) by smtp.corp.redhat.com (Postfix) with ESMTP id AB3628AC42; Wed, 26 Feb 2020 00:44:26 +0000 (UTC) From: John Snow To: qemu-devel@nongnu.org Subject: [PATCH 1/2] iotests: add JobRunner class Date: Tue, 25 Feb 2020 19:44:24 -0500 Message-Id: <20200226004425.1303-2-jsnow@redhat.com> In-Reply-To: <20200226004425.1303-1-jsnow@redhat.com> References: <20200226004425.1303-1-jsnow@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 205.139.110.120 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , John Snow , qemu-block@nongnu.org, Max Reitz Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" The idea is that instead of increasing the arguments to job_run all the time, create a more general-purpose job runner that can be subclassed to do interesting things with. Signed-off-by: John Snow --- tests/qemu-iotests/255 | 9 +- tests/qemu-iotests/257 | 12 ++- tests/qemu-iotests/287 | 19 +++- tests/qemu-iotests/iotests.py | 176 ++++++++++++++++++++++++---------- 4 files changed, 158 insertions(+), 58 deletions(-) diff --git a/tests/qemu-iotests/255 b/tests/qemu-iotests/255 index 4a4818bafb..513e9ebb58 100755 --- a/tests/qemu-iotests/255 +++ b/tests/qemu-iotests/255 @@ -71,8 +71,13 @@ with iotests.FilePath('t.qcow2') as disk_path, \ result = vm.qmp_log('block-commit', job_id='job0', auto_finalize=False, device='overlay', top_node='mid') - vm.run_job('job0', auto_finalize=False, pre_finalize=start_requests, - auto_dismiss=True) + class TestJobRunner(iotests.JobRunner): + def on_pending(self, event): + start_requests() + super().on_pending(event) + + runner = TestJobRunner(vm, 'job0', auto_finalize=False, auto_dismiss=True) + runner.run() vm.shutdown() diff --git a/tests/qemu-iotests/257 b/tests/qemu-iotests/257 index 2a81f9e30c..e73b0c20b3 100755 --- a/tests/qemu-iotests/257 +++ b/tests/qemu-iotests/257 @@ -265,9 +265,15 @@ def test_bitmap_sync(bsync_mode, msync_mode='bitmap', failure=None): ebitmap.clear() ebitmap.dirty_group(2) - vm.run_job(job, auto_dismiss=True, auto_finalize=False, - pre_finalize=_callback, - cancel=(failure == 'simulated')) + class TestJobRunner(iotests.JobRunner): + def on_pending(self, event): + _callback() + super().on_pending(event) + + runner = TestJobRunner(vm, job, cancel=(failure == 'simulated'), + auto_finalize=False, auto_dismiss=True) + runner.run() + bitmaps = vm.query_bitmaps() log({'bitmaps': bitmaps}, indent=2) log('') diff --git a/tests/qemu-iotests/287 b/tests/qemu-iotests/287 index 0ab58dc011..f06e6ff084 100755 --- a/tests/qemu-iotests/287 +++ b/tests/qemu-iotests/287 @@ -165,13 +165,22 @@ def test_bitmap_populate(config): if not config.disabled: ebitmap.dirty_group(2) + + class TestJobRunner(iotests.JobRunner): + def on_pending(self, event): + if config.mid_writes: + perform_writes(drive0, 2) + if not config.disabled: + ebitmap.dirty_group(2) + super().on_pending(event) + job = populate(drive0, 'target', 'bitpop0') assert job['return'] == {'return': {}} - vm.run_job(job['id'], - auto_dismiss=job['auto-dismiss'], - auto_finalize=job['auto-finalize'], - pre_finalize=pre_finalize, - cancel=config.cancel) + job_runner = TestJobRunner(vm, job['id'], + auto_dismiss=job['auto-dismiss'], + auto_finalize=job['auto-finalize'], + cancel=config.cancel) + job_runner.run() log('') diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 3390fab021..37a8b4d649 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -460,6 +460,130 @@ def remote_filename(path): else: raise Exception("Protocol %s not supported" % (imgproto)) + +class JobRunner: + def __init__(self, vm, job, + use_log=True, + cancel=False, + auto_finalize=True, + auto_dismiss=False): + self._vm = vm + self._id = job + self.logging = use_log + self.cancel = cancel + + self._auto_finalize = auto_finalize + self._auto_dismiss = auto_dismiss + self._exited = False + self._error = None + + match_device = {'data': {'device': job}} + match_id = {'data': {'id': job}} + self._events = { + 'BLOCK_JOB_COMPLETED': match_device, + 'BLOCK_JOB_CANCELLED': match_device, + 'BLOCK_JOB_ERROR': match_device, + 'BLOCK_JOB_READY': match_device, + 'BLOCK_JOB_PENDING': match_id, + 'JOB_STATUS_CHANGE': match_id + } + + self._dispatch = { + 'created': self.on_create, + 'running': self.on_run, + 'paused': self.on_pause, + 'ready': self.on_ready, + 'standby': self.on_standby, + 'waiting': self.on_waiting, + 'pending': self.on_pending, + 'aborting': self.on_abort, + 'concluded': self.on_conclude, + 'null': self.on_null, + } + + # Job events -- state changes. + + def on_create(self, event): + pass + + def on_run(self, event): + pass + + def on_pause(self, event): + pass + + def on_ready(self, event): + if self.logging: + self._vm.qmp_log('job-complete', id=self._id) + else: + self._vm.qmp('job-complete', id=self._id) + + def on_standby(self, event): + pass + + def on_waiting(self, event): + pass + + def on_pending(self, event): + if self._auto_finalize: + return + + if self.cancel: + if self.logging: + self._vm.qmp_log('job-cancel', id=self._id) + else: + self._vm.qmp('job-cancel', id=self._id) + else: + if self.logging: + self._vm.qmp_log('job-finalize', id=self._id) + else: + self._vm.qmp('job-finalize', id=self._id) + + def on_abort(self, event): + result = self._vm.qmp('query-jobs') + for j in result['return']: + if j['id'] == self._id: + self.error = j['error'] + if self.logging: + log('Job failed: %s' % (j['error'])) + + def on_conclude(self, event): + if self._auto_dismiss: + return + + if self.logging: + self._vm.qmp_log('job-dismiss', id=self._id) + else: + self._vm.qmp('job-dismiss', id=self._id) + + def on_null(self, event): + self._exited = True + + # Macro events -- QAPI events + + def on_change(self, event): + status = event['data']['status'] + assert status in self._dispatch + self._dispatch[status](event) + + def on_block_job_event(self, event): + if self.logging: + log(event) + + def _event(self, event): + assert event['event'] in self._events.keys() + if event['event'] == 'JOB_STATUS_CHANGE': + self.on_change(event) + else: + self.on_block_job_event(event) + + def run(self, wait=60.0): + while not self._exited: + raw_event = self._vm.events_wait(self._events, timeout=wait) + self._event(filter_qmp_event(raw_event)) + return self._error + + class VM(qtest.QEMUQtestMachine): '''A QEMU VM''' @@ -585,7 +709,7 @@ def qmp_log(self, cmd, filters=[], indent=None, **kwargs): # Returns None on success, and an error string on failure def run_job(self, job, auto_finalize=True, auto_dismiss=False, - pre_finalize=None, cancel=False, use_log=True, wait=60.0): + cancel=False, use_log=True, wait=60.0): """ run_job moves a job from creation through to dismissal. @@ -594,59 +718,15 @@ def run_job(self, job, auto_finalize=True, auto_dismiss=False, auto_finalize. Defaults to True. :param auto_dismiss: Bool. True if the job was launched with auto_dismiss=True. Defaults to False. - :param pre_finalize: Callback. A callable that takes no arguments to be - invoked prior to issuing job-finalize, if any. :param cancel: Bool. When true, cancels the job after the pre_finalize callback. :param use_log: Bool. When false, does not log QMP messages. :param wait: Float. Timeout value specifying how long to wait for any event, in seconds. Defaults to 60.0. """ - match_device = {'data': {'device': job}} - match_id = {'data': {'id': job}} - events = { - 'BLOCK_JOB_COMPLETED': match_device, - 'BLOCK_JOB_CANCELLED': match_device, - 'BLOCK_JOB_ERROR': match_device, - 'BLOCK_JOB_READY': match_device, - 'BLOCK_JOB_PENDING': match_id, - 'JOB_STATUS_CHANGE': match_id, - } - error = None - while True: - ev = filter_qmp_event(self.events_wait(events, timeout=wait)) - if ev['event'] != 'JOB_STATUS_CHANGE': - if use_log: - log(ev) - continue - status = ev['data']['status'] - if status == 'aborting': - result = self.qmp('query-jobs') - for j in result['return']: - if j['id'] == job: - error = j['error'] - if use_log: - log('Job failed: %s' % (j['error'])) - elif status == 'ready': - self.qmp_log('job-complete', id=job) - elif status == 'pending' and not auto_finalize: - if pre_finalize: - pre_finalize() - if cancel and use_log: - self.qmp_log('job-cancel', id=job) - elif cancel: - self.qmp('job-cancel', id=job) - elif use_log: - self.qmp_log('job-finalize', id=job) - else: - self.qmp('job-finalize', id=job) - elif status == 'concluded' and not auto_dismiss: - if use_log: - self.qmp_log('job-dismiss', id=job) - else: - self.qmp('job-dismiss', id=job) - elif status == 'null': - return error + job_runner = JobRunner(self, job, use_log, cancel, + auto_finalize, auto_dismiss) + return job_runner.run(wait=wait) # Returns None on success, and an error string on failure def blockdev_create(self, options, job_id='job0', filters=None): From patchwork Wed Feb 26 00:44:25 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Snow X-Patchwork-Id: 1244589 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=OZNSZ4Zd; dkim-atps=neutral Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 48Rxsp188kz9sRJ for ; Wed, 26 Feb 2020 11:45:18 +1100 (AEDT) Received: from localhost ([::1]:36504 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j6kpA-0005I0-2R for incoming@patchwork.ozlabs.org; Tue, 25 Feb 2020 19:45:16 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:46825) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j6koZ-000557-9Y for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:40 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j6koY-0003v7-6h for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:39 -0500 Received: from us-smtp-2.mimecast.com ([205.139.110.61]:47296 helo=us-smtp-delivery-1.mimecast.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1j6koY-0003tT-3R for qemu-devel@nongnu.org; Tue, 25 Feb 2020 19:44:38 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1582677877; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=sMtK6Wcuc47GOtf9RLanCByUeI3Pg0Njk7/8ydfywak=; b=OZNSZ4Zd6TgA2SPaL5R/kx8bCUAmGOmMfma86YMBwViTOc5vrSdUuJRAi3yT2HN+xftkdl E9y1DnqNd8tPDiQougcUpX/Sw/Sn342Rf3KRnMnApFDVUa8d2dldFUP0xEL9iyPKPzAF/x dis9YjgDPhv3UfpQSRFtFP5hALUsTxk= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-124--pQGCbREPoWvqUekZo_zWw-1; Tue, 25 Feb 2020 19:44:29 -0500 X-MC-Unique: -pQGCbREPoWvqUekZo_zWw-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 218A8800D5B; Wed, 26 Feb 2020 00:44:28 +0000 (UTC) Received: from probe.bos.redhat.com (dhcp-17-182.bos.redhat.com [10.18.17.182]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7AE1E8AC42; Wed, 26 Feb 2020 00:44:27 +0000 (UTC) From: John Snow To: qemu-devel@nongnu.org Subject: [PATCH 2/2] iotests: modify test 040 to use JobRunner Date: Tue, 25 Feb 2020 19:44:25 -0500 Message-Id: <20200226004425.1303-3-jsnow@redhat.com> In-Reply-To: <20200226004425.1303-1-jsnow@redhat.com> References: <20200226004425.1303-1-jsnow@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 205.139.110.61 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Kevin Wolf , John Snow , qemu-block@nongnu.org, Max Reitz Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Instead of having somewhat reproduced it for itself. Signed-off-by: John Snow --- tests/qemu-iotests/040 | 51 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 index 90b59081ff..579dafc797 100755 --- a/tests/qemu-iotests/040 +++ b/tests/qemu-iotests/040 @@ -483,34 +483,33 @@ class TestErrorHandling(iotests.QMPTestCase): file=('top-dbg' if top_debug else 'top-file'), backing='mid-fmt') + + class TestJobRunner(iotests.JobRunner): + expected_events = ('BLOCK_JOB_COMPLETED', + 'BLOCK_JOB_ERROR', + 'BLOCK_JOB_READY') + + def __init__(self, *args, test, **kwargs): + super().__init__(*args, **kwargs) + self.log = [] + self.test = test + + def on_pause(self, event): + result = self._vm.qmp('block-job-resume', device=self._id) + self.test.assert_qmp(result, 'return', {}) + super().on_pause(event) + + def on_block_job_event(self, event): + if event['event'] not in self.expected_events: + self.test.fail("Unexpected event: %s" % event) + super().on_block_job_event(event) + self.log.append(iotests.filter_qmp_event(event)) + def run_job(self, expected_events, error_pauses_job=False): - match_device = {'data': {'device': 'job0'}} - events = { - 'BLOCK_JOB_COMPLETED': match_device, - 'BLOCK_JOB_CANCELLED': match_device, - 'BLOCK_JOB_ERROR': match_device, - 'BLOCK_JOB_READY': match_device, - } - - completed = False - log = [] - while not completed: - ev = self.vm.events_wait(events, timeout=5.0) - if ev['event'] == 'BLOCK_JOB_COMPLETED': - completed = True - elif ev['event'] == 'BLOCK_JOB_ERROR': - if error_pauses_job: - result = self.vm.qmp('block-job-resume', device='job0') - self.assert_qmp(result, 'return', {}) - elif ev['event'] == 'BLOCK_JOB_READY': - result = self.vm.qmp('block-job-complete', device='job0') - self.assert_qmp(result, 'return', {}) - else: - self.fail("Unexpected event: %s" % ev) - log.append(iotests.filter_qmp_event(ev)) - + job = self.TestJobRunner(self.vm, 'job0', use_log=False, test=self) + job.run() self.maxDiff = None - self.assertEqual(expected_events, log) + self.assertEqual(expected_events, job.log) def event_error(self, op, action): return {