basic.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. # pylint: disable=invalid-name
  2. #
  3. # The Qubes OS Project, https://www.qubes-os.org/
  4. #
  5. # Copyright (C) 2014-2015
  6. # Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  7. # Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  8. #
  9. # This library is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU Lesser General Public
  11. # License as published by the Free Software Foundation; either
  12. # version 2.1 of the License, or (at your option) any later version.
  13. #
  14. # This library is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. # Lesser General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Lesser General Public
  20. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  21. #
  22. from distutils import spawn
  23. import asyncio
  24. import os
  25. import subprocess
  26. import tempfile
  27. import time
  28. import unittest
  29. import collections
  30. import pkg_resources
  31. import shutil
  32. import sys
  33. import qubes
  34. import qubes.firewall
  35. import qubes.tests
  36. import qubes.storage
  37. import qubes.vm.appvm
  38. import qubes.vm.qubesvm
  39. import qubes.vm.standalonevm
  40. import qubes.vm.templatevm
  41. import libvirt # pylint: disable=import-error
  42. class TC_00_Basic(qubes.tests.SystemTestCase):
  43. def setUp(self):
  44. super(TC_00_Basic, self).setUp()
  45. self.init_default_template()
  46. def test_000_qubes_create(self):
  47. self.assertIsInstance(self.app, qubes.Qubes)
  48. def test_100_qvm_create(self):
  49. vmname = self.make_vm_name('appvm')
  50. vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  51. name=vmname, template=self.app.default_template,
  52. label='red')
  53. self.assertIsNotNone(vm)
  54. self.assertEqual(vm.name, vmname)
  55. self.assertEqual(vm.template, self.app.default_template)
  56. self.loop.run_until_complete(vm.create_on_disk())
  57. with self.assertNotRaises(qubes.exc.QubesException):
  58. self.loop.run_until_complete(vm.storage.verify())
  59. def test_040_qdb_watch(self):
  60. flag = set()
  61. def handler(vm, event, path):
  62. if path == '/test-watch-path':
  63. flag.add(True)
  64. vm = self.app.domains[0]
  65. vm.watch_qdb_path('/test-watch-path')
  66. vm.add_handler('domain-qdb-change:/test-watch-path', handler)
  67. self.assertFalse(flag)
  68. vm.untrusted_qdb.write('/test-watch-path', 'test-value')
  69. self.loop.run_until_complete(asyncio.sleep(0.1))
  70. self.assertTrue(flag)
  71. @unittest.skipUnless(
  72. spawn.find_executable('xdotool'), "xdotool not installed")
  73. def test_120_start_standalone_with_cdrom_dom0(self):
  74. vmname = self.make_vm_name('appvm')
  75. self.vm = self.app.add_new_vm('StandaloneVM', label='red', name=vmname)
  76. self.loop.run_until_complete(self.vm.create_on_disk())
  77. self.vm.kernel = None
  78. self.vm.virt_mode = 'hvm'
  79. iso_path = self.create_bootable_iso()
  80. # start the VM using qvm-start tool, to test --cdrom option there
  81. p = self.loop.run_until_complete(asyncio.create_subprocess_exec(
  82. 'qvm-start', '--cdrom=dom0:' + iso_path, self.vm.name,
  83. stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
  84. (stdout, _) = self.loop.run_until_complete(p.communicate())
  85. self.assertEqual(p.returncode, 0, stdout)
  86. # check if VM do not crash instantly
  87. self.loop.run_until_complete(asyncio.sleep(5))
  88. self.assertTrue(self.vm.is_running())
  89. # Type 'poweroff'
  90. subprocess.check_call(['xdotool', 'search', '--name', self.vm.name,
  91. 'type', '--window', '%1', 'poweroff\r'])
  92. for _ in range(10):
  93. if not self.vm.is_running():
  94. break
  95. self.loop.run_until_complete(asyncio.sleep(1))
  96. self.assertFalse(self.vm.is_running())
  97. def test_130_autostart_disable_on_remove(self):
  98. vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  99. name=self.make_vm_name('vm'),
  100. template=self.app.default_template,
  101. label='red')
  102. self.assertIsNotNone(vm)
  103. self.loop.run_until_complete(vm.create_on_disk())
  104. vm.autostart = True
  105. self.assertTrue(os.path.exists(
  106. '/etc/systemd/system/multi-user.target.wants/'
  107. 'qubes-vm@{}.service'.format(vm.name)),
  108. "systemd service not enabled by autostart=True")
  109. del self.app.domains[vm]
  110. self.loop.run_until_complete(vm.remove_from_disk())
  111. self.assertFalse(os.path.exists(
  112. '/etc/systemd/system/multi-user.target.wants/'
  113. 'qubes-vm@{}.service'.format(vm.name)),
  114. "systemd service not disabled on domain remove")
  115. def _test_200_on_domain_start(self, vm, event, **_kwargs):
  116. '''Simulate domain crash just after startup'''
  117. vm.libvirt_domain.destroy()
  118. def test_200_shutdown_event_race(self):
  119. '''Regression test for 3164'''
  120. vmname = self.make_vm_name('appvm')
  121. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  122. name=vmname, template=self.app.default_template,
  123. label='red')
  124. # help the luck a little - don't wait for qrexec to easier win the race
  125. self.vm.features['qrexec'] = False
  126. self.loop.run_until_complete(self.vm.create_on_disk())
  127. # another way to help the luck a little - make sure the private
  128. # volume is first in (normally unordered) dict - this way if any
  129. # volume action fails, it will be at or after private volume - not
  130. # before (preventing private volume action)
  131. old_volumes = self.vm.volumes
  132. self.vm.volumes = collections.OrderedDict()
  133. self.vm.volumes['private'] = old_volumes.pop('private')
  134. self.vm.volumes.update(old_volumes.items())
  135. del old_volumes
  136. self.loop.run_until_complete(self.vm.start())
  137. # kill it the way it does not give a chance for domain-shutdown it
  138. # execute
  139. self.vm.libvirt_domain.destroy()
  140. # now, lets try to start the VM again, before domain-shutdown event
  141. # got handled (#3164), and immediately trigger second domain-shutdown
  142. self.vm.add_handler('domain-start', self._test_200_on_domain_start)
  143. self.loop.run_until_complete(self.vm.start())
  144. # and give a chance for both domain-shutdown handlers to execute
  145. self.loop.run_until_complete(asyncio.sleep(1))
  146. with self.assertNotRaises(qubes.exc.QubesException):
  147. # if the above caused two domain-shutdown handlers being called
  148. # one after another, private volume is gone
  149. self.loop.run_until_complete(self.vm.storage.verify())
  150. def _test_201_on_domain_pre_start(self, vm, event, **_kwargs):
  151. '''Simulate domain crash just after startup'''
  152. if not self.domain_shutdown_handled and not self.test_failure_reason:
  153. self.test_failure_reason = \
  154. 'domain-shutdown event was not dispatched before subsequent ' \
  155. 'start'
  156. self.domain_shutdown_handled = False
  157. def _test_201_domain_shutdown_handler(self, vm, event, **kwargs):
  158. if self.domain_shutdown_handled and not self.test_failure_reason:
  159. self.test_failure_reason = 'domain-shutdown event received twice'
  160. self.domain_shutdown_handled = True
  161. def test_201_shutdown_event_race(self):
  162. '''Regression test for 3164 - pure events edition'''
  163. vmname = self.make_vm_name('appvm')
  164. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  165. name=vmname, template=self.app.default_template,
  166. label='red')
  167. # help the luck a little - don't wait for qrexec to easier win the race
  168. self.vm.features['qrexec'] = False
  169. self.loop.run_until_complete(self.vm.create_on_disk())
  170. # do not throw exception from inside event handler - test framework
  171. # will not recover from it (various objects leaks)
  172. self.test_failure_reason = None
  173. self.domain_shutdown_handled = False
  174. self.vm.add_handler('domain-shutdown',
  175. self._test_201_domain_shutdown_handler)
  176. self.loop.run_until_complete(self.vm.start())
  177. if self.test_failure_reason:
  178. self.fail(self.test_failure_reason)
  179. self.vm.add_handler('domain-pre-start',
  180. self._test_201_on_domain_pre_start)
  181. # kill it the way it does not give a chance for domain-shutdown it
  182. # execute
  183. self.vm.libvirt_domain.destroy()
  184. # now, lets try to start the VM again, before domain-shutdown event
  185. # got handled (#3164), and immediately trigger second domain-shutdown
  186. self.vm.add_handler('domain-start', self._test_200_on_domain_start)
  187. self.loop.run_until_complete(self.vm.start())
  188. if self.test_failure_reason:
  189. self.fail(self.test_failure_reason)
  190. while self.vm.get_power_state() != 'Halted':
  191. self.loop.run_until_complete(asyncio.sleep(1))
  192. # and give a chance for both domain-shutdown handlers to execute
  193. self.loop.run_until_complete(asyncio.sleep(3))
  194. if self.test_failure_reason:
  195. self.fail(self.test_failure_reason)
  196. self.assertTrue(self.domain_shutdown_handled,
  197. 'second domain-shutdown event was not dispatched after domain '
  198. 'shutdown')
  199. def _check_udev_for_uuid(self, uuid_value):
  200. udev_data_path = '/run/udev/data'
  201. for udev_item in os.listdir(udev_data_path):
  202. # check only block devices
  203. if not udev_item.startswith('b'):
  204. continue
  205. with open(os.path.join(udev_data_path, udev_item)) as udev_file:
  206. self.assertNotIn(uuid_value, udev_file.read(),
  207. 'udev parsed filesystem UUID! ' + udev_item)
  208. def assertVolumesExcludedFromUdev(self, vm):
  209. try:
  210. # first boot, mkfs private volume
  211. self.loop.run_until_complete(vm.start())
  212. self.loop.run_until_complete(self.wait_for_session(vm))
  213. # get private volume UUID
  214. private_uuid, _ = self.loop.run_until_complete(
  215. vm.run_for_stdio('blkid -o value /dev/xvdb', user='root'))
  216. private_uuid = private_uuid.decode().splitlines()[0]
  217. # now check if dom0 udev know about it - it shouldn't
  218. self._check_udev_for_uuid(private_uuid)
  219. # now restart the VM and check again
  220. self.loop.run_until_complete(vm.shutdown(wait=True))
  221. self.loop.run_until_complete(vm.start())
  222. self._check_udev_for_uuid(private_uuid)
  223. finally:
  224. del vm
  225. def test_202_udev_block_exclude_default(self):
  226. '''Check if VM images are excluded from udev parsing -
  227. default volume pool'''
  228. vmname = self.make_vm_name('appvm')
  229. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  230. name=vmname, template=self.app.default_template,
  231. label='red')
  232. self.loop.run_until_complete(self.vm.create_on_disk())
  233. self.assertVolumesExcludedFromUdev(self.vm)
  234. def test_203_udev_block_exclude_varlibqubes(self):
  235. '''Check if VM images are excluded from udev parsing -
  236. varlibqubes pool'''
  237. vmname = self.make_vm_name('appvm')
  238. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  239. name=vmname, template=self.app.default_template,
  240. label='red')
  241. self.loop.run_until_complete(self.vm.create_on_disk(
  242. pool=self.app.pools['varlibqubes']))
  243. self.assertVolumesExcludedFromUdev(self.vm)
  244. def test_204_udev_block_exclude_custom_file(self):
  245. '''Check if VM images are excluded from udev parsing -
  246. custom file pool'''
  247. vmname = self.make_vm_name('appvm')
  248. pool_path = tempfile.mkdtemp(
  249. prefix='qubes-pool-', dir='/var/tmp')
  250. self.addCleanup(shutil.rmtree, pool_path)
  251. pool = self.loop.run_until_complete(
  252. self.app.add_pool('test-filep', dir_path=pool_path, driver='file'))
  253. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  254. name=vmname, template=self.app.default_template,
  255. label='red')
  256. self.loop.run_until_complete(self.vm.create_on_disk(
  257. pool=pool))
  258. self.assertVolumesExcludedFromUdev(self.vm)
  259. class TC_01_Properties(qubes.tests.SystemTestCase):
  260. # pylint: disable=attribute-defined-outside-init
  261. def setUp(self):
  262. super(TC_01_Properties, self).setUp()
  263. self.init_default_template()
  264. self.vmname = self.make_vm_name('appvm')
  265. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM, name=self.vmname,
  266. template=self.app.default_template,
  267. label='red')
  268. self.loop.run_until_complete(self.vm.create_on_disk())
  269. self.addCleanup(self.cleanup_props)
  270. def cleanup_props(self):
  271. del self.vm
  272. def test_030_clone(self):
  273. try:
  274. testvm1 = self.app.add_new_vm(
  275. qubes.vm.appvm.AppVM,
  276. name=self.make_vm_name("vm"),
  277. template=self.app.default_template,
  278. label='red')
  279. self.loop.run_until_complete(testvm1.create_on_disk())
  280. testvm2 = self.app.add_new_vm(testvm1.__class__,
  281. name=self.make_vm_name("clone"),
  282. template=testvm1.template,
  283. label='red')
  284. testvm2.clone_properties(testvm1)
  285. testvm2.firewall.clone(testvm1.firewall)
  286. self.loop.run_until_complete(testvm2.clone_disk_files(testvm1))
  287. self.assertTrue(self.loop.run_until_complete(testvm1.storage.verify()))
  288. self.assertIn('source', testvm1.volumes['root'].config)
  289. self.assertNotEquals(testvm2, None)
  290. self.assertNotEquals(testvm2.volumes, {})
  291. self.assertIn('source', testvm2.volumes['root'].config)
  292. # qubes.xml reload
  293. self.app.save()
  294. testvm1 = self.app.domains[testvm1.qid]
  295. testvm2 = self.app.domains[testvm2.qid]
  296. self.assertEqual(testvm1.label, testvm2.label)
  297. self.assertEqual(testvm1.netvm, testvm2.netvm)
  298. self.assertEqual(testvm1.property_is_default('netvm'),
  299. testvm2.property_is_default('netvm'))
  300. self.assertEqual(testvm1.kernel, testvm2.kernel)
  301. self.assertEqual(testvm1.kernelopts, testvm2.kernelopts)
  302. self.assertEqual(testvm1.property_is_default('kernel'),
  303. testvm2.property_is_default('kernel'))
  304. self.assertEqual(testvm1.property_is_default('kernelopts'),
  305. testvm2.property_is_default('kernelopts'))
  306. self.assertEqual(testvm1.memory, testvm2.memory)
  307. self.assertEqual(testvm1.maxmem, testvm2.maxmem)
  308. self.assertEqual(testvm1.devices, testvm2.devices)
  309. self.assertEqual(testvm1.include_in_backups,
  310. testvm2.include_in_backups)
  311. self.assertEqual(testvm1.default_user, testvm2.default_user)
  312. self.assertEqual(testvm1.features, testvm2.features)
  313. self.assertEqual(testvm1.firewall.rules,
  314. testvm2.firewall.rules)
  315. # now some non-default values
  316. testvm1.netvm = None
  317. testvm1.label = 'orange'
  318. testvm1.memory = 512
  319. firewall = testvm1.firewall
  320. firewall.rules = [
  321. qubes.firewall.Rule(None, action='accept', dsthost='1.2.3.0/24',
  322. proto='tcp', dstports=22)]
  323. firewall.save()
  324. testvm3 = self.app.add_new_vm(testvm1.__class__,
  325. name=self.make_vm_name("clone2"),
  326. template=testvm1.template,
  327. label='red',)
  328. testvm3.clone_properties(testvm1)
  329. testvm3.firewall.clone(testvm1.firewall)
  330. self.loop.run_until_complete(testvm3.clone_disk_files(testvm1))
  331. # qubes.xml reload
  332. self.app.save()
  333. testvm1 = self.app.domains[testvm1.qid]
  334. testvm3 = self.app.domains[testvm3.qid]
  335. self.assertEqual(testvm1.label, testvm3.label)
  336. self.assertEqual(testvm1.netvm, testvm3.netvm)
  337. self.assertEqual(testvm1.property_is_default('netvm'),
  338. testvm3.property_is_default('netvm'))
  339. self.assertEqual(testvm1.kernel, testvm3.kernel)
  340. self.assertEqual(testvm1.kernelopts, testvm3.kernelopts)
  341. self.assertEqual(testvm1.property_is_default('kernel'),
  342. testvm3.property_is_default('kernel'))
  343. self.assertEqual(testvm1.property_is_default('kernelopts'),
  344. testvm3.property_is_default('kernelopts'))
  345. self.assertEqual(testvm1.memory, testvm3.memory)
  346. self.assertEqual(testvm1.maxmem, testvm3.maxmem)
  347. self.assertEqual(testvm1.devices, testvm3.devices)
  348. self.assertEqual(testvm1.include_in_backups,
  349. testvm3.include_in_backups)
  350. self.assertEqual(testvm1.default_user, testvm3.default_user)
  351. self.assertEqual(testvm1.features, testvm3.features)
  352. self.assertEqual(testvm1.firewall.rules,
  353. testvm3.firewall.rules)
  354. finally:
  355. try:
  356. del firewall
  357. except NameError:
  358. pass
  359. try:
  360. del testvm1
  361. except NameError:
  362. pass
  363. try:
  364. del testvm2
  365. except NameError:
  366. pass
  367. try:
  368. del testvm3
  369. except NameError:
  370. pass
  371. def test_020_name_conflict_app(self):
  372. # TODO decide what exception should be here
  373. with self.assertRaises((qubes.exc.QubesException, ValueError)):
  374. self.vm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  375. name=self.vmname, template=self.app.default_template,
  376. label='red')
  377. self.loop.run_until_complete(self.vm2.create_on_disk())
  378. def test_021_name_conflict_template(self):
  379. # TODO decide what exception should be here
  380. with self.assertRaises((qubes.exc.QubesException, ValueError)):
  381. self.vm2 = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
  382. name=self.vmname, label='red')
  383. self.loop.run_until_complete(self.vm2.create_on_disk())
  384. class TC_03_QvmRevertTemplateChanges(qubes.tests.SystemTestCase):
  385. # pylint: disable=attribute-defined-outside-init
  386. def setUp(self):
  387. super(TC_03_QvmRevertTemplateChanges, self).setUp()
  388. self.init_default_template()
  389. def cleanup_template(self):
  390. del self.test_template
  391. def setup_template(self):
  392. self.test_template = self.app.add_new_vm(
  393. qubes.vm.templatevm.TemplateVM,
  394. name=self.make_vm_name("pv-clone"),
  395. label='red'
  396. )
  397. self.addCleanup(self.cleanup_template)
  398. self.test_template.clone_properties(self.app.default_template)
  399. self.test_template.features.update(self.app.default_template.features)
  400. self.test_template.tags.update(self.app.default_template.tags)
  401. self.loop.run_until_complete(
  402. self.test_template.clone_disk_files(self.app.default_template))
  403. self.test_template.volumes['root'].revisions_to_keep = 3
  404. self.app.save()
  405. def get_rootimg_checksum(self):
  406. return subprocess.check_output(
  407. ['sha1sum', self.test_template.volumes['root'].export()]).\
  408. decode().split(' ')[0]
  409. def _do_test(self):
  410. checksum_before = self.get_rootimg_checksum()
  411. self.loop.run_until_complete(self.test_template.start())
  412. self.loop.run_until_complete(self.wait_for_session(self.test_template))
  413. self.shutdown_and_wait(self.test_template)
  414. checksum_changed = self.get_rootimg_checksum()
  415. if checksum_before == checksum_changed:
  416. self.log.warning("template not modified, test result will be "
  417. "unreliable")
  418. self.assertNotEqual(self.test_template.volumes['root'].revisions, {})
  419. revert_cmd = ['qvm-volume', 'revert', self.test_template.name + ':root']
  420. p = self.loop.run_until_complete(asyncio.create_subprocess_exec(
  421. *revert_cmd))
  422. self.loop.run_until_complete(p.wait())
  423. self.assertEqual(p.returncode, 0)
  424. del p
  425. checksum_after = self.get_rootimg_checksum()
  426. self.assertEqual(checksum_before, checksum_after)
  427. def test_000_revert_linux(self):
  428. """
  429. Test qvm-revert-template-changes for PV template
  430. """
  431. self.setup_template()
  432. self._do_test()
  433. @unittest.skip('TODO: some non-linux system')
  434. def test_001_revert_non_linux(self):
  435. """
  436. Test qvm-revert-template-changes for HVM template
  437. """
  438. # TODO: have some system there, so the root.img will get modified
  439. self.setup_template()
  440. self._do_test()
  441. class TC_30_Gui_daemon(qubes.tests.SystemTestCase):
  442. def setUp(self):
  443. super(TC_30_Gui_daemon, self).setUp()
  444. self.init_default_template()
  445. @unittest.skipUnless(
  446. spawn.find_executable('xdotool'),
  447. "xdotool not installed")
  448. def test_000_clipboard(self):
  449. testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  450. name=self.make_vm_name('vm1'), label='red')
  451. self.loop.run_until_complete(testvm1.create_on_disk())
  452. testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  453. name=self.make_vm_name('vm2'), label='red')
  454. self.loop.run_until_complete(testvm2.create_on_disk())
  455. self.app.save()
  456. self.loop.run_until_complete(asyncio.wait([
  457. testvm1.start(),
  458. testvm2.start()]))
  459. self.loop.run_until_complete(asyncio.wait([
  460. self.wait_for_session(testvm1),
  461. self.wait_for_session(testvm2)]))
  462. window_title = 'user@{}'.format(testvm1.name)
  463. self.loop.run_until_complete(testvm1.run(
  464. 'zenity --text-info --editable --title={}'.format(window_title)))
  465. self.wait_for_window(window_title)
  466. time.sleep(0.5)
  467. test_string = "test{}".format(testvm1.xid)
  468. # Type and copy some text
  469. subprocess.check_call(['xdotool', 'search', '--name', window_title,
  470. 'windowactivate', '--sync',
  471. 'type', test_string])
  472. # second xdotool call because type --terminator do not work (SEGV)
  473. # additionally do not use search here, so window stack will be empty
  474. # and xdotool will use XTEST instead of generating events manually -
  475. # this will be much better - at least because events will have
  476. # correct timestamp (so gui-daemon would not drop the copy request)
  477. subprocess.check_call(['xdotool',
  478. 'key', 'ctrl+a', 'ctrl+c', 'ctrl+shift+c',
  479. 'Escape'])
  480. clipboard_content = \
  481. open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
  482. self.assertEqual(clipboard_content, test_string,
  483. "Clipboard copy operation failed - content")
  484. clipboard_source = \
  485. open('/var/run/qubes/qubes-clipboard.bin.source',
  486. 'r').read().strip()
  487. self.assertEqual(clipboard_source, testvm1.name,
  488. "Clipboard copy operation failed - owner")
  489. # Then paste it to the other window
  490. window_title = 'user@{}'.format(testvm2.name)
  491. p = self.loop.run_until_complete(testvm2.run(
  492. 'zenity --entry --title={} > /tmp/test.txt'.format(window_title)))
  493. self.wait_for_window(window_title)
  494. subprocess.check_call(['xdotool', 'key', '--delay', '100',
  495. 'ctrl+shift+v', 'ctrl+v', 'Return'])
  496. self.loop.run_until_complete(p.wait())
  497. # And compare the result
  498. (test_output, _) = self.loop.run_until_complete(
  499. testvm2.run_for_stdio('cat /tmp/test.txt'))
  500. self.assertEqual(test_string, test_output.strip().decode('ascii'))
  501. clipboard_content = \
  502. open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
  503. self.assertEqual(clipboard_content, "",
  504. "Clipboard not wiped after paste - content")
  505. clipboard_source = \
  506. open('/var/run/qubes/qubes-clipboard.bin.source', 'r').\
  507. read().strip()
  508. self.assertEqual(clipboard_source, "",
  509. "Clipboard not wiped after paste - owner")
  510. class TC_05_StandaloneVMMixin(object):
  511. def setUp(self):
  512. super(TC_05_StandaloneVMMixin, self).setUp()
  513. self.init_default_template(self.template)
  514. def test_000_create_start(self):
  515. self.testvm1 = self.app.add_new_vm(qubes.vm.standalonevm.StandaloneVM,
  516. name=self.make_vm_name('vm1'), label='red')
  517. self.testvm1.features.update(self.app.default_template.features)
  518. self.loop.run_until_complete(
  519. self.testvm1.clone_disk_files(self.app.default_template))
  520. self.app.save()
  521. self.loop.run_until_complete(self.testvm1.start())
  522. self.assertEqual(self.testvm1.get_power_state(), "Running")
  523. def test_100_resize_root_img(self):
  524. self.testvm1 = self.app.add_new_vm(qubes.vm.standalonevm.StandaloneVM,
  525. name=self.make_vm_name('vm1'), label='red')
  526. self.testvm1.features.update(self.app.default_template.features)
  527. self.loop.run_until_complete(
  528. self.testvm1.clone_disk_files(self.app.default_template))
  529. self.app.save()
  530. try:
  531. self.loop.run_until_complete(
  532. self.testvm1.storage.resize(self.testvm1.volumes['root'],
  533. 20 * 1024 ** 3))
  534. except (subprocess.CalledProcessError,
  535. qubes.storage.StoragePoolException) as e:
  536. # exception object would leak VM reference
  537. self.fail(str(e))
  538. self.assertEqual(self.testvm1.volumes['root'].size, 20 * 1024 ** 3)
  539. self.loop.run_until_complete(self.testvm1.start())
  540. # new_size in 1k-blocks
  541. (new_size, _) = self.loop.run_until_complete(
  542. self.testvm1.run_for_stdio('df --output=size /|tail -n 1'))
  543. # some safety margin for FS metadata
  544. self.assertGreater(int(new_size.strip()), 19 * 1024 ** 2)
  545. def test_101_resize_root_img_online(self):
  546. self.testvm1 = self.app.add_new_vm(qubes.vm.standalonevm.StandaloneVM,
  547. name=self.make_vm_name('vm1'), label='red')
  548. self.testvm1.features['qrexec'] = True
  549. self.loop.run_until_complete(
  550. self.testvm1.clone_disk_files(self.app.default_template))
  551. self.testvm1.features.update(self.app.default_template.features)
  552. self.app.save()
  553. self.loop.run_until_complete(self.testvm1.start())
  554. try:
  555. self.loop.run_until_complete(
  556. self.testvm1.storage.resize(self.testvm1.volumes['root'],
  557. 20 * 1024 ** 3))
  558. except (subprocess.CalledProcessError,
  559. qubes.storage.StoragePoolException) as e:
  560. # exception object would leak VM reference
  561. self.fail(str(e))
  562. self.assertEqual(self.testvm1.volumes['root'].size, 20 * 1024 ** 3)
  563. # new_size in 1k-blocks
  564. (new_size, _) = self.loop.run_until_complete(
  565. self.testvm1.run_for_stdio('df --output=size /|tail -n 1'))
  566. # some safety margin for FS metadata
  567. self.assertGreater(int(new_size.strip()), 19 * 1024 ** 2)
  568. class TC_06_AppVMMixin(object):
  569. template = None
  570. def setUp(self):
  571. super(TC_06_AppVMMixin, self).setUp()
  572. self.init_default_template(self.template)
  573. @unittest.skipUnless(
  574. spawn.find_executable('xdotool'), "xdotool not installed")
  575. def test_121_start_standalone_with_cdrom_vm(self):
  576. cdrom_vmname = self.make_vm_name('cdrom')
  577. self.cdrom_vm = self.app.add_new_vm('AppVM', label='red',
  578. name=cdrom_vmname)
  579. self.loop.run_until_complete(self.cdrom_vm.create_on_disk())
  580. self.loop.run_until_complete(self.cdrom_vm.start())
  581. iso_path = self.create_bootable_iso()
  582. with open(iso_path, 'rb') as iso_f:
  583. self.loop.run_until_complete(
  584. self.cdrom_vm.run_for_stdio('cat > /home/user/boot.iso',
  585. stdin=iso_f))
  586. vmname = self.make_vm_name('appvm')
  587. self.vm = self.app.add_new_vm('StandaloneVM', label='red', name=vmname)
  588. self.loop.run_until_complete(self.vm.create_on_disk())
  589. self.vm.kernel = None
  590. self.vm.virt_mode = 'hvm'
  591. # start the VM using qvm-start tool, to test --cdrom option there
  592. p = self.loop.run_until_complete(asyncio.create_subprocess_exec(
  593. 'qvm-start', '--cdrom=' + cdrom_vmname + ':/home/user/boot.iso',
  594. self.vm.name,
  595. stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
  596. (stdout, _) = self.loop.run_until_complete(p.communicate())
  597. self.assertEqual(p.returncode, 0, stdout)
  598. # check if VM do not crash instantly
  599. self.loop.run_until_complete(asyncio.sleep(5))
  600. self.assertTrue(self.vm.is_running())
  601. # Type 'poweroff'
  602. subprocess.check_call(['xdotool', 'search', '--name', self.vm.name,
  603. 'type', '--window', '%1', 'poweroff\r'])
  604. for _ in range(10):
  605. if not self.vm.is_running():
  606. break
  607. self.loop.run_until_complete(asyncio.sleep(1))
  608. self.assertFalse(self.vm.is_running())
  609. def create_testcases_for_templates():
  610. yield from qubes.tests.create_testcases_for_templates('TC_05_StandaloneVM',
  611. TC_05_StandaloneVMMixin, qubes.tests.SystemTestCase,
  612. module=sys.modules[__name__])
  613. yield from qubes.tests.create_testcases_for_templates('TC_06_AppVM',
  614. TC_06_AppVMMixin, qubes.tests.SystemTestCase,
  615. module=sys.modules[__name__])
  616. def load_tests(loader, tests, pattern):
  617. tests.addTests(loader.loadTestsFromNames(
  618. create_testcases_for_templates()))
  619. return tests
  620. qubes.tests.maybe_create_testcases_on_import(create_testcases_for_templates)
  621. # vim: ts=4 sw=4 et