basic.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  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 program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program 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
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along
  20. # with this program; if not, write to the Free Software Foundation, Inc.,
  21. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. #
  23. from distutils import spawn
  24. import asyncio
  25. import os
  26. import subprocess
  27. import tempfile
  28. import time
  29. import unittest
  30. import qubes
  31. import qubes.firewall
  32. import qubes.tests
  33. import qubes.vm.appvm
  34. import qubes.vm.qubesvm
  35. import qubes.vm.standalonevm
  36. import qubes.vm.templatevm
  37. import libvirt # pylint: disable=import-error
  38. class TC_00_Basic(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
  39. def setUp(self):
  40. super(TC_00_Basic, self).setUp()
  41. self.init_default_template()
  42. def test_000_qubes_create(self):
  43. self.assertIsInstance(self.app, qubes.Qubes)
  44. def test_100_qvm_create(self):
  45. vmname = self.make_vm_name('appvm')
  46. vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  47. name=vmname, template=self.app.default_template,
  48. label='red')
  49. self.assertIsNotNone(vm)
  50. self.assertEqual(vm.name, vmname)
  51. self.assertEqual(vm.template, self.app.default_template)
  52. vm.create_on_disk()
  53. with self.assertNotRaises(qubes.exc.QubesException):
  54. vm.storage.verify()
  55. class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
  56. # pylint: disable=attribute-defined-outside-init
  57. def setUp(self):
  58. super(TC_01_Properties, self).setUp()
  59. self.init_default_template()
  60. self.vmname = self.make_vm_name('appvm')
  61. self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM, name=self.vmname,
  62. template=self.app.default_template,
  63. label='red')
  64. self.loop.run_until_complete(self.vm.create_on_disk())
  65. def save_and_reload_db(self):
  66. super(TC_01_Properties, self).save_and_reload_db()
  67. if hasattr(self, 'vm'):
  68. self.vm = self.app.domains[self.vm.qid]
  69. if hasattr(self, 'netvm'):
  70. self.netvm = self.app.domains[self.netvm.qid]
  71. @unittest.expectedFailure
  72. def test_000_rename(self):
  73. newname = self.make_vm_name('newname')
  74. self.assertEqual(self.vm.name, self.vmname)
  75. self.vm.firewall.policy = 'drop'
  76. self.vm.firewall.rules = [
  77. qubes.firewall.Rule(None, action='accept', specialtarget='dns')
  78. ]
  79. self.vm.firewall.save()
  80. self.vm.autostart = True
  81. self.addCleanup(os.system,
  82. 'sudo systemctl -q disable qubes-vm@{}.service || :'.
  83. format(self.vmname))
  84. pre_rename_firewall = self.vm.firewall.rules
  85. with self.assertNotRaises(
  86. (OSError, libvirt.libvirtError, qubes.exc.QubesException)):
  87. self.vm.name = newname
  88. self.assertEqual(self.vm.name, newname)
  89. self.assertEqual(self.vm.dir_path,
  90. os.path.join(
  91. qubes.config.system_path['qubes_base_dir'],
  92. qubes.config.system_path['qubes_appvms_dir'], newname))
  93. self.assertTrue(os.path.exists(
  94. os.path.join(self.vm.dir_path, "apps", newname + "-vm.directory")))
  95. # FIXME: set whitelisted-appmenus.list first
  96. self.assertTrue(os.path.exists(os.path.join(
  97. self.vm.dir_path, "apps", newname + "-firefox.desktop")))
  98. self.assertTrue(os.path.exists(
  99. os.path.join(os.getenv("HOME"), ".local/share/desktop-directories",
  100. newname + "-vm.directory")))
  101. self.assertTrue(os.path.exists(
  102. os.path.join(os.getenv("HOME"), ".local/share/applications",
  103. newname + "-firefox.desktop")))
  104. self.assertFalse(os.path.exists(
  105. os.path.join(os.getenv("HOME"), ".local/share/desktop-directories",
  106. self.vmname + "-vm.directory")))
  107. self.assertFalse(os.path.exists(
  108. os.path.join(os.getenv("HOME"), ".local/share/applications",
  109. self.vmname + "-firefox.desktop")))
  110. self.vm.firewall.load()
  111. self.assertEqual(pre_rename_firewall, self.vm.firewall.rules)
  112. with self.assertNotRaises((qubes.exc.QubesException, OSError)):
  113. self.vm.firewall.save()
  114. self.assertTrue(self.vm.autostart)
  115. self.assertTrue(os.path.exists(
  116. '/etc/systemd/system/multi-user.target.wants/'
  117. 'qubes-vm@{}.service'.format(newname)))
  118. self.assertFalse(os.path.exists(
  119. '/etc/systemd/system/multi-user.target.wants/'
  120. 'qubes-vm@{}.service'.format(self.vmname)))
  121. def test_001_rename_libvirt_undefined(self):
  122. self.vm.libvirt_domain.undefine()
  123. self.vm._libvirt_domain = None # pylint: disable=protected-access
  124. newname = self.make_vm_name('newname')
  125. with self.assertNotRaises(
  126. (OSError, libvirt.libvirtError, qubes.exc.QubesException)):
  127. self.vm.name = newname
  128. @unittest.expectedFailure
  129. def test_030_clone(self):
  130. testvm1 = self.app.add_new_vm(
  131. qubes.vm.appvm.AppVM,
  132. name=self.make_vm_name("vm"),
  133. template=self.app.default_template,
  134. label='red')
  135. self.loop.run_until_complete(testvm1.create_on_disk())
  136. testvm2 = self.app.add_new_vm(testvm1.__class__,
  137. name=self.make_vm_name("clone"),
  138. template=testvm1.template,
  139. label='red')
  140. testvm2.clone_properties(testvm1)
  141. self.loop.run_until_complete(testvm2.clone_disk_files(testvm1))
  142. self.assertTrue(testvm1.storage.verify())
  143. self.assertIn('source', testvm1.volumes['root'].config)
  144. self.assertNotEquals(testvm2, None)
  145. self.assertNotEquals(testvm2.volumes, {})
  146. self.assertIn('source', testvm2.volumes['root'].config)
  147. # qubes.xml reload
  148. self.save_and_reload_db()
  149. testvm1 = self.app.domains[testvm1.qid]
  150. testvm2 = self.app.domains[testvm2.qid]
  151. self.assertEqual(testvm1.label, testvm2.label)
  152. self.assertEqual(testvm1.netvm, testvm2.netvm)
  153. self.assertEqual(testvm1.property_is_default('netvm'),
  154. testvm2.property_is_default('netvm'))
  155. self.assertEqual(testvm1.kernel, testvm2.kernel)
  156. self.assertEqual(testvm1.kernelopts, testvm2.kernelopts)
  157. self.assertEqual(testvm1.property_is_default('kernel'),
  158. testvm2.property_is_default('kernel'))
  159. self.assertEqual(testvm1.property_is_default('kernelopts'),
  160. testvm2.property_is_default('kernelopts'))
  161. self.assertEqual(testvm1.memory, testvm2.memory)
  162. self.assertEqual(testvm1.maxmem, testvm2.maxmem)
  163. self.assertEqual(testvm1.devices, testvm2.devices)
  164. self.assertEqual(testvm1.include_in_backups,
  165. testvm2.include_in_backups)
  166. self.assertEqual(testvm1.default_user, testvm2.default_user)
  167. self.assertEqual(testvm1.features, testvm2.features)
  168. self.assertEqual(testvm1.firewall.rules,
  169. testvm2.firewall.rules)
  170. # now some non-default values
  171. testvm1.netvm = None
  172. testvm1.label = 'orange'
  173. testvm1.memory = 512
  174. firewall = testvm1.firewall
  175. firewall.policy = 'drop'
  176. firewall.rules = [
  177. qubes.firewall.Rule(None, action='accept', dsthost='1.2.3.0/24',
  178. proto='tcp', dstports=22)]
  179. firewall.save()
  180. testvm3 = self.app.add_new_vm(testvm1.__class__,
  181. name=self.make_vm_name("clone2"),
  182. template=testvm1.template,
  183. label='red',)
  184. testvm3.clone_properties(testvm1)
  185. self.loop.run_until_complete(testvm3.clone_disk_files(testvm1))
  186. # qubes.xml reload
  187. self.save_and_reload_db()
  188. testvm1 = self.app.domains[testvm1.qid]
  189. testvm3 = self.app.domains[testvm3.qid]
  190. self.assertEqual(testvm1.label, testvm3.label)
  191. self.assertEqual(testvm1.netvm, testvm3.netvm)
  192. self.assertEqual(testvm1.property_is_default('netvm'),
  193. testvm3.property_is_default('netvm'))
  194. self.assertEqual(testvm1.kernel, testvm3.kernel)
  195. self.assertEqual(testvm1.kernelopts, testvm3.kernelopts)
  196. self.assertEqual(testvm1.property_is_default('kernel'),
  197. testvm3.property_is_default('kernel'))
  198. self.assertEqual(testvm1.property_is_default('kernelopts'),
  199. testvm3.property_is_default('kernelopts'))
  200. self.assertEqual(testvm1.memory, testvm3.memory)
  201. self.assertEqual(testvm1.maxmem, testvm3.maxmem)
  202. self.assertEqual(testvm1.devices, testvm3.devices)
  203. self.assertEqual(testvm1.include_in_backups,
  204. testvm3.include_in_backups)
  205. self.assertEqual(testvm1.default_user, testvm3.default_user)
  206. self.assertEqual(testvm1.features, testvm3.features)
  207. self.assertEqual(testvm1.firewall.rules,
  208. testvm2.firewall.rules)
  209. def test_020_name_conflict_app(self):
  210. # TODO decide what exception should be here
  211. with self.assertRaises((qubes.exc.QubesException, ValueError)):
  212. self.vm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  213. name=self.vmname, template=self.app.default_template,
  214. label='red')
  215. self.loop.run_until_complete(self.vm2.create_on_disk())
  216. def test_021_name_conflict_template(self):
  217. # TODO decide what exception should be here
  218. with self.assertRaises((qubes.exc.QubesException, ValueError)):
  219. self.vm2 = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
  220. name=self.vmname, label='red')
  221. self.loop.run_until_complete(self.vm2.create_on_disk())
  222. def test_030_rename_conflict_app(self):
  223. vm2name = self.make_vm_name('newname')
  224. self.vm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  225. name=vm2name, template=self.app.default_template, label='red')
  226. self.loop.run_until_complete(self.vm2.create_on_disk())
  227. with self.assertNotRaises(OSError):
  228. with self.assertRaises(qubes.exc.QubesException):
  229. self.vm2.name = self.vmname
  230. class TC_02_QvmPrefs(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
  231. # pylint: disable=attribute-defined-outside-init
  232. def setUp(self):
  233. super(TC_02_QvmPrefs, self).setUp()
  234. self.init_default_template()
  235. self.sharedopts = ['--qubesxml', qubes.tests.XMLPATH]
  236. def setup_appvm(self):
  237. self.testvm = self.app.add_new_vm(
  238. qubes.vm.appvm.AppVM,
  239. name=self.make_vm_name("vm"),
  240. label='red')
  241. self.loop.run_until_complete(self.testvm.create_on_disk())
  242. self.save_and_reload_db()
  243. def setup_hvm(self):
  244. self.testvm = self.app.add_new_vm(
  245. qubes.vm.appvm.AppVM,
  246. name=self.make_vm_name("hvm"),
  247. label='red')
  248. self.testvm.hvm = True
  249. self.loop.run_until_complete(self.testvm.create_on_disk())
  250. self.save_and_reload_db()
  251. def pref_set(self, name, value, valid=True):
  252. p = subprocess.Popen(
  253. ['qvm-prefs'] + self.sharedopts +
  254. (['--'] if value != '-D' else []) + [self.testvm.name, name, value],
  255. stdout=subprocess.PIPE,
  256. stderr=subprocess.PIPE,
  257. )
  258. (stdout, stderr) = p.communicate()
  259. if valid:
  260. self.assertEqual(p.returncode, 0,
  261. "qvm-prefs .. '{}' '{}' failed: {}{}".format(
  262. name, value, stdout, stderr
  263. ))
  264. else:
  265. self.assertNotEquals(p.returncode, 0,
  266. "qvm-prefs should reject value '{}' for "
  267. "property '{}'".format(value, name))
  268. def pref_get(self, name):
  269. p = subprocess.Popen(['qvm-prefs'] + self.sharedopts +
  270. ['--', self.testvm.name, name], stdout=subprocess.PIPE)
  271. (stdout, _) = p.communicate()
  272. self.assertEqual(p.returncode, 0)
  273. return stdout.strip()
  274. bool_test_values = [
  275. ('true', 'True', True),
  276. ('False', 'False', True),
  277. ('0', 'False', True),
  278. ('1', 'True', True),
  279. ('invalid', '', False)
  280. ]
  281. def execute_tests(self, name, values):
  282. """
  283. Helper function, which executes tests for given property.
  284. :param values: list of tuples (value, expected, valid),
  285. where 'value' is what should be set and 'expected' is what should
  286. qvm-prefs returns as a property value and 'valid' marks valid and
  287. invalid values - if it's False, qvm-prefs should reject the value
  288. :return: None
  289. """
  290. for (value, expected, valid) in values:
  291. self.pref_set(name, value, valid)
  292. if valid:
  293. self.assertEqual(self.pref_get(name), expected)
  294. @unittest.skip('test not converted to core3 API')
  295. def test_006_template(self):
  296. templates = [tpl for tpl in self.app.domains.values() if
  297. isinstance(tpl, qubes.vm.templatevm.TemplateVM)]
  298. if not templates:
  299. self.skipTest("No templates installed")
  300. some_template = templates[0].name
  301. self.setup_appvm()
  302. self.execute_tests('template', [
  303. (some_template, some_template, True),
  304. ('invalid', '', False),
  305. ])
  306. @unittest.skip('test not converted to core3 API')
  307. def test_014_pcidevs(self):
  308. self.setup_appvm()
  309. self.execute_tests('pcidevs', [
  310. ('[]', '[]', True),
  311. ('[ "00:00.0" ]', "['00:00.0']", True),
  312. ('invalid', '', False),
  313. ('[invalid]', '', False),
  314. # TODO:
  315. # ('["12:12.0"]', '', False)
  316. ])
  317. @unittest.skip('test not converted to core3 API')
  318. def test_024_pv_reject_hvm_props(self):
  319. self.setup_appvm()
  320. self.execute_tests('guiagent_installed', [('False', '', False)])
  321. self.execute_tests('qrexec_installed', [('False', '', False)])
  322. self.execute_tests('drive', [('/tmp/drive.img', '', False)])
  323. self.execute_tests('timezone', [('localtime', '', False)])
  324. @unittest.skip('test not converted to core3 API')
  325. def test_025_hvm_reject_pv_props(self):
  326. self.setup_hvm()
  327. self.execute_tests('kernel', [('default', '', False)])
  328. self.execute_tests('kernelopts', [('default', '', False)])
  329. class TC_03_QvmRevertTemplateChanges(qubes.tests.SystemTestsMixin,
  330. qubes.tests.QubesTestCase):
  331. # pylint: disable=attribute-defined-outside-init
  332. def setUp(self):
  333. super(TC_03_QvmRevertTemplateChanges, self).setUp()
  334. self.init_default_template()
  335. def setup_pv_template(self):
  336. self.test_template = self.app.add_new_vm(
  337. qubes.vm.templatevm.TemplateVM,
  338. name=self.make_vm_name("pv-clone"),
  339. label='red'
  340. )
  341. self.test_template.clone_properties(self.app.default_template)
  342. self.loop.run_until_complete(
  343. self.test_template.clone_disk_files(self.app.default_template))
  344. self.save_and_reload_db()
  345. def setup_hvm_template(self):
  346. self.test_template = self.app.add_new_vm(
  347. qubes.vm.templatevm.TemplateVM,
  348. name=self.make_vm_name("hvm"),
  349. label='red',
  350. hvm=True
  351. )
  352. self.loop.run_until_complete(self.test_template.create_on_disk())
  353. self.save_and_reload_db()
  354. def get_rootimg_checksum(self):
  355. p = subprocess.Popen(
  356. ['sha1sum', self.test_template.volumes['root'].path],
  357. stdout=subprocess.PIPE)
  358. return p.communicate()[0]
  359. def _do_test(self):
  360. checksum_before = self.get_rootimg_checksum()
  361. self.loop.run_until_complete(self.test_template.start())
  362. self.shutdown_and_wait(self.test_template)
  363. checksum_changed = self.get_rootimg_checksum()
  364. if checksum_before == checksum_changed:
  365. self.log.warning("template not modified, test result will be "
  366. "unreliable")
  367. self.assertNotEqual(self.test_template.volumes['root'].revisions, {})
  368. with self.assertNotRaises(subprocess.CalledProcessError):
  369. pool_vid = repr(self.test_template.volumes['root']).strip("'")
  370. revert_cmd = ['qvm-block', 'revert', pool_vid]
  371. subprocess.check_call(revert_cmd)
  372. checksum_after = self.get_rootimg_checksum()
  373. self.assertEqual(checksum_before, checksum_after)
  374. @unittest.expectedFailure
  375. def test_000_revert_pv(self):
  376. """
  377. Test qvm-revert-template-changes for PV template
  378. """
  379. self.setup_pv_template()
  380. self._do_test()
  381. @unittest.skip('HVM not yet implemented')
  382. def test_000_revert_hvm(self):
  383. """
  384. Test qvm-revert-template-changes for HVM template
  385. """
  386. # TODO: have some system there, so the root.img will get modified
  387. self.setup_hvm_template()
  388. self._do_test()
  389. class TC_30_Gui_daemon(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
  390. def setUp(self):
  391. super(TC_30_Gui_daemon, self).setUp()
  392. self.init_default_template()
  393. @unittest.skipUnless(
  394. spawn.find_executable('xdotool'),
  395. "xdotool not installed")
  396. def test_000_clipboard(self):
  397. testvm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  398. name=self.make_vm_name('vm1'), label='red')
  399. self.loop.run_until_complete(testvm1.create_on_disk())
  400. testvm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  401. name=self.make_vm_name('vm2'), label='red')
  402. self.loop.run_until_complete(testvm2.create_on_disk())
  403. self.app.save()
  404. self.loop.run_until_complete(asyncio.wait([
  405. testvm1.start(),
  406. testvm2.start()]))
  407. window_title = 'user@{}'.format(testvm1.name)
  408. self.loop.run_until_complete(testvm1.run(
  409. 'zenity --text-info --editable --title={}'.format(window_title)))
  410. self.wait_for_window(window_title)
  411. time.sleep(0.5)
  412. test_string = "test{}".format(testvm1.xid)
  413. # Type and copy some text
  414. subprocess.check_call(['xdotool', 'search', '--name', window_title,
  415. 'windowactivate', '--sync',
  416. 'type', test_string])
  417. # second xdotool call because type --terminator do not work (SEGV)
  418. # additionally do not use search here, so window stack will be empty
  419. # and xdotool will use XTEST instead of generating events manually -
  420. # this will be much better - at least because events will have
  421. # correct timestamp (so gui-daemon would not drop the copy request)
  422. subprocess.check_call(['xdotool',
  423. 'key', 'ctrl+a', 'ctrl+c', 'ctrl+shift+c',
  424. 'Escape'])
  425. clipboard_content = \
  426. open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
  427. self.assertEqual(clipboard_content, test_string,
  428. "Clipboard copy operation failed - content")
  429. clipboard_source = \
  430. open('/var/run/qubes/qubes-clipboard.bin.source',
  431. 'r').read().strip()
  432. self.assertEqual(clipboard_source, testvm1.name,
  433. "Clipboard copy operation failed - owner")
  434. # Then paste it to the other window
  435. window_title = 'user@{}'.format(testvm2.name)
  436. p = self.loop.run_until_complete(testvm2.run(
  437. 'zenity --entry --title={} > test.txt'.format(window_title)))
  438. self.wait_for_window(window_title)
  439. subprocess.check_call(['xdotool', 'key', '--delay', '100',
  440. 'ctrl+shift+v', 'ctrl+v', 'Return'])
  441. self.loop.run_until_complete(p.wait())
  442. # And compare the result
  443. (test_output, _) = self.loop.run_until_complete(
  444. testvm2.run_for_stdio('cat test.txt'))
  445. self.assertEqual(test_string, test_output.strip().decode('ascii'))
  446. clipboard_content = \
  447. open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
  448. self.assertEqual(clipboard_content, "",
  449. "Clipboard not wiped after paste - content")
  450. clipboard_source = \
  451. open('/var/run/qubes/qubes-clipboard.bin.source', 'r').\
  452. read().strip()
  453. self.assertEqual(clipboard_source, "",
  454. "Clipboard not wiped after paste - owner")
  455. class TC_05_StandaloneVM(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
  456. def setUp(self):
  457. super(TC_05_StandaloneVM, self).setUp()
  458. self.init_default_template()
  459. @unittest.expectedFailure
  460. def test_000_create_start(self):
  461. testvm1 = self.app.add_new_vm(qubes.vm.standalonevm.StandaloneVM,
  462. name=self.make_vm_name('vm1'), label='red')
  463. self.loop.run_until_complete(
  464. testvm1.clone_disk_files(self.app.default_template))
  465. self.app.save()
  466. self.loop.run_until_complete(testvm1.start())
  467. self.assertEqual(testvm1.get_power_state(), "Running")
  468. @unittest.expectedFailure
  469. def test_100_resize_root_img(self):
  470. testvm1 = self.app.add_new_vm(qubes.vm.standalonevm.StandaloneVM,
  471. name=self.make_vm_name('vm1'), label='red')
  472. self.loop.run_until_complete(
  473. testvm1.clone_disk_files(self.app.default_template))
  474. self.app.save()
  475. self.loop.run_until_complete(
  476. testvm1.storage.resize(testvm1.volumes['root'], 20 * 1024 ** 3))
  477. self.assertEqual(testvm1.volumes['root'].size, 20 * 1024 ** 3)
  478. self.loop.run_until_complete(testvm1.start())
  479. # new_size in 1k-blocks
  480. (new_size, _) = self.loop.run_until_complete(
  481. testvm1.run_for_stdio('df --output=size /|tail -n 1'))
  482. # some safety margin for FS metadata
  483. self.assertGreater(int(new_size.strip()), 19 * 1024 ** 2)
  484. # vim: ts=4 sw=4 et