From 79d4b7162af292f037c3a504a8a1314ec4251f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 30 Oct 2020 15:39:57 +0100 Subject: [PATCH 1/9] tests: fail the test early if VM fails to start Make an exception in vm.start() actually interrupt the test. The asyncio.wait() returns list of completed tasks, where exception may be stored - but is not raised directly. Change to asyncio.gather() that will propagate the exception by default. As a side effect, avoid deprecated direct coroutine passing to asyncio.wait(). This functionality in asyncio.gather() is not deprecated. --- qubes/tests/integ/basic.py | 8 +++--- qubes/tests/integ/mime.py | 4 +-- qubes/tests/integ/network.py | 4 +-- qubes/tests/integ/network_ipv6.py | 4 +-- qubes/tests/integ/qrexec.py | 40 +++++++++++++++--------------- qubes/tests/integ/vm_qrexec_gui.py | 24 +++++++++--------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/qubes/tests/integ/basic.py b/qubes/tests/integ/basic.py index 39fb9b61..31a9d10f 100644 --- a/qubes/tests/integ/basic.py +++ b/qubes/tests/integ/basic.py @@ -580,12 +580,12 @@ class TC_30_Gui_daemon(qubes.tests.SystemTestCase): self.loop.run_until_complete(testvm2.create_on_disk()) self.app.save() - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( testvm1.start(), - testvm2.start()])) - self.loop.run_until_complete(asyncio.wait([ + testvm2.start())) + self.loop.run_until_complete(asyncio.gather( self.wait_for_session(testvm1), - self.wait_for_session(testvm2)])) + self.wait_for_session(testvm2))) window_title = 'user@{}'.format(testvm1.name) self.loop.run_until_complete(testvm1.run( 'zenity --text-info --editable --title={}'.format(window_title))) diff --git a/qubes/tests/integ/mime.py b/qubes/tests/integ/mime.py index 2dbbfc8b..c66c9b70 100644 --- a/qubes/tests/integ/mime.py +++ b/qubes/tests/integ/mime.py @@ -65,9 +65,9 @@ class TC_50_MimeHandlers: self.target_vm.template_for_dispvms = True self.source_vm.default_dispvm = self.target_vm - done, not_done = self.loop.run_until_complete(asyncio.wait([ + done, not_done = self.loop.run_until_complete(asyncio.gather( self.source_vm.start(), - self.target_vm.start()])) + self.target_vm.start())) for result in itertools.chain(done, not_done): # catch any exceptions result.result() diff --git a/qubes/tests/integ/network.py b/qubes/tests/integ/network.py index f9c28bc9..45918adf 100644 --- a/qubes/tests/integ/network.py +++ b/qubes/tests/integ/network.py @@ -350,9 +350,9 @@ class VmNetworkingMixin(object): self.testvm2.netvm = self.proxy self.app.save() - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_cmd.format(target=self.testvm2.ip)), 0) diff --git a/qubes/tests/integ/network_ipv6.py b/qubes/tests/integ/network_ipv6.py index 702e9e64..51a0783a 100644 --- a/qubes/tests/integ/network_ipv6.py +++ b/qubes/tests/integ/network_ipv6.py @@ -307,9 +307,9 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.testvm2.netvm = self.proxy self.app.save() - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_cmd.format(target=self.testvm2.ip6)), 0) diff --git a/qubes/tests/integ/qrexec.py b/qubes/tests/integ/qrexec.py index 2776e3de..dc7f0201 100644 --- a/qubes/tests/integ/qrexec.py +++ b/qubes/tests/integ/qrexec.py @@ -123,12 +123,12 @@ class TC_00_QrexecMixin(object): def test_052_qrexec_vm_service_eof(self): """Test for EOF transmission VM(src)->VM(dst)""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) - self.loop.run_until_complete(asyncio.wait([ + self.testvm2.start())) + self.loop.run_until_complete(asyncio.gather( self.wait_for_session(self.testvm1), - self.wait_for_session(self.testvm2)])) + self.wait_for_session(self.testvm2))) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.EOF', '#!/bin/sh\n/bin/cat\n') @@ -153,9 +153,9 @@ class TC_00_QrexecMixin(object): def test_053_qrexec_vm_service_eof_reverse(self): """Test for EOF transmission VM(src)<-VM(dst)""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.EOF', '#!/bin/sh\n' 'echo test; exec >&-; cat >/dev/null') @@ -219,9 +219,9 @@ class TC_00_QrexecMixin(object): self.assertEqual(e.exception.returncode, 3) def test_065_qrexec_exit_code_vm(self): - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) with self.qrexec_policy('test.Retcode', self.testvm1, self.testvm2): self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Retcode', @@ -254,9 +254,9 @@ class TC_00_QrexecMixin(object): handling anything else. """ - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.write', '''\ # first write a lot of data @@ -381,9 +381,9 @@ class TC_00_QrexecMixin(object): def test_080_qrexec_service_argument_allow_default(self): """Qrexec service call with argument""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument', '/usr/bin/printf %s "$1"') @@ -397,9 +397,9 @@ class TC_00_QrexecMixin(object): def test_081_qrexec_service_argument_allow_specific(self): """Qrexec service call with argument - allow only specific value""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument', '/usr/bin/printf %s "$1"') @@ -416,9 +416,9 @@ class TC_00_QrexecMixin(object): def test_082_qrexec_service_argument_deny_specific(self): """Qrexec service call with argument - deny specific value""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument', '/usr/bin/printf %s "$1"') @@ -436,9 +436,9 @@ class TC_00_QrexecMixin(object): def test_083_qrexec_service_argument_specific_implementation(self): """Qrexec service call with argument - argument specific implementatation""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument', @@ -457,9 +457,9 @@ class TC_00_QrexecMixin(object): def test_084_qrexec_service_argument_extra_env(self): """Qrexec service call with argument - extra env variables""" - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument', '/usr/bin/printf "%s %s" ' diff --git a/qubes/tests/integ/vm_qrexec_gui.py b/qubes/tests/integ/vm_qrexec_gui.py index 4d20ffb5..e321447b 100644 --- a/qubes/tests/integ/vm_qrexec_gui.py +++ b/qubes/tests/integ/vm_qrexec_gui.py @@ -166,9 +166,9 @@ class TC_00_AppVMMixin(object): self.wait_for_window(title, show=False) def test_100_qrexec_filecopy(self): - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.loop.run_until_complete(self.testvm1.run_for_stdio( 'cp /etc/passwd /tmp/passwd')) @@ -195,9 +195,9 @@ class TC_00_AppVMMixin(object): self.fail('source file got removed') def test_105_qrexec_filemove(self): - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.loop.run_until_complete(self.testvm1.run_for_stdio( 'cp /etc/passwd /tmp/passwd')) @@ -254,9 +254,9 @@ class TC_00_AppVMMixin(object): self.fail('source file got removed') def test_110_qrexec_filecopy_deny(self): - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2, allow=False): @@ -275,9 +275,9 @@ class TC_00_AppVMMixin(object): # The operation should not hang when qrexec-agent is down on target # machine, see QubesOS/qubes-issues#5347. - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2): try: @@ -318,9 +318,9 @@ class TC_00_AppVMMixin(object): @unittest.skipUnless(spawn.find_executable('xdotool'), "xdotool not installed") def test_130_qrexec_filemove_disk_full(self): - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start()])) + self.testvm2.start())) self.loop.run_until_complete(self.wait_for_session(self.testvm1)) @@ -373,9 +373,9 @@ class TC_00_AppVMMixin(object): """Test time synchronization mechanism""" if self.template.startswith('whonix-'): self.skipTest('qvm-sync-clock disabled for Whonix VMs') - self.loop.run_until_complete(asyncio.wait([ + self.loop.run_until_complete(asyncio.gather( self.testvm1.start(), - self.testvm2.start(),])) + self.testvm2.start())) start_time = subprocess.check_output(['date', '-u', '+%s']) try: From 14c636469f0407d43534720eed55baa4bc24fd5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 30 Oct 2020 21:09:47 +0100 Subject: [PATCH 2/9] tests: make sure dnsmasq is stopped before starting it again Avoid conflict on listening port ("Address already in use" error). Send SIGTERM until all instances of dnsmasq exit. --- qubes/tests/integ/network.py | 2 +- qubes/tests/integ/network_ipv6.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qubes/tests/integ/network.py b/qubes/tests/integ/network.py index 45918adf..263472c6 100644 --- a/qubes/tests/integ/network.py +++ b/qubes/tests/integ/network.py @@ -141,7 +141,7 @@ class VmNetworkingMixin(object): run_netvm_cmd("iptables -I INPUT -d {} -j ACCEPT --wait".format( self.test_ip)) # ignore failure - self.run_cmd(self.testnetvm, "pkill dnsmasq") + self.run_cmd(self.testnetvm, "while pkill dnsmasq; do sleep 1; done") run_netvm_cmd("dnsmasq -a {ip} -A /{name}/{ip} -i test0 -z".format( ip=self.test_ip, name=self.test_name)) run_netvm_cmd("echo nameserver {} > /etc/resolv.conf".format( diff --git a/qubes/tests/integ/network_ipv6.py b/qubes/tests/integ/network_ipv6.py index 51a0783a..cc3422b0 100644 --- a/qubes/tests/integ/network_ipv6.py +++ b/qubes/tests/integ/network_ipv6.py @@ -75,7 +75,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): run_netvm_cmd( "ip6tables -I INPUT -d {} -j ACCEPT".format(self.test_ip6)) # ignore failure - self.run_cmd(self.testnetvm, "pkill dnsmasq") + self.run_cmd(self.testnetvm, "while pkill dnsmasq; do sleep 1; done") run_netvm_cmd( "dnsmasq -a {ip} -A /{name}/{ip} -A /{name}/{ip6} -i test0 -z". format(ip=self.test_ip, ip6=self.test_ip6, name=self.test_name)) From 2b49979c2ad806fd31a6266c51777e77b2f08cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 2 Nov 2020 01:42:28 +0100 Subject: [PATCH 3/9] tests: remove test_030_clone Since qubesd-side clone_vm function is gone, it doesn't make sense to test it. For some time already this tested only if manual step-by-step clone implemented _in the test itself_ was done correctly. Actual cloning is part of qubes-core-admin-client and is tested there. --- qubes/tests/integ/basic.py | 106 ------------------------------------- 1 file changed, 106 deletions(-) diff --git a/qubes/tests/integ/basic.py b/qubes/tests/integ/basic.py index 31a9d10f..28463d3c 100644 --- a/qubes/tests/integ/basic.py +++ b/qubes/tests/integ/basic.py @@ -370,112 +370,6 @@ class TC_01_Properties(qubes.tests.SystemTestCase): def cleanup_props(self): del self.vm - def test_030_clone(self): - try: - testvm1 = self.app.add_new_vm( - qubes.vm.appvm.AppVM, - name=self.make_vm_name("vm"), - template=self.app.default_template, - label='red') - self.loop.run_until_complete(testvm1.create_on_disk()) - testvm2 = self.app.add_new_vm(testvm1.__class__, - name=self.make_vm_name("clone"), - template=testvm1.template, - label='red') - testvm2.clone_properties(testvm1) - testvm2.firewall.clone(testvm1.firewall) - self.loop.run_until_complete(testvm2.clone_disk_files(testvm1)) - self.assertTrue(self.loop.run_until_complete(testvm1.storage.verify())) - self.assertIn('source', testvm1.volumes['root'].config) - self.assertNotEquals(testvm2, None) - self.assertNotEquals(testvm2.volumes, {}) - self.assertIn('source', testvm2.volumes['root'].config) - - # qubes.xml reload - self.app.save() - testvm1 = self.app.domains[testvm1.qid] - testvm2 = self.app.domains[testvm2.qid] - - self.assertEqual(testvm1.label, testvm2.label) - self.assertEqual(testvm1.netvm, testvm2.netvm) - self.assertEqual(testvm1.property_is_default('netvm'), - testvm2.property_is_default('netvm')) - self.assertEqual(testvm1.kernel, testvm2.kernel) - self.assertEqual(testvm1.kernelopts, testvm2.kernelopts) - self.assertEqual(testvm1.property_is_default('kernel'), - testvm2.property_is_default('kernel')) - self.assertEqual(testvm1.property_is_default('kernelopts'), - testvm2.property_is_default('kernelopts')) - self.assertEqual(testvm1.memory, testvm2.memory) - self.assertEqual(testvm1.maxmem, testvm2.maxmem) - self.assertEqual(testvm1.devices, testvm2.devices) - self.assertEqual(testvm1.include_in_backups, - testvm2.include_in_backups) - self.assertEqual(testvm1.default_user, testvm2.default_user) - self.assertEqual(testvm1.features, testvm2.features) - self.assertEqual(testvm1.firewall.rules, - testvm2.firewall.rules) - - # now some non-default values - testvm1.netvm = None - testvm1.label = 'orange' - testvm1.memory = 512 - firewall = testvm1.firewall - firewall.rules = [ - qubes.firewall.Rule(None, action='accept', dsthost='1.2.3.0/24', - proto='tcp', dstports=22)] - firewall.save() - - testvm3 = self.app.add_new_vm(testvm1.__class__, - name=self.make_vm_name("clone2"), - template=testvm1.template, - label='red',) - testvm3.clone_properties(testvm1) - testvm3.firewall.clone(testvm1.firewall) - self.loop.run_until_complete(testvm3.clone_disk_files(testvm1)) - - # qubes.xml reload - self.app.save() - testvm1 = self.app.domains[testvm1.qid] - testvm3 = self.app.domains[testvm3.qid] - - self.assertEqual(testvm1.label, testvm3.label) - self.assertEqual(testvm1.netvm, testvm3.netvm) - self.assertEqual(testvm1.property_is_default('netvm'), - testvm3.property_is_default('netvm')) - self.assertEqual(testvm1.kernel, testvm3.kernel) - self.assertEqual(testvm1.kernelopts, testvm3.kernelopts) - self.assertEqual(testvm1.property_is_default('kernel'), - testvm3.property_is_default('kernel')) - self.assertEqual(testvm1.property_is_default('kernelopts'), - testvm3.property_is_default('kernelopts')) - self.assertEqual(testvm1.memory, testvm3.memory) - self.assertEqual(testvm1.maxmem, testvm3.maxmem) - self.assertEqual(testvm1.devices, testvm3.devices) - self.assertEqual(testvm1.include_in_backups, - testvm3.include_in_backups) - self.assertEqual(testvm1.default_user, testvm3.default_user) - self.assertEqual(testvm1.features, testvm3.features) - self.assertEqual(testvm1.firewall.rules, - testvm3.firewall.rules) - finally: - try: - del firewall - except NameError: - pass - try: - del testvm1 - except NameError: - pass - try: - del testvm2 - except NameError: - pass - try: - del testvm3 - except NameError: - pass - def test_020_name_conflict_app(self): # TODO decide what exception should be here with self.assertRaises((qubes.exc.QubesException, ValueError)): From 73e55eb99a3455ae06489e95f52ee404ab5f4dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 1 Nov 2020 05:04:59 +0100 Subject: [PATCH 4/9] tests: adjust for applications list stored in features dict --- qubes/tests/integ/backupcompatibility.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/qubes/tests/integ/backupcompatibility.py b/qubes/tests/integ/backupcompatibility.py index 3be56541..1acadba3 100644 --- a/qubes/tests/integ/backupcompatibility.py +++ b/qubes/tests/integ/backupcompatibility.py @@ -426,12 +426,17 @@ class TC_00_BackupCompatibility( 'debug': False, 'maxmem': min(int(self.app.host.memory_total / 1024 / 2), 4000), 'memory': 400, - 'features': {}, + 'features': { + 'menu-items': 'gnome-terminal.desktop nautilus.desktop ' + 'firefox.desktop mozilla-thunderbird.desktop ' + 'libreoffice-startcenter.desktop', + }, } template_standalone_props = common_props.copy() template_standalone_props['features'] = { 'qrexec': '1', 'gui': '1', + 'menu-items': common_props['features']['menu-items'], } self.assertRestored("test-template-clone", klass=qubes.vm.templatevm.TemplateVM, @@ -481,12 +486,17 @@ class TC_00_BackupCompatibility( 'debug': False, 'maxmem': min(int(self.app.host.memory_total / 1024 / 2), 4000), 'memory': 400, - 'features': {}, + 'features': { + 'menu-items': 'gnome-terminal.desktop nautilus.desktop ' + 'firefox.desktop mozilla-thunderbird.desktop ' + 'libreoffice-startcenter.desktop', + }, } template_standalone_props = common_props.copy() template_standalone_props['features'] = { 'qrexec': '1', 'gui': '1', + 'menu-items': common_props['features']['menu-items'], } self.assertRestored("test-template-clone", klass=qubes.vm.templatevm.TemplateVM, From 6db24d3eafadd665f0f35b1f9bf241e160e7fd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 11 Nov 2020 05:57:49 +0100 Subject: [PATCH 5/9] tests: add PVH grub2 tests --- qubes/tests/integ/grub.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qubes/tests/integ/grub.py b/qubes/tests/integ/grub.py index 285f4da7..1fb9a775 100644 --- a/qubes/tests/integ/grub.py +++ b/qubes/tests/integ/grub.py @@ -158,6 +158,12 @@ class TC_41_HVMGrub(GrubBase): virt_mode = 'hvm' kernel = None +@unittest.skipUnless(os.path.exists('/var/lib/qubes/vm-kernels/pvgrub2-pvh'), + 'grub2-xen-pvh package not installed') +class TC_42_PVHGrub(GrubBase): + virt_mode = 'pvh' + kernel = 'pvgrub2-pvh' + def create_testcases_for_templates(): yield from qubes.tests.create_testcases_for_templates('TC_40_PVGrub', TC_40_PVGrub, qubes.tests.SystemTestCase, @@ -165,6 +171,9 @@ def create_testcases_for_templates(): yield from qubes.tests.create_testcases_for_templates('TC_41_HVMGrub', TC_41_HVMGrub, qubes.tests.SystemTestCase, module=sys.modules[__name__]) + yield from qubes.tests.create_testcases_for_templates('TC_42_PVHGrub', + TC_42_PVHGrub, qubes.tests.SystemTestCase, + module=sys.modules[__name__]) def load_tests(loader, tests, pattern): tests.addTests(loader.loadTestsFromNames( From 79fb0cade1ab0d23b7e7d9e392170667e1a87821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 13 Nov 2020 03:21:37 +0100 Subject: [PATCH 6/9] tests: use dup-ed stdin in wait_on_fail transport.close() (necessary to detach reader from the loop) will close the FD. In case of stdin, it will prevent another call to this waiting function. Use dup(2) to register cloned FD, which is safe to close without side effects. --- qubes/tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index 32af1540..528eaf04 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -249,7 +249,7 @@ def wait_on_fail(func): transport, protocol = self.loop.run_until_complete( self.loop.connect_read_pipe( lambda: asyncio.StreamReaderProtocol(reader), - sys.stdin)) + os.fdopen(os.dup(sys.stdin.fileno())))) self.loop.run_until_complete(reader.readline()) transport.close() raise From 8233009e2a9c1f2bfd4f34503ded324a9848d044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 13 Nov 2020 04:39:22 +0100 Subject: [PATCH 7/9] tests: fix logging network info on failed test - log from requested VM - use 'critical' log level to reach journalctl - log /var/log/xen/xen-hotplug.log too --- qubes/tests/integ/network.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qubes/tests/integ/network.py b/qubes/tests/integ/network.py index 263472c6..187f7023 100644 --- a/qubes/tests/integ/network.py +++ b/qubes/tests/integ/network.py @@ -92,9 +92,9 @@ class VmNetworkingMixin(object): if not vm.is_running(): return with contextlib.suppress(subprocess.CalledProcessError): - output = self.loop.run_until_complete( - self.testnetvm.run_for_stdio(cmd, user='root')) - self.log.error('{}: {}: {}'.format(vm.name, cmd, output)) + output, _ = self.loop.run_until_complete( + vm.run_for_stdio(cmd, user='root', stderr=subprocess.STDOUT)) + self.log.critical('{}: {}: {}'.format(vm.name, cmd, output)) def tearDown(self): # collect more info on failure @@ -110,6 +110,7 @@ class VmNetworkingMixin(object): self._run_cmd_and_log_output(vm, 'systemctl --no-pager status qubes-firewall') self._run_cmd_and_log_output(vm, 'systemctl --no-pager status qubes-iptables') self._run_cmd_and_log_output(vm, 'systemctl --no-pager status xendriverdomain') + self._run_cmd_and_log_output(vm, 'cat /var/log/xen/xen-hotplug.log') super(VmNetworkingMixin, self).tearDown() From 7d8d0c10d15f7578e04b183564ebff2cee1b7ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 20 Nov 2020 05:36:46 +0100 Subject: [PATCH 8/9] tests: wait for full VM startup before testing network and few other Network may be configured (by qubes-misc-post service in some cases) after qrexec is started. Delay any test after VM is fully started (as the user would do). Adjust wait_for_session function, to re-use VM startup timeout, which make it adjustable for slower machines (like nested virt on openQA). --- qubes/tests/__init__.py | 8 ++++- qubes/tests/integ/grub.py | 3 ++ qubes/tests/integ/network.py | 54 ++++++++++++++----------------- qubes/tests/integ/network_ipv6.py | 20 ++++++------ 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index 528eaf04..8af3dd24 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -1277,7 +1277,7 @@ class SystemTestCase(QubesTestCase): @asyncio.coroutine def wait_for_session(self, vm): - timeout = 30 + timeout = vm.qrexec_timeout if getattr(vm, 'template', None) and 'whonix-ws' in vm.template.name: # first boot of whonix-ws takes more time because of /home # initialization, including Tor Browser copying @@ -1287,6 +1287,12 @@ class SystemTestCase(QubesTestCase): 'qubes.WaitForSession', input=vm.default_user.encode()), timeout=timeout) + @asyncio.coroutine + def start_vm(self, vm): + """Start a VM and wait for it to be fully up""" + yield from vm.start() + yield from self.wait_for_session(vm) + _templates = None diff --git a/qubes/tests/integ/grub.py b/qubes/tests/integ/grub.py index 1fb9a775..11b84ca4 100644 --- a/qubes/tests/integ/grub.py +++ b/qubes/tests/integ/grub.py @@ -57,6 +57,9 @@ class GrubBase(object): else: assert False, "Unsupported template?!" + # wait for full VM startup first, to have functional network + self.loop.run_until_complete(self.wait_for_session(vm)) + for cmd in [cmd_install1, cmd_install2, cmd_update_grub]: try: self.loop.run_until_complete(vm.run_for_stdio( diff --git a/qubes/tests/integ/network.py b/qubes/tests/integ/network.py index 187f7023..19886c8e 100644 --- a/qubes/tests/integ/network.py +++ b/qubes/tests/integ/network.py @@ -154,7 +154,7 @@ class VmNetworkingMixin(object): ''' :type self: qubes.tests.SystemTestCase | VMNetworkingMixin ''' - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0) @@ -172,7 +172,7 @@ class VmNetworkingMixin(object): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0, "Ping by IP from ProxyVM failed") @@ -201,7 +201,7 @@ class VmNetworkingMixin(object): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0, "Ping by IP failed") @@ -252,7 +252,7 @@ class VmNetworkingMixin(object): self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')] self.testvm1.firewall.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) server = self.loop.run_until_complete(self.testnetvm.run( @@ -352,8 +352,8 @@ class VmNetworkingMixin(object): self.app.save() self.loop.run_until_complete(asyncio.gather( - self.testvm1.start(), - self.testvm2.start())) + self.start_vm(self.testvm1), + self.start_vm(self.testvm2))) self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_cmd.format(target=self.testvm2.ip)), 0) @@ -377,7 +377,7 @@ class VmNetworkingMixin(object): :type self: qubes.tests.SystemTestCase | VMNetworkingMixin ''' - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testnetvm, @@ -410,7 +410,7 @@ class VmNetworkingMixin(object): cmd = "systemctl stop xendriverdomain" if self.run_cmd(self.testnetvm, cmd) != 0: self.fail("Command '%s' failed" % cmd) - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) cmd = "systemctl start xendriverdomain" if self.run_cmd(self.testnetvm, cmd) != 0: @@ -423,7 +423,7 @@ class VmNetworkingMixin(object): def test_110_dynamic_attach(self): self.testvm1.netvm = None - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.testvm1.netvm = self.testnetvm # wait for it to settle down self.loop.run_until_complete(self.testvm1.run_for_stdio( @@ -431,7 +431,7 @@ class VmNetworkingMixin(object): self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) def test_111_dynamic_detach_attach(self): - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.testvm1.netvm = None # wait for it to settle down self.loop.run_until_complete(self.testvm1.run_for_stdio( @@ -451,9 +451,9 @@ class VmNetworkingMixin(object): self.loop.run_until_complete(self.proxy.create_on_disk()) self.testvm1.netvm = self.proxy - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.loop.run_until_complete(self.proxy.shutdown(force=True, wait=True)) - self.loop.run_until_complete(self.proxy.start()) + self.loop.run_until_complete(self.start_vm(self.proxy)) # wait for it to settle down self.loop.run_until_complete(self.wait_for_session(self.proxy)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) @@ -467,11 +467,9 @@ class VmNetworkingMixin(object): self.loop.run_until_complete(self.proxy.create_on_disk()) self.testvm1.netvm = self.proxy - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.loop.run_until_complete(self.proxy.kill()) - self.loop.run_until_complete(self.proxy.start()) - # wait for it to settle down - self.loop.run_until_complete(self.wait_for_session(self.proxy)) + self.loop.run_until_complete(self.start_vm(self.proxy)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) def test_114_reattach_after_provider_crash(self): @@ -483,7 +481,7 @@ class VmNetworkingMixin(object): self.loop.run_until_complete(self.proxy.create_on_disk()) self.testvm1.netvm = self.proxy - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) p = self.loop.run_until_complete(self.proxy.run( 'echo c > /proc/sysrq-trigger', user='root')) self.loop.run_until_complete(p.wait()) @@ -493,9 +491,7 @@ class VmNetworkingMixin(object): timeout -= 1 self.assertGreater(timeout, 0, 'timeout waiting for crash cleanup') - self.loop.run_until_complete(self.proxy.start()) - # wait for it to settle down - self.loop.run_until_complete(self.wait_for_session(self.proxy)) + self.loop.run_until_complete(self.start_vm(self.proxy)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) def test_200_fake_ip_simple(self): @@ -507,7 +503,7 @@ class VmNetworkingMixin(object): self.testvm1.features['net.fake-gateway'] = '192.168.1.1' self.testvm1.features['net.fake-netmask'] = '255.255.255.0' self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0) @@ -539,7 +535,7 @@ class VmNetworkingMixin(object): ''' self.testvm1.features['net.fake-ip'] = '192.168.1.128' self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0) @@ -579,7 +575,7 @@ class VmNetworkingMixin(object): qubes.firewall.Rule(None, action='accept', specialtarget='dns'), ] self.testvm1.firewall.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) server = self.loop.run_until_complete(self.testnetvm.run( @@ -624,8 +620,8 @@ class VmNetworkingMixin(object): self.testvm2.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) - self.loop.run_until_complete(self.testvm2.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) + self.loop.run_until_complete(self.start_vm(self.testvm2)) cmd = 'iptables -I FORWARD -s {} -d {} -j ACCEPT'.format( self.testvm2.ip, self.testvm1.ip) @@ -673,7 +669,7 @@ class VmNetworkingMixin(object): self.proxy.features['net.fake-netmask'] = '255.255.255.0' self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.proxy, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.proxy, self.ping_name), 0) @@ -728,7 +724,7 @@ class VmNetworkingMixin(object): ''' self.testvm1.ip = '192.168.1.1' self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0) @@ -747,7 +743,7 @@ class VmNetworkingMixin(object): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping_name), 0) @@ -775,7 +771,7 @@ class VmNetworkingMixin(object): qubes.firewall.Rule(None, action='accept', specialtarget='dns'), ] self.testvm1.firewall.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) server = self.loop.run_until_complete(self.testnetvm.run( diff --git a/qubes/tests/integ/network_ipv6.py b/qubes/tests/integ/network_ipv6.py index cc3422b0..af39ccc1 100644 --- a/qubes/tests/integ/network_ipv6.py +++ b/qubes/tests/integ/network_ipv6.py @@ -84,7 +84,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): ''' :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin ''' - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0) @@ -102,7 +102,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) self.assertEqual(self.run_cmd(self.proxy, self.ping6_ip), 0, "Ping by IP from ProxyVM failed") @@ -131,7 +131,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0, "Ping by IP failed") @@ -187,7 +187,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.testvm1.firewall.rules = [qubes.firewall.Rule(action='drop')] self.testvm1.firewall.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) server = self.loop.run_until_complete(self.testnetvm.run( @@ -308,8 +308,8 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.app.save() self.loop.run_until_complete(asyncio.gather( - self.testvm1.start(), - self.testvm2.start())) + self.start_vm(self.testvm1), + self.start_vm(self.testvm2))) self.assertNotEqual(self.run_cmd(self.testvm1, self.ping_cmd.format(target=self.testvm2.ip6)), 0) @@ -335,7 +335,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): :type self: qubes.tests.SystemTestCase | VmIPv6NetworkingMixin ''' - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0) # add a simple rule counting packets @@ -369,7 +369,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): ''' self.testvm1.ip6 = '2000:aaaa:bbbb::1' self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0) @@ -388,7 +388,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.testvm1.netvm = self.proxy self.app.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_ip), 0) self.assertEqual(self.run_cmd(self.testvm1, self.ping6_name), 0) @@ -416,7 +416,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): qubes.firewall.Rule(None, action='accept', specialtarget='dns'), ] self.testvm1.firewall.save() - self.loop.run_until_complete(self.testvm1.start()) + self.loop.run_until_complete(self.start_vm(self.testvm1)) self.assertTrue(self.proxy.is_running()) server = self.loop.run_until_complete(self.testnetvm.run( From 86ccbcc206c4975987357a320cdb885669cea386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 25 Nov 2020 00:16:19 +0100 Subject: [PATCH 9/9] tests: fix test_550_ipv6_spoof_ip Use 'ip route replace' instead of 'ip route add' to not fail on already existing route entry. This worked before only because of a race condition with a network setup. --- qubes/tests/integ/network_ipv6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubes/tests/integ/network_ipv6.py b/qubes/tests/integ/network_ipv6.py index af39ccc1..a8fd53b5 100644 --- a/qubes/tests/integ/network_ipv6.py +++ b/qubes/tests/integ/network_ipv6.py @@ -345,7 +345,7 @@ class VmIPv6NetworkingMixin(VmNetworkingMixin): self.loop.run_until_complete(self.testvm1.run_for_stdio( 'ip -6 addr flush dev eth0 && ' 'ip -6 addr add {}/128 dev eth0 && ' - 'ip -6 route add default via {} dev eth0'.format( + 'ip -6 route replace default via {} dev eth0'.format( str(self.testvm1.visible_ip6) + '1', str(self.testvm1.visible_gateway6)), user='root'))